C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Rust;
using Rust.Modular;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using System.Text;
namespace Oxide.Plugins {
[Info("Spawn Modular Car", "WhiteThunder", "5.0.1")]
[Description("Allows players to spawn modular cars.")]
internal class SpawnModularCar : CovalencePlugin {
#region Fields
[PluginReference]
private Plugin CarCodeLocks, 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 Vector3(1, 1.1f, 1.5f);
private static readonly Vector3 MediumCarExtents = new Vector3(1, 1.1f, 2.3f);
private static readonly Vector3 LongCarExtents = new Vector3(1, 1.1f, 3);
private static readonly Vector3 ShortCarFrontLeft = new Vector3(ShortCarExtents.x, 0, ShortCarExtents.z);
private static readonly Vector3 ShortCarFrontRight = new Vector3(-ShortCarExtents.x, 0, ShortCarExtents.z);
private static readonly Vector3 ShortCarBackLeft = new Vector3(ShortCarExtents.x, 0, -ShortCarExtents.z);
private static readonly Vector3 ShortCarBackRight = new Vector3(-ShortCarExtents.x, 0, -ShortCarExtents.z);
private static readonly Vector3 MediumCarFrontLeft = new Vector3(MediumCarExtents.x, 0, MediumCarExtents.z);
private static readonly Vector3 MediumCarFrontRight = new Vector3(-MediumCarExtents.x, 0, MediumCarExtents.z);
private static readonly Vector3 MediumCarBackLeft = new Vector3(MediumCarExtents.x, 0, -MediumCarExtents.z);
private static readonly Vector3 MediumCarBackRight = new Vector3(-MediumCarExtents.x, 0, -MediumCarExtents.z);
private static readonly Vector3 LongCarFrontLeft = new Vector3(LongCarExtents.x, 0, LongCarExtents.z);
private static readonly Vector3 LongCarFrontRight = new Vector3(-LongCarExtents.x, 0, LongCarExtents.z);
private static readonly Vector3 LongCarBackLeft = new Vector3(LongCarExtents.x, 0, -LongCarExtents.z);
private static readonly Vector3 LongCarBackRight = new Vector3(-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 Dictionary<string, PlayerConfig>();
#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 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;
string userId = _pluginData.PlayerCars.FirstOrDefault(x => x.Value == car.net.ID).Key;
BasePlayer player = BasePlayer.Find(userId);
if (player != null) ChatMessage(player, "Generic.Info.CarDestroyed");
_pluginData.UnregisterCar(userId);
}
private object OnEngineStart(ModularCar car) {
if (car == null || car.OwnerID == 0 || !_pluginData.PlayerCars.ContainsValue(car.net.ID) || !permission.UserHasPermission(car.OwnerID.ToString(), PermissionAutoStartEngine)) return null;
car.FinishStartingEngine();
return false;
}
#endregion
#region API
private ModularCar API_SpawnPresetCar(BasePlayer player, Dictionary<string, object> options, Action<ModularCar> onReady = null) {
var apiOptions = new APIOptions(options);
if (apiOptions.ModuleIDs == null) {
LogError("[API_SpawnPresetCar] '{0}' field is missing or unrecognizable.", APIOptions.ModulesField);
return null;
}
if (apiOptions.ModuleIDs.Length < 2 || apiOptions.ModuleIDs.Length > 4) {
LogError("[API_SpawnPresetCar] Requested a car with {0} sockets, but only 2-4 sockets is supported.", apiOptions.ModuleIDs.Length);
return null;
}
if (SpawnWasBlocked(player)) return null;
var presetOptions = apiOptions.ToPresetOptions();
Vector3 spawnPosition;
Quaternion rotation;
if (!TryGetIdealCarPositionAndRotation(player, presetOptions.Length, out spawnPosition, out rotation)) {
spawnPosition = GetFixedCarPosition(player);
rotation = GetRelativeCarRotation(player);
}
var car = SpawnCarForPlayer(player, presetOptions, spawnPosition, rotation, 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;
}
internal class APIOptions {
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 readonly bool CodeLock;
public readonly bool KeyLock;
public readonly int EnginePartsTier;
public readonly int FreshWaterAmount;
public readonly int FuelAmount;
public readonly int[] ModuleIDs;
public APIOptions(Dictionary<string, object> options) {
CodeLock = BoolOption(options, CodeLockField);
KeyLock = BoolOption(options, KeyLockField);
EnginePartsTier = IntOption(options, EnginePartsTierField);
FreshWaterAmount = IntOption(options, FreshWaterAmountField);
FuelAmount = IntOption(options, FuelAmountField);
ModuleIDs = ParseModulesOption(options);
}
private bool BoolOption(Dictionary<string, object> options, string name) => options.ContainsKey(name) && options[name] is bool ? (bool)options[name] : false;
private int IntOption(Dictionary<string, object> options, string name) => options.ContainsKey(name) && options[name] is int ? (int)options[name] : 0;
public int[] ParseModulesOption(Dictionary<string, object> options) {
if (!options.ContainsKey(ModulesField)) return null;
var moduleArray = options[ModulesField] as object[];
if (moduleArray == null) return null;
return _pluginInstance.ValidateModules(moduleArray);
}
public PresetCarOptions ToPresetOptions() {
return new PresetCarOptions {
CodeLock = CodeLock, KeyLock = KeyLock, EnginePartsTier = EnginePartsTier, FreshWaterAmount = FreshWaterAmount, FuelAmount = FuelAmount, NormalizedModuleIDs = ModuleIDs
}
;
}
}
#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;
}
foreach (var preset in _pluginConfig.Presets) {
if (preset.Name.ToLower() == presetNameArg.ToLower()) {
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;
}
Vector3 spawnPosition;
Quaternion rotation;
if (!TryGetIdealCarPositionAndRotation(targetPlayer, preset.Options.Length, out spawnPosition, out rotation)) {
spawnPosition = GetFixedCarPosition(targetPlayer);
rotation = GetRelativeCarRotation(targetPlayer);
}
var car = SpawnCarForPlayer(targetPlayer, carOptions, spawnPosition, rotation, shouldTrackCar : false);
if (car != null) ReplyToPlayer(player, "Command.Give.Success", targetPlayer.displayName, preset.Name);
return;
}
}
ReplyToPlayer(player, "Generic.Error.PresetNotFound", presetNameArg);
}
[Command("mycar")]
private void MyCarCommand(IPlayer player, string cmd, string[] args) {
if (player.IsServer) 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) {
ushort 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) {
ushort 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) {
int desiredSockets;
if (int.TryParse(args[0], out desiredSockets)) {
if (desiredSockets < 2 || desiredSockets > 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];
SimplePreset preset;
if (!VerifyOnlyOneMatchingPreset(player, GetPlayerConfig(player), presetNameArg, out 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) {
ushort 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];
SimplePreset preset;
if (!VerifyOnlyOneMatchingPreset(player, _commonPresets, presetNameArg, out preset)) return;
SpawnPresetCarForPlayer(player, preset);
}
private void SubCommand_FixCar(IPlayer player, string[] args) {
if (!VerifyPermissionAny(player, PermissionFix)) return;
ModularCar car;
if (!VerifyHasCar(player, out 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;
ModularCar car;
Vector3 fetchPosition;
Quaternion fetchRotation;
if (!VerifyHasCar(player, out car) || !_pluginConfig.CanFetchOccupied && !VerifyCarNotOccupied(player, car) || !VerifyOffCooldown(player, CooldownType.Fetch) || !VerifyLocationNotRestricted(player) || !_pluginConfig.CanFetchBuildingBlocked && !VerifyNotBuildingBlocked(player) || !VerifySufficientSpace(player, car.TotalSockets, out fetchPosition, out 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;
ModularCar car;
if (!VerifyHasCar(player, out car) || !_pluginConfig.CanDespawnOccupied && !VerifyCarNotOccupied(player, car) || DestroyMyCarWasBlocked(basePlayer, car)) return;
MaybeRemoveMatchingKeysFromPlayer(basePlayer, car);
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;
PlayerConfig 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;
}
ushort 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;
ModularCar car;
if (!VerifyHasCar(player, out 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];
ModularCar car;
if (!VerifyHasCar(player, out 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) {
ModularCar car;
if (!VerifyHasCar(player, out car)) return;
SimplePreset preset;
if (!VerifyHasPreset(player, presetManager, presetNameArg, out 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;
ModularCar car;
if (!VerifyHasCar(player, out car) || !VerifyCarNotOccupied(player, car) || !VerifyOffCooldown(player, CooldownType.Load) || LoadMyCarPresetWasBlocked(basePlayer, car)) return;
SimplePreset preset;
if (!VerifyOnlyOneMatchingPreset(player, presetManager, presetNameArg, out 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.CanRunEngines())
car.FinishStartingEngine();
MaybeFillTankerModules(car, GetPlayerAllowedFreshWater(player.Id));
if (car.carLock.HasALock && !car.carLock.CanHaveALock()) {
MaybeRemoveMatchingKeysFromPlayer(basePlayer, car);
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) {
SimplePreset preset;
if (!VerifyHasPreset(player, presetManager, oldName, out preset)) return;
// Cache actual old preset name since matching is case-insensitive.var actualOldPresetName = preset.Name;
SimplePreset 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) {
SimplePreset preset;
if (!VerifyHasPreset(player, presetManager, presetNameArg, out 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 Helper Methods - Command Checks
private static bool SpawnWasBlocked(BasePlayer player) {
object hookResult = Interface.CallHook("CanSpawnModularCar", player);
return hookResult is bool && (bool)hookResult == false;
}
private static bool SpawnMyCarWasBlocked(BasePlayer player) {
if (SpawnWasBlocked(player)) return true;
object hookResult = Interface.CallHook("CanSpawnMyCar", player);
return hookResult is bool && (bool)hookResult == false;
}
private static bool FetchMyCarWasBlocked(BasePlayer player, ModularCar car) {
object hookResult = Interface.CallHook("CanFetchMyCar", player, car);
return hookResult is bool && (bool)hookResult == false;
}
private static bool FixMyCarWasBlocked(BasePlayer player, ModularCar car) {
object hookResult = Interface.CallHook("CanFixMyCar", player, car);
return hookResult is bool && (bool)hookResult == false;
}
private static bool LoadMyCarPresetWasBlocked(BasePlayer player, ModularCar car) {
object hookResult = Interface.CallHook("CanLoadMyCarPreset", player, car);
return hookResult is bool && (bool)hookResult == false;
}
private static bool DestroyMyCarWasBlocked(BasePlayer player, ModularCar car) {
object hookResult = Interface.CallHook("CanDestroyMyCar", player, car);
return hookResult is bool && (bool)hookResult == false;
}
private bool VerifyPermissionAny(IPlayer player, params string[] permissionNames) {
foreach (var perm in permissionNames) {
if (!permission.UserHasPermission(player.Id, perm)) {
ReplyToPlayer(player, "Generic.Error.NoPermission");
return false;
}
}
return true;
}
private bool VerifyLocationNotRestricted(IPlayer player) {
if ((player.Object as BasePlayer).GetComponentInParent<CargoShip>() != null) {
ReplyToPlayer(player, "Generic.Error.LocationRestricted");
return false;
}
return true;
}
private bool VerifyNotBuildingBlocked(IPlayer player) {
if ((player.Object as BasePlayer).IsBuildingBlocked()) {
ReplyToPlayer(player, "Generic.Error.BuildingBlocked");
return false;
}
return true;
}
private bool VerifySufficientSpace(IPlayer player, int numSockets, out Vector3 determinedPosition, out Quaternion determinedRotation) {
var basePlayer = player.Object as BasePlayer;
if (!TryGetIdealCarPositionAndRotation(basePlayer, numSockets, out determinedPosition, out determinedRotation) || !HasSufficientSpace(basePlayer, numSockets, determinedPosition, determinedRotation)) {
ReplyToPlayer(player, "Generic.Error.InsufficientSpace");
return false;
}
return true;
}
private bool VerifyHasPreset(IPlayer player, SimplePresetManager presetManager, string presetName, out SimplePreset preset) {
preset = presetManager.FindPreset(presetName);
if (preset == null) {
ReplyToPlayer(player, "Generic.Error.PresetNotFound", presetName);
return false;
}
return true;
}
private bool VerifyNoMatchingPreset(IPlayer player, SimplePresetManager presetManager, string presetName) {
var existingPreset = presetManager.FindPreset(presetName);
if (existingPreset != null) {
ReplyToPlayer(player, "Command.SavePreset.Error.PresetAlreadyExists", existingPreset.Name);
return false;
}
return true;
}
private bool VerifyHasCar(IPlayer player, out ModularCar car) {
car = FindPlayerCar(player);
if (car == null) {
ReplyToPlayer(player, "Generic.Error.CarNotFound");
return false;
}
return true;
}
private bool VerifyHasNoCar(IPlayer player) {
if (FindPlayerCar(player) == null) return true;
var messages = new List<string> {
GetMessage(player, "Command.Spawn.Error.CarAlreadyExists")
}
;
if (permission.UserHasPermission(player.Id, PermissionFetch)) messages.Add(GetMessage(player, "Command.Spawn.Error.CarAlreadyExists.Help"));
player.Reply(string.Join(" ", messages));
return false;
}
private bool VerifyCarNotOccupied(IPlayer player, ModularCar car) {
// Players can either be mounted in seats, or standing on flatbed modules.if (car.AnyMounted()
|| car.AttachedModuleEntities.Any(module => module.children.Any(child => child is BasePlayer))) {
ReplyToPlayer(player, "Generic.Error.CarOccupied");
return false;
}
return true;
}
private bool VerifyOffCooldown(IPlayer player, CooldownType cooldownType) {
var secondsRemaining = _pluginData.GetRemainingCooldownSeconds(player.Id, cooldownType);
if (secondsRemaining > 0) {
ReplyToPlayer(player, "Generic.Error.Cooldown", secondsRemaining);
return false;
}
return true;
}
private bool VerifyOnlyOneMatchingPreset(IPlayer player, SimplePresetManager presetManager, string presetName, out SimplePreset preset) {
preset = presetManager.FindPreset(presetName);
if (preset != null) return true;
var matchingPresets = presetManager.FindMatchingPresets(presetName);
var matchCount = matchingPresets.Count;
if (matchCount == 0) {
ReplyToPlayer(player, "Generic.Error.PresetNotFound", presetName);
return false;
}
else if (matchCount > 1) {
ReplyToPlayer(player, "Generic.Error.PresetMultipleMatches", presetName);
return false;
}
preset = matchingPresets.F
// ... [Code preview truncated due to length. Full source in attached file] ...