diff --git a/SharedClasses/ConfigManager.cs b/SharedClasses/ConfigManager.cs
index 26a0693d7..c8b07fe7a 100644
--- a/SharedClasses/ConfigManager.cs
+++ b/SharedClasses/ConfigManager.cs
@@ -30,7 +30,6 @@ public enum Setting
vmenu_enable_animals_spawn_menu,
vmenu_pvp_mode,
keep_player_head_props,
- vmenu_disable_server_info_convars,
vmenu_player_names_distance,
vmenu_disable_entity_outlines_tool,
vmenu_disable_player_stats_setup,
@@ -38,6 +37,11 @@ public enum Setting
// Vehicle Chameleon Colours
vmenu_using_chameleon_colours,
+ // Prevent Extras Abuse
+ vmenu_prevent_extras_when_damaged,
+ vmenu_allowed_engine_damage_for_extra_change,
+ vmenu_allowed_body_damage_for_extra_change,
+
// MP Ped preview setting,
vmenu_mp_ped_preview,
diff --git a/SharedClasses/PermissionsManager.cs b/SharedClasses/PermissionsManager.cs
index a076bd751..2420e41da 100644
--- a/SharedClasses/PermissionsManager.cs
+++ b/SharedClasses/PermissionsManager.cs
@@ -28,6 +28,7 @@ public enum Permission
OPTeleport,
OPWaypoint,
OPSpectate,
+ OPSendMessage,
OPIdentifiers,
OPSummon,
OPKill,
@@ -105,6 +106,7 @@ public enum Permission
VOInfiniteFuel,
VOFlares,
VOPlaneBombs,
+ VOBypassExtraDamage,
#endregion
// Vehicle Spawner
@@ -384,9 +386,16 @@ public enum Permission
///
///
///
- /// if true, then the permissions will be checked even if they aren't setup yet.
///
public static bool IsAllowed(Permission permission, Player source) => IsAllowedServer(permission, source);
+
+ ///
+ /// Public function to check if a permission is allowed.
+ ///
+ ///
+ ///
+ ///
+ public static bool IsAllowed(Permission permission, string playerHandle) => IsAllowedServer(permission, playerHandle);
#endif
#if CLIENT
@@ -455,11 +464,23 @@ private static bool IsAllowedServer(Permission permission, Player source)
return false;
}
- if (IsPlayerAceAllowed(source.Handle, GetAceName(permission)))
+ return IsAllowedServer(permission, source.Handle);
+ }
+
+ ///
+ /// Checks if the player is allowed that specific permission.
+ ///
+ ///
+ ///
+ ///
+ private static bool IsAllowedServer(Permission permission, string playerHandle)
+ {
+ if (!DoesPlayerExist(playerHandle))
{
- return true;
+ return false;
}
- return false;
+
+ return IsPlayerAceAllowed(playerHandle, GetAceName(permission));
}
#endif
diff --git a/vMenu/CommonFunctions.cs b/vMenu/CommonFunctions.cs
index c4d6d6d0c..8c940f6ff 100644
--- a/vMenu/CommonFunctions.cs
+++ b/vMenu/CommonFunctions.cs
@@ -423,6 +423,11 @@ public static async Task TeleportToPlayer(IPlayer player, bool inVehicle = false
else
{
playerPos = await MainMenu.RequestPlayerCoordinates(player.ServerId);
+ if (playerPos == Vector3.Zero)
+ {
+ Notify.Error("Could not retrieve the coordinates of the specified player. Teleport cancelled.");
+ return;
+ }
wasActive = false;
}
@@ -961,7 +966,13 @@ public static async void CommitSuicide()
/// Summon player.
///
///
- public static void SummonPlayer(IPlayer player) => TriggerServerEvent("vMenu:SummonPlayer", player.ServerId);
+ public static void SummonPlayer(IPlayer player)
+ {
+ Vehicle currentVehicle = GetVehicle();
+ int numberOfSeats = currentVehicle is not null ? GetVehicleModelNumberOfSeats(currentVehicle.Model) : 0;
+
+ TriggerServerEvent("vMenu:SummonPlayer", player.ServerId, numberOfSeats);
+ }
#endregion
#region Spectate function
@@ -1529,7 +1540,7 @@ public static async void SaveVehicle(string updateExistingSavedVehicleName = nul
#region new saving method
var mods = new Dictionary();
- foreach (var mod in veh.Mods.GetAllMods())
+ foreach (var mod in GetAllVehicleMods(veh))
{
mods.Add((int)mod.ModType, mod.Index);
}
@@ -1569,17 +1580,32 @@ public static async void SaveVehicle(string updateExistingSavedVehicleName = nul
colors.Add("tyresmokeR", tyresmokeR);
colors.Add("tyresmokeG", tyresmokeG);
colors.Add("tyresmokeB", tyresmokeB);
+
int customPrimaryR = -1;
int customPrimaryG = -1;
int customPrimaryB = -1;
- GetVehicleCustomPrimaryColour(veh.Handle, ref customPrimaryR, ref customPrimaryG, ref customPrimaryB);
+ bool primaryColorIsCustom = GetIsVehiclePrimaryColourCustom(veh.Handle);
+
+ if (primaryColorIsCustom)
+ {
+ GetVehicleCustomPrimaryColour(veh.Handle, ref customPrimaryR, ref customPrimaryG, ref customPrimaryB);
+ }
+
colors.Add("customPrimaryR", customPrimaryR);
colors.Add("customPrimaryG", customPrimaryG);
colors.Add("customPrimaryB", customPrimaryB);
+
int customSecondaryR = -1;
int customSecondaryG = -1;
int customSecondaryB = -1;
- GetVehicleCustomSecondaryColour(veh.Handle, ref customSecondaryR, ref customSecondaryG, ref customSecondaryB);
+
+ bool secondaryColorIsCustom = GetIsVehicleSecondaryColourCustom(veh.Handle);
+
+ if (secondaryColorIsCustom)
+ {
+ GetVehicleCustomSecondaryColour(veh.Handle, ref customSecondaryR, ref customSecondaryG, ref customSecondaryB);
+ }
+
colors.Add("customSecondaryR", customSecondaryR);
colors.Add("customSecondaryG", customSecondaryG);
colors.Add("customSecondaryB", customSecondaryB);
@@ -2024,9 +2050,12 @@ public static Dictionary JsonToDictionary(string json)
/// Update the server with the new weather type, blackout status and dynamic weather changes enabled status.
///
/// The new weather type.
- /// Manual blackout mode enabled/disabled.
/// Dynamic weather changes enabled/disabled.
- public static void UpdateServerWeather(string newWeather, bool blackout, bool dynamicChanges, bool isSnowEnabled) => TriggerServerEvent("vMenu:UpdateServerWeather", newWeather, blackout, dynamicChanges, isSnowEnabled);
+ public static void UpdateServerWeather(string newWeather, bool dynamicChanges, bool isSnowEnabled) => TriggerServerEvent("vMenu:UpdateServerWeather", newWeather, dynamicChanges, isSnowEnabled);
+
+ public static void UpdateServerBlackout(bool value) => TriggerServerEvent("vMenu:UpdateServerBlackout", value);
+
+ public static void UpdateServerVehicleBlackout(bool value) => TriggerServerEvent("vMenu:UpdateServerVehicleBlackout", value);
///
/// Modify the clouds for everyone. If removeClouds is true, then remove all clouds. If it's false, then randomize the clouds.
@@ -2041,8 +2070,7 @@ public static Dictionary JsonToDictionary(string json)
///
/// Hours (0-23)
/// Minutes (0-59)
- /// Should the time be frozen?
- public static void UpdateServerTime(int hours, int minutes, bool freezeTime)
+ public static void UpdateServerTime(int hours, int minutes)
{
var realHours = hours;
var realMinutes = minutes;
@@ -2054,8 +2082,14 @@ public static void UpdateServerTime(int hours, int minutes, bool freezeTime)
{
realMinutes = 0;
}
- TriggerServerEvent("vMenu:UpdateServerTime", realHours, realMinutes, freezeTime);
+ TriggerServerEvent("vMenu:UpdateServerTime", realHours, realMinutes);
}
+
+ ///
+ /// Updates the server on if time should be frozen or not.
+ ///
+ /// `true` to freeze time, `false` to unfreeze time
+ public static void FreezeServerTime(bool freezeTime) => TriggerServerEvent("vMenu:FreezeServerTime", freezeTime);
#endregion
#region StringToStringArray
@@ -3265,10 +3299,6 @@ public static async void PrivateMessage(string source, string message, bool sent
if (MainMenu.MiscSettingsMenu == null || MainMenu.MiscSettingsMenu.MiscDisablePrivateMessages)
{
- if (!(sent && source == Game.Player.ServerId.ToString()))
- {
- TriggerServerEvent("vMenu:PmsDisabled", source);
- }
return;
}
@@ -3456,5 +3486,26 @@ public static async void SavePlayerLocationToLocationsFile()
Notify.Success("The location was successfully saved.");
}
#endregion
+
+ #region Get all vehicle mods
+ public static VehicleMod[] GetAllVehicleMods(Vehicle vehicle)
+ {
+ int vehicleHandle = vehicle.Handle;
+
+ bool HasVehicleMod(VehicleData.ModType modType)
+ {
+ return GetNumVehicleMods(vehicleHandle, (int)modType) > 0;
+ }
+
+ return
+ [
+ .. Enum.GetValues(typeof(VehicleData.ModType))
+ .Cast()
+ .Where(HasVehicleMod)
+ // The cast to `VehicleModType` is fine here because `VehicleMod` casts `VehicleModType` to `int`
+ .Select(modType => vehicle.Mods[(VehicleModType)modType])
+ ];
+ }
+ #endregion
}
}
diff --git a/vMenu/EventManager.cs b/vMenu/EventManager.cs
index c0cc95f50..909516b6c 100644
--- a/vMenu/EventManager.cs
+++ b/vMenu/EventManager.cs
@@ -28,7 +28,7 @@ public class EventManager : BaseScript
public static string GetServerWeather => GetSettingsString(Setting.vmenu_current_weather, "CLEAR");
public static bool DynamicWeatherEnabled => GetSettingsBool(Setting.vmenu_enable_dynamic_weather);
public static bool IsBlackoutEnabled => GetSettingsBool(Setting.vmenu_blackout_enabled);
- public static bool IsVehicleLightsEnabled { get; set; } = GetSettingsBool(Setting.vmenu_vehicle_blackout_enabled);
+ public static bool IsVehicleLightsEnabled => GetSettingsBool(Setting.vmenu_vehicle_blackout_enabled);
public static int WeatherChangeTime => MathUtil.Clamp(GetSettingsInt(Setting.vmenu_weather_change_duration), 0, 45);
///
@@ -40,16 +40,14 @@ public EventManager()
EventHandlers.Add("vMenu:SetAddons", new Action(SetConfigOptions)); // DEPRECATED: Backwards-compatible event handler; use 'vMenu:SetConfigOptions' instead
EventHandlers.Add("vMenu:SetConfigOptions", new Action(SetConfigOptions));
EventHandlers.Add("vMenu:SetPermissions", new Action(MainMenu.SetPermissions));
- EventHandlers.Add("vMenu:GoToPlayer", new Action(SummonPlayer));
EventHandlers.Add("vMenu:KillMe", new Action(KillMe));
EventHandlers.Add("vMenu:Notify", new Action(NotifyPlayer));
EventHandlers.Add("vMenu:SetClouds", new Action(SetClouds));
EventHandlers.Add("vMenu:GoodBye", new Action(GoodBye));
EventHandlers.Add("vMenu:SetBanList", new Action(UpdateBanList));
- EventHandlers.Add("vMenu:ClearArea", new Action(ClearAreaNearPos));
+ EventHandlers.Add("vMenu:ClearArea", new Action(ClearAreaNearPos));
EventHandlers.Add("vMenu:updatePedDecors", new Action(UpdatePedDecors));
EventHandlers.Add("playerSpawned", new Action(SetAppearanceOnFirstSpawn));
- EventHandlers.Add("vMenu:GetOutOfCar", new Action(GetOutOfCar));
EventHandlers.Add("vMenu:PrivateMessage", new Action(PrivateMessage));
EventHandlers.Add("vMenu:UpdateTeleportLocations", new Action(UpdateTeleportLocations));
@@ -284,11 +282,6 @@ private async Task UpdateWeatherParticles()
///
private async Task WeatherSync()
{
- if (MainMenu.WeatherOptionsMenu == null)
- {
- return;
- }
-
await UpdateWeatherParticles();
SetArtificialLightsState(IsBlackoutEnabled);
SetArtificialLightsStateAffectsVehicles(!IsVehicleLightsEnabled);
@@ -358,24 +351,6 @@ private void KillMe(string sourceName)
SetEntityHealth(Game.PlayerPed.Handle, 0);
}
- ///
- /// Teleport to the specified player.
- ///
- ///
- private async void SummonPlayer(string targetPlayer)
- {
- // ensure the player list is requested in case of Infinity
- MainMenu.PlayersList.RequestPlayerList();
- await MainMenu.PlayersList.WaitRequested();
-
- var player = MainMenu.PlayersList.FirstOrDefault(a => a.ServerId == int.Parse(targetPlayer));
-
- if (player != null)
- {
- _ = TeleportToPlayer(player);
- }
- }
-
///
/// Clear the area around the provided x, y, z coordinates. Clears everything like (destroyed) objects, peds, (ai) vehicles, etc.
/// Also restores broken streetlights, etc.
@@ -383,67 +358,7 @@ private async void SummonPlayer(string targetPlayer)
///
///
///
- private void ClearAreaNearPos(float x, float y, float z)
- {
- ClearAreaOfEverything(x, y, z, 100f, false, false, false, false);
- }
-
- ///
- /// Kicks the current player from the specified vehicle if they're inside and don't own the vehicle themselves.
- ///
- ///
- ///
- private async void GetOutOfCar(int vehNetId, int vehicleOwnedBy)
- {
- if (NetworkDoesNetworkIdExist(vehNetId))
- {
- var veh = NetToVeh(vehNetId);
- if (DoesEntityExist(veh))
- {
- var vehicle = new Vehicle(veh);
-
- if (vehicle == null || !vehicle.Exists())
- {
- return;
- }
-
- if (Game.PlayerPed.IsInVehicle(vehicle) && vehicleOwnedBy != Game.Player.ServerId)
- {
- if (!vehicle.IsStopped)
- {
- Notify.Alert("The owner of this vehicle is reclaiming their personal vehicle. You will be kicked from this vehicle in about 10 seconds. Stop the vehicle now to avoid taking damage.", false, true);
- }
-
- // Wait for the vehicle to come to a stop, or 10 seconds, whichever is faster.
- var timer = GetGameTimer();
- while (vehicle != null && vehicle.Exists() && !vehicle.IsStopped)
- {
- await Delay(0);
- if (GetGameTimer() - timer > (10 * 1000)) // 10 second timeout
- {
- break;
- }
- }
-
- // just to make sure they're actually still inside the vehicle and the vehicle still exists.
- if (vehicle != null && vehicle.Exists() && Game.PlayerPed.IsInVehicle(vehicle))
- {
- // Make the ped jump out because the car isn't stopped yet.
- if (!vehicle.IsStopped)
- {
- Notify.Info("You were warned, now you'll have to suffer the consequences!");
- TaskLeaveVehicle(Game.PlayerPed.Handle, vehicle.Handle, 4160);
- }
- // Make the ped exit gently.
- else
- {
- TaskLeaveVehicle(Game.PlayerPed.Handle, vehicle.Handle, 0);
- }
- }
- }
- }
- }
- }
+ private void ClearAreaNearPos(Vector3 position) => ClearAreaOfEverything(position.X, position.Y, position.Z, 100f, false, false, false, false);
///
/// Updates ped decorators for the clothing animation when players have joined.
diff --git a/vMenu/MainMenu.cs b/vMenu/MainMenu.cs
index fcc5d037d..4f936d07a 100644
--- a/vMenu/MainMenu.cs
+++ b/vMenu/MainMenu.cs
@@ -120,7 +120,7 @@ public MainMenu()
}
#endregion
#region keymapping
- string KeyMappingID = String.IsNullOrWhiteSpace(GetSettingsString(Setting.vmenu_keymapping_id)) ? "Default" : GetSettingsString(Setting.vmenu_keymapping_id);
+ string KeyMappingID = GetKeyMappingId();
RegisterCommand($"vMenu:{KeyMappingID}:NoClip", new Action, string>((dynamic source, List args, string rawCommand) =>
{
if (IsAllowed(Permission.NoClip))
@@ -144,20 +144,6 @@ public MainMenu()
}
}
}), false);
- RegisterCommand($"vMenu:{KeyMappingID}:MenuToggle", new Action, string>((dynamic source, List args, string rawCommand) =>
- {
- if (MenuEnabled)
- {
- if (!MenuController.IsAnyMenuOpen())
- {
- Menu.OpenMenu();
- }
- else
- {
- MenuController.CloseAllMenus();
- }
- }
- }), false);
if (!(GetSettingsString(Setting.vmenu_noclip_toggle_key) == null))
{
@@ -340,55 +326,59 @@ public MainMenu()
// Clear all previous pause menu info/brief messages on resource start.
ClearBrief();
- // Request the permissions data from the server.
- TriggerServerEvent("vMenu:RequestPermissions");
-
- // Request server state from the server.
- TriggerServerEvent("vMenu:RequestServerState");
- }
-
- #region Infinity bits
- [EventHandler("vMenu:SetServerState")]
- public void SetServerState(IDictionary data)
- {
- if (data.TryGetValue("IsInfinity", out var isInfinity))
+ if (GlobalState.Get("vmenu_onesync") ?? false)
{
- if (isInfinity is bool isInfinityBool)
- {
- if (isInfinityBool)
- {
- PlayersList = new InfinityPlayerList(Players);
- }
- }
+ PlayersList = new InfinityPlayerList(Players);
}
}
+ #region Infinity bits
[EventHandler("vMenu:ReceivePlayerList")]
public void ReceivedPlayerList(IList
///
///
- ///
[EventHandler("vMenu:GetOutOfCar")]
- internal void GetOutOfCar([FromSource] Player source, int vehicleNetId, int playerOwner)
+ internal void GetOutOfCar([FromSource] Player source, int vehicleNetId)
{
- if (source != null)
+ if (!PermissionsManager.IsAllowed(PermissionsManager.Permission.PVKickPassengers, source) && !PermissionsManager.IsAllowed(PermissionsManager.Permission.PVAll, source))
+ {
+ BanManager.BanCheater(source);
+ return;
+ }
+
+ Entity vehicle = Entity.FromNetworkId(vehicleNetId);
+
+ if (vehicle is null)
+ {
+ return;
+ }
+
+ int vehicleHandle = vehicle.Handle;
+
+ for (int i = -1; i < 15; i++)
{
- if (vMenuShared.PermissionsManager.GetPermissionAndParentPermissions(vMenuShared.PermissionsManager.Permission.PVKickPassengers).Any(perm => vMenuShared.PermissionsManager.IsAllowed(perm, source)))
+ int pedHandle = GetPedInVehicleSeat(vehicleHandle, i);
+
+ if (pedHandle == 0 || !IsPedAPlayer(pedHandle))
{
- TriggerClientEvent("vMenu:GetOutOfCar", vehicleNetId, playerOwner);
- source.TriggerEvent("vMenu:Notify", "All passengers will be kicked out as soon as the vehicle stops moving, or after 10 seconds if they refuse to stop the vehicle.");
+ continue;
}
+
+ int playerHandle = NetworkGetEntityOwner(pedHandle);
+ Player player = GetPlayerFromServerId(playerHandle);
+
+ if (player is null || player == source)
+ {
+ continue;
+ }
+
+ int warpOutFlag = 16;
+
+ TaskLeaveVehicle(pedHandle, vehicleHandle, warpOutFlag);
+
+ player.TriggerEvent("vMenu:Notify", "The owner of the vehicle has kicked you out.");
}
}
#endregion
@@ -539,13 +573,13 @@ internal void GetOutOfCar([FromSource] Player source, int vehicleNetId, int play
///
/// Clear the area near this point for all players.
///
- ///
- ///
- ///
[EventHandler("vMenu:ClearArea")]
- internal void ClearAreaNearPos(float x, float y, float z)
+ internal void ClearAreaNearPos([FromSource] Player source)
{
- TriggerClientEvent("vMenu:ClearArea", x, y, z);
+ Ped ped = source.Character;
+ Vector3 position = ped.Position;
+
+ TriggerClientEvent("vMenu:ClearArea", position);
}
#endregion
@@ -669,11 +703,15 @@ private void RefreshWeather()
/// Update the weather for all clients.
///
///
- ///
///
[EventHandler("vMenu:UpdateServerWeather")]
- internal void UpdateWeather(string newWeather, bool blackoutNew, bool dynamicWeatherNew, bool enableSnow)
+ internal void UpdateWeather([FromSource] Player source, string newWeather, bool dynamicWeatherNew, bool enableSnow)
{
+ if (!PermissionsManager.IsAllowed(PermissionsManager.Permission.WOSetWeather, source) && !PermissionsManager.IsAllowed(PermissionsManager.Permission.WOAll, source))
+ {
+ BanManager.BanCheater(source);
+ return;
+ }
// Automatically enable snow effects whenever one of the snow weather types is selected.
if (newWeather is "XMAS" or "SNOWLIGHT" or "SNOW" or "BLIZZARD")
@@ -683,7 +721,6 @@ internal void UpdateWeather(string newWeather, bool blackoutNew, bool dynamicWea
// Update the new weather related variables.
CurrentWeather = newWeather;
- BlackoutEnabled = blackoutNew;
DynamicWeatherEnabled = dynamicWeatherNew;
ManualSnowEnabled = enableSnow;
@@ -691,19 +728,57 @@ internal void UpdateWeather(string newWeather, bool blackoutNew, bool dynamicWea
lastWeatherChange = GetGameTimer();
}
+ [EventHandler("vMenu:UpdateServerBlackout")]
+ internal void UpdateBlackout([FromSource] Player source, bool value)
+ {
+ if (!PermissionsManager.IsAllowed(PermissionsManager.Permission.WOBlackout, source) && !PermissionsManager.IsAllowed(PermissionsManager.Permission.WOAll, source))
+ {
+ BanManager.BanCheater(source);
+ return;
+ }
+
+ BlackoutEnabled = value;
+ }
+
+ [EventHandler("vMenu:UpdateServerVehicleBlackout")]
+ internal void UpdateVehicleBlackout([FromSource] Player source, bool value)
+ {
+ if (!PermissionsManager.IsAllowed(PermissionsManager.Permission.WOVehBlackout, source) && !PermissionsManager.IsAllowed(PermissionsManager.Permission.WOAll, source))
+ {
+ BanManager.BanCheater(source);
+ return;
+ }
+
+ VehicleBlackoutEnabled = value;
+ }
+
///
/// Set a new random clouds type and opacity for all clients.
///
///
[EventHandler("vMenu:UpdateServerWeatherCloudsType")]
- internal void UpdateWeatherCloudsType(bool removeClouds)
+ internal void UpdateWeatherCloudsType([FromSource] Player source, bool removeClouds)
{
+ bool allWOPermissions = PermissionsManager.IsAllowed(PermissionsManager.Permission.WOAll, source);
+
if (removeClouds)
{
+ if (!PermissionsManager.IsAllowed(PermissionsManager.Permission.WORemoveClouds, source) && !allWOPermissions)
+ {
+ BanManager.BanCheater(source);
+ return;
+ }
+
TriggerClientEvent("vMenu:SetClouds", 0f, "removed");
}
else
{
+ if (!PermissionsManager.IsAllowed(PermissionsManager.Permission.WORandomizeClouds, source) && !allWOPermissions)
+ {
+ BanManager.BanCheater(source);
+ return;
+ }
+
var opacity = float.Parse(new Random().NextDouble().ToString());
var type = CloudTypes[new Random().Next(0, CloudTypes.Count)];
TriggerClientEvent("vMenu:SetClouds", opacity, type);
@@ -715,13 +790,34 @@ internal void UpdateWeatherCloudsType(bool removeClouds)
///
///
///
- ///
[EventHandler("vMenu:UpdateServerTime")]
- internal void UpdateTime(int newHours, int newMinutes, bool freezeTimeNew)
+ internal void UpdateTime([FromSource] Player source, int newHours, int newMinutes)
{
+ if (!PermissionsManager.IsAllowed(PermissionsManager.Permission.TOSetTime, source) && !PermissionsManager.IsAllowed(PermissionsManager.Permission.TOAll, source))
+ {
+ BanManager.BanCheater(source);
+ return;
+ }
+
CurrentHours = newHours;
CurrentMinutes = newMinutes;
- FreezeTime = freezeTimeNew;
+ }
+
+ ///
+ /// Set and sync if time is frozen for all clients.
+ ///
+ ///
+ ///
+ [EventHandler("vMenu:FreezeServerTime")]
+ internal void FreezeServerTime([FromSource] Player source, bool freezeTime)
+ {
+ if (!PermissionsManager.IsAllowed(PermissionsManager.Permission.TOFreezeTime, source) && !PermissionsManager.IsAllowed(PermissionsManager.Permission.TOAll, source))
+ {
+ BanManager.BanCheater(source);
+ return;
+ }
+
+ FreezeTime = freezeTime;
}
#endregion
@@ -735,34 +831,31 @@ internal void UpdateTime(int newHours, int newMinutes, bool freezeTimeNew)
[EventHandler("vMenu:KickPlayer")]
internal void KickPlayer([FromSource] Player source, int target, string kickReason = "You have been kicked from the server.")
{
- if (IsPlayerAceAllowed(source.Handle, "vMenu.OnlinePlayers.Kick") || IsPlayerAceAllowed(source.Handle, "vMenu.Everything") ||
- IsPlayerAceAllowed(source.Handle, "vMenu.OnlinePlayers.All"))
+ if (!PermissionsManager.IsAllowed(PermissionsManager.Permission.OPKick, source) && !PermissionsManager.IsAllowed(PermissionsManager.Permission.OPAll, source))
{
- // If the player is allowed to be kicked.
- var targetPlayer = Players[target];
- if (targetPlayer != null)
- {
- if (!IsPlayerAceAllowed(targetPlayer.Handle, "vMenu.DontKickMe"))
- {
- TriggerEvent("vMenu:KickSuccessful", source.Name, kickReason, targetPlayer.Name);
+ BanManager.BanCheater(source);
+ return;
+ }
- KickLog($"Player: {source.Name} has kicked: {targetPlayer.Name} for: {kickReason}.");
- TriggerClientEvent(player: source, eventName: "vMenu:Notify", args: $"The target player ({targetPlayer.Name}) has been kicked.");
+ Player targetPlayer = GetPlayerFromServerId(target);
- // Kick the player from the server using the specified reason.
- DropPlayer(targetPlayer.Handle, kickReason);
- return;
- }
- // Trigger the client event on the source player to let them know that kicking this player is not allowed.
- TriggerClientEvent(player: source, eventName: "vMenu:Notify", args: "Sorry, this player can ~r~not ~w~be kicked.");
- return;
- }
- TriggerClientEvent(player: source, eventName: "vMenu:Notify", args: "An unknown error occurred. Report it here: vespura.com/vmenu");
+ if (targetPlayer is null)
+ {
+ source.TriggerEvent("vMenu:Notify", "Failed to kick target, because the target could not be found. Did they already leave?");
+ return;
}
- else
+
+ if (PermissionsManager.IsAllowed(PermissionsManager.Permission.DontKickMe, targetPlayer))
{
- BanManager.BanCheater(source);
+ source.TriggerEvent("vMenu:Notify", "Sorry, this player can ~r~not ~w~be kicked.");
+ return;
}
+
+ KickLog($"Player: {source.Name} has kicked: {targetPlayer.Name} for: {kickReason}.");
+
+ source.TriggerEvent("vMenu:Notify", $"The target player ({targetPlayer.Name}) has been kicked.");
+
+ targetPlayer.Drop(kickReason);
}
///
@@ -773,22 +866,20 @@ internal void KickPlayer([FromSource] Player source, int target, string kickReas
[EventHandler("vMenu:KillPlayer")]
internal void KillPlayer([FromSource] Player source, int target)
{
- if (IsPlayerAceAllowed(source.Handle, "vMenu.OnlinePlayers.Kill") || IsPlayerAceAllowed(source.Handle, "vMenu.Everything") ||
- IsPlayerAceAllowed(source.Handle, "vMenu.OnlinePlayers.All"))
+ if (!PermissionsManager.IsAllowed(PermissionsManager.Permission.OPKill, source) && !PermissionsManager.IsAllowed(PermissionsManager.Permission.OPAll, source))
{
- var targetPlayer = Players[target];
- if (targetPlayer != null)
- {
- // Trigger the client event on the target player to make them kill themselves. R.I.P.
- TriggerClientEvent(player: targetPlayer, eventName: "vMenu:KillMe", args: source.Name);
- return;
- }
- TriggerClientEvent(player: source, eventName: "vMenu:Notify", args: "An unknown error occurred. Report it here: vespura.com/vmenu");
+ BanManager.BanCheater(source);
+ return;
}
- else
+
+ Player targetPlayer = GetPlayerFromServerId(target);
+
+ if (targetPlayer is null)
{
- BanManager.BanCheater(source);
+ return;
}
+
+ targetPlayer.TriggerEvent("vMenu:KillMe", source.Name);
}
///
@@ -797,52 +888,129 @@ internal void KillPlayer([FromSource] Player source, int target)
///
///
[EventHandler("vMenu:SummonPlayer")]
- internal void SummonPlayer([FromSource] Player source, int target)
+ internal async void SummonPlayer([FromSource] Player source, int target, int numberOfSeats)
{
- if (IsPlayerAceAllowed(source.Handle, "vMenu.OnlinePlayers.Summon") || IsPlayerAceAllowed(source.Handle, "vMenu.Everything") ||
- IsPlayerAceAllowed(source.Handle, "vMenu.OnlinePlayers.All"))
+ if (!PermissionsManager.IsAllowed(PermissionsManager.Permission.OPSummon, source) && !PermissionsManager.IsAllowed(PermissionsManager.Permission.OPAll, source))
+ {
+ BanManager.BanCheater(source);
+ return;
+ }
+
+ Player targetPlayer = GetPlayerFromServerId(target);
+
+ if (targetPlayer is null)
+ {
+ return;
+ }
+
+ int targetPedHandle;
+ Ped targetPed = targetPlayer.Character;
+
+ if (targetPed is null || !DoesEntityExist(targetPedHandle = targetPed.Handle))
+ {
+ return;
+ }
+
+ bool lastVehicle = false;
+ Ped sourcePed = source.Character;
+ int sourcePedHandle = sourcePed.Handle;
+ int sourcePedVehicle = GetVehiclePedIsIn(sourcePedHandle, lastVehicle);
+
+ if (sourcePedVehicle == 0)
+ {
+ targetPed.Position = sourcePed.Position;
+ return;
+ }
+
+ bool seatFound = false;
+
+ // Seat indices start at `-1`
+ numberOfSeats -= 1;
+
+ for (int i = -1; i < numberOfSeats; i++)
{
- // Trigger the client event on the target player to make them teleport to the source player.
- var targetPlayer = Players[target];
- if (targetPlayer != null)
+ bool seatFree = GetPedInVehicleSeat(sourcePedVehicle, i) == 0;
+
+ if (!seatFree)
{
- TriggerClientEvent(player: targetPlayer, eventName: "vMenu:GoToPlayer", args: source.Handle);
- return;
+ continue;
+ }
+
+ Vector3 checkPosition;
+ long timeout = GetGameTimer() + 1_500;
+ Vector3 priorPosition = targetPed.Position;
+ Vector3 newPosition = sourcePed.Position + new Vector3(0f, 0f, 5f);
+
+ seatFound = true;
+ targetPed.Position = newPosition;
+
+ while (timeout > GetGameTimer() && priorPosition.DistanceToSquared(checkPosition = targetPed.Position) < newPosition.DistanceToSquared(checkPosition))
+ {
+ await Delay(100);
+ }
+
+ if (timeout < GetGameTimer())
+ {
+ source.TriggerEvent("vMenu:Notify", "Failed to teleport player.");
+ break;
}
- TriggerClientEvent(player: source, eventName: "vMenu:Notify", args: "An unknown error occurred. Report it here: vespura.com/vmenu");
+
+ SetPedIntoVehicle(targetPedHandle, sourcePedVehicle, i);
+ break;
}
- else
+
+ if (!seatFound)
{
- BanManager.BanCheater(source);
+ source.TriggerEvent("vMenu:Notify", "No free seats in your vehicle for summoned player.");
}
}
[EventHandler("vMenu:SendMessageToPlayer")]
- internal void SendPrivateMessage([FromSource] Player source, int targetServerId, string message)
+ internal void SendPrivateMessage([FromSource] Player source, int target, string message)
{
- var targetPlayer = Players[targetServerId];
- if (targetPlayer != null)
+ if (!PermissionsManager.IsAllowed(PermissionsManager.Permission.OPSendMessage, source) && !PermissionsManager.IsAllowed(PermissionsManager.Permission.OPAll, source))
+ {
+ BanManager.BanCheater(source);
+ return;
+ }
+
+ bool sourcePmsDisabled = source.State.Get("vmenu_pms_disabled") ?? false;
+
+ if (sourcePmsDisabled)
{
- targetPlayer.TriggerEvent("vMenu:PrivateMessage", source.Handle, message);
+ source.TriggerEvent("vMenu:Notify", "You can't send a private message if you have private messages disabled yourself. Enable them in the Misc Settings menu and try again.");
+ return;
+ }
+
+ Player targetPlayer = GetPlayerFromServerId(target);
+
+ if (targetPlayer is null)
+ {
+ source.TriggerEvent("vMenu:Notify", "Failed to send message because the target could not be found. Did they disconnect?");
+ return;
+ }
- foreach (var p in Players)
+ bool targetPmsDisabled = targetPlayer.State.Get("vmenu_pms_disabled") ?? false;
+
+ if (targetPmsDisabled)
+ {
+ source.TriggerEvent("vMenu:Notify", $"Sorry, your private message to {source.Name}~s~ could not be delivered because they have private messages disabled.");
+ return;
+ }
+
+ targetPlayer.TriggerEvent("vMenu:PrivateMessage", source.Handle, message);
+
+ foreach (string playerHandle in joinedPlayers)
+ {
+ if (!PermissionsManager.IsAllowed(PermissionsManager.Permission.OPSeePrivateMessages, playerHandle) && !PermissionsManager.IsAllowed(PermissionsManager.Permission.OPAll, playerHandle))
{
- if (p != source && p != targetPlayer)
- {
- if (vMenuShared.PermissionsManager.IsAllowed(vMenuShared.PermissionsManager.Permission.OPSeePrivateMessages, p))
- {
- p.TriggerEvent("vMenu:Notify", $"[vMenu Staff Log] {source.Name}~s~ sent a PM to {targetPlayer.Name}~s~: {message}");
- }
- }
+ continue;
}
- }
- }
- [EventHandler("vMenu:PmsDisabled")]
- internal void NotifySenderThatDmsAreDisabled([FromSource] Player source, string senderServerId)
- {
- var p = Players[int.Parse(senderServerId)];
- p?.TriggerEvent("vMenu:Notify", $"Sorry, your private message to {source.Name}~s~ could not be delivered because they disabled private messages.");
+ Player player = GetPlayerFromServerId(playerHandle);
+
+ player?.TriggerEvent("vMenu:Notify", $"[vMenu Staff Log] {source.Name}~s~ sent a PM to {targetPlayer.Name}~s~: {message}");
+ }
}
#endregion
@@ -874,16 +1042,33 @@ private static void KickLog(string kickLogMesage)
#region Add teleport location
[EventHandler("vMenu:SaveTeleportLocation")]
- internal void AddTeleportLocation([FromSource] Player _, string locationJson)
+ internal void AddTeleportLocation([FromSource] Player source, string locationJson)
{
- var location = JsonConvert.DeserializeObject(locationJson);
- if (GetTeleportLocationsData().Any(loc => loc.name == location.name))
+ if (!PermissionsManager.IsAllowed(PermissionsManager.Permission.MSTeleportSaveLocation, source) && !PermissionsManager.IsAllowed(PermissionsManager.Permission.MSAll, source))
+ {
+ BanManager.BanCheater(source);
+ return;
+ }
+
+ TeleportLocation teleportLocation;
+
+ try
+ {
+ teleportLocation = JsonConvert.DeserializeObject(locationJson);
+ }
+ catch
+ {
+ Log("Teleport location could not be deserialized, location was not saved.", LogLevel.error);
+ return;
+ }
+
+ if (GetTeleportLocationsData().Exists(loc => loc.name == teleportLocation.name))
{
Log("A teleport location with this name already exists, location was not saved.", LogLevel.error);
return;
}
var locs = GetLocations();
- locs.teleports.Add(location);
+ locs.teleports.Add(teleportLocation);
if (!SaveResourceFile(GetCurrentResourceName(), "config/locations.json", JsonConvert.SerializeObject(locs, Formatting.Indented), -1))
{
Log("Could not save locations.json file, reason unknown.", LogLevel.error);
@@ -893,14 +1078,7 @@ internal void AddTeleportLocation([FromSource] Player _, string locationJson)
#endregion
#region Infinity bits
- private void RequestServerStateFromPlayer([FromSource] Player player)
- {
- player.TriggerEvent("vMenu:SetServerState", new
- {
- IsInfinity = GetConvar("onesync_enableInfinity", "false") == "true"
- });
- }
-
+ // TODO: Replace this logic and all child logic with statebags (server set, client read)
[EventHandler("vMenu:RequestPlayerList")]
internal void RequestPlayerListFromPlayer([FromSource] Player player)
{
@@ -912,35 +1090,67 @@ internal void RequestPlayerListFromPlayer([FromSource] Player player)
}
[EventHandler("vMenu:GetPlayerCoords")]
- internal void GetPlayerCoords([FromSource] Player source, int playerId, NetworkCallbackDelegate callback)
+ internal void GetPlayerCoords([FromSource] Player source, long rpcId, int playerId, NetworkCallbackDelegate callback)
{
- if (IsPlayerAceAllowed(source.Handle, "vMenu.OnlinePlayers.Teleport") || IsPlayerAceAllowed(source.Handle, "vMenu.Everything") ||
- IsPlayerAceAllowed(source.Handle, "vMenu.OnlinePlayers.All"))
+ var coords = Vector3.Zero;
+
+ if (PermissionsManager.IsAllowed(PermissionsManager.Permission.OPTeleport, source) || PermissionsManager.IsAllowed(PermissionsManager.Permission.OPAll, source))
{
- var coords = Players[playerId]?.Character?.Position ?? Vector3.Zero;
+ Player targetPlayer = GetPlayerFromServerId(playerId);
- _ = callback(coords);
+ if (targetPlayer is not null)
+ {
+ Ped targetPed = targetPlayer.Character;
- return;
+ if (targetPed is not null && DoesEntityExist(targetPed.Handle))
+ {
+ coords = targetPed.Position;
+ }
+ }
}
- _ = callback(Vector3.Zero);
+ source.TriggerEvent("vMenu:GetPlayerCoords:reply", rpcId, coords);
}
#endregion
#region Player join/quit
private readonly HashSet joinedPlayers = new();
- private Task PlayersFirstTick()
+ private IEnumerable GetJoinQuitNotifPlayers()
+ {
+ List players = [];
+
+ foreach (string playerHandle in joinedPlayers)
+ {
+ if (!PermissionsManager.IsAllowed(PermissionsManager.Permission.MSJoinQuitNotifs, playerHandle) && !PermissionsManager.IsAllowed(PermissionsManager.Permission.MSAll, playerHandle))
+ {
+ continue;
+ }
+
+ Player player = GetPlayerFromServerId(playerHandle);
+
+ if (player is not null)
+ {
+ players.Add(player);
+ }
+ }
+
+ return players;
+ }
+
+ private async Task PlayersFirstTick()
{
Tick -= PlayersFirstTick;
+ // Allow plenty of time for the connected clients to restart their client scripts
+ await Delay(3_000);
+
foreach (var player in Players)
{
joinedPlayers.Add(player.Handle);
- }
- return Task.FromResult(0);
+ PermissionsManager.SetPermissionsForPlayer(player);
+ }
}
[EventHandler("playerJoining")]
@@ -948,13 +1158,13 @@ internal void OnPlayerJoining([FromSource] Player sourcePlayer)
{
joinedPlayers.Add(sourcePlayer.Handle);
- foreach (var player in Players)
+ PermissionsManager.SetPermissionsForPlayer(sourcePlayer);
+
+ string sourcePlayerName = sourcePlayer.Name;
+
+ foreach (Player notifPlayer in GetJoinQuitNotifPlayers())
{
- if (IsPlayerAceAllowed(player.Handle, "vMenu.MiscSettings.JoinQuitNotifs") ||
- IsPlayerAceAllowed(player.Handle, "vMenu.MiscSettings.All"))
- {
- player.TriggerEvent("vMenu:PlayerJoinQuit", sourcePlayer.Name, null);
- }
+ notifPlayer.TriggerEvent("vMenu:PlayerJoinQuit", sourcePlayerName, null);
}
}
@@ -968,15 +1178,37 @@ internal void OnPlayerDropped([FromSource] Player sourcePlayer, string reason)
joinedPlayers.Remove(sourcePlayer.Handle);
- foreach (var player in Players)
+ string sourcePlayerName = sourcePlayer.Name;
+
+ foreach (Player notifPlayer in GetJoinQuitNotifPlayers())
{
- if (IsPlayerAceAllowed(player.Handle, "vMenu.MiscSettings.JoinQuitNotifs") ||
- IsPlayerAceAllowed(player.Handle, "vMenu.MiscSettings.All"))
- {
- player.TriggerEvent("vMenu:PlayerJoinQuit", sourcePlayer.Name, reason);
- }
+ notifPlayer.TriggerEvent("vMenu:PlayerJoinQuit", sourcePlayerName, reason);
}
}
#endregion
+
+ #region Utilities
+ private Player GetPlayerFromServerId(string serverId)
+ {
+ if (!int.TryParse(serverId, out int serverIdInt))
+ {
+ return null;
+ }
+
+ return GetPlayerFromServerId(serverIdInt);
+ }
+
+ private Player GetPlayerFromServerId(int serverId)
+ {
+ string serverIdString = serverId.ToString();
+
+ if (serverId <= 0 || !DoesPlayerExist(serverIdString))
+ {
+ return null;
+ }
+
+ return Players[serverId];
+ }
+ #endregion
}
}
diff --git a/vMenuServer/config/permissions.cfg b/vMenuServer/config/permissions.cfg
index 960eb4ae4..303848d28 100644
--- a/vMenuServer/config/permissions.cfg
+++ b/vMenuServer/config/permissions.cfg
@@ -80,9 +80,6 @@ setr vmenu_pvp_mode 0
# false = vMenu will not touch this feature, by default this means that head props will fall off when the player is hit, which is the default behavior for GTA V Single Player peds.
setr keep_player_head_props true
-# Set this to true if you don't want vMenu to use any server information convars.
-setr vmenu_disable_server_info_convars false
-
# Distance for playerblips to showup. This is using "game units" as measurement. It's unknown
# what this is in relation to meters or something similar, but 500.0 seems fine in most cases.
setr vmenu_player_names_distance 500.0
@@ -133,6 +130,12 @@ setr vmenu_sync_to_machine_time false
# Setting this to true will enable the chameleon colour vehicle paints category in the primary colours menu.
# You must be streaming the chameleon colours in order for this to function properly.
setr vmenu_using_chameleon_colours false
+# Setting this to true will prevent players from modifying vehicle extras to repair their vehicle when it is damaged.
+setr vmenu_prevent_extras_when_damaged false
+# This is the amount of engine damage before the extras will be blocked. Value must be between 0 and 1000 (inclusive)
+setr vmenu_allowed_engine_damage_for_extra_change 800
+# This is the amount of body damage before the extras will be blocked. Value must be between 0 and 1000 (inclusive)
+setr vmenu_allowed_body_damage_for_extra_change 800
### MP Ped options ###
# Setting this to true will enable a 3D ped preview when viewing saved MP Peds.
@@ -199,6 +202,7 @@ add_ace builtin.everyone "vMenu.OnlinePlayers.Menu" allow
add_ace builtin.everyone "vMenu.OnlinePlayers.Teleport" allow
add_ace builtin.everyone "vMenu.OnlinePlayers.Waypoint" allow
add_ace builtin.everyone "vMenu.OnlinePlayers.Spectate" allow
+add_ace builtin.everyone "vMenu.OnlinePlayers.SendMessage" allow
# Moderators & admins only:
add_ace group.moderator "vMenu.OnlinePlayers.Summon" allow
@@ -285,6 +289,7 @@ add_ace builtin.everyone "vMenu.VehicleOptions.All" allow
#add_ace builtin.everyone "vMenu.VehicleOptions.InfiniteFuel" allow
#add_ace builtin.everyone "vMenu.VehicleOptions.VOFlares" allow
#add_ace builtin.everyone "vMenu.VehicleOptions.VOPlaneBombs" allow
+#add_ace group.moderator "vMenu.VehicleOptions.BypassExtraDamage" allow
####################################
# VEHICLE SPAWNER MENU #
diff --git a/vMenuServer/vMenuServer.csproj b/vMenuServer/vMenuServer.csproj
index dec00d96b..1d9c44a3c 100644
--- a/vMenuServer/vMenuServer.csproj
+++ b/vMenuServer/vMenuServer.csproj
@@ -32,13 +32,7 @@
-
- Always
-
-
- Always
-
-
+
Always