C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Rust;
using Rust.Modular;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using System.Text;
namespace Oxide.Plugins
{
[Info("Spawn Modular Car", "WhiteThunder", "5.3.0")]
[Description("Allows players to spawn modular cars.")]
internal class SpawnModularCar : CovalencePlugin
{
#region Fields
[PluginReference]
private readonly Plugin MonumentFinder, VehicleDeployedLocks;
private static SpawnModularCar _pluginInstance;
private static Configuration _pluginConfig;
private PluginData _pluginData;
private CommonPresets _commonPresets;
private const string DefaultPresetName = "default";
private const int PresetMaxLength = 30;
private const string PermissionSpawnSockets2 = "spawnmodularcar.spawn.2";
private const string PermissionSpawnSockets3 = "spawnmodularcar.spawn.3";
private const string PermissionSpawnSockets4 = "spawnmodularcar.spawn.4";
private const string PermissionEnginePartsTier1 = "spawnmodularcar.engineparts.tier1";
private const string PermissionEnginePartsTier2 = "spawnmodularcar.engineparts.tier2";
private const string PermissionEnginePartsTier3 = "spawnmodularcar.engineparts.tier3";
private const string PermissionFix = "spawnmodularcar.fix";
private const string PermissionFetch = "spawnmodularcar.fetch";
private const string PermissionDespawn = "spawnmodularcar.despawn";
private const string PermissionAutoFuel = "spawnmodularcar.autofuel";
private const string PermissionAutoCodeLock = "spawnmodularcar.autocodelock";
private const string PermissionAutoKeyLock = "spawnmodularcar.autokeylock";
private const string PermissionAutoStartEngine = "spawnmodularcar.autostartengine";
private const string PermissionAutoFillTankers = "spawnmodularcar.autofilltankers";
private const string PermissionGiveCar = "spawnmodularcar.givecar";
private const string PermissionPresets = "spawnmodularcar.presets";
private const string PermissionPresetLoad = "spawnmodularcar.presets.load";
private const string PermissionCommonPresets = "spawnmodularcar.presets.common";
private const string PermissionManageCommonPresets = "spawnmodularcar.presets.common.manage";
private const string PrefabSockets2 = "assets/content/vehicles/modularcar/2module_car_spawned.entity.prefab";
private const string PrefabSockets3 = "assets/content/vehicles/modularcar/3module_car_spawned.entity.prefab";
private const string PrefabSockets4 = "assets/content/vehicles/modularcar/4module_car_spawned.entity.prefab";
private const string ItemDropPrefab = "assets/prefabs/misc/item drop/item_drop.prefab";
private const string RepairEffectPrefab = "assets/bundled/prefabs/fx/build/promote_toptier.prefab";
private const string TankerFilledEffectPrefab = "assets/prefabs/food/water jug/effects/water-jug-fill-container.prefab";
// These layers are used to preventing spawning inside walls or players.private const int BoxcastLayers = Layers.Mask.Default
+ Layers.Mask.Deployed
+ Layers.Mask.Player_Server
+ Layers.Mask.AI
+ Layers.Mask.Vehicle_Detailed
+ Layers.Mask.Vehicle_World
+ Layers.Mask.World
+ Layers.Mask.Construction
+ Layers.Mask.Tree;
// These layers are used to find a surface to spawn on.private const int RaycastLayers = Layers.Mask.Default
+ Layers.Mask.Terrain
+ Layers.World
+ Layers.Mask.Construction;
private static readonly Vector3 ShortCarExtents = new(1, 1.1f, 1.5f);
private static readonly Vector3 MediumCarExtents = new(1, 1.1f, 2.3f);
private static readonly Vector3 LongCarExtents = new(1, 1.1f, 3);
private static readonly Vector3 ShortCarFrontLeft = new(ShortCarExtents.x, 0, ShortCarExtents.z);
private static readonly Vector3 ShortCarFrontRight = new(-ShortCarExtents.x, 0, ShortCarExtents.z);
private static readonly Vector3 ShortCarBackLeft = new(ShortCarExtents.x, 0, -ShortCarExtents.z);
private static readonly Vector3 ShortCarBackRight = new(-ShortCarExtents.x, 0, -ShortCarExtents.z);
private static readonly Vector3 MediumCarFrontLeft = new(MediumCarExtents.x, 0, MediumCarExtents.z);
private static readonly Vector3 MediumCarFrontRight = new(-MediumCarExtents.x, 0, MediumCarExtents.z);
private static readonly Vector3 MediumCarBackLeft = new(MediumCarExtents.x, 0, -MediumCarExtents.z);
private static readonly Vector3 MediumCarBackRight = new(-MediumCarExtents.x, 0, -MediumCarExtents.z);
private static readonly Vector3 LongCarFrontLeft = new(LongCarExtents.x, 0, LongCarExtents.z);
private static readonly Vector3 LongCarFrontRight = new(-LongCarExtents.x, 0, LongCarExtents.z);
private static readonly Vector3 LongCarBackLeft = new(LongCarExtents.x, 0, -LongCarExtents.z);
private static readonly Vector3 LongCarBackRight = new(-LongCarExtents.x, 0, -LongCarExtents.z);
private static readonly float ForwardRaycastDistance = 1.5f + ShortCarExtents.x;
private const float DownwardRaycastDistance = 4;
private readonly RaycastHit[] _raycastBuffer = new RaycastHit[1];
private readonly Dictionary<string, PlayerConfig> _playerConfigsMap = new();
#endregion
#region Hooks
private void Init()
{
_pluginInstance = this;
_pluginData = PluginData.LoadData();
_commonPresets = CommonPresets.LoadData(_pluginData);
MigrateConfig();
permission.RegisterPermission(PermissionSpawnSockets2, this);
permission.RegisterPermission(PermissionSpawnSockets3, this);
permission.RegisterPermission(PermissionSpawnSockets4, this);
permission.RegisterPermission(PermissionEnginePartsTier1, this);
permission.RegisterPermission(PermissionEnginePartsTier2, this);
permission.RegisterPermission(PermissionEnginePartsTier3, this);
permission.RegisterPermission(PermissionFix, this);
permission.RegisterPermission(PermissionFetch, this);
permission.RegisterPermission(PermissionDespawn, this);
permission.RegisterPermission(PermissionAutoFuel, this);
permission.RegisterPermission(PermissionAutoCodeLock, this);
permission.RegisterPermission(PermissionAutoKeyLock, this);
permission.RegisterPermission(PermissionAutoStartEngine, this);
permission.RegisterPermission(PermissionAutoFillTankers, this);
permission.RegisterPermission(PermissionGiveCar, this);
permission.RegisterPermission(PermissionPresets, this);
permission.RegisterPermission(PermissionPresetLoad, this);
permission.RegisterPermission(PermissionCommonPresets, this);
permission.RegisterPermission(PermissionManageCommonPresets, this);
}
private void OnServerInitialized()
{
if (_pluginConfig.HasMonumentRestriction && MonumentFinder == null)
{
LogWarning("The Monument Finder plugin is not loaded, so monument restrictions will not work.If you don't want monument restrictions, set \"DisallowedMonuments\": [] in the config to stop seeing this warning.");
}
}
private void Unload()
{
_pluginInstance = null;
_pluginConfig = null;
}
private void OnNewSave(string filename)
{
_pluginData.PlayerCars.Clear();
_pluginData.Cooldowns.ClearAll();
_pluginData.SaveData();
}
private void OnEntityKill(ModularCar car)
{
if (!IsPlayerCar(car))
return;
var userId = _pluginData.PlayerCars.FirstOrDefault(x => x.Value == car.net.ID.Value).Key;
var player = BasePlayer.Find(userId);
if (player != null)
ChatMessage(player, "Generic.Info.CarDestroyed");
_pluginData.UnregisterCar(userId);
}
private void OnEngineStarted(ModularCar car, BasePlayer player)
{
if (car == null
|| car.OwnerID == 0
|| !_pluginData.PlayerCars.ContainsValue(car.net.ID.Value)
|| !permission.UserHasPermission(car.OwnerID.ToString(), PermissionAutoStartEngine))
return;
if (car.engineController.IsStarting)
{
car.CancelInvoke(car.engineController.FinishStartingEngine);
car.engineController.FinishStartingEngine();
}
}
#endregion
#region API
private static class ApiParser
{
public static string CodeLockField = "CodeLock";
public static string KeyLockField = "KeyLock";
public static string EnginePartsTierField = "EnginePartsTier";
public static string FreshWaterAmountField = "FreshWaterAmount";
public static string FuelAmountField = "FuelAmount";
public static string ModulesField = "Modules";
public static bool TryParseOptions(Dictionary<string, object> options, out PresetCarOptions presetOptions)
{
var codeLock = BoolOption(options, CodeLockField);
var keyLock = BoolOption(options, KeyLockField);
var enginePartsTier = IntOption(options, EnginePartsTierField);
var freshWaterAmount = IntOption(options, FreshWaterAmountField);
var fuelAmount = IntOption(options, FuelAmountField);
var moduleIDs = ParseModulesOption(options);
presetOptions = null;
if (moduleIDs == null)
{
_pluginInstance.LogError($"[API] '{ApiParser.ModulesField}' field is missing or unrecognizable.");
return false;
}
if (moduleIDs.Length is < 2 or > 4)
{
_pluginInstance.LogError($"[API] Requested a car with {moduleIDs.Length} sockets, but only 2-4 sockets is supported.");
return false;
}
presetOptions = new PresetCarOptions
{
CodeLock = codeLock,
KeyLock = keyLock,
EnginePartsTier = enginePartsTier,
FreshWaterAmount = freshWaterAmount,
FuelAmount = fuelAmount,
NormalizedModuleIDs = moduleIDs
};
return true;
}
private static bool BoolOption(Dictionary<string, object> options, string name)
{
return options.TryGetValue(name, out var value) && value is true;
}
private static int IntOption(Dictionary<string, object> options, string name)
{
return options.TryGetValue(name, out var value) && value is int i
? i
: 0;
}
public static int[] ParseModulesOption(Dictionary<string, object> options)
{
if (!options.ContainsKey(ModulesField))
return null;
if (options[ModulesField] is not object[] moduleArray)
return null;
return _pluginInstance.ValidateModules(moduleArray);
}
}
private ModularCar API_SpawnPreset(Dictionary<string, object> options, BasePlayer player, Vector3 position, Quaternion rotation)
{
if (!ApiParser.TryParseOptions(options, out var presetOptions))
return null;
if (SpawnWasBlocked(player))
return null;
if (position == Vector3.zero && player != null)
DetermineCarPositionAndRotation(player, presetOptions.Length, out position, out rotation);
return SpawnCar(presetOptions, position, rotation, player, shouldTrackCar: false);
}
private ModularCar API_SpawnNamedPreset(string presetName, BasePlayer player, Vector3 position, Quaternion rotation)
{
var presetOptions = _pluginConfig.FindPreset(presetName)?.Options;
if (presetOptions == null)
{
LogError($"[API] Server preset '{presetName}' not found.");
return null;
}
if (presetOptions.Length is < 2 or > 4)
{
LogError($"[API] Requested a car with {presetOptions.Length} sockets, but only 2-4 sockets is supported.");
return null;
}
if (SpawnWasBlocked(player))
return null;
if (position == Vector3.zero && player != null)
DetermineCarPositionAndRotation(player, presetOptions.Length, out position, out rotation);
return SpawnCar(presetOptions, position, rotation, player, shouldTrackCar: false);
}
private ModularCar API_SpawnPresetCar(BasePlayer player, Dictionary<string, object> options, Action<ModularCar> onReady = null)
{
if (!ApiParser.TryParseOptions(options, out var presetOptions))
return null;
if (SpawnWasBlocked(player))
return null;
if (!TryGetIdealCarPositionAndRotation(player, presetOptions.Length, out var spawnPosition, out var rotation))
{
spawnPosition = GetFixedCarPosition(player);
rotation = GetRelativeCarRotation(player);
}
var car = SpawnCar(presetOptions, spawnPosition, rotation, player, shouldTrackCar: false);
if (car != null)
{
// Note: Consumers no longer need to use this callback since this plugin now forces synchronous module registration.onReady?.Invoke(car);
}
return car;
}
#endregion
#region Commands
[Command("givecar")]
private void SpawnCarServerCommand(IPlayer player, string cmd, string[] args)
{
if (!player.IsServer && !VerifyPermissionAny(player, PermissionGiveCar))
return;
if (args.Length < 2)
{
ReplyToPlayer(player, "Command.Give.Error.Syntax");
return;
}
var playerNameOrIdArg = args[0];
var presetNameArg = args[1];
var targetPlayer = BasePlayer.Find(playerNameOrIdArg);
if (targetPlayer == null)
{
ReplyToPlayer(player, "Command.Give.Error.PlayerNotFound", playerNameOrIdArg);
return;
}
var preset = _pluginConfig.FindPreset(presetNameArg);
if (preset == null)
{
ReplyToPlayer(player, "Generic.Error.PresetNotFound", presetNameArg);
return;
}
var carOptions = preset.Options;
if (carOptions.Length < 2)
{
ReplyToPlayer(player, "Command.Give.Error.PresetTooFewModules", preset.Name, carOptions.Length);
return;
}
if (carOptions.Length > 4)
{
ReplyToPlayer(player, "Command.Give.Error.PresetTooManyModules", preset.Name, carOptions.Length);
return;
}
if (!TryGetIdealCarPositionAndRotation(targetPlayer, preset.Options.Length, out var spawnPosition, out var rotation))
{
spawnPosition = GetFixedCarPosition(targetPlayer);
rotation = GetRelativeCarRotation(targetPlayer);
}
var car = SpawnCar(carOptions, spawnPosition, rotation, targetPlayer, shouldTrackCar: false);
if (car != null)
{
ReplyToPlayer(player, "Command.Give.Success", targetPlayer.displayName, preset.Name);
}
}
[Command("mycar")]
private void MyCarCommand(IPlayer player, string cmd, string[] args)
{
if (player.IsServer)
return;
var basePlayer = player.Object as BasePlayer;
if (!basePlayer.CanInteract())
return;
if (args.Length == 0)
{
SubCommand_SpawnCar(player, args);
return;
}
switch (args[0].ToLower())
{
case "help":
SubCommand_Help(player, args.Skip(1).ToArray());
return;
case "list":
SubCommand_ListPresets(player, args.Skip(1).ToArray());
return;
case "save":
SubCommand_SavePreset(player, args.Skip(1).ToArray());
return;
case "update":
SubCommand_UpdatePreset(player, args.Skip(1).ToArray());
return;
case "load":
SubCommand_LoadPreset(player, args.Skip(1).ToArray());
return;
case "rename":
SubCommand_RenamePreset(player, args.Skip(1).ToArray());
return;
case "delete":
SubCommand_DeletePreset(player, args.Skip(1).ToArray());
return;
case "fix":
SubCommand_FixCar(player, args.Skip(1).ToArray());
return;
case "fetch":
SubCommand_FetchCar(player, args.Skip(1).ToArray());
return;
case "destroy":
SubCommand_DestroyCar(player, args.Skip(1).ToArray());
return;
case "autocodelock":
SubCommand_ToggleAutoCodeLock(player, args.Skip(1).ToArray());
return;
case "autokeylock":
SubCommand_ToggleAutoKeyLock(player, args.Skip(1).ToArray());
return;
case "autofilltankers":
SubCommand_ToggleAutoFillTankers(player, args.Skip(1).ToArray());
return;
case "common":
SubCommand_CommonPreset(player, args.Skip(1).ToArray());
return;
default:
SubCommand_SpawnCar(player, args);
return;
}
}
private void SubCommand_CommonPreset(IPlayer player, string[] args)
{
if (args.Length == 0)
{
ReplyToPlayer(player, "Command.Common.Error.Syntax");
return;
}
switch (args[0].ToLower())
{
case "list":
SubCommand_Common_ListPresets(player, args.Skip(1).ToArray());
return;
case "load":
SubCommand_Common_LoadPreset(player, args.Skip(1).ToArray());
return;
case "save":
SubCommand_Common_SavePreset(player, args.Skip(1).ToArray());
return;
case "update":
SubCommand_Common_UpdatePreset(player, args.Skip(1).ToArray());
return;
case "rename":
SubCommand_Common_RenamePreset(player, args.Skip(1).ToArray());
return;
case "delete":
SubCommand_Common_DeletePreset(player, args.Skip(1).ToArray());
return;
default:
SubCommand_Common_SpawnCar(player, args);
return;
}
}
private void SubCommand_Help(IPlayer player, string[] args)
{
var maxAllowedSockets = GetPlayerMaxAllowedCarSockets(player.Id);
if (maxAllowedSockets == 0)
{
ReplyToPlayer(player, "Generic.Error.NoPermission");
return;
}
var canUsePresets = permission.UserHasPermission(player.Id, PermissionPresets);
var canLoadPresets = permission.UserHasPermission(player.Id, PermissionPresetLoad);
var sb = new StringBuilder();
sb.AppendLine(GetMessage(player, "Command.Help"));
if (canUsePresets)
{
sb.AppendLine(GetMessage(player, "Command.Help.Spawn.Basic.PresetsAllowed"));
}
else
{
sb.AppendLine(GetMessage(player, "Command.Help.Spawn.Basic"));
}
sb.AppendLine(GetMessage(player, "Command.Help.Spawn.Sockets"));
if (permission.UserHasPermission(player.Id, PermissionFix))
{
sb.AppendLine(GetMessage(player, "Command.Help.Fix"));
}
if (permission.UserHasPermission(player.Id, PermissionFetch))
{
sb.AppendLine(GetMessage(player, "Command.Help.Fetch"));
}
if (permission.UserHasPermission(player.Id, PermissionDespawn))
{
sb.AppendLine(GetMessage(player, "Command.Help.Destroy"));
}
if (canUsePresets)
{
sb.AppendLine(GetMessage(player, "Command.Help.Section.PersonalPresets"));
sb.AppendLine(GetMessage(player, "Command.Help.ListPresets"));
sb.AppendLine(GetMessage(player, "Command.Help.Spawn.Preset"));
if (canLoadPresets)
{
sb.AppendLine(GetMessage(player, "Command.Help.LoadPreset"));
}
sb.AppendLine(GetMessage(player, "Command.Help.SavePreset"));
sb.AppendLine(GetMessage(player, "Command.Help.UpdatePreset"));
sb.AppendLine(GetMessage(player, "Command.Help.RenamePreset"));
sb.AppendLine(GetMessage(player, "Command.Help.DeletePreset"));
}
if (permission.UserHasPermission(player.Id, PermissionCommonPresets))
{
sb.AppendLine(GetMessage(player, "Command.Help.Section.CommonPresets"));
sb.AppendLine(GetMessage(player, "Command.Help.Common.ListPresets"));
sb.AppendLine(GetMessage(player, "Command.Help.Common.Spawn"));
if (canLoadPresets)
{
sb.AppendLine(GetMessage(player, "Command.Help.Common.LoadPreset"));
}
if (permission.UserHasPermission(player.Id, PermissionManageCommonPresets))
{
sb.AppendLine(GetMessage(player, "Command.Help.Common.SavePreset"));
sb.AppendLine(GetMessage(player, "Command.Help.Common.UpdatePreset"));
sb.AppendLine(GetMessage(player, "Command.Help.Common.RenamePreset"));
sb.AppendLine(GetMessage(player, "Command.Help.Common.DeletePreset"));
}
}
var canCodeLock = VehicleDeployedLocks != null && permission.UserHasPermission(player.Id, PermissionAutoCodeLock);
var canKeyLock = permission.UserHasPermission(player.Id, PermissionAutoKeyLock);
var canFillTankers = permission.UserHasPermission(player.Id, PermissionAutoFillTankers);
if (canCodeLock || canKeyLock || canFillTankers)
{
sb.AppendLine(GetMessage(player, "Command.Help.Section.PersonalSettings"));
}
if (canCodeLock)
{
sb.AppendLine(GetMessage(player, "Command.Help.ToggleAutoCodeLock",
BooleanToLocalizedString(player, GetPlayerConfig(player).Settings.AutoCodeLock)));
}
if (canKeyLock)
{
sb.AppendLine(GetMessage(player, "Command.Help.ToggleAutoKeyLock",
BooleanToLocalizedString(player, GetPlayerConfig(player).Settings.AutoKeyLock)));
}
if (canFillTankers)
{
sb.AppendLine(GetMessage(player, "Command.Help.ToggleAutoFillTankers",
BooleanToLocalizedString(player, GetPlayerConfig(player).Settings.AutoFillTankers)));
}
if (permission.UserHasPermission(player.Id, PermissionGiveCar))
{
sb.AppendLine("Command.Help.Section.OtherCommands");
sb.AppendLine(GetMessage(player, "Command.Help.Give"));
}
player.Reply(sb.ToString());
}
private void SubCommand_SpawnCar(IPlayer player, string[] args)
{
var maxAllowedSockets = GetPlayerMaxAllowedCarSockets(player.Id);
if (maxAllowedSockets == 0)
{
ReplyToPlayer(player, "Generic.Error.NoPermission");
return;
}
if (!VerifyHasNoCar(player)
|| !VerifyOffCooldown(player, CooldownType.Spawn)
|| !VerifyLocationNotRestricted(player)
|| !_pluginConfig.CanSpawnBuildingBlocked && !VerifyNotBuildingBlocked(player))
return;
// Key binds automatically pass the "True" argument.var wasPassedArgument = args.Length > 0 && args[0] != "True";
if (wasPassedArgument)
{
if (int.TryParse(args[0], out var desiredSockets))
{
if (desiredSockets is < 2 or > 4)
{
ReplyToPlayer(player, "Command.Spawn.Error.SocketSyntax");
return;
}
if (desiredSockets > maxAllowedSockets)
{
ReplyToPlayer(player, "Generic.Error.NoPermission");
return;
}
SpawnRandomCarForPlayer(player, desiredSockets);
return;
}
if (!VerifyPermissionAny(player, PermissionPresets))
return;
var presetNameArg = args[0];
if (!VerifyOnlyOneMatchingPreset(player, GetPlayerConfig(player), presetNameArg, out var preset))
return;
SpawnPresetCarForPlayer(player, preset);
}
else
{
if (permission.UserHasPermission(player.Id, PermissionPresets))
{
var preset = GetPlayerConfig(player).FindPreset(DefaultPresetName);
if (preset != null)
{
SpawnPresetCarForPlayer(player, preset);
return;
}
}
SpawnRandomCarForPlayer(player, maxAllowedSockets);
}
}
private void SubCommand_Common_SpawnCar(IPlayer player, string[] args)
{
var maxAllowedSockets = GetPlayerMaxAllowedCarSockets(player.Id);
if (maxAllowedSockets == 0)
{
ReplyToPlayer(player, "Generic.Error.NoPermission");
return;
}
if (!VerifyPermissionAny(player, PermissionCommonPresets)
|| !VerifyHasNoCar(player)
|| !VerifyOffCooldown(player, CooldownType.Spawn)
|| !VerifyLocationNotRestricted(player)
|| !_pluginConfig.CanSpawnBuildingBlocked && !VerifyNotBuildingBlocked(player))
return;
var presetNameArg = args[0];
if (!VerifyOnlyOneMatchingPreset(player, _commonPresets, presetNameArg, out var preset))
return;
SpawnPresetCarForPlayer(player, preset);
}
private void SubCommand_FixCar(IPlayer player, string[] args)
{
if (!VerifyPermissionAny(player, PermissionFix))
return;
if (!VerifyHasCar(player, out var car)
|| !VerifyOffCooldown(player, CooldownType.Fix)
|| FixMyCarWasBlocked(player.Object as BasePlayer, car))
return;
if (car.IsDead())
ReviveCar(car);
FixCar(car, GetPlayerAllowedFuel(player.Id), GetPlayerEnginePartsTier(player.Id));
MaybeFillTankerModules(car, GetPlayerAllowedFreshWater(player.Id));
_pluginData.StartCooldown(player.Id, CooldownType.Fix);
MaybePlayCarRepairEffects(car);
ReplyToPlayer(player, "Command.Fix.Success");
}
private void SubCommand_FetchCar(IPlayer player, string[] args)
{
if (!VerifyPermissionAny(player, PermissionFetch))
return;
var basePlayer = player.Object as BasePlayer;
if (!VerifyHasCar(player, out var car)
|| !_pluginConfig.CanFetchOccupied && !VerifyCarNotOccupied(player, car)
|| !VerifyOffCooldown(player, CooldownType.Fetch)
|| !VerifyLocationNotRestricted(player)
|| !_pluginConfig.CanFetchBuildingBlocked && !VerifyNotBuildingBlocked(player)
|| !VerifySufficientSpace(player, car.TotalSockets, out var fetchPosition, out var fetchRotation)
|| FetchMyCarWasBlocked(basePlayer, car))
return;
// This is a hacky way to determine that the car is on a lift.if (car.rigidBody.isKinematic && !TryReleaseCarFromLift(car))
{
var messages = new List<string> { GetMessage(player, "Command.Fetch.Error.StuckOnLift") };
if (permission.UserHasPermission(player.Id, PermissionDespawn))
messages.Add(GetMessage(player, "Command.Fetch.Error.StuckOnLift.Help"));
player.Reply(string.Join(" ", messages));
return;
}
if (_pluginConfig.DismountPlayersOnFetch)
{
DismountAllPlayersFromCar(car);
}
// Temporarily clear max angular velocity to prevent the car from unexpectedly spinning when teleporting really far.var maxAngularVelocity = car.rigidBody.maxAngularVelocity;
car.rigidBody.maxAngularVelocity = 0;
car.transform.SetPositionAndRotation(fetchPosition, fetchRotation);
car.SetVelocity(Vector3.zero);
car.SetAngularVelocity(Vector3.zero);
car.UpdateNetworkGroup();
car.SendNetworkUpdateImmediate();
timer.Once(1f, () =>
{
if (car != null)
{
car.rigidBody.maxAngularVelocity = maxAngularVelocity;
}
});
_pluginData.StartCooldown(player.Id, CooldownType.Fetch);
ReplyToPlayer(player, "Command.Fetch.Success");
}
private void SubCommand_DestroyCar(IPlayer player, string[] args)
{
if (!VerifyPermissionAny(player, PermissionDespawn))
return;
var basePlayer = player.Object as BasePlayer;
if (!VerifyHasCar(player, out var car)
|| !_pluginConfig.CanDespawnOccupied && !VerifyCarNotOccupied(player, car)
|| DestroyMyCarWasBlocked(basePlayer, car))
return;
var extractedEngineParts = ExtractEnginePartsAboveTierAndDeleteRest(car, GetPlayerEnginePartsTier(player.Id));
car.Kill();
if (extractedEngineParts.Count > 0)
{
GiveItemsToPlayerOrDrop(basePlayer, extractedEngineParts);
ReplyToPlayer(player, "Generic.Info.PartsRecovered");
}
}
private void SubCommand_ListPresets(IPlayer player, string[] args)
{
if (!VerifyPermissionAny(player, PermissionPresets))
return;
var config = GetPlayerConfig(player);
if (config.Presets.Count == 0)
{
ReplyToPlayer(player, "Generic.Error.NoPresets");
return;
}
var presetList = config.Presets.Select(p => p).ToList();
presetList.Sort(SortPresetNames);
var sb = new StringBuilder();
sb.AppendLine(GetMessage(player, "Command.List"));
foreach (var preset in presetList)
{
sb.AppendLine(GetMessage(player, "Command.List.Item", preset.Name, preset.NumSockets));
}
player.Reply(sb.ToString());
}
private void SubCommand_Common_ListPresets(IPlayer player, string[] args)
{
if (!VerifyPermissionAny(player, PermissionCommonPresets))
return;
if (_commonPresets.Presets.Count == 0)
{
ReplyToPlayer(player, "Generic.Error.NoCommonPresets");
return;
}
var maxAllowedSockets = GetPlayerMaxAllowedCarSockets(player.Id);
var presetList = _commonPresets.Presets.Where(p => p.NumSockets <= maxAllowedSockets).ToList();
presetList.Sort(SortPresetNames);
var sb = new StringBuilder();
sb.AppendLine(GetMessage(player, "Command.Common.List"));
foreach (var preset in presetList)
{
sb.AppendLine(GetMessage(player, "Command.List.Item", preset.Name, preset.NumSockets));
}
player.Reply(sb.ToString());
}
private void SubCommand_SavePreset(IPlayer player, string[] args)
{
if (!VerifyPermissionAny(player, PermissionPresets))
return;
if (!VerifyHasCar(player, out var car))
return;
var presetNameArg = args.Length == 0 ? DefaultPresetName : args[0];
var presetManager = GetPlayerConfig(player);
if (!VerifyNoMatchingPreset(player, presetManager, presetNameArg))
return;
if (presetManager.Presets.Count >= _pluginConfig.MaxPresetsPerPlayer)
{
ReplyToPlayer(player, "Command.SavePreset.Error.TooManyPresets", _pluginConfig.MaxPresetsPerPlayer);
return;
}
SavePreset(player, presetManager, presetNameArg, car);
}
private void SubCommand_Common_SavePreset(IPlayer player, string[] args)
{
if (!VerifyPermissionAny(player, PermissionManageCommonPresets))
return;
if (args.Length == 0)
{
ReplyToPlayer(player, "Command.Common.SavePreset.Error.Syntax");
return;
}
var presetNameArg = args[0];
if (!VerifyHasCar(player, out var car)
|| !VerifyNoMatchingPreset(player, _commonPresets, presetNameArg))
return;
SavePreset(player, _commonPresets, presetNameArg, car);
}
private void SavePreset(IPlayer player, SimplePresetManager presetManager, string presetNameArg, ModularCar car)
{
if (presetNameArg.Length > PresetMaxLength)
{
ReplyToPlayer(player, "Generic.Error.PresetNameLength", PresetMaxLength);
return;
}
presetManager.SavePreset(SimplePreset.FromCar(car, presetNameArg));
ReplyToPlayer(player, "Command.SavePreset.Success", presetNameArg);
}
private void SubCommand_UpdatePreset(IPlayer player, string[] args)
{
if (!VerifyPermissionAny(player, PermissionPresets))
return;
var presetNameArg = args.Length == 0 ? DefaultPresetName : args[0];
UpdatePreset(player, GetPlayerConfig(player), presetNameArg);
}
private void SubCommand_Common_UpdatePreset(IPlayer player, string[] args)
{
if (!VerifyPermissionAny(player, PermissionManageCommonPresets))
return;
if (args.Length == 0)
{
ReplyToPlayer(player, "Command.Common.UpdatePreset.Error.Syntax");
return;
}
UpdatePreset(player, _commonPresets, args[0]);
}
private void UpdatePreset(IPlayer player, SimplePresetManager presetManager, string presetNameArg)
{
if (!VerifyHasCar(player, out var car))
return;
if (!VerifyHasPreset(player, presetManager, presetNameArg, out var preset))
return;
presetManager.UpdatePreset(SimplePreset.FromCar(car, preset.Name));
ReplyToPlayer(player, "Command.UpdatePreset.Success", preset.Name);
}
private void SubCommand_LoadPreset(IPlayer player, string[] args)
{
if (!VerifyPermissionAny(player, PermissionPresetLoad))
return;
var presetNameArg = args.Length == 0 ? DefaultPresetName : args[0];
LoadPreset(player, GetPlayerConfig(player.Id), presetNameArg);
}
private void SubCommand_Common_LoadPreset(IPlayer player, string[] args)
{
if (!VerifyPermissionAny(player, PermissionPresetLoad)
|| !VerifyPermissionAny(player, PermissionCommonPresets))
return;
if (args.Length == 0)
{
ReplyToPlayer(player, "Command.Common.LoadPreset.Error.Syntax");
return;
}
var presetNameArg = args[0];
LoadPreset(player, _commonPresets, presetNameArg);
}
private void LoadPreset(IPlayer player, SimplePresetManager presetManager, string presetNameArg)
{
var basePlayer = player.Object as BasePlayer;
if (!VerifyHasCar(player, out var car)
|| !VerifyCarNotOccupied(player, car)
|| !VerifyOffCooldown(player, CooldownType.Load)
|| LoadMyCarPresetWasBlocked(basePlayer, car))
return;
if (!VerifyOnlyOneMatchingPreset(player, presetManager, presetNameArg, out var preset))
return;
var presetNumSockets = preset.NumSockets;
if (presetNumSockets > GetPlayerMaxAllowedCarSockets(player.Id))
{
ReplyToPlayer(player, "Generic.Error.NoPermissionToPresetSocketCount", preset.Name, preset.NumSockets);
return;
}
if (presetNumSockets != car.TotalSockets)
{
ReplyToPlayer(player, "Command.LoadPreset.Error.SocketCount", preset.Name, presetNumSockets, car.TotalSockets);
return;
}
if (car.IsDead())
{
ReviveCar(car);
}
var wasEngineOn = car.IsOn();
var enginePartsTier = GetPlayerEnginePartsTier(player.Id);
var extractedEngineParts = ExtractEnginePartsAboveTierAndDeleteRest(car, enginePartsTier);
UpdateCarModules(car, preset.ModuleIDs);
_pluginData.StartCooldown(player.Id, CooldownType.Load);
NextTick(() =>
{
var wereExtraParts = false;
if (extractedEngineParts.Count > 0)
{
var remainingEngineParts = AddEngineItemsAndReturnRemaining(car, extractedEngineParts);
if (remainingEngineParts.Count > 0)
{
wereExtraParts = true;
GiveItemsToPlayerOrDrop(basePlayer, remainingEngineParts);
}
}
FixCar(car, GetPlayerAllowedFuel(player.Id), enginePartsTier);
// Restart the engine if it turned off during the brief moment it had no engine or no parts.if (wasEngineOn && !car.IsOn() && car.engineController.CanRunEngine())
car.engineController.FinishStartingEngine();
MaybeFillTankerModules(car, GetPlayerAllowedFreshWater(player.Id));
if (car.CarLock.HasALock && !car.CarLock.CanHaveALock())
{
car.RemoveLock();
}
MaybePlayCarRepairEffects(car);
var chatMessages = new List<string>() { GetMessage(player, "Command.LoadPreset.Success", preset.Name) };
if (wereExtraParts)
chatMessages.Add(GetMessage(player, "Generic.Info.PartsRecovered"));
player.Reply(string.Join(" ", chatMessages));
});
}
private void SubCommand_RenamePreset(IPlayer player, string[] args)
{
if (!VerifyPermissionAny(player, PermissionPresets))
return;
if (args.Length < 2)
{
ReplyToPlayer(player, "Command.RenamePreset.Error.Syntax");
return;
}
RenamePreset(player, GetPlayerConfig(player), args[0], args[1]);
}
private void SubCommand_Common_RenamePreset(IPlayer player, string[] args)
{
if (!VerifyPermissionAny(player, PermissionManageCommonPresets))
return;
if (args.Length < 2)
{
ReplyToPlayer(player, "Command.Common.RenamePreset.Error.Syntax");
return;
}
RenamePreset(player, _commonPresets, args[0], args[1]);
}
private void RenamePreset(IPlayer player, SimplePresetManager presetManager, string oldName, string newName)
{
if (!VerifyHasPreset(player, presetManager, oldName, out var preset))
return;
// Cache actual old preset name since matching is case-insensitive.var actualOldPresetName = preset.Name;
var existingPresetWithNewName = presetManager.FindPreset(newName);
if (newName.Length > PresetMaxLength)
{
ReplyToPlayer(player, "Generic.Error.PresetNameLength", PresetMaxLength);
return;
}
// Allow renaming if just changing case.if (existingPresetWithNewName != null && preset != existingPresetWithNewName)
{
ReplyToPlayer(player, "Generic.Error.PresetAlreadyTaken", existingPresetWithNewName.Name);
return;
}
presetManager.RenamePreset(preset, newName);
ReplyToPlayer(player, "Command.RenamePreset.Success", actualOldPresetName, newName);
}
private void SubCommand_DeletePreset(IPlayer player, string[] args)
{
if (!VerifyPermissionAny(player, PermissionPresets))
return;
var presetNameArg = args.Length == 0 ? DefaultPresetName : args[0];
DeletePreset(player, GetPlayerConfig(player), presetNameArg);
}
private void SubCommand_Common_DeletePreset(IPlayer player, string[] args)
{
if (!VerifyPermissionAny(player, PermissionManageCommonPresets))
return;
if (args.Length == 0)
{
ReplyToPlayer(player, "Command.Common.DeletePreset.Error.Syntax");
return;
}
DeletePreset(player, _commonPresets, args[0]);
}
private void DeletePreset(IPlayer player, SimplePresetManager presetManager, string presetNameArg)
{
if (!VerifyHasPreset(player, presetManager, presetNameArg, out var preset))
return;
presetManager.DeletePreset(preset);
ReplyToPlayer(player, "Command.DeletePreset.Success", preset.Name);
}
private void SubCommand_ToggleAutoCodeLock(IPlayer player, string[] args)
{
if (VehicleDeployedLocks == null
|| !VerifyPermissionAny(player, PermissionAutoCodeLock))
return;
var config = GetPlayerConfig(player);
config.Settings.AutoCodeLock = !config.Settings.AutoCodeLock;
config.SaveData();
ReplyToPlayer(player, "Command.ToggleAutoCodeLock.Success", BooleanToLocalizedString(player, config.Settings.AutoCodeLock));
}
private void SubCommand_ToggleAutoKeyLock(IPlayer player, string[] args)
{
if (!VerifyPermissionAny(player, PermissionAutoKeyLock))
return;
var config = GetPlayerConfig(player);
config.Settings.AutoKeyLock = !config.Settings.AutoKeyLock;
config.SaveData();
ReplyToPlayer(player, "Command.ToggleAutoKeyLock.Success", BooleanToLocalizedString(player, config.Settings.AutoKeyLock));
}
private void SubCommand_ToggleAutoFillTankers(IPlayer player, string[] args)
{
if (!VerifyPermissionAny(player, PermissionAutoFillTankers))
return;
var config = GetPlayerConfig(player);
config.Settings.AutoFillTankers = !config.Settings.AutoFillTankers;
config.SaveData();
ReplyToPlayer(player, "Command.ToggleAutoFillTankers.Success", BooleanToLocalizedString(player, config.Settings.AutoFillTankers));
}
#endregion
#region Dependencies
private class MonumentAdapter
{
public string ShortName => (string)_monumentInfo["ShortName"];
private Dictionary<string, object> _monumentInfo;
public MonumentAdapter(Dictionary<string, object> monumentInfo)
{
_monumentInfo = monumentInfo;
}
public bool IsInBounds(Vector3 position) =>
((Func<Vector3, bool>)_monumentInfo["IsInBounds"]).Invoke(position);
}
private MonumentAdapter GetClosestMonument(Vector3 position)
{
return MonumentFinder?.Call("API_GetClosest", position) is Dictionary<string, object> dictResult
? new MonumentAdapter(dictResult)
: null;
}
#endregion
#region Helper Methods - Command Checks
private static bool SpawnWasBlocked(BasePlayer player)
{
var hookResult = Interface.CallHook("CanSpawnModularCar", player);
return hookResult is false;
}
private static bool SpawnMyCarWasBlocked(BasePlayer player)
{
if (SpawnWasBlocked(player))
return true;
var hookResult = Interface.CallHook("CanSpawnMyCar", player);
return hookResult is false;
}
private static bool FetchMyCarWasBlocked(BasePlayer player, ModularCar car)
{
return Interface.CallHook("CanFetchMyCar", player, car) is false;
}
private static bool FixMyCarWasBlocked(BasePlayer player, ModularCar car)
{
return Interface.CallHook("CanFixMyCar", player, car) is false;
}
private static bool LoadMyCarPresetWasBlocked(BasePlayer player, ModularCar car)
{
return Interface.CallHook("CanLoadMyCarPreset", player, car) is false;
}
private static bool DestroyMyCarWasBlocked(BasePlayer player, ModularCar car)
{
return Interface.CallHook("CanDestroyMyCar", player, car) is false;
}
private static bool HasParent<T>(BasePlayer player) where T : BaseEntity
{
v
// ... [Code preview truncated due to length. Full source in attached file] ...