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 System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Vehicle Deployed Locks", "WhiteThunder", "1.13.6")]
[Description("Allows players to deploy code locks and key locks to vehicles.")]
internal class VehicleDeployedLocks : CovalencePlugin
{
#region Fields
[PluginReference]
private Plugin Clans, Friends;
private const string Permission_CodeLock_Prefix = "vehicledeployedlocks.codelock";
private const string Permission_KeyLock_Prefix = "vehicledeployedlocks.keylock";
private const string Permission_MasterKey = "vehicledeployedlocks.masterkey";
private const string Prefab_CodeLock_DeployedEffect = "assets/prefabs/locks/keypad/effects/lock-code-deploy.prefab";
private const string Prefab_CodeLock_DeniedEffect = "assets/prefabs/locks/keypad/effects/lock.code.denied.prefab";
private const string Prefab_CodeLock_UnlockEffect = "assets/prefabs/locks/keypad/effects/lock.code.unlock.prefab";
private const float MaxDeployDistance = 3;
private Configuration _config;
private CooldownManager _craftCodeLockCooldowns;
private CooldownManager _craftKeyLockCooldowns;
private readonly VehicleInfoManager _vehicleInfoManager;
private readonly LockedVehicleTracker _lockedVehicleTracker;
private readonly AutoUnlockManager _autoUnlockManager;
private readonly ReskinManager _reskinManager;
private readonly object False = false;
private enum PayType { Item, Resources, Free }
public VehicleDeployedLocks()
{
_vehicleInfoManager = new VehicleInfoManager(this);
_lockedVehicleTracker = new LockedVehicleTracker(_vehicleInfoManager);
_autoUnlockManager = new AutoUnlockManager(this, _lockedVehicleTracker);
_reskinManager = new ReskinManager(_vehicleInfoManager, _lockedVehicleTracker);
}
#endregion
#region Hooks
private void Init()
{
permission.RegisterPermission(Permission_MasterKey, this);
permission.RegisterPermission(LockType.Code.PermissionFree, this);
permission.RegisterPermission(LockType.Code.PermissionAllVehicles, this);
permission.RegisterPermission(LockType.Key.PermissionFree, this);
permission.RegisterPermission(LockType.Key.PermissionAllVehicles, this);
_craftKeyLockCooldowns = new CooldownManager(_config.CraftCooldownSeconds);
_craftCodeLockCooldowns = new CooldownManager(_config.CraftCooldownSeconds);
if (_config.AllowPushWhileLockedOut)
{
Unsubscribe(nameof(OnVehiclePush));
}
Unsubscribe(nameof(OnEntityKill));
}
private void OnServerInitialized()
{
_vehicleInfoManager.OnServerInitialized();
_lockedVehicleTracker.OnServerInitialized();
_autoUnlockManager.OnServerInitialized(_config.AutoUnlockSettings);
if (_config.UpdateLockPositions)
{
foreach (var networkable in BaseNetworkable.serverEntities)
{
var entity = networkable as BaseEntity;
if ((object)entity == null)
continue;
var vehicleInfo = _vehicleInfoManager.GetVehicleInfo(entity);
if (vehicleInfo == null)
continue;
var lockEntity = GetVehicleLock(entity);
if (lockEntity == null)
continue;
var transform = lockEntity.transform;
transform.localPosition = vehicleInfo.LockPosition;
transform.localRotation = vehicleInfo.LockRotation;
lockEntity.SendNetworkUpdate_Position();
}
}
Subscribe(nameof(OnEntityKill));
}
private object CanMountEntity(BasePlayer player, BaseMountable entity)
{
// Don't lock taxi modules
if (entity is ModularCarSeat carSeat && IsTaxiSeat(carSeat))
return null;
return CanPlayerInteractWithParentVehicle(player, entity);
}
private object CanLootEntity(BasePlayer player, StorageContainer container)
{
// Don't lock taxi module shopfronts
if (container is ModularVehicleShopFront)
return null;
return CanPlayerInteractWithParentVehicle(player, container);
}
private object CanLootEntity(BasePlayer player, ContainerIOEntity container)
{
return CanPlayerInteractWithParentVehicle(player, container);
}
private object CanLootEntity(BasePlayer player, RidableHorse horse)
{
return CanPlayerInteractWithVehicle(player, horse);
}
private object CanLootEntity(BasePlayer player, ModularCarGarage carLift)
{
if (carLift == null
|| _config.ModularCarSettings.AllowEditingWhileLockedOut
|| !carLift.PlatformIsOccupied)
return null;
return CanPlayerInteractWithVehicle(player, carLift.carOccupant);
}
private object OnHorseLead(RidableHorse horse, BasePlayer player)
{
return CanPlayerInteractWithVehicle(player, horse);
}
private object OnHotAirBalloonToggle(HotAirBalloon hab, BasePlayer player)
{
return CanPlayerInteractWithVehicle(player, hab);
}
private object OnSwitchToggle(ElectricSwitch electricSwitch, BasePlayer player)
{
if (electricSwitch == null)
return null;
var autoTurret = electricSwitch.GetParentEntity() as AutoTurret;
if (autoTurret != null)
return CanPlayerInteractWithParentVehicle(player, autoTurret);
return null;
}
private object OnTurretAuthorize(AutoTurret entity, BasePlayer player)
{
return CanPlayerInteractWithParentVehicle(player, entity);
}
private object OnTurretTarget(AutoTurret autoTurret, BasePlayer player)
{
if (autoTurret == null || player == null || player.UserIDString == null)
return null;
var turretParent = autoTurret.GetParentEntity();
var vehicle = turretParent as BaseVehicle ?? (turretParent as BaseVehicleModule)?.Vehicle;
if (vehicle == null)
return null;
var baseLock = GetVehicleLock(vehicle);
if (baseLock == null)
return null;
if (CanPlayerBypassLock(player, baseLock, provideFeedback: false))
return False;
return null;
}
private object CanSwapToSeat(BasePlayer player, ModularCarSeat carSeat)
{
// Don't lock taxi modules
if (IsTaxiSeat(carSeat))
return null;
return CanPlayerInteractWithParentVehicle(player, carSeat, provideFeedback: false);
}
private object OnVehiclePush(BaseVehicle vehicle, BasePlayer player)
{
return CanPlayerInteractWithVehicle(player, vehicle);
}
private void OnEntityKill(BaseLock baseLock)
{
var vehicle = GetParentVehicle(baseLock);
if (vehicle == null)
return;
_lockedVehicleTracker.OnLockRemoved(vehicle);
}
// Handle the case where a cockpit is removed but the car remains
// If a lock is present, either move the lock to another cockpit or destroy it
private void OnEntityKill(VehicleModuleSeating seatingModule)
{
if (seatingModule == null || !seatingModule.HasADriverSeat())
return;
var car = seatingModule.Vehicle as ModularCar;
if (car == null)
return;
var baseLock = seatingModule.GetComponentInChildren<BaseLock>();
if (baseLock == null)
return;
baseLock.SetParent(null);
var car2 = car;
var baseLock2 = baseLock;
NextTick(() =>
{
if (car2 == null)
{
_lockedVehicleTracker.OnLockRemoved(car2);
baseLock2.Kill();
}
else
{
var driverModule = FindFirstDriverModule(car2);
if (driverModule == null)
{
_lockedVehicleTracker.OnLockRemoved(car2);
baseLock2.Kill();
}
else
{
baseLock2.SetParent(driverModule);
}
}
});
}
// Allow players to deploy locks directly without any commands.private object CanDeployItem(BasePlayer basePlayer, Deployer deployer, NetworkableId entityId)
{
if (basePlayer == null || deployer == null)
return null;
var deployable = deployer.GetDeployable();
if (deployable == null)
return null;
var activeItem = basePlayer.GetActiveItem();
if (activeItem == null)
return null;
var itemId = activeItem.info.itemid;
LockInfo lockInfo;
if (itemId == LockInfo.CodeLock.ItemId)
{
lockInfo = LockInfo.CodeLock;
}
else if (itemId == LockInfo.PilotCodeLock.ItemId)
{
lockInfo = LockInfo.PilotCodeLock;
}
else if (itemId == LockInfo.KeyLock.ItemId)
{
lockInfo = LockInfo.KeyLock;
}
else
{
return null;
}
var vehicleInfo = GetVehicleAndInfo(BaseNetworkable.serverEntities.Find(entityId) as BaseEntity, basePlayer, out var vehicle, fromDeployHook: true);
if (vehicleInfo == null)
return null;
var player = basePlayer.IPlayer;
// Trick to make sure the replies are in chat instead of console.player.LastCommand = CommandType.Chat;
if (!VerifyCanDeploy(player, vehicle, vehicleInfo, lockInfo.LockType, out _, out var payType)
|| !VerifyDeployDistance(player, vehicle))
return False;
DeployLockForPlayer(vehicle, vehicleInfo, lockInfo, basePlayer, payType);
return False;
}
private object OnEntityReskin(Snowmobile snowmobile, ItemSkinDirectory.Skin skin, BasePlayer player)
{
var baseLock = GetVehicleLock(snowmobile);
if (baseLock == null)
return null;
if (_vehicleInfoManager.GetVehicleInfo(snowmobile) == null)
return null;
if (baseLock.IsLocked() && !CanPlayerBypassLock(player, baseLock, provideFeedback: true))
return False;
_reskinManager.HandleReskinPre(snowmobile, baseLock);
// In case another plugin blocks the pre-hook, add back or destroy the lock.NextTick(_reskinManager.CleanupAction);
return null;
}
private void OnEntityReskinned(Snowmobile snowmobile, ItemSkinDirectory.Skin skin, BasePlayer player)
{
_reskinManager.HandleReskinPost(snowmobile);
}
#endregion
#region API
[HookMethod(nameof(API_DeployCodeLock))]
public CodeLock API_DeployCodeLock(BaseEntity vehicle, BasePlayer player, bool isFree = true)
{
return DeployLockForAPI(vehicle, player, LockType.Code, isFree) as CodeLock;
}
[HookMethod(nameof(API_DeployKeyLock))]
public KeyLock API_DeployKeyLock(BaseEntity vehicle, BasePlayer player, bool isFree = true)
{
return DeployLockForAPI(vehicle, player, LockType.Key, isFree) as KeyLock;
}
[HookMethod(nameof(API_CanPlayerDeployCodeLock))]
public bool API_CanPlayerDeployCodeLock(BasePlayer player, BaseEntity vehicle)
{
return CanPlayerDeployLockForAPI(player, vehicle, LockType.Code);
}
[HookMethod(nameof(API_CanPlayerDeployKeyLock))]
public bool API_CanPlayerDeployKeyLock(BasePlayer player, BaseEntity vehicle)
{
return CanPlayerDeployLockForAPI(player, vehicle, LockType.Key);
}
[HookMethod(nameof(API_CanAccessVehicle))]
public bool API_CanAccessVehicle(BasePlayer player, BaseEntity vehicle, bool provideFeedback = true)
{
return CanPlayerInteractWithVehicle(player, vehicle, provideFeedback) == null;
}
[HookMethod(nameof(API_RegisterCustomVehicleType))]
public void API_RegisterCustomVehicleType(string vehicleType, Vector3 lockPosition, Quaternion lockRotation, string parentBone, Func<BaseEntity, BaseEntity> determineLockParent)
{
_vehicleInfoManager.RegisterCustomVehicleType(this, new VehicleInfo()
{
VehicleType = vehicleType,
LockPosition = lockPosition,
LockRotation = lockRotation,
ParentBone = parentBone,
DetermineLockParent = determineLockParent,
});
}
#endregion
#region Commands
[Command("vehiclecodelock", "vcodelock", "vlock")]
private void CodeLockCommand(IPlayer player, string cmd, string[] args)
{
LockCommand(player, LockType.Code);
}
[Command("vehiclekeylock", "vkeylock")]
private void KeyLockCommand(IPlayer player, string cmd, string[] args)
{
LockCommand(player, LockType.Key);
}
private void LockCommand(IPlayer player, LockType lockType)
{
if (player.IsServer)
return;
var basePlayer = player.Object as BasePlayer;
var vehicleInfo = GetVehicleAndInfo(GetLookEntity(basePlayer, MaxDeployDistance), basePlayer, out var vehicle);
if (vehicleInfo == null)
{
ReplyToPlayer(player, Lang.DeployErrorNoVehicleFound);
return;
}
if (!VerifyCanDeploy(player, vehicle, vehicleInfo, lockType, out var lockInfo, out var payType))
return;
DeployLockForPlayer(vehicle, vehicleInfo, lockInfo, basePlayer, payType);
}
#endregion
#region Helper Methods - General
private static bool HasPermissionAny(IPlayer player, params string[] permissionNames)
{
foreach (var perm in permissionNames)
{
if (player.HasPermission(perm))
return true;
}
return false;
}
private static BaseLock GetVehicleLock(BaseEntity vehicle)
{
return vehicle.GetSlot(BaseEntity.Slot.Lock) as BaseLock;
}
private static bool IsLockableEntity(BaseEntity entity)
{
if (entity.IsBusy())
return false;
return entity.HasSlot(BaseEntity.Slot.Lock);
}
private static string[] FindPrefabsOfType<T>() where T : BaseEntity
{
var prefabList = new List<string>();
foreach (var assetPath in GameManifest.Current.entities)
{
var entity = GameManager.server.FindPrefab(assetPath)?.GetComponent<T>();
if (entity == null)
continue;
prefabList.Add(entity.PrefabName);
}
return prefabList.ToArray();
}
private static bool IsTaxiSeat(ModularCarSeat carSeat)
{
return carSeat is { associatedSeatingModule.DoorsAreLockable: false };
}
#endregion
#region Helper Methods - Lock Authorization
private bool IsPlayerAuthorizedToCodeLock(ulong userID, CodeLock codeLock)
{
return codeLock.whitelistPlayers.Contains(userID)
|| codeLock.guestPlayers.Contains(userID);
}
private bool IsPlayerAuthorizedToLock(BasePlayer player, BaseLock baseLock)
{
return (baseLock as KeyLock)?.HasLockPermission(player)
?? IsPlayerAuthorizedToCodeLock(player.userID, baseLock as CodeLock);
}
private bool PlayerHasMasterKeyForLock(BasePlayer player)
{
return permission.UserHasPermission(player.UserIDString, Permission_MasterKey);
}
private bool IsLockSharedWithPlayer(BasePlayer player, BaseLock baseLock)
{
var ownerId = baseLock.OwnerID;
if (ownerId == 0 || ownerId == player.userID)
return false;
// In case the owner was locked out for some reason
var codeLock = baseLock as CodeLock;
if (codeLock != null && !IsPlayerAuthorizedToCodeLock(ownerId, codeLock))
return false;
var sharingSettings = _config.SharingSettings;
if (sharingSettings.Team && player.currentTeam != 0)
{
var team = RelationshipManager.ServerInstance.FindTeam(player.currentTeam);
if (team != null && team.members.Contains(ownerId))
return true;
}
if (sharingSettings.Friends && Friends != null)
{
var friendsResult = Friends.Call("HasFriend", baseLock.OwnerID, player.userID);
if (friendsResult is true)
return true;
}
if ((sharingSettings.Clan || sharingSettings.ClanOrAlly) && Clans != null)
{
var clanMethodName = sharingSettings.ClanOrAlly ? "IsMemberOrAlly" : "IsClanMember";
var clanResult = Clans.Call(clanMethodName, ownerId.ToString(), player.UserIDString);
if (clanResult is true)
return true;
}
return false;
}
private bool CanPlayerBypassLock(BasePlayer player, BaseLock baseLock, bool provideFeedback)
{
var hookResult = Interface.CallHook("CanUseLockedEntity", player, baseLock);
if (hookResult is bool result)
return result;
if (_config.AllowNPCsToBypassLocks && (player.IsNpc || !player.userID.IsSteamId()))
return true;
var canAccessLock = IsPlayerAuthorizedToLock(player, baseLock)
|| IsLockSharedWithPlayer(player, baseLock)
|| PlayerHasMasterKeyForLock(player);
if (canAccessLock)
{
if (provideFeedback && !(baseLock is KeyLock))
{
Effect.server.Run(Prefab_CodeLock_UnlockEffect, baseLock, 0, Vector3.zero, Vector3.forward);
}
return true;
}
if (provideFeedback)
{
Effect.server.Run(Prefab_CodeLock_DeniedEffect, baseLock, 0, Vector3.zero, Vector3.forward);
ChatMessage(player, Lang.GenericErrorVehicleLocked);
}
return false;
}
private object CanPlayerInteractWithVehicle(BasePlayer player, BaseEntity vehicle, bool provideFeedback = true)
{
if (player == null || vehicle == null)
return null;
var baseLock = GetVehicleLock(vehicle);
if (baseLock == null || !baseLock.IsLocked())
return null;
if (CanPlayerBypassLock(player, baseLock, provideFeedback))
return null;
return False;
}
private BaseEntity GetParentVehicle(BaseEntity entity)
{
var parent = entity.GetParentEntity();
if (parent == null)
return null;
// Check for a vehicle module first since they are considered vehicles.var parentModule = parent as BaseVehicleModule;
if (parentModule != null)
return parentModule.Vehicle;
if (parent is HotAirBalloon or BaseVehicle)
return parent;
return _vehicleInfoManager.GetCustomVehicleParent(entity);
}
private object CanPlayerInteractWithParentVehicle(BasePlayer player, BaseEntity entity, bool provideFeedback = true)
{
return CanPlayerInteractWithVehicle(player, GetParentVehicle(entity), provideFeedback);
}
#endregion
#region Helper Methods - Deploying Locks
private static bool DeployWasBlocked(BaseEntity vehicle, BasePlayer player, LockType lockType)
{
return Interface.CallHook(lockType.PreHookName, vehicle, player) is false;
}
private static BaseEntity GetLookEntity(BasePlayer player, float maxDistance)
{
if (!Physics.Raycast(player.eyes.HeadRay(), out var hit, maxDistance, Physics.DefaultRaycastLayers, QueryTriggerInteraction.Ignore))
return null;
return hit.GetEntity();
}
private static bool IsDead(BaseEntity entity)
{
return (entity as BaseCombatEntity)?.IsDead() ?? false;
}
private static VehicleModuleSeating FindFirstDriverModule(ModularCar car)
{
for (var socketIndex = 0; socketIndex < car.TotalSockets; socketIndex++)
{
if (car.TryGetModuleAt(socketIndex, out var module))
{
var seatingModule = module as VehicleModuleSeating;
if (seatingModule != null && seatingModule.HasADriverSeat())
return seatingModule;
}
}
return null;
}
private static bool CanCarHaveLock(ModularCar car)
{
return FindFirstDriverModule(car) != null;
}
private static bool CanVehicleHaveALock(BaseEntity vehicle)
{
// Only modular cars have restrictions
var car = vehicle as ModularCar;
return car == null || CanCarHaveLock(car);
}
private static Item GetPlayerLockItem(BasePlayer player, LockType lockType, out LockInfo lockInfo)
{
if (lockType == LockType.Key)
{
lockInfo = LockInfo.KeyLock;
return GetPlayerLockItem(player, LockInfo.KeyLock);
}
// Prioritize the original code lock over the pilot code lock.var codeLockItem = GetPlayerLockItem(player, LockInfo.CodeLock);
if (codeLockItem != null)
{
lockInfo = LockInfo.CodeLock;
return codeLockItem;
}
var pilotCodeLockItem = GetPlayerLockItem(player, LockInfo.PilotCodeLock);
if (pilotCodeLockItem != null)
{
lockInfo = LockInfo.PilotCodeLock;
return pilotCodeLockItem;
}
lockInfo = LockInfo.CodeLock;
return null;
}
private static Item GetPlayerLockItem(BasePlayer player, LockInfo lockInfo)
{
return player.inventory.FindItemByItemID(lockInfo.ItemId);
}
private static PayType DeterminePayType(IPlayer player, LockType lockType, out LockInfo lockInfo)
{
// Find a matching lock item in order to determine which code lock type should be deployed.var lockItem = GetPlayerLockItem(player.Object as BasePlayer, lockType, out lockInfo);
if (player.HasPermission(lockType.PermissionFree))
return PayType.Free;
return lockItem != null
? PayType.Item
: PayType.Resources;
}
private static bool CanPlayerAffordLock(BasePlayer player, LockType lockType, out LockInfo lockInfo, out PayType payType)
{
payType = DeterminePayType(player.IPlayer, lockType, out lockInfo);
if (payType != PayType.Resources)
return true;
return player.inventory.crafting.CanCraft(lockInfo.ItemDefinition);
}
private static RidableHorse GetClosestHorse(HitchTrough hitchTrough, BasePlayer player)
{
var closestDistance = float.MaxValue;
RidableHorse closestHorse = null;
foreach (var hitchSpot in hitchTrough.hitchSpots)
{
if (!hitchSpot.IsOccupied())
continue;
var distance = Vector3.Distance(player.transform.position, hitchSpot.tr.position);
if (distance < closestDistance)
{
closestDistance = distance;
if (hitchSpot.hitchableEntRef.Get(serverside: true) is RidableHorse ridableHorse)
{
closestHorse = ridableHorse;
}
}
}
return closestHorse;
}
private static void ClaimVehicle(BaseEntity vehicle, ulong ownerId)
{
vehicle.OwnerID = ownerId;
Interface.CallHook("OnVehicleOwnershipChanged", vehicle);
}
private bool IsVehicleConnectedEntity(BasePlayer basePlayer, BaseEntity entity, out BaseEntity vehicle)
{
vehicle = null;
if (entity is BaseVehicleModule module)
{
vehicle = module.Vehicle ?? module.GetParentEntity();
return true;
}
if (entity is ModularCarGarage carLift)
{
vehicle = carLift.carOccupant;
return true;
}
if (entity is HitchTrough hitchTrough)
{
vehicle = GetClosestHorse(hitchTrough, basePlayer);
return true;
}
return false;
}
private VehicleInfo GetVehicleAndInfo(BaseEntity entity, BasePlayer basePlayer, out BaseEntity vehicle, bool fromDeployHook = false)
{
if (entity == null)
{
vehicle = null;
return null;
}
var vehicleInfo = _vehicleInfoManager.GetVehicleInfo(entity);
if (vehicleInfo != null)
{
vehicle = entity;
return vehicleInfo;
}
if (IsVehicleConnectedEntity(basePlayer, entity, out vehicle) && vehicle != null)
return _vehicleInfoManager.GetVehicleInfo(vehicle);
if (fromDeployHook && IsLockableEntity(entity))
{
// Let the game decide whether to lock the entity, instead of resolving the parent vehicle.vehicle = null;
return null;
}
vehicle = entity.GetParentEntity();
if (vehicle == null)
return null;
return _vehicleInfoManager.GetVehicleInfo(vehicle);
}
private bool AllowNoOwner(BaseEntity vehicle)
{
return _config.AllowIfNoOwner
|| vehicle.OwnerID != 0;
}
private bool AllowDifferentOwner(IPlayer player, BaseEntity vehicle)
{
return _config.AllowIfDifferentOwner
|| vehicle.OwnerID == 0
|| vehicle.OwnerID.ToString() == player.Id;
}
private void MaybeChargePlayerForLock(BasePlayer player, LockInfo lockInfo, PayType payType)
{
if (payType == PayType.Free)
return;
if (payType == PayType.Item)
{
// Prefer taking the item they are holding in case they are deploying directly.var heldItem = player.GetActiveItem();
if (heldItem != null && heldItem.info.itemid == lockInfo.ItemId)
{
heldItem.UseItem(1);
}
else
{
player.inventory.Take(null, lockInfo.ItemId, 1);
}
player.Command("note.inv", lockInfo.ItemId, -1);
return;
}
foreach (var ingredient in lockInfo.Blueprint.ingredients)
{
player.inventory.Take(null, ingredient.itemid, (int)ingredient.amount);
player.Command("note.inv", ingredient.itemid, -ingredient.amount);
GetCooldownManager(lockInfo.LockType).UpdateLastUsedForPlayer(player.UserIDString);
}
}
private BaseLock DeployLock(BaseEntity vehicle, VehicleInfo vehicleInfo, LockInfo lockInfo, ulong ownerId = 0)
{
var parentToEntity = vehicleInfo.DetermineLockParent(vehicle);
if (parentToEntity == null)
return null;
var baseLock = GameManager.server.CreateEntity(lockInfo.Prefab, vehicleInfo.LockPosition, vehicleInfo.LockRotation) as BaseLock;
if (baseLock == null)
return null;
var keyLock = baseLock as KeyLock;
if (keyLock != null)
{
keyLock.keyCode = UnityEngine.Random.Range(1, 100000);
}
// Assign lock ownership when the lock is being deployed by/for a player.if (ownerId != 0)
{
baseLock.OwnerID = ownerId;
}
baseLock.SetParent(parentToEntity, vehicleInfo.ParentBone);
baseLock.Spawn();
vehicle.SetSlot(BaseEntity.Slot.Lock, baseLock);
// Auto lock key locks to be consistent with vanilla.if (ownerId != 0 && keyLock != null)
{
keyLock.SetFlag(BaseEntity.Flags.Locked, true);
}
Effect.server.Run(Prefab_CodeLock_DeployedEffect, baseLock.transform.position);
Interface.CallHook("OnVehicleLockDeployed", vehicle, baseLock);
_lockedVehicleTracker.OnLockAdded(vehicle);
return baseLock;
}
private BaseLock DeployLockForPlayer(BaseEntity vehicle, VehicleInfo vehicleInfo, LockInfo lockInfo, BasePlayer player, PayType payType)
{
var originalVehicleOwnerId = vehicle.OwnerID;
// Temporarily set the player as the owner of the vehicle, for compatibility with AutoCodeLock (OnItemDeployed).vehicle.OwnerID = player.userID;
var baseLock = DeployLock(vehicle, vehicleInfo, lockInfo, player.userID);
if (baseLock == null)
{
vehicle.OwnerID = originalVehicleOwnerId;
return null;
}
// Allow other plugins to detect the code lock being deployed (e.g., to auto lock).var lockItem = GetPlayerLockItem(player, lockInfo);
if (lockItem != null)
{
Interface.CallHook("OnItemDeployed", lockItem.GetHeldEntity(), vehicle, baseLock);
}
else
{
// Temporarily increase the player inventory capacity to ensure there is enough space.player.inventory.containerMain.capacity++;
var temporaryLockItem = ItemManager.CreateByItemID(lockInfo.ItemId);
if (player.inventory.GiveItem(temporaryLockItem))
{
Interface.CallHook("OnItemDeployed", temporaryLockItem.GetHeldEntity(), vehicle, baseLock);
temporaryLockItem.RemoveFromContainer();
}
temporaryLockItem.Remove();
player.inventory.containerMain.capacity--;
}
// Revert the vehicle owner to the original, after OnItemDeployed is called.vehicle.OwnerID = originalVehicleOwnerId;
MaybeChargePlayerForLock(player, lockInfo, payType);
// Potentially assign vehicle ownership when the lock is being deployed by/for a player.if (vehicle.OwnerID == 0)
{
if (_config.AutoClaimUnownedVehicles)
{
ClaimVehicle(vehicle, player.userID);
}
}
else if (vehicle.OwnerID != player.userID && _config.AutoReplaceVehicleOwnership)
{
ClaimVehicle(vehicle, player.userID);
}
return baseLock;
}
private BaseLock DeployLockForAPI(BaseEntity vehicle, BasePlayer player, LockType lockType, bool isFree)
{
if (vehicle == null || IsDead(vehicle))
return null;
var vehicleInfo = _vehicleInfoManager.GetVehicleInfo(vehicle);
if (vehicleInfo == null
|| GetVehicleLock(vehicle) != null
|| !CanVehicleHaveALock(vehicle))
return null;
GetPlayerLockItem(player, lockType, out var lockInfo);
PayType payType;
if (isFree)
{
payType = PayType.Free;
}
else if (!VerifyPlayerCanDeployLock(player.IPlayer, lockType, out _, out payType))
{
return null;
}
if (DeployWasBlocked(vehicle, player, lockType))
return null;
return player != null
? DeployLockForPlayer(vehicle, vehicleInfo, lockInfo, player, payType)
: DeployLock(vehicle, vehicleInfo, lockInfo);
}
private bool CanPlayerDeployLockForAPI(BasePlayer player, BaseEntity vehicle, LockType lockType)
{
return vehicle != null
&& !IsDead(vehicle)
&& _vehicleInfoManager.GetVehicleInfo(vehicle) != null
&& AllowNoOwner(vehicle)
&& AllowDifferentOwner(player.IPlayer, vehicle)
&& player.CanBuild()
&& GetVehicleLock(vehicle) == null
&& CanVehicleHaveALock(vehicle)
&& CanPlayerAffordLock(player, lockType, out _, out _)
&& !DeployWasBlocked(vehicle, player, lockType);
}
#endregion
#region Helper Methods - Command Checks
private bool VerifyDeployDistance(IPlayer player, BaseEntity vehicle)
{
if (vehicle.Distance(player.Object as BasePlayer) <= MaxDeployDistance)
return true;
ReplyToPlayer(player, Lang.DeployErrorDistance);
return false;
}
private bool VerifyPermissionToVehicleAndLockType(IPlayer player, VehicleInfo vehicleInfo, LockType lockType)
{
var vehiclePerm = lockType == LockType.Code
? vehicleInfo.CodeLockPermission
: vehicleInfo.KeyLockPermission;
if (vehiclePerm != null && HasPermissionAny(player, lockType.PermissionAllVehicles, vehiclePerm))
return true;
ReplyToPlayer(player, Lang.GenericErrorNoPermission);
return false;
}
private bool VerifyVehicleIsNotDead(IPlayer player, BaseEntity vehicle)
{
if (!IsDead(vehicle))
return true;
ReplyToPlayer(player, Lang.DeployErrorVehicleDead);
return false;
}
private bool VerifyNotForSale(IPlayer player, BaseEntity vehicle)
{
var horse = vehicle as RidableHorse;
if (horse == null || !horse.IsForSale)
return true;
ReplyToPlayer(player, Lang.DeployErrorOther);
return false;
}
private bool VerifyNoOwnershipRestriction(IPlayer player, BaseEntity vehicle)
{
if (!AllowNoOwner(vehicle))
{
ReplyToPlayer(player, Lang.DeployErrorNoOwner);
return false;
}
if (!AllowDifferentOwner(player, vehicle))
{
ReplyToPlayer(player, Lang.DeployErrorDifferentOwner);
return false;
}
return true;
}
private bool VerifyCanBuild(IPlayer player, BaseEntity vehicle)
{
var basePlayer = player.Object as BasePlayer;
if (basePlayer == null)
return false;
if (vehicle.OwnerID == 0 && _config.RequireTCIfNoOwner)
{
if (!basePlayer.IsBuildingAuthed() || !basePlayer.IsBuildingAuthed(vehicle.WorldSpaceBounds()))
{
ReplyToPlayer(player, Lang.DeployErrorNoOwnerRequiresTC);
return false;
}
}
else if (basePlayer.IsBuildingBlocked() || basePlayer.IsBuildingBlocked(vehicle.WorldSpaceBounds()))
{
ReplyToPlayer(player, Lang.GenericErrorBuildingBlocked);
return false;
}
return true;
}
private bool VerifyVehicleHasNoLock(IPlayer player, BaseEntity vehicle)
{
if (GetVehicleLock(vehicle) == null)
return true;
ReplyToPlayer(player, Lang.DeployErrorHasLock);
return false;
}
private bool VerifyVehicleCanHaveALock(IPlayer player, BaseEntity vehicle)
{
if (CanVehicleHaveALock(vehicle))
return true;
ReplyToPlayer(player, Lang.DeployErrorModularCarNoCockpit);
return false;
}
private bool VerifyPlayerCanAffordLock(BasePlayer player, LockType lockType, out LockInfo lockInfo, out PayType payType)
{
if (CanPlayerAffordLock(player, lockType, out lockInfo, out payType))
return true;
ChatMessage(player, Lang.DeployErrorInsufficientResources, lockInfo.ItemDefinition.displayName.translated);
return false;
}
private bool VerifyOffCooldown(IPlayer player, LockType lockType, PayType payType)
{
if (payType != PayType.Resources)
return true;
var secondsRemaining = GetCooldownManager(lockType).GetSecondsRemaining(player.Id);
if (secondsRemaining <= 0)
return true;
ChatMessage(player.Object as BasePlayer, Lang.GenericErrorCooldown, Math.Ceiling(secondsRemaining));
return false;
}
private bool VerifyPlayerCanDeployLock(IPlayer player, LockType lockType, out LockInfo lockInfo, out PayType payType)
{
return VerifyPlayerCanAffordLock(player.Object as BasePlayer, lockType, out lockInfo, out payType)
&& VerifyOffCooldown(player, lockType, payType);
}
private bool VerifyNotMounted(IPlayer player, BaseEntity vehicle, VehicleInfo vehicleInfo)
{
if (!vehicleInfo.IsMounted(vehicle))
return true;
ReplyToPlayer(player, Lang.DeployErrorMounted);
return false;
}
private bool VerifyCanDeploy(IPlayer player, BaseEntity vehicle, VehicleInfo vehicleInfo, LockType lockType, out LockInfo lockInfo, out PayType payType)
{
var basePlayer = player.Object as BasePlayer;
payType = PayType.Item;
if (!VerifyPermissionToVehicleAndLockType(player, vehicleInfo, lockType)
|| !VerifyVehicleIsNotDead(player, vehicle)
|| !VerifyNotForSale(player, vehicle)
|| !VerifyNoOwnershipRestriction(player, vehicle)
|| !VerifyCanBuild(player, vehicle)
|| !VerifyVehicleHasNoLock(player, vehicle)
|| !VerifyVehicleCanHaveALock(player, vehicle))
{
lockInfo = null;
return false;
}
return VerifyPlayerCanDeployLock(player, lockType, out lockInfo, out payType)
&& VerifyNotMounted(player, vehicle, vehicleInfo)
&& !DeployWasBlocked(vehicle, basePlayer, lockType);
}
#endregion
#region Vehicle Info
private class VehicleInfo
{
public string VehicleType;
public string[] PrefabPaths;
public Vector3 LockPosition;
public Quaternion LockRotation;
public string ParentBone;
public string CodeLockPermission { get; private set; }
public string KeyLockPermission { get; private set; }
public uint[] PrefabIds { get; private set; }
public Func<BaseEntity, BaseEntity> DetermineLockParent = entity => entity;
public Func<BaseEntity, float> TimeSinceLastUsed = _ => 0;
public void OnServerInitialized(VehicleDeployedLocks plugin)
{
CodeLockPermission = $"{Permission_CodeLock_Prefix}.{VehicleType}";
KeyLockPermission = $"{Permission_KeyLock_Prefix}.{VehicleType}";
if (!plugin.permission.PermissionExists(CodeLockPermission, plugin))
{
plugin.permission.RegisterPermission(CodeLockPermission, plugin);
}
if (!plugin.permission.PermissionExists(KeyLockPermission, plugin))
{
plugin.permission.RegisterPermission(KeyLockPermission, plugin);
}
// Custom vehicles aren't currently allowed to specify prefabs since they reuse existing prefabs.if (PrefabPaths != null)
{
var prefabIds = new List<uint>();
foreach (var prefabName in PrefabPaths)
{
var prefabId = StringPool.Get(prefabName);
if (prefabId != 0)
{
prefabIds.Add(prefabId);
}
else
{
plugin.LogError($"Invalid prefab.Please alert the plugin maintainer -- {prefabName}");
}
}
PrefabIds = prefabIds.ToArray();
}
}
// In the future, custom vehicles may be able to pass in a method to override this.public bool IsMounted(BaseEntity entity)
{
var vehicle = entity as BaseVehicle;
if (vehicle != null)
return vehicle.AnyMounted();
var mountable = entity as BaseMountable;
if (mountable != null)
return mountable.AnyMounted();
return false;
}
}
private class VehicleInfoManager
{
private static readonly FieldInfo BikeTimeSinceLastUsedField = typeof(Bike).GetField("timeSinceLastUsed",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
private readonly VehicleDeployedLocks _plugin;
private readonly Dictionary<uint, VehicleInfo> _prefabIdToVehicleInfo = new();
private readonly Dictionary<string, VehicleInfo> _customVehicleTypes = new();
public VehicleInfoManager(VehicleDeployedLocks plugin)
{
_plugin = plugin;
}
public void OnServerInitialized()
{
var allVehicles = new[]
{
new VehicleInfo
{
VehicleType = "attackhelicopter",
PrefabPaths = new[] { "assets/content/vehicles/attackhelicopter/attackhelicopter.entity.prefab" },
LockPosition = new Vector3(-0.6f, 1.08f, 1.01f),
TimeSinceLastUsed = vehicle => Time.time - (vehicle as AttackHelicopter)?.lastEngineOnTime ?? Time.time,
},
new VehicleInfo
{
VehicleType = "chinook",
PrefabPaths = new[] { "assets/prefabs/npc/ch47/ch47.entity.prefab" },
LockPosition = new Vector3(-1.175f, 2, 6.5f),
TimeSinceLastUsed = vehicle => Time.time - (vehicle as CH47Helicopter)?.lastPlayerInputTime ?? Time.time,
},
new VehicleInfo
{
VehicleType = "duosub",
PrefabPaths = new[] { "assets/content/vehicles/submarine/submarineduo.entity.prefab" },
LockPosition = new Vector3(-0.455f, 1.29f, 0.75f),
LockRotation = Quaternion.Euler(0, 180, 10),
TimeSinceLastUsed = vehicle => (vehicle as SubmarineDuo)?.timeSinceLastUsed ?? 0,
},
new VehicleInfo
{
VehicleType = "hotairballoon",
PrefabPaths = new[] { "assets/prefabs/deployable/hot air balloon/hotairballoon.prefab" },
LockPosition = new Vector3(1.45f, 0.9f, 0),
TimeSinceLastUsed = vehicle => Time.time - (vehicle as HotAirBalloon)?.sinceLastBlast ?? Time.time,
},
new VehicleInfo
{
VehicleType = "kayak",
PrefabPaths = new[] { "assets/content/vehicles/boats/kayak/kayak.prefab" },
LockPosition = new Vector3(-0.43f, 0.2f, 0.2f),
LockRotation = Quaternion.Euler(0, 90, 90),
TimeSinceLastUsed = vehicle => (vehicle as Kayak)?.timeSinceLastUsed ?? 0,
},
new VehicleInfo
{
VehicleType = "locomotive",
PrefabPaths = new[] { "assets/content/vehicles/trains/locomotive/locomotive.entity.prefab" },
LockPosition = new Vector3(-0.11f, 2.89f, 4.95f),
TimeSinceLastUsed = vehicle => (vehicle as TrainEngine)?.decayingFor ?? 0,
},
new VehicleInfo
{
VehicleType = "magnetcrane",
PrefabPaths = new[] { "assets/content/vehicles/crane_magnet/magnetcrane.entity.prefab" },
LockPosition = new Vector3(-1.735f, -1.445f, 0.79f),
LockRotation = Quaternion.Euler(0, 0, 90),
ParentBone = "Top",
TimeSinceLastUsed = vehicle => Time.realtimeSinceStartup - (vehicle as MagnetCrane)?.lastDrivenTime ?? Time.realtimeSinceStartup,
},
new VehicleInfo
{
VehicleType = "minicopter",
PrefabPaths = new[] { "assets/content/vehicles/minicopter/minicopter.entity.prefab" },
LockPosition = new Vector3(-0.15f, 0.7f, -0.1f),
TimeSinceLastUsed = vehicle => Time.time - (vehicle as Minicopter)?.lastEngineOnTime ?? Time.time,
},
new VehicleInfo
{
VehicleType = "modularcar",
// There are at least 37 valid Modular Car prefabs.PrefabPaths = FindPrefabsOfType<ModularCar>(),
LockPosition = new Vector3(-0.9f, 0.35f, -0.5f),
DetermineLockParent = vehicle => FindFirstDriverModule((Modular
// ... [Code preview truncated due to length. Full source in attached file] ...