C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Facepunch;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Spawn Heli", "SpooksAU", "3.2.0")]
[Description("Allows players to spawn helicopters")]
internal class SpawnHeli : CovalencePlugin
{
#region Fields
private const string LegacyPluginName = "SpawnMini";
private const string LegacyPermissionPrefix = "spawnmini.";
private const string PermissionMinicopter = "minicopter";
private const string PermissionScrapHeli = "scraptransport";
private const string PermissionAttackHeli = "attackhelicopter";
private const int SpawnPointLayerMask = Rust.Layers.Solid | Rust.Layers.Mask.Water;
private const int SpaceCheckLayerMask = Rust.Layers.Solid;
private const float VerticalSpawnOffset = 1;
private SaveData _data;
private Configuration _config;
private readonly VehicleInfoManager _vehicleInfoManager;
private readonly object True = true;
private readonly object False = false;
public SpawnHeli()
{
_vehicleInfoManager = new VehicleInfoManager(this);
}
#endregion
#region Hooks
private void Init()
{
_data = SaveData.Load();
_config.Init();
foreach (var perm in VehicleInfo.All.GetAllPermissions())
{
permission.RegisterPermission(perm, this);
}
_vehicleInfoManager.Init();
if (!_vehicleInfoManager.AnyOwnerOnly)
{
Unsubscribe(nameof(CanMountEntity));
}
if (!_vehicleInfoManager.AnyDespawnOnDisconnect)
{
Unsubscribe(nameof(OnPlayerDisconnected));
Unsubscribe(nameof(OnEntityDismounted));
}
if (!_vehicleInfoManager.AnyInstantTakeoff)
{
Unsubscribe(nameof(OnEngineStarted));
}
}
private void OnServerInitialized()
{
if (plugins.PluginManager.GetPlugin(LegacyPluginName) != null)
{
LogWarning($"Detected conflicting plugin {LegacyPluginName}.Please remove that plugin to avoid issues.");
}
_vehicleInfoManager.OnServerInitialized();
foreach (var networkable in BaseNetworkable.serverEntities)
{
var heli = networkable as PlayerHelicopter;
if (heli == null)
continue;
var vehicleInfo = _vehicleInfoManager.GetVehicleInfo(heli);
if (vehicleInfo == null || !vehicleInfo.Data.HasVehicle(heli))
continue;
SetupHeli(heli);
if (heli.OwnerID != 0 && HasPermission(heli.OwnerID.ToString(), vehicleInfo.Permissions.UnlimitedFuel, VehicleInfo.All.UnlimitedFuel))
{
EnableUnlimitedFuel(heli);
}
}
_data.Clean();
}
private void Unload()
{
_data.SaveIfChanged();
}
private void OnServerSave()
{
_data.SaveIfChanged();
}
private void OnNewSave()
{
_data.Reset();
_data.SaveIfChanged();
}
private void OnEntityKill(PlayerHelicopter heli)
{
var vehicleInfo = _vehicleInfoManager.GetVehicleInfo(heli);
if (vehicleInfo == null)
return;
var ownerIdString = heli.OwnerID.ToString();
var playerVehicle = vehicleInfo.Data.GetVehicle(ownerIdString);
if (playerVehicle == null || playerVehicle != heli)
return;
_data.UnregisterVehicle(vehicleInfo, ownerIdString);
var basePlayer = BasePlayer.FindByID(heli.OwnerID);
if (basePlayer != null)
{
basePlayer.ChatMessage(GetMessage(basePlayer.UserIDString, vehicleInfo.Messages.Destroyed));
}
}
private object OnEntityTakeDamage(PlayerHelicopter heli, HitInfo info)
{
if (heli == null || info == null || heli.OwnerID == 0)
return null;
if (!IsPlayerVehicle(heli, out var vehicleInfo))
return null;
if (info.damageTypes.Has(Rust.DamageType.Decay)
&& HasPermission(heli.OwnerID.ToString(), vehicleInfo.Permissions.NoDecay, VehicleInfo.All.NoDecay))
return True;
return null;
}
private object CanMountEntity(BasePlayer player, BaseVehicleMountPoint mountPoint)
{
if (player == null || mountPoint == null)
return null;
var heli = mountPoint.GetParentEntity() as PlayerHelicopter;
if (heli == null
|| heli.OwnerID == 0
|| !IsPlayerVehicle(heli, out var vehicleInfo)
|| !vehicleInfo.Config.OnlyOwnerAndTeamCanMount)
return null;
// Vehicle owner is allowed to mount.if (heli.OwnerID == player.userID)
return null;
// Team members are allowed to mount.if (player.Team != null && player.Team.members.Contains(heli.OwnerID))
return null;
player.ChatMessage(GetMessage(player.UserIDString, LangEntry.ErrorCannotMount));
return False;
}
private void OnPlayerDisconnected(BasePlayer player)
{
if (player == null)
return;
if (_config.Minicopter.DespawnOnDisconnect)
{
var heli = _data.Minicopter.GetVehicle(player.UserIDString);
if (heli != null)
{
ScheduleDespawnVehicleIfUnmounted(heli);
}
}
if (_config.ScrapTransportHelicopter.DespawnOnDisconnect)
{
var heli = _data.ScrapTransportHelicopter.GetVehicle(player.UserIDString);
if (heli != null)
{
ScheduleDespawnVehicleIfUnmounted(heli);
}
}
if (_config.AttackHelicopter.DespawnOnDisconnect)
{
var heli = _data.AttackHelicopter.GetVehicle(player.UserIDString);
if (heli != null)
{
ScheduleDespawnVehicleIfUnmounted(heli);
}
}
}
private void ScheduleDespawnVehicleIfUnmounted(PlayerHelicopter heli)
{
NextTick(() =>
{
// Despawn vehicle when the owner disconnects.// If mounted, we will despawn it later when all players dismount.if (heli == null || heli.AnyMounted())
return;
heli.Kill();
});
}
private void OnEntityDismounted(BaseVehicleSeat seat)
{
if (seat == null)
return;
var heli = seat.GetParentEntity() as PlayerHelicopter;
if (heli == null
|| !heli.AnyMounted()
|| !IsPlayerVehicle(heli, out var vehicleInfo)
|| !vehicleInfo.Config.DespawnOnDisconnect)
return;
// Despawn minicopter when fully dismounted, if the owner player has disconnected.var ownerPlayer = BasePlayer.FindByID(heli.OwnerID);
if (ownerPlayer != null && ownerPlayer.IsConnected)
return;
heli.Kill();
}
private void CanLootEntity(BasePlayer player, StorageContainer container)
{
if (container == null || !container.IsLocked())
return;
var heli = container.GetParentEntity() as PlayerHelicopter;
if (heli == null || !IsPlayerVehicle(heli, out var vehicleInfo))
return;
if (!HasPermission(heli.OwnerID.ToString(), vehicleInfo.Permissions.UnlimitedFuel, VehicleInfo.All.UnlimitedFuel))
return;
player.ChatMessage(GetMessage(player.UserIDString, LangEntry.ErrorUnlimitedFuel));
}
private void OnEngineStarted(PlayerHelicopter heli, BasePlayer player)
{
if (!heli.engineController.IsStarting)
return;
if (!IsPlayerVehicle(heli, out var vehicleInfo))
return;
if (!vehicleInfo.Config.InstantTakeoff.Enabled)
return;
if (vehicleInfo.Config.InstantTakeoff.RequirePermission
&& !HasPermission(player, vehicleInfo.Permissions.InstantTakeoff, VehicleInfo.All.InstantTakeoff))
return;
heli.CancelInvoke(heli.engineController.FinishStartingEngine);
heli.engineController.FinishStartingEngine();
}
#endregion
#region API
[HookMethod(nameof(API_GetMinicopter))]
public PlayerHelicopter API_GetMinicopter(BasePlayer player)
{
return FindPlayerVehicle(_vehicleInfoManager.Minicopter, player);
}
[HookMethod(nameof(API_GetScrapTransportHelicopter))]
public PlayerHelicopter API_GetScrapTransportHelicopter(BasePlayer player)
{
return FindPlayerVehicle(_vehicleInfoManager.ScrapTransportHelicopter, player);
}
[HookMethod(nameof(API_GetAttackHelicopter))]
public PlayerHelicopter API_GetAttackHelicopter(BasePlayer player)
{
return FindPlayerVehicle(_vehicleInfoManager.AttackHelicopter, player);
}
[HookMethod(nameof(API_SpawnMinicopter))]
public PlayerHelicopter API_SpawnMinicopter(BasePlayer player, Dictionary<string, object> rawOptions)
{
return SpawnHeliForApi(_vehicleInfoManager.Minicopter, player, rawOptions);
}
[HookMethod(nameof(API_SpawnScrapTransportHelicopter))]
public PlayerHelicopter API_SpawnScrapTransportHelicopter(BasePlayer player, Dictionary<string, object> rawOptions)
{
return SpawnHeliForApi(_vehicleInfoManager.ScrapTransportHelicopter, player, rawOptions);
}
[HookMethod(nameof(API_SpawnAttackHelicopter))]
public PlayerHelicopter API_SpawnAttackHelicopter(BasePlayer player, Dictionary<string, object> rawOptions)
{
return SpawnHeliForApi(_vehicleInfoManager.AttackHelicopter, player, rawOptions);
}
private PlayerHelicopter SpawnHeliForApi(VehicleInfo vehicleInfo, BasePlayer player, Dictionary<string, object> rawOptions)
{
SpawnOptions parsedOptions;
if (rawOptions == null)
{
parsedOptions = default;
}
else if (!SpawnOptions.TryParseApiOptions(rawOptions, out parsedOptions))
{
return null;
}
return AttemptSpawnOrFetchHeli(vehicleInfo, player.IPlayer, player, parsedOptions);
}
private struct SpawnOptions
{
private static readonly string FieldPosition = "Position";
private static readonly string FieldRotation = "Rotation";
private static readonly string FieldCheckHooks = "CheckHooks";
private static readonly string FieldCheckCooldown = "CheckCooldown";
private static readonly string FieldCheckBuildingBlocked = "CheckBuildingBlocked";
private static readonly string FieldCheckSpace = "CheckSpace";
private static readonly string FieldAutoMount = "AutoMount";
private static readonly string FieldStartCooldown = "StartCooldown";
private static readonly string FieldAutoRepair = "AutoRepair";
private static readonly string FieldAutoFetch = "AutoFetch";
private static readonly string FieldEnforceHelicopterLimit = "EnforceHelicopterLimit";
private static readonly string FieldAutoDespawnOtherHelicopterTypes = "AutoDespawnOtherHelicopterTypes";
private static readonly string FieldAllowWhileOccupied = "AllowWhileOccupied";
private static readonly string FieldMaxFetchDistance = "MaxFetchDistance";
private static readonly HashSet<string> KnownFields = new()
{
FieldPosition, FieldRotation, FieldCheckHooks, FieldCheckCooldown,
FieldCheckBuildingBlocked, FieldCheckSpace, FieldAutoMount, FieldStartCooldown,
FieldAutoRepair, FieldAutoFetch, FieldEnforceHelicopterLimit,
FieldAllowWhileOccupied, FieldMaxFetchDistance,
};
public static bool TryParseApiOptions(Dictionary<string, object> rawOptions, out SpawnOptions parsedOptions)
{
parsedOptions = new SpawnOptions();
if (!TryGetOption(rawOptions, FieldPosition, out parsedOptions.Position)
|| !TryGetOption(rawOptions, FieldRotation, out parsedOptions.Rotation)
|| !TryGetOption(rawOptions, FieldCheckHooks, out parsedOptions.CheckHooks)
|| !TryGetOption(rawOptions, FieldCheckCooldown, out parsedOptions.CheckCooldown)
|| !TryGetOption(rawOptions, FieldCheckBuildingBlocked, out parsedOptions.CheckBuildingBlocked)
|| !TryGetOption(rawOptions, FieldCheckSpace, out parsedOptions.CheckSpace)
|| !TryGetOption(rawOptions, FieldAutoMount, out parsedOptions.AutoMount)
|| !TryGetOption(rawOptions, FieldStartCooldown, out parsedOptions.StartCooldown)
|| !TryGetOption(rawOptions, FieldAutoRepair, out parsedOptions.AutoRepair)
|| !TryGetOption(rawOptions, FieldAutoFetch, out parsedOptions.AutoFetch)
|| !TryGetOption(rawOptions, FieldEnforceHelicopterLimit, out parsedOptions.EnforceHelicopterLimit)
|| !TryGetOption(rawOptions, FieldAutoDespawnOtherHelicopterTypes, out parsedOptions.AutoDespawnOtherHelicopterTypes)
|| !TryGetOption(rawOptions, FieldAllowWhileOccupied, out parsedOptions.AllowWhileOccupied)
|| !TryGetOption(rawOptions, FieldMaxFetchDistance, out parsedOptions.MaxFetchDistance))
return false;
foreach (var key in rawOptions.Keys)
{
if (!KnownFields.Contains(key))
{
LogWarning($"Unknown option '{key}' in API options.Known options are: {string.Join(", ", KnownFields)}");
}
}
return true;
}
private static bool TryGetOption<T>(Dictionary<string, object> dict, string key, out T result)
{
result = default;
// Missing fields are OK since all are optional.if (!dict.TryGetValue(key, out var value))
return true;
if (value is not T valueOfType)
{
LogError($"Invalid type for option '{key}': expected {typeof(T).Name}, got {value.GetType().Name}");
return false;
}
result = valueOfType;
return true;
}
public Vector3? Position;
public Quaternion? Rotation;
public bool? CheckHooks;
public bool? CheckCooldown;
public bool? CheckSpace;
public bool? CheckBuildingBlocked;
public bool? AutoMount;
public bool? StartCooldown;
public bool? AutoRepair;
public bool? AutoFetch;
public bool? EnforceHelicopterLimit;
public bool? AutoDespawnOtherHelicopterTypes;
public bool? AllowWhileOccupied;
public float? MaxFetchDistance;
}
#endregion
#region Commands
private void CommandSpawnMinicopter(IPlayer player, string cmd, string[] args)
{
SpawnCommandInternal(_vehicleInfoManager.Minicopter, player, cmd, args);
}
private void CommandSpawnScrapTransportHelicopter(IPlayer player, string cmd, string[] args)
{
SpawnCommandInternal(_vehicleInfoManager.ScrapTransportHelicopter, player, cmd, args);
}
private void CommandSpawnAttackHelicopter(IPlayer player, string cmd, string[] args)
{
SpawnCommandInternal(_vehicleInfoManager.AttackHelicopter, player, cmd, args);
}
private PlayerHelicopter AttemptSpawnOrFetchHeli(VehicleInfo vehicleInfo, IPlayer player, BasePlayer basePlayer, SpawnOptions options = default)
{
var heli = FindPlayerVehicle(vehicleInfo, basePlayer);
if (heli != null)
{
if ((options.AutoFetch ?? vehicleInfo.Config.AutoFetch)
&& HasPermission(player, vehicleInfo.Permissions.Fetch, VehicleInfo.All.Fetch))
{
if (TryFetchVehicle(vehicleInfo, player, basePlayer, heli, options))
return heli;
}
else
{
player.Reply(GetMessage(player.Id, vehicleInfo.Messages.AlreadySpawned));
}
return null;
}
if (options.EnforceHelicopterLimit ?? _config.LimitPlayersToOneHelicopterType)
{
foreach (var otherVehicleInfo in _vehicleInfoManager.AllVehicles)
{
if (otherVehicleInfo == vehicleInfo)
continue;
var otherVehicle = otherVehicleInfo.Data.GetVehicle(player.Id);
if (otherVehicle == null || otherVehicle.IsDestroyed)
continue;
if (!TryDespawnConflictingHeli(otherVehicleInfo, otherVehicle, basePlayer, options))
{
player.Reply(GetMessage(player.Id, LangEntry.ErrorConflictingHeli));
return null;
}
otherVehicle.Kill();
}
}
if (options.CheckCooldown != false
&& !VerifyOffCooldown(vehicleInfo, basePlayer, vehicleInfo.Config.SpawnCooldowns, vehicleInfo.Data.SpawnCooldowns))
return null;
if ((options.CheckBuildingBlocked ?? !vehicleInfo.Config.CanSpawnBuildingBlocked)
&& !VerifyNotBuildingBlocked(player, basePlayer))
return null;
if (options.CheckHooks != false
&& SpawnWasBlocked(vehicleInfo, basePlayer))
return null;
if (!VerifyValidSpawnOrFetchPosition(vehicleInfo, basePlayer, out var position, out var rotation, options))
return null;
heli = SpawnVehicle(vehicleInfo, basePlayer, position, rotation, options);
if (heli == null)
return null;
if (options.StartCooldown != false
&& !HasPermission(basePlayer, vehicleInfo.Permissions.NoCooldown, VehicleInfo.All.NoCooldown))
{
_data.StartSpawnCooldown(vehicleInfo, basePlayer);
}
return heli;
}
private void SpawnCommandInternal(VehicleInfo vehicleInfo, IPlayer player, string command, string[] args)
{
if (vehicleInfo == null
|| !VerifyPlayer(player, out var basePlayer)
|| !VerifyPermission(player, vehicleInfo.Permissions.Spawn, VehicleInfo.All.Spawn))
return;
AttemptSpawnOrFetchHeli(vehicleInfo, player, basePlayer);
}
private void CommandFetchMinicopter(IPlayer player, string cmd, string[] args)
{
FetchCommandInternal(_vehicleInfoManager.Minicopter, player, cmd, args);
}
private void CommandFetchScrapTransportHelicopter(IPlayer player, string cmd, string[] args)
{
FetchCommandInternal(_vehicleInfoManager.ScrapTransportHelicopter, player, cmd, args);
}
private void CommandFetchAttackHelicopter(IPlayer player, string cmd, string[] args)
{
FetchCommandInternal(_vehicleInfoManager.AttackHelicopter, player, cmd, args);
}
private void FetchCommandInternal(VehicleInfo vehicleInfo, IPlayer player, string command, string[] args)
{
if (vehicleInfo == null
|| !VerifyPlayer(player, out var basePlayer)
|| !VerifyPermission(player, vehicleInfo.Permissions.Fetch, VehicleInfo.All.Fetch)
|| !VerifyVehicleExists(player, basePlayer, vehicleInfo, out var heli))
return;
TryFetchVehicle(vehicleInfo, player, basePlayer, heli);
}
private void CommandDespawnMinicopter(IPlayer player, string cmd, string[] args)
{
DespawnCommandInternal(_vehicleInfoManager.Minicopter, player, cmd, args);
}
private void CommandDespawnScrapTransportHelicopter(IPlayer player, string cmd, string[] args)
{
DespawnCommandInternal(_vehicleInfoManager.ScrapTransportHelicopter, player, cmd, args);
}
private void CommandDespawnAttackHelicopter(IPlayer player, string cmd, string[] args)
{
DespawnCommandInternal(_vehicleInfoManager.AttackHelicopter, player, cmd, args);
}
private void DespawnCommandInternal(VehicleInfo vehicleInfo, IPlayer player, string command, string[] args)
{
if (vehicleInfo == null
|| !VerifyPlayer(player, out var basePlayer)
|| !VerifyPermission(player, vehicleInfo.Permissions.Despawn, VehicleInfo.All.Despawn)
|| !VerifyVehicleExists(player, basePlayer, vehicleInfo, out var heli))
return;
if (!vehicleInfo.Config.CanDespawnWhileOccupied && IsHeliOccupied(heli))
{
player.Reply(GetMessage(player.Id, LangEntry.ErrorHeliOccupied));
return;
}
if (!VerifyVehicleWithinDistance(player, basePlayer, heli, vehicleInfo.Config.MaxDespawnDistance)
|| DespawnWasBlocked(vehicleInfo, basePlayer, heli))
return;
heli.Kill();
}
// Old command for backwards compatibility.[Command("spawnmini.give")]
private void CommandGiveMinicopter(IPlayer player, string cmd, string[] args)
{
GiveCommandInternal(_vehicleInfoManager.Minicopter, player, cmd, args);
}
private void CommandGiveScrapTransportHelicopter(IPlayer player, string cmd, string[] args)
{
GiveCommandInternal(_vehicleInfoManager.ScrapTransportHelicopter, player, cmd, args);
}
private void CommandGiveAttackHelicopter(IPlayer player, string cmd, string[] args)
{
GiveCommandInternal(_vehicleInfoManager.AttackHelicopter, player, cmd, args);
}
private void GiveCommandInternal(VehicleInfo vehicleInfo, IPlayer player, string cmd, string[] args)
{
if (!player.IsServer)
return;
if (args.Length < 1)
{
PrintError($"Syntax: {cmd} <name or steamid>");
return;
}
var recipientPlayer = BasePlayer.Find(args[0]);
if (recipientPlayer == null)
{
PrintError($"{cmd}: No player found matching '{args[0]}'");
return;
}
if (args.Length > 1)
{
if (args.Length < 4 ||
!float.TryParse(args[1], out var x) ||
!float.TryParse(args[2], out var y) ||
!float.TryParse(args[3], out var z))
{
Puts($"Syntax: {cmd} <name or steamid> <x> <y> <z>");
return;
}
GiveVehicle(vehicleInfo, recipientPlayer, new Vector3(x, y, z));
return;
}
GiveVehicle(vehicleInfo, recipientPlayer);
}
#endregion
#region Helpers/Functions
private static class StringUtils
{
public static string StripPrefix(string subject, string prefix)
{
return subject.StartsWith(prefix) ? subject[prefix.Length..] : subject;
}
public static string StripPrefixes(string subject, params string[] prefixes)
{
foreach (var prefix in prefixes)
{
subject = StripPrefix(subject, prefix);
}
return subject;
}
}
private static class NetworkUtils
{
public static void SendUpdateImmediateRecursive(BaseEntity entity)
{
entity.SendNetworkUpdateImmediate();
foreach (var child in entity.children)
{
SendUpdateImmediateRecursive(child);
}
}
}
private static class Ddraw
{
public static void Sphere(BasePlayer player, Vector3 origin, float radius, Color color, float duration) =>
player.SendConsoleCommand("ddraw.sphere", duration, color, origin, radius);
public static void Line(BasePlayer player, Vector3 origin, Vector3 target, Color color, float duration) =>
player.SendConsoleCommand("ddraw.line", duration, color, origin, target);
public static void Box(BasePlayer player, Vector3 center, Quaternion rotation, Vector3 extents, Color color, float duration)
{
var sphereRadius = 0.1f;
var forwardUpperLeft = center + rotation * extents.WithX(-extents.x);
var forwardUpperRight = center + rotation * extents;
var forwardLowerLeft = center + rotation * extents.WithX(-extents.x).WithY(-extents.y);
var forwardLowerRight = center + rotation * extents.WithY(-extents.y);
var backLowerRight = center + rotation * -extents.WithX(-extents.x);
var backLowerLeft = center + rotation * -extents;
var backUpperRight = center + rotation * -extents.WithX(-extents.x).WithY(-extents.y);
var backUpperLeft = center + rotation * -extents.WithY(-extents.y);
Sphere(player, forwardUpperLeft, sphereRadius, color, duration);
Sphere(player, forwardUpperRight, sphereRadius, color, duration);
Sphere(player, forwardLowerLeft, sphereRadius, color, duration);
Sphere(player, forwardLowerRight, sphereRadius, color, duration);
Sphere(player, backLowerRight, sphereRadius, color, duration);
Sphere(player, backLowerLeft, sphereRadius, color, duration);
Sphere(player, backUpperRight, sphereRadius, color, duration);
Sphere(player, backUpperLeft, sphereRadius, color, duration);
Line(player, forwardUpperLeft, forwardUpperRight, color, duration);
Line(player, forwardLowerLeft, forwardLowerRight, color, duration);
Line(player, forwardUpperLeft, forwardLowerLeft, color, duration);
Line(player, forwardUpperRight, forwardLowerRight, color, duration);
Line(player, backUpperLeft, backUpperRight, color, duration);
Line(player, backLowerLeft, backLowerRight, color, duration);
Line(player, backUpperLeft, backLowerLeft, color, duration);
Line(player, backUpperRight, backLowerRight, color, duration);
Line(player, forwardUpperLeft, backUpperLeft, color, duration);
Line(player, forwardLowerLeft, backLowerLeft, color, duration);
Line(player, forwardUpperRight, backUpperRight, color, duration);
Line(player, forwardLowerRight, backLowerRight, color, duration);
}
public static void Box(BasePlayer player, OBB obb, Color color, float duration)
{
Box(player, obb.position, obb.rotation, obb.extents, color, duration);
}
}
public static void LogWarning(string message) => Interface.Oxide.LogWarning($"[Spawn Heli] {message}");
public static void LogError(string message) => Interface.Oxide.LogError($"[Spawn Heli] {message}");
private static bool VerifyPlayer(IPlayer player, out BasePlayer basePlayer)
{
if (player.IsServer)
{
basePlayer = null;
return false;
}
basePlayer = player.Object as BasePlayer;
return true;
}
private static void TryMountPlayer(BasePlayer player, PlayerHelicopter heli)
{
foreach (var mountPoint in heli.mountPoints)
{
if (mountPoint.isDriver)
{
mountPoint.mountable.AttemptMount(player, doMountChecks: false);
break;
}
}
}
private bool HasPermission(string playerId, string perm1, string perm2 = null)
{
return permission.UserHasPermission(playerId, perm1)
|| (perm2 != null && permission.UserHasPermission(playerId, perm2));
}
private bool HasPermission(BasePlayer player, string perm1, string perm2 = null)
{
return HasPermission(player.UserIDString, perm1, perm2);
}
private bool HasPermission(IPlayer player, string perm1, string perm2 = null)
{
return HasPermission(player.Id, perm1, perm2);
}
private bool VerifyPermission(IPlayer player, string perm1, string perm2 = null)
{
if (HasPermission(player, perm1, perm2))
return true;
player.Reply(GetMessage(player.Id, LangEntry.ErrorNoPermission));
return false;
}
private bool VerifyVehicleExists(IPlayer player, BasePlayer basePlayer, VehicleInfo vehicleInfo, out PlayerHelicopter heli)
{
heli = FindPlayerVehicle(vehicleInfo, basePlayer);
if (heli != null)
return true;
player.Reply(GetMessage(player.Id, vehicleInfo.Messages.NotFound));
return false;
}
private bool VerifyVehicleWithinDistance(IPlayer player, BasePlayer basePlayer, PlayerHelicopter heli, float maxDistance)
{
if (maxDistance < 0 || Vector3.Distance(basePlayer.transform.position, heli.transform.position) < maxDistance)
return true;
player.Reply(GetMessage(player.Id, LangEntry.ErrorHeliDistance));
return false;
}
private static bool CheckBox(OBB obb, int layerMask, BaseEntity ignoreEntity = null)
{
if (ignoreEntity == null)
return Physics.CheckBox(obb.position, obb.extents, obb.rotation, layerMask, QueryTriggerInteraction.Ignore);
var colliderList = Pool.Get<List<Collider>>();
Vis.Colliders(obb, colliderList, layerMask, QueryTriggerInteraction.Ignore);
var hitSomething = false;
foreach (var collider in colliderList)
{
var hitEntity = collider.ToBaseEntity();
if (hitEntity == ignoreEntity || hitEntity?.GetParentEntity() == ignoreEntity)
continue;
hitSomething = true;
break;
}
Pool.FreeUnmanaged(ref colliderList);
return hitSomething;
}
private bool VerifyValidSpawnOrFetchPosition(VehicleInfo vehicleInfo, BasePlayer player, out Vector3 position, out Quaternion rotation, SpawnOptions options, PlayerHelicopter existingHeli = null)
{
position = Vector3.zero;
rotation = Quaternion.identity;
if (vehicleInfo.Config.FixedSpawnDistanceConfig.Enabled)
{
position = options.Position ?? GetFixedPositionForPlayer(vehicleInfo, player);
rotation = options.Rotation ?? GetFixedRotationForPlayer(vehicleInfo, player);
}
else if (options.Position.HasValue)
{
position = options.Position.Value;
}
else
{
if (!Physics.Raycast(player.eyes.HeadRay(), out var hit, Mathf.Infinity, SpawnPointLayerMask))
{
player.ChatMessage(GetMessage(player.UserIDString, LangEntry.ErrorNoSpawnLocationFound));
return false;
}
if (hit.distance > vehicleInfo.Config.MaxSpawnDistance)
{
player.ChatMessage(GetMessage(player.UserIDString, LangEntry.ErrorSpawnDistance));
return false;
}
position = hit.point + Vector3.up * VerticalSpawnOffset;
}
var mainExtents = vehicleInfo.Bounds.extents;
var mainCenter = position + rotation * vehicleInfo.Bounds.center;
var mainObb = new OBB(mainCenter, 2 * mainExtents, rotation);
var playerObb = new OBB(
mainCenter - new Vector3(0, mainExtents.y + VerticalSpawnOffset / 2f, 0),
2 * mainExtents.WithY(VerticalSpawnOffset / 2f),
rotation
);
var tailCenter = mainCenter + rotation * new Vector3(0, 0, -mainExtents.z - vehicleInfo.TailLength / 2);
tailCenter.y = position.y + vehicleInfo.TailYOffset;
var tailExtents = new Vector3(vehicleInfo.TailThickness, vehicleInfo.TailThickness, vehicleInfo.TailLength / 2);
var tailObb = new OBB(tailCenter, 2 * tailExtents, rotation);
if (_config.AdminDebugBounds && player.IsAdmin)
{
Ddraw.Box(player, mainObb, Color.magenta, 5f);
Ddraw.Box(player, tailObb, Color.magenta, 5f);
Ddraw.Box(player, playerObb, Color.cyan, 5f);
}
if (options.CheckSpace != false && (
CheckBox(mainObb, SpaceCheckLayerMask, existingHeli)
|| CheckBox(tailObb, SpaceCheckLayerMask, existingHeli)
|| CheckBox(playerObb, Rust.Layers.Mask.Player_Server, player)))
{
player.ChatMessage(GetMessage(player.UserIDString, LangEntry.InsufficientSpace));
return false;
}
return true;
}
private bool VerifyOffCooldown(VehicleInfo vehicleInfo, BasePlayer player, CooldownConfig cooldownConfig, Dictionary<string, DateTime> cooldownMap)
{
if (!cooldownMap.TryGetValue(player.UserIDString, out var cooldownStart)
|| HasPermission(player.UserIDString, vehicleInfo.Permissions.NoCooldown))
return true;
var timeRemaining = CeilingTimeSpan(cooldownStart.AddSeconds(GetPlayerCooldownSeconds(cooldownConfig, player)) - DateTime.Now);
if (timeRemaining.TotalSeconds <= 0)
{
_data.RemoveCooldown(cooldownMap, player);
return true;
}
player.ChatMessage(GetMessage(player.UserIDString, LangEntry.ErrorOnCooldown, timeRemaining.ToString("g")));
return false;
}
private bool VerifyNotBuildingBlocked(IPlayer player, BasePlayer basePlayer)
{
if (!basePlayer.IsBuildingBlocked())
return true;
player.Reply(GetMessage(basePlayer.UserIDString, LangEntry.ErrorBuildingBlocked));
return false;
}
private static bool SpawnWasBlocked(VehicleInfo vehicleInfo, BasePlayer player)
{
return Interface.CallHook(vehicleInfo.Hooks.Spawn, player) is false;
}
private static bool FetchWasBlocked(VehicleInfo vehicleInfo, BasePlayer player, PlayerHelicopter heli)
{
return Interface.CallHook(vehicleInfo.Hooks.Fetch, player, heli) is false;
}
private static bool DespawnWasBlocked(VehicleInfo vehicleInfo, BasePlayer player, PlayerHelicopter heli)
{
return Interface.CallHook(vehicleInfo.Hooks.Despawn, player, heli) is false;
}
private static TimeSpan CeilingTimeSpan(TimeSpan timeSpan)
{
return new TimeSpan((long)Math.Ceiling(1.0 * timeSpan.Ticks / 10000000) * 10000000);
}
private static Vector3 GetFixedPositionForPlayer(VehicleInfo vehicleInfo, BasePlayer player)
{
var forward = player.eyes.BodyForward();
forward.y = 0;
return player.transform.position + forward.normalized * vehicleInfo.Config.FixedSpawnDistanceConfig.Distance + Vector3.up * VerticalSpawnOffset;
}
private static Quaternion GetFixedRotationForPlayer(VehicleInfo vehicleInfo, BasePlayer player)
{
return Quaternion.Euler(0, player.eyes.rotation.eulerAngles.y - vehicleInfo.Config.FixedSpawnDistanceConfig.RotationAngle, 0);
}
private static void SetupHeli(PlayerHelicopter heli)
{
UnityEngine.Object.Destroy(heli.GetComponent<MagnetLiftable>());
}
private static void EnableUnlimitedFuel(PlayerHelicopter heli)
{
if (heli.GetFuelSystem() is not EntityFuelSystem fuelSystem)
return;
fuelSystem.cachedHasFuel = true;
fuelSystem.nextFuelCheckTime = float.MaxValue;
fuelSystem.GetFuelContainer().SetFlag(BaseEntity.Flags.Locked, true);
}
private static bool AnyParentedPlayers(PlayerHelicopter heli)
{
foreach (var entity in heli.children)
{
if (entity is BasePlayer)
return true;
}
return false;
}
private static bool IsHeliOccupied(PlayerHelicopter heli)
{
return heli.AnyMounted() || AnyParentedPlayers(heli);
}
private static void UnparentPlayers(PlayerHelicopter heli)
{
var tempList = Pool.Get<List<BasePlayer>>();
try
{
foreach (var entity in heli.children)
{
var player = entity as BasePlayer;
if (player == null)
continue;
tempList.Add(player);
}
foreach (var player in tempList)
{
player.SetParent(null, worldPositionStays: true);
}
}
finally
{
Pool.FreeUnmanaged(ref tempList);
}
}
private bool IsPlayerVehicle(PlayerHelicopter heli, out VehicleInfo vehicleInfo)
{
vehicleInfo = _vehicleInfoManager.GetVehicleInfo(heli);
return vehicleInfo != null && vehicleInfo.Data.HasVehicle(heli);
}
private PlayerHelicopter FindPlayerVehicle(VehicleInfo vehicleInfo, BasePlayer player)
{
if (!vehicleInfo.Data.Vehicles.TryGetValue(player.UserIDString, out var heliNetId))
return null;
var heli = BaseNetworkable.serverEntities.Find(new NetworkableId(heliNetId)) as PlayerHelicopter;
if (heli == null)
{
_data.UnregisterVehicle(vehicleInfo, player.UserIDString);
}
return heli;
}
private bool ShouldAutoMount(VehicleInfo vehicleInfo, BasePlayer player)
{
var mountConfig = vehicleInfo.Config.AutoMount;
if (!mountConfig.Enabled)
return false;
return !mountConfig.RequirePermission
|| HasPermission(player, vehicleInfo.Permissions.AutoMount, VehicleInfo.All.AutoMount);
}
private void MaybeAutoMount(VehicleInfo vehicleInfo, BasePlayer player, PlayerHelicopter heli, SpawnOptions options)
{
if (!(options.AutoMount ?? ShouldAutoMount(vehicleInfo, player)))
return;
TryMountPlayer(player, heli);
}
private bool TryFetchVehicle(VehicleInfo vehicleInfo, IPlayer player, BasePlayer basePlayer, PlayerHelicopter heli, SpawnOptions options = default)
{
var isOccupied = IsHeliOccupied(heli);
var canFetchWhileOccupied = options.AllowWhileOccupied ?? vehicleInfo.Config.CanFetchWhileOccupied;
if (isOccupied && (!canFetchWhileOccupied
|| basePlayer.GetMountedVehicle() == heli
|| basePlayer.GetParentEntity() == heli))
{
basePlayer.ChatMessage(GetMessage(basePlayer.UserIDString, LangEntry.ErrorHeliOccupied));
return false;
}
var maxFetchDistance = options.MaxFetchDistance ?? vehicleInfo.Config.MaxFetchDistance;
if (!VerifyVehicleWithinDistance(player, basePlayer, heli, maxFetchDistance))
return false;
if (options.CheckCooldown != false
&& !VerifyOffCooldown(vehicleInfo, basePlayer, vehicleInfo.Config.FetchCooldowns, vehicleInfo.Data.FetchCooldowns))
return false;
if ((options.CheckBuildingBlocked ?? !vehicleInfo.Config.CanFetchBuildingBlocked)
&& !VerifyNotBuildingBlocked(player, basePlayer))
return false;
if (options.CheckHooks != false
&& FetchWasBlocked(vehicleInfo, basePlayer, heli))
return false;
if (!VerifyValidSpawnOrFetchPosition(vehicleInfo, basePlayer, out var position, out var rotation, options, heli))
return false;
if (isOccupied)
{
foreach (var mountPoint in heli.mountPoints)
{
mountPoint.mountable?.DismountAllPlayers();
}
}
if (AnyParentedPlayers(heli))
{
UnparentPlayers(heli);
}
if ((options.AutoRepair ?? vehicleInfo.Config.RepairOnFetch)
&& vehicleInfo.Config.SpawnHealth > 0)
{
heli.SetHealth(Math.Max(heli.Health(), vehicleInfo.Config.SpawnHealth));
}
// Terminate on client so that it doesn't animate from the previous location, since that can hinder stealth.heli.TerminateOnClient(BaseNetworkable.DestroyMode.None);
heli.rigidBody.velocity = Vector3.zero;
heli.transform.SetPositionAndRotation(position, rotation);
heli.rigidBody.WakeUp();
heli.timeSinceLastPush = 0f;
heli.UpdateNetworkGroup();
NetworkUtils.SendUpdateImmediateRecursive(heli);
if (options.StartCooldown != false
&& !HasPermission(basePlayer, vehicleInfo.Permissions.NoCooldown, VehicleInfo.All.NoCooldown))
{
_data.StartFetchCooldown(vehicleInfo, basePlayer);
}
MaybeAutoMount(vehicleInfo, basePlayer, heli, options);
return true;
}
private PlayerHelicopter SpawnVehicle(VehicleInfo vehicleInfo, BasePlayer player, Vector3 position, Quaternion rotation, SpawnOptions options)
{
var heli = GameManager.server.CreateEntity(vehicleInfo.PrefabPath, position, rotation) as PlayerHelicopter;
if (heli == null)
return null;
heli.OwnerID = player.userID;
if (vehicleInfo.Config.SpawnHealth > 0)
{
heli.startHealth = vehicleInfo.Config.SpawnHealth;
}
SetupHeli(heli);
heli.Spawn();
if (HasPermission(player, vehicleInfo.Permissions.UnlimitedFuel, VehicleInfo.All.UnlimitedFuel))
{
EnableUnlimitedFuel(heli);
}
else
{
AddInitialFuel(vehicleInfo, heli, player);
}
_data.RegisterVehicle(vehicleInfo, player.UserIDString, heli);
MaybeAutoMount(vehicleInfo, player, heli, options);
return heli;
}
private void GiveVehicle(VehicleInfo vehicleInfo, BasePlayer player, Vector3? customPosition = null)
{
// Note: The give command does not auto fetch, but that could be changed in the future.if (FindPlayerVehicle(vehicleInfo, player) != null)
{
player.ChatMessage(GetMessage(player.UserIDString, vehicleInfo.Messages.AlreadySpawned));
return;
}
var position = customPosition ?? GetFixedPositionForPlayer(vehicleInfo, player);
var rotation = customPosition.HasValue ? Quaternion.identity : GetFixedRotationForPlayer(vehicleInfo, player);
SpawnVehicle(vehicleInfo, player, position, rotation, new SpawnOptions { AutoMount = false });
}
private bool TryDespawnConflictingHeli(VehicleInfo vehicleInfo, PlayerHelicopter heli, BasePlayer basePlayer, SpawnOptions options)
{
var autoDespawn = options.AutoDespawnOtherHelicopterTypes ?? _config.AutoDespawnOtherHelicopterTypes;
if (!autoDespawn)
return false;
var allowWhileOccupied = options.AllowWhileOccupied ?? vehicleInfo.Config.CanDespawnWhileOccupied;
if (!allowWhileOccupied && IsHeliOccupied(heli))
return false;
if (DespawnWasBlocked(vehicleInfo, basePlayer, heli))
return false;
return true;
}
private float GetPlayerCooldownSeconds(CooldownConfig cooldownConfig, BasePlayer player)
{
var profileList = cooldownConfig.CooldownProfiles;
if (profileList != null)
{
for (var i = profileList.Length - 1; i >= 0; i--)
{
var profile = profileList[i];
if (profile.Permission != null && HasPermission(player, profile.Permission))
return profile.CooldownSeconds;
}
}
return cooldownConfig.DefaultCooldown;
}
private int GetPlayerAllowedFuel(VehicleInfo vehicleInfo, BasePlayer player)
{
var fuelConfig = vehicleInfo.Config.FuelConfig;
var profileList = fuelConfig.FuelProfiles;
if (profileList != null)
{
for (var i = profileList.Length - 1; i >= 0; i--)
{
var profile = profileList[i];
if (profile.Permission != null && HasPermission(player, profile.Permission))
return profile.FuelAmount;
}
}
return fuelConfig.DefaultFuelAmount;
}
private void AddInitialFuel(VehicleInfo vehicleInfo, PlayerHelicopter heli, BasePlayer player)
{
var fuelAmount = GetPlayerAllowedFuel(vehicleInfo, player);
if (fuelAmount == 0)
return;
if (heli.GetFuelSystem() is not EntityFuelSystem fuelSystem)
return;
var fuelContainer = fuelSystem.GetFuelContainer();
// ... [Code preview truncated due to length. Full source in attached file] ...