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 UnityEngine;
namespace Oxide.Plugins
{
[Info("Spawn Heli", "SpooksAU", "3.1.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;
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 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 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;
var heli = FindPlayerVehicle(vehicleInfo, basePlayer);
if (heli != null)
{
if (vehicleInfo.Config.AutoFetch && HasPermission(player, vehicleInfo.Permissions.Fetch, VehicleInfo.All.Fetch))
{
FetchVehicle(vehicleInfo, player, basePlayer, heli);
}
else
{
player.Reply(GetMessage(player.Id, vehicleInfo.Messages.AlreadySpawned));
}
return;
}
if (_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 (!TryDespawnHeli(otherVehicleInfo, otherVehicle, basePlayer))
{
player.Reply(GetMessage(player.Id, LangEntry.ErrorConflictingHeli));
return;
}
otherVehicle.Kill();
}
}
if (!VerifyOffCooldown(vehicleInfo, basePlayer, vehicleInfo.Config.SpawnCooldowns, vehicleInfo.Data.SpawnCooldowns)
|| !vehicleInfo.Config.CanSpawnBuildingBlocked && !VerifyNotBuildingBlocked(player, basePlayer)
|| SpawnWasBlocked(vehicleInfo, basePlayer)
|| !VerifyValidSpawnOrFetchPosition(vehicleInfo, basePlayer, out var position, out var rotation))
return;
heli = SpawnVehicle(vehicleInfo, basePlayer, position, rotation);
if (heli == null)
return;
if (!HasPermission(basePlayer, vehicleInfo.Permissions.NoCooldown, VehicleInfo.All.NoCooldown))
{
_data.StartSpawnCooldown(vehicleInfo, 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;
FetchVehicle(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);
}
}
}
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 bool VerifyValidSpawnOrFetchPosition(VehicleInfo vehicleInfo, BasePlayer player, out Vector3 position, out Quaternion rotation)
{
position = Vector3.zero;
rotation = Quaternion.identity;
if (vehicleInfo.Config.FixedSpawnDistanceConfig.Enabled)
{
position = GetFixedPositionForPlayer(vehicleInfo, player);
rotation = GetFixedRotationForPlayer(vehicleInfo, player);
}
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 extents = vehicleInfo.Bounds.extents;
var boundsCenter = position + rotation * vehicleInfo.Bounds.center;
if (Physics.CheckBox(boundsCenter, extents, rotation, SpaceCheckLayerMask, QueryTriggerInteraction.Ignore)
|| Physics.CheckBox(position + Vector3.down * VerticalSpawnOffset / 2f, extents.WithY(VerticalSpawnOffset / 2f), rotation, Rust.Layers.Mask.Player_Server, QueryTriggerInteraction.Ignore))
{
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.GetNetworkRotation() * Vector3.forward;
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.GetNetworkRotation().eulerAngles.y - vehicleInfo.Config.FixedSpawnDistanceConfig.RotationAngle, 0);
}
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.GetList<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.FreeList(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)
{
if (!ShouldAutoMount(vehicleInfo, player))
return;
TryMountPlayer(player, heli);
}
private void FetchVehicle(VehicleInfo vehicleInfo, IPlayer player, BasePlayer basePlayer, PlayerHelicopter heli)
{
var isOccupied = IsHeliOccupied(heli);
if (isOccupied && (!vehicleInfo.Config.CanFetchWhileOccupied
|| basePlayer.GetMountedVehicle() == heli
|| basePlayer.GetParentEntity() == heli))
{
basePlayer.ChatMessage(GetMessage(basePlayer.UserIDString, LangEntry.ErrorHeliOccupied));
return;
}
if (!VerifyVehicleWithinDistance(player, basePlayer, heli, vehicleInfo.Config.MaxFetchDistance)
|| !VerifyOffCooldown(vehicleInfo, basePlayer, vehicleInfo.Config.FetchCooldowns, vehicleInfo.Data.FetchCooldowns)
|| !vehicleInfo.Config.CanFetchBuildingBlocked && !VerifyNotBuildingBlocked(player, basePlayer)
|| FetchWasBlocked(vehicleInfo, basePlayer, heli)
|| !VerifyValidSpawnOrFetchPosition(vehicleInfo, basePlayer, out var position, out var rotation))
return;
if (isOccupied)
{
foreach (var mountPoint in heli.mountPoints)
{
mountPoint.mountable?.DismountAllPlayers();
}
}
if (AnyParentedPlayers(heli))
{
UnparentPlayers(heli);
}
if (vehicleInfo.Config.RepairOnFetch && vehicleInfo.Config.SpawnHealth > 0)
{
heli.SetHealth(Math.Max(heli.Health(), vehicleInfo.Config.SpawnHealth));
}
// Terminate on client so 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 (!HasPermission(basePlayer, vehicleInfo.Permissions.NoCooldown, VehicleInfo.All.NoCooldown))
{
_data.StartFetchCooldown(vehicleInfo, basePlayer);
}
MaybeAutoMount(vehicleInfo, basePlayer, heli);
}
private PlayerHelicopter SpawnVehicle(VehicleInfo vehicleInfo, BasePlayer player, Vector3 position, Quaternion rotation, bool allowAutoMount = true)
{
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;
}
heli.Spawn();
if (HasPermission(player, vehicleInfo.Permissions.UnlimitedFuel, VehicleInfo.All.UnlimitedFuel))
{
EnableUnlimitedFuel(heli);
}
else
{
AddInitialFuel(vehicleInfo, heli, player);
}
_data.RegisterVehicle(vehicleInfo, player.UserIDString, heli);
if (allowAutoMount)
{
MaybeAutoMount(vehicleInfo, player, heli);
}
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, allowAutoMount: false);
}
private bool TryDespawnHeli(VehicleInfo vehicleInfo, PlayerHelicopter heli, BasePlayer basePlayer)
{
if (!_config.AutoDespawnOtherHelicopterTypes)
return false;
if (!vehicleInfo.Config.CanDespawnWhileOccupied && 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();
if (fuelAmount < 0)
{
// Value of -1 is documented to represent max stack size.fuelAmount = fuelContainer.allowedItem.stackable;
}
fuelContainer.inventory.AddItem(fuelContainer.allowedItem, fuelAmount);
}
#endregion
#region Vehicle Info
private class VehicleInfo
{
public static PermissionSet All = PermissionSet.ForVehicle("all");
public class PermissionSet
{
public static PermissionSet ForVehicle(string vehicleName)
{
return new PermissionSet
{
Spawn = BuildPermission(vehicleName, "spawn"),
Fetch = BuildPermission(vehicleName, "fetch"),
Despawn = BuildPermission(vehicleName, "despawn"),
UnlimitedFuel = BuildPermission(vehicleName, "unlimitedfuel"),
NoDecay = BuildPermission(vehicleName, "nodecay"),
NoCooldown = BuildPermission(vehicleName, "nocooldown"),
AutoMount = BuildPermission(vehicleName, "automount"),
InstantTakeoff = BuildPermission(vehicleName, "instanttakeoff"),
};
}
private static string BuildPermission(string vehicleName, string featureName)
{
return $"{nameof(SpawnHeli)}.{vehicleName}.{featureName}".ToLower();
}
public string Spawn { get; private set; }
public string Fetch { get; private set; }
public string Despawn { get; private set; }
public string UnlimitedFuel { get; private set; }
public string NoDecay { get; private set; }
public string NoCooldown { get; private set; }
public string AutoMount { get; private set; }
public string InstantTakeoff { get; private set; }
private PermissionSet() {}
public IEnumerable<string> GetAllPermissions()
{
yield return Spawn;
yield return Fetch;
yield return Despawn;
yield return UnlimitedFuel;
yield return NoDecay;
yield return NoCooldown;
yield return AutoMount;
yield return InstantTakeoff;
}
}
public class HookSet
{
public string Spawn;
public string Fetch;
public string Despawn;
}
public class MessageSet
{
public LangEntry Destroyed;
public LangEntry AlreadySpawned;
public LangEntry NotFound;
}
public string VehicleName { private get; set; }
public string PrefabPath;
public Bounds Bounds;
public string GiveCommand { get; private set; }
public VehicleConfig Config;
public VehicleData Data;
public uint PrefabId { get; private set; }
public PermissionSet Permissions;
public HookSet Hooks;
public MessageSet Messages;
public void Init(SpawnHeli plugin)
{
GiveCommand = $"{nameof(SpawnHeli)}.{VehicleName}.give".ToLower();
Permissions = PermissionSet.ForVehicle(VehicleName);
foreach (var perm in Permissions.GetAllPermissions())
{
plugin.permission.RegisterPermission(perm, plugin);
}
if (Config.FuelConfig.FuelProfiles != null)
{
foreach (var profile in Config.FuelConfig.FuelProfiles)
{
if (profile.Permission != null)
{
plugin.permission.RegisterPermission(profile.Permission, plugin);
}
}
}
if (Config.SpawnCooldowns.CooldownProfiles != null)
{
foreach (var profile in Config.SpawnCooldowns.CooldownProfiles)
{
if (profile.Permission != null)
{
plugin.permission.RegisterPermission(profile.Permission, plugin);
}
}
}
if (Config.FetchCooldowns.CooldownProfiles != null)
{
foreach (var profile in Config.FetchCooldowns.CooldownProfiles)
{
if (profile.Permission != null)
{
plugin.permission.RegisterPermission(profile.Permission, plugin);
}
}
}
}
public void OnServerInitialized()
{
PrefabId = GameManager.server.FindPrefab(PrefabPath)?.GetComponent<BaseEntity>()?.prefabID ?? 0;
}
}
private class VehicleInfoManager
{
public VehicleInfo Minicopter { get; private set; }
public VehicleInfo ScrapTransportHelicopter { get; private set; }
public VehicleInfo AttackHelicopter { get; private set; }
public VehicleInfo[] AllVehicles { get; private set; }
private readonly SpawnHeli _plugin;
private readonly Dictionary<uint, VehicleInfo> _prefabIdToVehicleInfo = new();
public bool AnyOwnerOnly => AllVehicles.Any(vehicleInfo => vehicleInfo.Config.OnlyOwnerAndTeamCanMount);
public bool AnyDespawnOnDisconnect => AllVehicles.Any(vehicleInfo => vehicleInfo.Config.DespawnOnDisconnect);
public bool AnyInstantTakeoff => AllVehicles.Any(vehicleInfo => vehicleInfo.Config.InstantTakeoff.Enabled);
private Configuration _config => _plugin._config;
private SaveData _data => _plugin._data;
public VehicleInfoManager(SpawnHeli plugin)
{
_plugin = plugin;
}
public void Init()
{
AllVehicles = new[]
{
Minicopter = new VehicleInfo
{
VehicleName = PermissionMinicopter,
PrefabPath = "assets/content/vehicles/minicopter/minicopter.entity.prefab",
Config = _config.Minicopter,
Data = _data.Minicopter,
Hooks = new VehicleInfo.HookSet
{
Spawn = "OnMyMiniSpawn",
Fetch = "OnMyMiniFetch",
Despawn = "OnMyMiniDespawn",
},
Messages = new VehicleInfo.MessageSet
{
Destroyed = LangEntry.MiniDestroyed,
AlreadySpawned = LangEntry.ErrorMiniExists,
NotFound = LangEntry.ErrorMiniNotFound,
},
Bounds = new Bounds
{
center = new Vector3(-0.001f, 1.114f, -0.36f),
extents = new Vector3(1.32f, 0.87f, 2.16f),
},
},
ScrapTransportHelicopter = new VehicleInfo
{
VehicleName = PermissionScrapHeli,
PrefabPath = "assets/content/vehicles/scrap heli carrier/scraptransporthelicopter.prefab",
Config = _config.ScrapTransportHelicopter,
Data = _data.ScrapTransportHelicopter,
Hooks = new VehicleInfo.HookSet
{
Spawn = "OnMyScrapHeliSpawn",
Fetch = "OnMyScrapHeliFetch",
Despawn = "OnMyScrapHeliDespawn",
},
Messages = new VehicleInfo.MessageSet
{
Destroyed = LangEntry.ScrapHeliDestroyed,
AlreadySpawned = LangEntry.ErrorScrapHeliExist,
NotFound = LangEntry.ErrorScrapHeliNotFound,
},
Bounds = new Bounds
{
center = new Vector3(0, 2.25f, -1.25f),
extents = new Vector3(2.2f, 2.25f, 6f),
},
},
AttackHelicopter = new VehicleInfo
{
VehicleName = PermissionAttackHeli,
PrefabPath = "assets/content/vehicles/attackhelicopter/attackhelicopter.entity.prefab",
Config = _config.AttackHelicopter,
Data = _data.AttackHelicopter,
Hooks = new VehicleInfo.HookSet
{
Spawn = "OnMyAttackHeliSpawn",
Fetch = "OnMyAttackHeliFetch",
Despawn = "OnMyAttackHeliDespawn",
},
Messages = new VehicleInfo.MessageSet
{
Destroyed = LangEntry.AttackHeliDestroyed,
AlreadySpawned = LangEntry.ErrorAttackHeliExists,
NotFound = LangEntry.ErrorAttackHeliNotFound,
},
Bounds = new Bounds
{
center = new Vector3(0, 1.678f, -1.676f),
extents = new Vector3(1.303f, 1.678f, 5.495f),
},
},
};
foreach (var vehicleInfo in AllVehicles)
{
vehicleInfo.Init(_plugin);
}
_plugin.AddCovalenceCommand(Minicopter.Config.SpawnCommands, nameof(CommandSpawnMinicopter));
_plugin.AddCovalenceCommand(Minicopter.Config.FetchCommands, nameof(CommandFetchMinicopter));
_plugin.AddCovalenceCommand(Minicopter.Config.DespawnCommands, nameof(CommandDespawnMinicopter));
_plugin.AddCovalenceCommand(Minicopter.GiveCommand, nameof(CommandGiveMinicopter));
_plugin.AddCovalenceCommand(ScrapTransportHelicopter.Config.SpawnCommands, nameof(CommandSpawnScrapTransportHelicopter));
_plugin.AddCovalenceCommand(ScrapTransportHelicopter.Config.FetchCommands, nameof(CommandFetchScrapTransportHelicopter));
_plugin.AddCovalenceCommand(ScrapTransportHelicopter.Config.DespawnCommands, nameof(CommandDespawnScrapTransportHelicopter));
_plugin.AddCovalenceCommand(ScrapTransportHelicopter.GiveCommand, nameof(CommandGiveScrapTransportHelicopter));
_plugin.AddCovalenceCommand(AttackHelicopter.Config.SpawnCommands, nameof(CommandSpawnAttackHelicopter));
_plugin.AddCovalenceCommand(AttackHelicopter.Config.FetchCommands, nameof(CommandFetchAttackHelicopter));
_plugin.AddCovalenceCommand(AttackHelicopter.Config.DespawnCommands, nameof(CommandDespawnAttackHelicopter));
_plugin.AddCovalenceCommand(AttackHelicopter.GiveCommand, nameof(CommandGiveAttackHelicopter));
}
public void OnServerInitialized()
{
foreach (var vehicleInfo in AllVehicles)
{
vehicleInfo.OnServerInitialized();
if (vehicleInfo.PrefabId != 0)
{
_prefabIdToVehicleInfo[vehicleInfo.PrefabId] = vehicleInfo;
}
else
{
LogError($"Unable to determine Prefab ID for prefab: {vehicleInfo.PrefabPath}");
}
}
}
public VehicleInfo GetVehicleInfo(BaseEntity entity)
{
return _prefabIdToVehicleInfo.TryGetValue(entity.prefabID, out var vehicleInfo)
? vehicleInfo
: null;
}
}
#endregion
#region Data
[JsonObject(MemberSerialization.OptIn)]
private class LegacySaveData
{
private const string Filename = LegacyPluginName;
public static LegacySaveData LoadIfExists()
{
return Interface.Oxide.DataFileSystem.ExistsDatafile(Filename)
? Interface.Oxide.DataFileSystem.ReadObject<LegacySaveData>(Filename)
: null;
}
[JsonProperty("playerMini")]
public Dictionary<string, ulong> playerMini = new();
[JsonProperty("spawnCooldowns")]
public Dictionary<string, DateTime> spawnCooldowns = new();
[JsonProperty("cooldown")]
private Dictionary<string, DateTime> deprecatedCooldown
{
set => spawnCooldowns = value;
}
[JsonProperty("fetchCooldowns")]
public Dictionary<string, DateTime> fetchCooldowns = new();
public void Delete()
{
Interface.Oxide.DataFileSystem.DeleteDataFile(Filename);
}
}
[JsonObject(MemberSerialization.OptIn)]
private class VehicleData
{
[JsonProperty("Vehicles")]
public Dictionary<string, ulong> Vehicles = new();
[JsonProperty("SpawnCooldowns")]
public Dictionary<string, Date
// ... [Code preview truncated due to length. Full source in attached file] ...