C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Claim Vehicle", "WhiteThunder", "1.8.1")]
[Description("Allows players to claim ownership of unowned vehicles.")]
internal class ClaimVehicle : CovalencePlugin
{
#region Fields
private Configuration _config;
private const string Permission_Unclaim = "claimvehicle.unclaim";
private const string Permission_NoClaimCooldown = "claimvehicle.nocooldown";
private const string Permission_Claim_AllVehicles = "claimvehicle.claim.allvehicles";
private readonly VehicleInfoManager _vehicleInfoManager;
private CooldownManager _cooldownManager;
public ClaimVehicle()
{
_vehicleInfoManager = new VehicleInfoManager(this);
}
#endregion
#region Hooks
private void Init()
{
_config.Init(this);
_cooldownManager = new CooldownManager(_config.ClaimCooldownSeconds);
permission.RegisterPermission(Permission_Unclaim, this);
permission.RegisterPermission(Permission_NoClaimCooldown, this);
permission.RegisterPermission(Permission_Claim_AllVehicles, this);
}
private void OnServerInitialized()
{
_vehicleInfoManager.OnServerInitialized();
}
#endregion
#region Exposed Hooks
private static class ExposedHooks
{
public static object OnVehicleClaim(BasePlayer player, BaseCombatEntity vehicle)
{
return Interface.CallHook("OnVehicleClaim", player, vehicle);
}
public static object OnVehicleUnclaim(BasePlayer player, BaseCombatEntity vehicle)
{
return Interface.CallHook("OnVehicleUnclaim", player, vehicle);
}
public static void OnVehicleOwnershipChanged(BaseCombatEntity vehicle)
{
Interface.CallHook("OnVehicleOwnershipChanged", vehicle);
}
}
#endregion
#region Commands
private void ClaimVehicleCommand(IPlayer player, string cmd, string[] args)
{
if (player.IsServer)
return;
var basePlayer = player.Object as BasePlayer;
if (!VerifySupportedVehicleFound(player, basePlayer, GetLookEntity(basePlayer), out var vehicle, out var vehicleInfo) ||
!VerifyPermissionAny(player, Permission_Claim_AllVehicles, vehicleInfo.Permission) ||
!VerifyVehicleIsNotDead(player, vehicle) ||
!VerifyNotOwned(player, vehicle) ||
!VerifyOffCooldown(player) ||
!VerifyCanBuild(player) ||
!VerifyNoLockRestriction(player, vehicle) ||
!VerifyNotMounted(player, vehicle) ||
ClaimWasBlocked(basePlayer, vehicle))
return;
ChangeVehicleOwnership(vehicle, basePlayer.userID);
_cooldownManager.UpdateLastUsedForPlayer(basePlayer.userID);
ReplyToPlayer(player, "Claim.Success");
}
private void UnclaimVehicleCommand(IPlayer player, string cmd, string[] args)
{
if (player.IsServer || !VerifyPermissionAny(player, Permission_Unclaim))
return;
var basePlayer = player.Object as BasePlayer;
if (!VerifySupportedVehicleFound(player, basePlayer, GetLookEntity(basePlayer), out var vehicle, out _) ||
!VerifyCurrentlyOwned(player, vehicle) ||
UnclaimWasBlocked(basePlayer, vehicle))
return;
ChangeVehicleOwnership(vehicle, 0);
ReplyToPlayer(player, "Unclaim.Success");
}
#endregion
#region Helper Methods
private static bool ClaimWasBlocked(BasePlayer player, BaseCombatEntity vehicle)
{
return ExposedHooks.OnVehicleClaim(player, vehicle) is false;
}
private static bool UnclaimWasBlocked(BasePlayer player, BaseCombatEntity vehicle)
{
return ExposedHooks.OnVehicleUnclaim(player, vehicle) is false;
}
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 BaseEntity GetLookEntity(BasePlayer player, float maxDistance = 9)
{
return Physics.Raycast(player.eyes.HeadRay(), out var hit, maxDistance, Physics.DefaultRaycastLayers, QueryTriggerInteraction.Ignore)
? hit.GetEntity()
: null;
}
private static void ChangeVehicleOwnership(BaseCombatEntity vehicle, ulong userId)
{
vehicle.OwnerID = userId;
ExposedHooks.OnVehicleOwnershipChanged(vehicle);
}
private static string FormatDuration(double seconds)
{
return TimeSpan.FromSeconds(Math.Ceiling(seconds)).ToString("g");
}
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 BaseCombatEntity GetAppropriateVehicle(BaseEntity entity, BasePlayer player)
{
var vehicleModule = entity as BaseVehicleModule;
if ((object)vehicleModule != null)
return vehicleModule.Vehicle;
var carLift = entity as ModularCarGarage;
if ((object)carLift != null)
return carLift.carOccupant;
var hitchTrough = entity as HitchTrough;
if ((object)hitchTrough != null)
return GetClosestHorse(hitchTrough, player);
return entity as BaseCombatEntity;
}
private bool VerifySupportedVehicleFound(IPlayer player, BasePlayer basePlayer, BaseEntity entity, out BaseCombatEntity vehicle, out IVehicleInfo vehicleInfo)
{
vehicle = GetAppropriateVehicle(entity, basePlayer);
if ((object)vehicle != null)
{
vehicleInfo = _vehicleInfoManager.GetVehicleInfo(vehicle);
if (vehicleInfo != null)
return true;
}
vehicleInfo = null;
ReplyToPlayer(player, "Generic.Error.NoSupportedVehicleFound");
return false;
}
private bool VerifyPermissionAny(IPlayer player, params string[] permissionNames)
{
foreach (var perm in permissionNames)
{
if (permission.UserHasPermission(player.Id, perm))
return true;
}
ReplyToPlayer(player, "Generic.Error.NoPermission");
return false;
}
private bool VerifyVehicleIsNotDead(IPlayer player, BaseCombatEntity vehicle)
{
if (!vehicle.IsDead())
return true;
ReplyToPlayer(player, "Generic.Error.VehicleDead");
return false;
}
private bool VerifyNotOwned(IPlayer player, BaseEntity vehicle)
{
if (vehicle.OwnerID == 0)
return true;
var basePlayer = player.Object as BasePlayer;
if (vehicle.OwnerID == basePlayer.userID)
{
ReplyToPlayer(player, "Claim.Error.AlreadyOwnedByYou");
}
else
{
ReplyToPlayer(player, "Claim.Error.DifferentOwner");
}
return false;
}
private bool VerifyOffCooldown(IPlayer player)
{
if (player.HasPermission(Permission_NoClaimCooldown))
return true;
var basePlayer = player.Object as BasePlayer;
var secondsRemaining = _cooldownManager.GetSecondsRemaining(basePlayer.userID);
if (secondsRemaining > 0)
{
ReplyToPlayer(player, "Generic.Error.Cooldown", FormatDuration(secondsRemaining));
return false;
}
return true;
}
private bool VerifyCanBuild(IPlayer player)
{
if ((player.Object as BasePlayer).CanBuild())
return true;
ReplyToPlayer(player, "Generic.Error.BuildingBlocked");
return false;
}
private bool VerifyNoLockRestriction(IPlayer player, BaseCombatEntity vehicle)
{
var basePlayer = player.Object as BasePlayer;
var baseLock = vehicle.GetSlot(BaseEntity.Slot.Lock);
if (baseLock == null || baseLock.OwnerID == basePlayer.userID)
return true;
ReplyToPlayer(player, "Claim.Error.LockedByAnother");
return false;
}
private bool VerifyNotMounted(IPlayer player, BaseCombatEntity entity)
{
var vehicle = entity as BaseVehicle;
if (vehicle == null || !vehicle.AnyMounted())
return true;
ReplyToPlayer(player, "Claim.Error.Mounted");
return false;
}
private bool VerifyCurrentlyOwned(IPlayer player, BaseCombatEntity vehicle)
{
var basePlayer = player.Object as BasePlayer;
if (vehicle.OwnerID == basePlayer.userID)
return true;
ReplyToPlayer(player, "Unclaim.Error.NotOwned");
return false;
}
#endregion
#region Helper Classes
private class CooldownManager
{
private readonly Dictionary<ulong, float> _cooldownMap = new();
private readonly float _cooldownDuration;
public CooldownManager(float duration)
{
_cooldownDuration = duration;
}
public void UpdateLastUsedForPlayer(ulong userId)
{
_cooldownMap[userId] = Time.realtimeSinceStartup;
}
public float GetSecondsRemaining(ulong userId)
{
return _cooldownMap.TryGetValue(userId, out var duration)
? duration + _cooldownDuration - Time.realtimeSinceStartup
: 0;
}
}
private interface IVehicleInfo
{
uint[] PrefabIds { get; }
string Permission { get; }
void OnServerInitialized(ClaimVehicle plugin);
bool IsCorrectType(BaseEntity entity);
}
private class VehicleInfo<T> : IVehicleInfo where T : BaseEntity
{
public uint[] PrefabIds { get; private set; }
public string Permission { get; private set; }
public string VehicleName { get; set; }
public string[] PrefabPaths { get; set; }
public void OnServerInitialized(ClaimVehicle plugin)
{
Permission = $"{nameof(ClaimVehicle)}.claim.{VehicleName}".ToLower();
plugin.permission.RegisterPermission(Permission, plugin);
var prefabIds = new List<uint>(PrefabPaths.Length);
foreach (var prefabName in PrefabPaths)
{
var prefab = GameManager.server.FindPrefab(prefabName)?.GetComponent<T>();
if (prefab == null)
{
plugin.LogError($"Invalid or incorrect prefab.Please alert the plugin maintainer -- {prefabName}");
continue;
}
prefabIds.Add(prefab.prefabID);
}
PrefabIds = prefabIds.ToArray();
}
public bool IsCorrectType(BaseEntity entity)
{
return entity is T;
}
}
private class VehicleInfoManager
{
private readonly ClaimVehicle _plugin;
private readonly Dictionary<uint, IVehicleInfo> _prefabIdToVehicleInfo = new();
private IVehicleInfo[] _allVehicles;
public VehicleInfoManager(ClaimVehicle plugin)
{
_plugin = plugin;
}
public void OnServerInitialized()
{
_allVehicles = new IVehicleInfo[]
{
new VehicleInfo<AttackHelicopter>
{
VehicleName = "attackhelicopter",
PrefabPaths = new[] { "assets/content/vehicles/attackhelicopter/attackhelicopter.entity.prefab" },
},
new VehicleInfo<Ballista>
{
VehicleName = "ballista",
PrefabPaths = new[] { "assets/content/vehicles/siegeweapons/ballista/ballista.entity.prefab" },
},
new VehicleInfo<BatteringRam>
{
VehicleName = "batteringram",
PrefabPaths = new[] { "assets/content/vehicles/siegeweapons/batteringram/batteringram.entity.prefab" },
},
new VehicleInfo<TrainCar>
{
VehicleName = "caboose",
PrefabPaths = new[] { "assets/content/vehicles/trains/caboose/traincaboose.entity.prefab" },
},
new VehicleInfo<Catapult>
{
VehicleName = "catapult",
PrefabPaths = new[] { "assets/content/vehicles/siegeweapons/catapult/catapult.entity.prefab" },
},
new VehicleInfo<CH47Helicopter>
{
VehicleName = "chinook",
PrefabPaths = new[] { "assets/prefabs/npc/ch47/ch47.entity.prefab" },
},
new VehicleInfo<SubmarineDuo>
{
VehicleName = "duosub",
PrefabPaths = new[] { "assets/content/vehicles/submarine/submarineduo.entity.prefab" },
},
new VehicleInfo<HotAirBalloon>
{
VehicleName = "hotairballoon",
PrefabPaths = new[] { "assets/prefabs/deployable/hot air balloon/hotairballoon.prefab" },
},
new VehicleInfo<TrainEngine>
{
VehicleName = "locomotive",
PrefabPaths = new[] { "assets/content/vehicles/trains/locomotive/locomotive.entity.prefab" },
},
new VehicleInfo<Minicopter>
{
VehicleName = "minicopter",
PrefabPaths = new[] { "assets/content/vehicles/minicopter/minicopter.entity.prefab" },
},
new VehicleInfo<ModularCar>
{
VehicleName = "modularcar",
PrefabPaths = FindPrefabsOfType<ModularCar>(),
},
new VehicleInfo<Bike>
{
VehicleName = "motorbike.sidecar",
PrefabPaths = new[] { "assets/content/vehicles/bikes/motorbike_sidecar.prefab" },
},
new VehicleInfo<Bike>
{
VehicleName = "motorbike",
PrefabPaths = new[] { "assets/content/vehicles/bikes/motorbike.prefab" },
},
new VehicleInfo<Bike>
{
VehicleName = "pedalbike",
PrefabPaths = new[] { "assets/content/vehicles/bikes/pedalbike.prefab" },
},
new VehicleInfo<Bike>
{
VehicleName = "pedaltrike",
PrefabPaths = new[] { "assets/content/vehicles/bikes/pedaltrike.prefab" },
},
new VehicleInfo<RHIB>
{
VehicleName = "rhib",
PrefabPaths = new[] { "assets/content/vehicles/boats/rhib/rhib.prefab" },
},
new VehicleInfo<RidableHorse>
{
VehicleName = "ridablehorse",
PrefabPaths = new[] { "assets/content/vehicles/horse/ridablehorse.prefab" },
},
new VehicleInfo<MotorRowboat>
{
VehicleName = "rowboat",
PrefabPaths = new[] { "assets/content/vehicles/boats/rowboat/rowboat.prefab" },
},
new VehicleInfo<ScrapTransportHelicopter>
{
VehicleName = "scraptransporthelicopter",
PrefabPaths = new[] { "assets/content/vehicles/scrap heli carrier/scraptransporthelicopter.prefab" },
},
new VehicleInfo<BasicCar>
{
VehicleName = "sedan",
PrefabPaths = new[] { "assets/content/vehicles/sedan_a/sedantest.entity.prefab" },
},
new VehicleInfo<TrainEngine>
{
VehicleName = "sedanrail",
PrefabPaths = new[] { "assets/content/vehicles/sedan_a/sedanrail.entity.prefab" },
},
new VehicleInfo<SiegeTower>
{
VehicleName = "siegetower",
PrefabPaths = new[] { "assets/content/vehicles/siegeweapons/siegetower/siegetower.entity.prefab" },
},
new VehicleInfo<Snowmobile>
{
VehicleName = "snowmobile",
PrefabPaths = new[] { "assets/content/vehicles/snowmobiles/snowmobile.prefab" },
},
new VehicleInfo<BaseSubmarine>
{
VehicleName = "solosub",
PrefabPaths = new[] { "assets/content/vehicles/submarine/submarinesolo.entity.prefab" },
},
new VehicleInfo<Snowmobile>
{
VehicleName = "tomaha",
PrefabPaths = new[] { "assets/content/vehicles/snowmobiles/tomahasnowmobile.prefab" },
},
new VehicleInfo<TrainCar>
{
VehicleName = "wagona",
PrefabPaths = new[] { "assets/content/vehicles/trains/wagons/trainwagona.entity.prefab" },
},
new VehicleInfo<TrainCar>
{
VehicleName = "wagonb",
PrefabPaths = new[] { "assets/content/vehicles/trains/wagons/trainwagonb.entity.prefab" },
},
new VehicleInfo<TrainCar>
{
VehicleName = "wagonc",
PrefabPaths = new[] { "assets/content/vehicles/trains/wagons/trainwagonc.entity.prefab" },
},
new VehicleInfo<TrainEngine>
{
VehicleName = "workcart",
PrefabPaths = new[] { "assets/content/vehicles/trains/workcart/workcart.entity.prefab" },
},
new VehicleInfo<TrainEngine>
{
VehicleName = "workcartaboveground",
PrefabPaths = new[] { "assets/content/vehicles/trains/workcart/workcart_aboveground.entity.prefab" },
},
new VehicleInfo<TrainEngine>
{
VehicleName = "workcartcovered",
PrefabPaths = new[] { "assets/content/vehicles/trains/workcart/workcart_aboveground2.entity.prefab" },
},
};
foreach (var vehicleInfo in _allVehicles)
{
vehicleInfo.OnServerInitialized(_plugin);
foreach (var prefabId in vehicleInfo.PrefabIds)
{
_prefabIdToVehicleInfo[prefabId] = vehicleInfo;
}
}
}
public IVehicleInfo GetVehicleInfo(BaseEntity entity)
{
return _prefabIdToVehicleInfo.TryGetValue(entity.prefabID, out var vehicleInfo) && vehicleInfo.IsCorrectType(entity)
? vehicleInfo
: null;
}
}
#endregion
#region Configuration
[JsonObject(MemberSerialization.OptIn)]
private class Configuration : SerializableConfiguration
{
[JsonProperty("ClaimCooldownSeconds")]
private float DeprecatedClaimCooldownSeconds { set => ClaimCooldownSeconds = value; }
[JsonProperty("Claim cooldown (seconds)")]
public float ClaimCooldownSeconds = 3600;
[JsonProperty("Claim commands")]
public string[] ClaimCommands = { "vclaim" };
[JsonProperty("Unclaim commands")]
public string[] UnclaimCommands = { "vunclaim" };
public void Init(ClaimVehicle plugin)
{
plugin.AddCovalenceCommand(ClaimCommands, nameof(ClaimVehicleCommand));
plugin.AddCovalenceCommand(UnclaimCommands, nameof(UnclaimVehicleCommand));
}
}
private Configuration GetDefaultConfig() => new();
#region Configuration Helpers
private class SerializableConfiguration
{
public string ToJson() => JsonConvert.SerializeObject(this);
public Dictionary<string, object> ToDictionary() => JsonHelper.Deserialize(ToJson()) as Dictionary<string, object>;
}
private static class JsonHelper
{
public static object Deserialize(string json) => ToObject(JToken.Parse(json));
private static object ToObject(JToken token)
{
switch (token.Type)
{
case JTokenType.Object:
return token.Children<JProperty>().ToDictionary(prop => prop.Name,
prop => ToObject(prop.Value));
case JTokenType.Array:
return token.Select(ToObject).ToList();
default:
return ((JValue)token).Value;
}
}
}
private bool MaybeUpdateConfig(SerializableConfiguration config)
{
var currentWithDefaults = config.ToDictionary();
var currentRaw = Config.ToDictionary(x => x.Key, x => x.Value);
return MaybeUpdateConfigDict(currentWithDefaults, currentRaw);
}
private bool MaybeUpdateConfigDict(Dictionary<string, object> currentWithDefaults, Dictionary<string, object> currentRaw)
{
var changed = false;
foreach (var key in currentWithDefaults.Keys)
{
if (currentRaw.TryGetValue(key, out var currentRawValue))
{
var currentDictValue = currentRawValue as Dictionary<string, object>;
if (currentWithDefaults[key] is Dictionary<string, object> defaultDictValue)
{
if (currentDictValue == null)
{
currentRaw[key] = currentWithDefaults[key];
changed = true;
}
else if (MaybeUpdateConfigDict(defaultDictValue, currentDictValue))
{
changed = true;
}
}
}
else
{
currentRaw[key] = currentWithDefaults[key];
changed = true;
}
}
return changed;
}
protected override void LoadDefaultConfig() => _config = GetDefaultConfig();
protected override void LoadConfig()
{
base.LoadConfig();
try
{
_config = Config.ReadObject<Configuration>();
if (_config == null)
{
throw new JsonException();
}
if (MaybeUpdateConfig(_config))
{
LogWarning("Configuration appears to be outdated; updating and saving");
SaveConfig();
}
}
catch (Exception e)
{
LogError(e.Message);
LogWarning($"Configuration file {Name}.json is invalid; using defaults");
LoadDefaultConfig();
}
}
protected override void SaveConfig()
{
Log($"Configuration changes saved to {Name}.json");
Config.WriteObject(_config, true);
}
#endregion
#endregion
#region Localization
private void ReplyToPlayer(IPlayer player, string messageName, params object[] args) =>
player.Reply(string.Format(GetMessage(player, messageName), args));
private string GetMessage(IPlayer player, string messageName, params object[] args)
{
var message = lang.GetMessage(messageName, this, player.Id);
return args.Length > 0 ? string.Format(message, args) : message;
}
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["Generic.Error.NoPermission"] = "You don't have permission to do that.",
["Generic.Error.BuildingBlocked"] = "Error: Cannot do that while building blocked.",
["Generic.Error.NoSupportedVehicleFound"] = "Error: No supported vehicle found.",
["Generic.Error.VehicleDead"] = "Error: That vehicle is dead.",
["Generic.Error.Cooldown"] = "Please wait <color=red>{0}</color> and try again.",
["Claim.Error.AlreadyOwnedByYou"] = "You already own that vehicle.",
["Claim.Error.DifferentOwner"] = "Error: Someone else already owns that vehicle.",
["Claim.Error.LockedByAnother"] = "Error: Someone else placed a lock on that vehicle.",
["Claim.Error.Mounted"] = "Error: That vehicle is currently occupied.",
["Claim.Success"] = "You now own that vehicle.",
["Unclaim.Error.NotOwned"] = "Error: You do not own that vehicle.",
["Unclaim.Success"] = "You no longer own that vehicle.",
}, this, "en");
}
#endregion
}
}