Иконка ресурса

FREE RidableDrones : 2.1.0 2.1.0

Нет прав для скачивания
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 UnityEngine;
using VLB;
namespace Oxide.Plugins {
    [Info("Ridable Drones", "WhiteThunder", "2.1.0")]
    [Description("Allows players to deploy signs and chairs onto RC drones to allow riding them.")]
    internal class RidableDrones : CovalencePlugin {
#region Fields
        [PluginReference]
        private readonly Plugin DroneSettings;
        private Configuration _config;
        private const string PermissionSignDeploy = "ridabledrones.sign.deploy";
        private const string PermissionSignDeployFree = "ridabledrones.sign.deploy.free";
        private const string PermissionChairDeploy = "ridabledrones.chair.deploy";
        private const string PermissionChairDeployFree = "ridabledrones.chair.deploy.free";
        private const string PermissionChairAutoDeploy = "ridabledrones.chair.autodeploy";
        private const string PermissionChairPilot = "ridabledrones.chair.pilot";
        private const string SmallWoodenSignPrefab = "assets/prefabs/deployable/signs/sign.small.wood.prefab";
        private const string DeploySignEffectPrefab = "assets/prefabs/deployable/signs/effects/wood-sign-deploy.prefab";
        private const string PilotChairPrefab = "assets/prefabs/vehicle/seats/miniheliseat.prefab";
        private const string PassengerChairPrefab = "assets/prefabs/deployable/secretlab chair/secretlabchair.deployed.prefab";
        private const string DeprecatedPassengerChairPrefab = "assets/prefabs/deployable/chair/chair.deployed.prefab";
        private const string DeprecatedVisibleChairPrefab = "assets/prefabs/vehicle/seats/passengerchair.prefab";
        private const string ChairDeployEffectPrefab = "assets/prefabs/deployable/chair/effects/chair-deploy.prefab";
        private const int SignItemId = -1138208076;
        private const int ChairItemId = 1534542921;
        private static SlotConfig ChairSlots = new(BaseEntity.Slot.UpperModifier);
        private static SlotConfig SignSlots = new(BaseEntity.Slot.MiddleModifier, BaseEntity.Slot.UpperModifier);
        private readonly object True = true;
        private readonly object False = false;
        private static readonly Vector3 SignLocalPosition = new(0, 0.114f, 0.265f);
        private static readonly Vector3 SignLocalRotationAngles = new(270, 0, 0);
        private static readonly Vector3 PassengerChairLocalPosition = new(0, 0.081f, 0);
        private static readonly Vector3 PilotChairLocalPosition = new(-0.0045f, 0.0845f, 0.41f);
        private readonly Dictionary<string, object> _signRemoveInfo = new();
        private readonly Dictionary<string, object> _refundInfo = new() {
            ["sign.wooden.small"]
            = new Dictionary<string, object> {
                ["Amount"]
                = 1,
            }
            ,
        }
        ;
        private readonly Dictionary<string, object> _chairRemoveInfo = new();
        private readonly Dictionary<string, object> _chairRefundInfo = new() {
            ["chair"]
            = new Dictionary<string, object> {
                ["Amount"]
                = 1,
            }
            ,
        }
        ;
        private readonly ObservableHashSet<BaseEntity> _chairDrones = new();
        private readonly ObservableHashSet<BaseEntity> _mountedChairDrones = new();
        private readonly ObservableHashSet<BaseEntity> _signDrones = new();
        private readonly GatedHookCollection[] _hookCollections;
        public RidableDrones() {
            var anySignDrones = new ObservableGate(_chairDrones, () => _chairDrones.Count > 0);
            var anyChairDrones = new ObservableGate(_chairDrones, () => _chairDrones.Count > 0);
            var anyChairDronesMounted = new ObservableGate(_mountedChairDrones, () => _mountedChairDrones.Count > 0);
            _hookCollections = new[] {
                new GatedHookCollection(this, anyChairDrones, nameof(OnEntityMounted), nameof(OnEntityDismounted), nameof(OnPlayerDismountFailed) ), new GatedHookCollection(this, anyChairDronesMounted, nameof(OnServerCommand) ), new GatedHookCollection(this, new MultiObservableGate(anySignDrones, anyChairDrones), nameof(OnEntityTakeDamage), nameof(CanPickupEntity), nameof(OnRemovableEntityInfo), nameof(canRemove) ),
            }
            ;
        }
#endregion
#region Hooks
        private void Init() {
            permission.RegisterPermission(PermissionSignDeploy, this);
            permission.RegisterPermission(PermissionSignDeployFree, this);
            permission.RegisterPermission(PermissionChairDeploy, this);
            permission.RegisterPermission(PermissionChairDeployFree, this);
            permission.RegisterPermission(PermissionChairAutoDeploy, this);
            permission.RegisterPermission(PermissionChairPilot, this);
            Unsubscribe(nameof(OnEntitySpawned));
            foreach (var hookCollection in _hookCollections) {
                hookCollection.Unsubscribe();
            }
        }
        private void OnServerInitialized() {
            foreach (var drone in BaseNetworkable.serverEntities.OfType<Drone>().ToList()) {
                if (drone == null || !IsDroneEligible(drone)) continue;
                MaybeRefreshDroneSign(drone);
                MaybeAddOrRefreshChairs(drone);
            }
            foreach (var player in BasePlayer.activePlayerList) {
                var drone = GetMountedDrone(player, out var currentChair);
                if (drone == null) continue;
                if (!TryGetChairs(drone, out var pilotChair, out _)) continue;
                if (!permission.UserHasPermission(player.UserIDString, PermissionChairPilot)) continue;
                var isPilotChair = currentChair == pilotChair;
                DroneController.Mount(this, player, drone, isPilotChair);
            }
            Subscribe(nameof(OnEntitySpawned));
        }
        private void Unload() {
            foreach (var entity in BaseNetworkable.serverEntities) {
                var drone = entity as Drone;
                if (drone == null) continue;
                var sign = GetDroneSign(drone);
                if (sign != null) {
                    SignComponent.RemoveFromSign(sign);
                }
                if (TryGetPassengerChair(drone, out var passengerChair)) {
                    ChairComponent.RemoveFromChair(passengerChair);
                }
            }
            foreach (var player in BasePlayer.activePlayerList) {
                DroneController.RemoveFromPlayer(player);
            }
        }
        private void OnEntitySpawned(Drone drone) {
            if (!IsDroneEligible(drone)) return;
            var drone2 = drone;
            // Delay to give other plugins a moment to cache the drone id so they can block this.NextTick(() =>
            {
                if (drone2 == null || drone2.IsDestroyed) return;
                MaybeAutoDeployChair(drone2);
            }
            );
        }
        private void OnEntityBuilt(Planner planner, GameObject go) {
            if (planner == null || go == null) return;
            var drone = go.ToBaseEntity() as Drone;
            if (drone == null) return;
            var player = planner.GetOwnerPlayer();
            if (player == null) return;
            var drone2 = drone;
            NextTick(() => {
                // Delay this check to allow time for other plugins to deploy an entity to this slot.if (drone2 == null || player == null)
                return;
                if (SignSlots.IsCompatibleWithHost(drone2) && permission.UserHasPermission(player.UserIDString, PermissionSignDeploy) && UnityEngine.Random.Range(0, 100) < _config.SignTipChance) {
                    ChatMessage(player, Lang.TipDeploySignCommand);
                }
                if (ChairSlots.IsCompatibleWithHost(drone2) && permission.UserHasPermission(player.UserIDString, PermissionChairDeploy) && !permission.UserHasPermission(player.UserIDString, PermissionChairAutoDeploy) && UnityEngine.Random.Range(0, 100) < _config.ChairTipChance) {
                    ChatMessage(player, Lang.TipDeployChairCommand);
                }
            }
            );
        }
        private object OnEntityTakeDamage(BaseChair mountable, HitInfo info) {
            if (mountable.PrefabName != PassengerChairPrefab) return null;
            var drone = GetParentDrone(mountable);
            if (drone == null) return null;
            drone.Hurt(info);
            HitNotify(drone, info);
            return True;
        }
        private object OnEntityTakeDamage(Signage sign, HitInfo info) {
            var drone = GetParentDrone(sign);
            if (drone == null) return null;
            drone.Hurt(info);
            HitNotify(drone, info);
            return True;
        }
        // Allow swapping between the seating modes
        private void OnServerCommand(ConsoleSystem.Arg arg) {
            if (arg.Connection == null || arg.cmd.FullName != "vehicle.swapseats") return;
            var player = arg.Player();
            if (player == null) return;
            var drone = GetMountedDrone(player, out var currentChair);
            if (drone == null) return;
            // Only players with the pilot permission may switch chairs.if (!permission.UserHasPermission(player.UserIDString, PermissionChairPilot))
            return;
            if (!TryGetChairs(drone, out var pilotChair, out var passengerChair)) return;
            var desiredChair = currentChair == passengerChair ? pilotChair : passengerChair;
            SwitchToChair(player, currentChair, desiredChair);
        }
        private void OnEntityMounted(BaseMountable currentChair, BasePlayer player) {
            var drone = GetParentDrone(currentChair);
            if (drone == null) return;
            if (!TryGetChairs(drone, out var pilotChair, out var passengerChair)) return;
            // The rest of the logic is only for pilots.if (!permission.UserHasPermission(player.UserIDString, PermissionChairPilot))
            return;
            var isPilotChair = currentChair == pilotChair;
            if (isPilotChair) {
                // Since the passenger chair is the mount ingress, prevent it from being mounted while the pilot chair is mounted.passengerChair.SetFlag(BaseEntity.Flags.Busy, true);
            }
            else if (!DroneController.Exists(player)) {
                // The player is mounting the drone fresh (not switching chairs), so automatically switch to the pilot chair.SwitchToChair(player, currentChair, pilotChair);
                return;
            }
            DroneController.Mount(this, player, drone, isPilotChair);
        }
        private void OnEntityDismounted(BaseMountable previousChair, BasePlayer player) {
            var drone = GetParentDrone(previousChair);
            if (drone == null) return;
            if (!TryGetChairs(drone, out var pilotChair, out var passengerChair)) return;
            if (previousChair == pilotChair) {
                // Since the passenger chair is the mount ingress, re-enable it when the pilot chair is dismounted.passengerChair.SetFlag(BaseEntity.Flags.Busy, false);
            }
            DroneController.Dismount(this, player, drone);
        }
        private void OnPlayerDismountFailed(BasePlayer player, BaseMountable mountable) {
            var drone = GetMountedDrone(player);
            if (drone == null) return;
            var droneTransform = drone.transform;
            droneTransform.rotation = Quaternion.Euler(0, droneTransform.rotation.eulerAngles.y, 0);
        }
        private object CanPickupEntity(BasePlayer player, Drone drone) {
            if (CanPickupInternal(drone, out var errorLangKey)) return null;
            ChatMessage(player, errorLangKey);
            return False;
        }
        private object CanPickupEntity(BasePlayer player, Signage sign) {
            if (CanPickupInternal(sign, out var errorLangKey)) return null;
            ChatMessage(player, errorLangKey);
            return False;
        }
        private object CanPickupEntity(BasePlayer player, BaseChair chair) {
            if (CanPickupInternal(chair, out var errorLangKey)) return null;
            ChatMessage(player, errorLangKey);
            return False;
        }
        // This hook is exposed by plugin: Remover Tool (RemoverTool).private Dictionary<string, object> OnRemovableEntityInfo(Signage sign, BasePlayer player)
        {
            if (!IsDroneSign(sign)) return null;
            _signRemoveInfo["DisplayName"] = GetMessage(player.UserIDString, Lang.InfoSignName);
            if (sign.pickup.enabled) {
                _signRemoveInfo["Refund"] = _refundInfo;
            }
            else {
                _signRemoveInfo.Remove("Refund");
            }
            return _signRemoveInfo;
        }
        // This hook is exposed by plugin: Remover Tool (RemoverTool).private Dictionary<string, object> OnRemovableEntityInfo(BaseChair chair, BasePlayer player)
        {
            if (!IsDroneChair(chair)) return null;
            _chairRemoveInfo["DisplayName"] = GetMessage(player.UserIDString, Lang.InfoChairName);
            if (chair.pickup.enabled) {
                _chairRemoveInfo["Refund"] = _chairRefundInfo;
            }
            else {
                _chairRemoveInfo.Remove("Refund");
            }
            return _chairRemoveInfo;
        }
        // This hook is exposed by plugin: Remover Tool (RemoverTool).private string canRemove(BasePlayer player, Drone drone)
        {
            if (CanPickupInternal(drone, out var errorLangKey)) return null;
            return GetMessage(player.UserIDString, errorLangKey);
        }
        // This hook is exposed by plugin: Drone Settings (DroneSettings).private string OnDroneTypeDetermine(Drone drone, List<string> droneTypeList)
        {
            var droneType = DetermineDroneType(drone);
            if (droneType == null) return null;
            if (droneTypeList == null) return droneType;
            droneTypeList.Add(droneType);
            return null;
        }
#endregion
#region Exposed Hooks
        private static class ExposedHooks {
            public static object OnDroneSignDeploy(Drone drone, BasePlayer deployer) {
                return Interface.CallHook("OnDroneSignDeploy", drone, deployer);
            }
            public static void OnDroneSignDeployed(Drone drone, BasePlayer deployer) {
                Interface.CallHook("OnDroneSignDeployed", drone, deployer);
            }
            public static object OnDroneChairDeploy(Drone drone, BasePlayer deployer) {
                return Interface.CallHook("OnDroneChairDeploy", drone, deployer);
            }
            public static void OnDroneChairDeployed(Drone drone, BasePlayer deployer) {
                Interface.CallHook("OnDroneChairDeployed", drone, deployer);
            }
            public static void OnDroneControlStarted(Drone drone, BasePlayer player) {
                Interface.CallHook("OnDroneControlStarted", drone, player);
            }
            public static void OnDroneControlEnded(Drone drone, BasePlayer player) {
                Interface.CallHook("OnDroneControlEnded", drone, player);
            }
        }
#endregion
#region Commands
        [Command("dronesign")]
        private void DroneSignCommand(IPlayer player) {
            if (!VerifyPlayer(player, out var basePlayer) || !VerifyPermission(player, PermissionSignDeploy) || !VerifyDroneFound(player, out var drone)) return;
            if (GetDroneSign(drone) != null) {
                ReplyToPlayer(player, Lang.ErrorAlreadyHasSign);
                return;
            }
            if (!VerifyDroneHasSlotVacant(player, drone, SignSlots)) return;
            var isFree = player.HasPermission(PermissionSignDeployFree);
            if (!isFree && basePlayer.inventory.FindItemByItemID(SignItemId) == null) {
                ReplyToPlayer(player, Lang.ErrorNoSignItem);
                return;
            }
            if (TryDeploySign(drone, basePlayer, allowRefund : !isFree) == null) {
                ReplyToPlayer(player, Lang.ErrorDeploySignFailed);
            }
            else if (!isFree) {
                basePlayer.inventory.Take(null, SignItemId, 1);
                basePlayer.Command("note.inv", SignItemId, -1);
            }
        }
        [Command("dronechair", "droneseat")]
        private void DroneChairCommand(IPlayer player) {
            if (!VerifyPlayer(player, out var basePlayer) || !VerifyPermission(player, PermissionChairDeploy) || !VerifyDroneFound(player, out var drone)) return;
            if (HasChair(drone)) {
                ReplyToPlayer(player, Lang.ErrorAlreadyHasChair);
                return;
            }
            if (!VerifyDroneHasSlotVacant(player, drone, ChairSlots)) return;
            var isFree = player.HasPermission(PermissionChairDeployFree);
            if (!isFree && basePlayer.inventory.FindItemByItemID(ChairItemId) == null) {
                ReplyToPlayer(player, Lang.ErrorNoChairItem);
                return;
            }
            if (TryDeployChairs(drone, basePlayer, allowRefund : !isFree) == null) {
                ReplyToPlayer(player, Lang.ErrorDeployChairFailed);
            }
            else if (!isFree) {
                basePlayer.inventory.Take(null, ChairItemId, 1);
                basePlayer.Command("note.inv", ChairItemId, -1);
            }
        }
#endregion
#region Helper Methods - Command Checks
        private bool VerifyPlayer(IPlayer player, out BasePlayer basePlayer) {
            basePlayer = player.Object as BasePlayer;
            return !player.IsServer && basePlayer != null;
        }
        private bool VerifyPermission(IPlayer player, string perm) {
            if (player.HasPermission(perm)) return true;
            ReplyToPlayer(player, Lang.ErrorNoPermission);
            return false;
        }
        private bool VerifyDroneFound(IPlayer player, out Drone drone) {
            var basePlayer = player.Object as BasePlayer;
            drone = GetLookEntity(basePlayer, 3) as Drone;
            if (drone != null && IsDroneEligible(drone)) return true;
            ReplyToPlayer(player, Lang.ErrorNoDroneFound);
            return false;
        }
        private bool VerifyDroneHasSlotVacant(IPlayer player, Drone drone, SlotConfig slotConfig) {
            if (slotConfig.IsCompatibleWithHost(drone)) return true;
            ReplyToPlayer(player, Lang.ErrorIncompatibleAttachment);
            return false;
        }
#endregion
#region Helpers
        private static class RCUtils {
            public static bool IsRCDrone(Drone drone) {
                return drone is not DeliveryDrone;
            }
        }
        private static bool IsDroneEligible(Drone drone) {
            return drone.skinID == 0 && RCUtils.IsRCDrone(drone);
        }
        private static Drone GetParentDrone(BaseEntity entity) {
            return entity.GetParentEntity() as Drone;
        }
        private static Drone GetMountedDrone(BasePlayer player, out BaseMountable currentChair) {
            currentChair = player.GetMounted();
            if (currentChair == null) return null;
            return currentChair.PrefabName is PilotChairPrefab or PassengerChairPrefab ? GetParentDrone(currentChair) : null;
        }
        private static Drone GetMountedDrone(BasePlayer player) {
            return GetMountedDrone(player, out _);
        }
        private static Signage GetDroneSign(Drone drone) {
            return SignSlots.GetOccupant(drone) as Signage;
        }
        private static bool IsDroneSign(Signage sign) {
            return GetParentDrone(sign) != null;
        }
        private static bool TryGetChairs(Drone drone, out BaseMountable pilotChair, out BaseMountable passengerChair) {
            pilotChair = null;
            passengerChair = null;
            foreach (var child in drone.children) {
                var mountable = child as BaseMountable;
                if (mountable == null) continue;
                switch (mountable.PrefabName) {
                    case PilotChairPrefab : pilotChair = mountable;
                    break;
                    case PassengerChairPrefab : passengerChair = mountable;
                    break;
                }
            }
            return pilotChair != null && passengerChair != null;
        }
        private static bool TryGetPassengerChair(Drone drone, out BaseMountable passengerChair) {
            return TryGetChairs(drone, out _, out passengerChair);
        }
        private static bool HasChair(Drone drone) {
            return TryGetPassengerChair(drone, out _);
        }
        private static bool IsDroneChair(BaseChair chair) {
            return GetParentDrone(chair) != null;
        }
        private static void HitNotify(BaseEntity entity, HitInfo info) {
            var player = info.Initiator as BasePlayer;
            if (player == null) return;
            entity.ClientRPCPlayer(null, player, "HitNotify");
        }
        private static void RemoveProblemComponents(BaseEntity entity) {
            foreach (var collider in entity.GetComponentsInChildren<MeshCollider>()) {
                UnityEngine.Object.DestroyImmediate(collider);
            }
        }
        private static void SetupChair(BaseMountable mountable) {
            if (!BaseMountable.AllMountables.Contains(mountable)) {
                BaseMountable.AllMountables.Add(mountable);
            }
            mountable.isMobile = true;
            mountable.EnableSaving(true);
            RemoveProblemComponents(mountable);
        }
        private static BaseEntity GetLookEntity(BasePlayer basePlayer, float maxDistance = 3) {
            return Physics.Raycast(basePlayer.eyes.HeadRay(), out var hit, maxDistance, Physics.DefaultRaycastLayers, QueryTriggerInteraction.Ignore) ? hit.GetEntity() : null;
        }
        private static void SwitchToChair(BasePlayer player, BaseMountable currentChair, BaseMountable desiredChair) {
            currentChair.DismountPlayer(player, lite : true);
            desiredChair.MountPlayer(player);
        }
        private static string DetermineDroneType(Drone drone) {
            if (GetDroneSign(drone) != null) return "DroneSign";
            if (HasChair(drone)) return "DroneChair";
            return null;
        }
        private static bool CanPickupInternal(Drone drone, out string errorLangKey) {
            errorLangKey = null;
            if (!RCUtils.IsRCDrone(drone)) return true;
            // Prevent drone pickup while there is a sign attached that can be picked up.var sign = GetDroneSign(drone);
            if (sign != null) {
                errorLangKey = Lang.ErrorCannotPickupWithSign;
                return !sign.pickup.enabled;
            }
            // Prevent drone pickup while there is a chair attached that can be picked up.if (TryGetPassengerChair(drone, out var passengerChair))
            {
                errorLangKey = Lang.ErrorCannotPickupWithChair;
                return !passengerChair.pickup.enabled;
            }
            return true;
        }
        private static bool CanPickupInternal(Signage sign, out string errorLangKey) {
            errorLangKey = null;
            if (!IsDroneSign(sign)) return true;
            errorLangKey = Lang.ErrorCannotPickupAttachment;
            return sign.pickup.enabled;
        }
        private static bool CanPickupInternal(BaseChair chair, out string errorLangKey) {
            errorLangKey = null;
            if (!IsDroneChair(chair)) return true;
            errorLangKey = Lang.ErrorCannotPickupAttachment;
            return chair.pickup.enabled;
        }
        private void RefreshDroneSettingsProfile(Drone drone) {
            DroneSettings?.Call("API_RefreshDroneProfile", drone);
        }
        private void SetupSign(Drone drone, Signage sign) {
            drone.playerCheckRadius = 0;
            // Damage will be processed by the drone.sign.baseProtection = null;
            UnityEngine.Object.DestroyImmediate(sign.GetComponent<GroundWatch>());
            UnityEngine.Object.DestroyImmediate(sign.GetComponent<DestroyOnGroundMissing>());
            SignComponent.AddToDrone(this, drone, sign);
        }
        private Signage TryDeploySign(Drone drone, BasePlayer deployer = null, bool allowRefund = false) {
            if (ExposedHooks.OnDroneSignDeploy(drone, deployer) is false) return null;
            var sign = GameManager.server.CreateEntity(SmallWoodenSignPrefab, SignLocalPosition, Quaternion.Euler(SignLocalRotationAngles)) as Signage;
            if (sign == null) return null;
            SetupSign(drone, sign);
            if (deployer != null) {
                sign.OwnerID = deployer.userID;
            }
            sign.SetParent(drone);
            sign.Spawn();
            // Claim slots to prevent deploying incompatible attachments.SignSlots.OccupyHost(drone, sign);
            // This flag is used to remember whether the sign should be refundable.// This information is lost on restart but that's a minor concern.sign.pickup.enabled = allowRefund;
            Effect.server.Run(DeploySignEffectPrefab, sign.transform.position);
            ExposedHooks.OnDroneSignDeployed(drone, deployer);
            RefreshDroneSettingsProfile(drone);
            return sign;
        }
        private void SetupAllChairs(Drone drone, BaseMountable pilotChair, BaseMountable passengerChair) {
            SetupChair(pilotChair);
            SetupChair(passengerChair);
            pilotChair.dismountPositions = passengerChair.dismountPositions;
            // Damage will be processed by the drone.passengerChair.baseProtection = null;
            UnityEngine.Object.DestroyImmediate(passengerChair.GetComponent<GroundWatch>());
            UnityEngine.Object.DestroyImmediate(passengerChair.GetComponent<DestroyOnGroundMissing>());
            // Box colliders on the deployable chair block dismount from the pilot chair.foreach (var collider in passengerChair.GetComponentsInChildren<BoxCollider>())
            {
                UnityEngine.Object.DestroyImmediate(collider);
            }
            ChairComponent.AddToDrone(this, drone, pilotChair, passengerChair);
        }
        private BaseMountable SpawnPassengerChair(Drone drone, BasePlayer deployer = null, bool allowRefund = false) {
            var passengerChair = GameManager.server.CreateEntity(PassengerChairPrefab, PassengerChairLocalPosition) as BaseMountable;
            if (passengerChair == null) return null;
            passengerChair.pickup.enabled = allowRefund;
            if (deployer != null) {
                passengerChair.OwnerID = deployer.userID;
            }
            passengerChair.SetParent(drone);
            passengerChair.Spawn();
            // Claim slots to prevent deploying incompatible attachments.ChairSlots.OccupyHost(drone, passengerChair);
            return passengerChair;
        }
        private BaseMountable TryDeployChairs(Drone drone, BasePlayer deployer = null, bool allowRefund = false) {
            var hookResult = ExposedHooks.OnDroneChairDeploy(drone, deployer);
            if (hookResult is false) return null;
            // The driver chair is ideal for mouse movement since it locks the player view angles.var pilotChair = GameManager.server.CreateEntity(PilotChairPrefab, PilotChairLocalPosition) as BaseMountable;
            if (pilotChair == null) return null;
            pilotChair.SetParent(drone);
            pilotChair.Spawn();
            // The passenger chair shows the "mount" prompt and allows for unlocking view angles.var passengerChair = SpawnPassengerChair(drone, deployer, allowRefund);
            if (passengerChair == null) {
                pilotChair.Kill();
                return null;
            }
            SetupAllChairs(drone, pilotChair, passengerChair);
            Effect.server.Run(ChairDeployEffectPrefab, passengerChair.transform.position);
            ExposedHooks.OnDroneChairDeployed(drone, deployer);
            RefreshDroneSettingsProfile(drone);
            _chairDrones.Add(drone);
            return passengerChair;
        }
        private void MaybeRefreshDroneSign(Drone drone) {
            var sign = GetDroneSign(drone);
            if (sign == null) return;
            SetupSign(drone, sign);
        }
        private void MaybeAutoDeployChair(Drone drone) {
            if (drone.OwnerID == 0 || !ChairSlots.IsCompatibleWithHost(drone) || !permission.UserHasPermission(drone.OwnerID.ToString(), PermissionChairAutoDeploy)) return;
            TryDeployChairs(drone);
        }
        private void ReplaceOldChairs(Drone drone) {
            BaseMountable pilotChair = null;
            BaseMountable passengerChair = null;
            BaseMountable oldPassengerChair = null;
            BaseMountable oldVisibleChair = null;
            foreach (var child in drone.children) {
                var mountable = child as BaseMountable;
                if (mountable == null) continue;
                switch (mountable.PrefabName) {
                    case PilotChairPrefab : pilotChair = mountable;
                    break;
                    case PassengerChairPrefab : passengerChair = mountable;
                    break;
                    case DeprecatedPassengerChairPrefab : oldPassengerChair = mountable;
                    break;
                    case DeprecatedVisibleChairPrefab : oldVisibleChair = mountable;
                    break;
                }
            }
            // Skip if the pilot chair is not present, as that indicates this is not a ridable drone.if (pilotChair is null)
            return;
            // Skip if the new passenger chair is already present.if (passengerChair is not null)
            return;
            // Skip if either the old passenger or visible chairs are not present.if (oldPassengerChair == null || oldVisibleChair == null)
            return;
            var mountedPlayer = oldPassengerChair.GetMounted();
            if (mountedPlayer != null) {
                mountedPlayer.EnsureDismounted();
            }
            passengerChair = SpawnPassengerChair(drone);
            passengerChair.OwnerID = oldPassengerChair.OwnerID;
            passengerChair.pickup.enabled = oldPassengerChair.pickup.enabled;
            oldPassengerChair.Kill();
            oldVisibleChair.Kill();
            if (mountedPlayer is not null) {
                passengerChair.AttemptMount(mountedPlayer, doMountChecks : false);
            }
        }
        private void MaybeAddOrRefreshChairs(Drone drone) {
            ReplaceOldChairs(drone);
            if (!TryGetChairs(drone, out var pilotChair, out var passengerChair)) {
                MaybeAutoDeployChair(drone);
                return;
            }
            SetupAllChairs(drone, pilotChair, passengerChair);
            RefreshDroneSettingsProfile(drone);
            _chairDrones.Add(drone);
        }
        private class ChairComponent : FacepunchBehaviour {
            public static void AddToDrone(RidableDrones plugin, Drone drone, BaseMountable pilotChair, BaseMountable passengerChair) {
                var component = passengerChair.gameObject.AddComponent<ChairComponent>();
                component._plugin = plugin;
                component._drone = drone;
                component._chairs = new[] {
                    pilotChair, passengerChair
                }
                ;
                component.CreateCollider(drone, passengerChair);
            }
            public static void RemoveFromChair(BaseMountable chair) {
                var component = chair.gameObject.GetComponent<ChairComponent>();
                if (component == null) return;
                component._isUnloading = true;
                DestroyImmediate(component);
            }
            private const float ColliderHeight = 3;
            private RidableDrones _plugin;
            private BaseMountable[] _chairs;
            private BaseEntity _drone;
            private GameObject _child;
            private bool _isUnloading;
            private void CreateCollider(Drone drone, BaseMountable passengerChair) {
                var centerOfMass = drone.body.centerOfMass;
                _child = passengerChair.gameObject.CreateChild();
                // Layers that seem to work as desired (no player collision): 9, 12, 15, 20, 22, 26._child.gameObject.layer = (int)Rust.Layer.Vehicle_World;
                _child.transform.localPosition = new Vector3(0, ColliderHeight / 4, 0);
                var collider = _child.AddComponent<BoxCollider>();
                var droneExtents = drone.bounds.extents;
                collider.size = droneExtents.WithY(ColliderHeight / 2);
                drone.body.centerOfMass = centerOfMass;
            }
            private void OnDestroy() {
                if (_child != null) {
                    Destroy(_child);
                }
                if (!_isUnloading) {
                    foreach (var chair in _chairs) {
                        if (chair == null || chair.IsDestroyed) continue;
                        chair.Kill();
                    }
                }
                _plugin._chairDrones.Remove(_drone);
                _plugin._mountedChairDrones.Remove(_drone);
            }
        }
#endregion
#region Dynamic Hooks
        private interface IGate {
            bool Enabled {
                get;
            }
        }
        private interface IObservable {
            event Action OnChange;
        }
        private interface IObservableGate : IGate, IObservable {
        }
        private class ObservableHashSet<T> : HashSet<T>, IObservable {
            public event Action OnChange;
            public new bool Add(T item) {
                var added = base.Add(item);
                if (added) {
                    OnChange?.Invoke();
                }
                return added;
            }
            public new bool Remove(T item) {
                var removed = base.Remove(item);
                if (removed) {
                    OnChange?.Invoke();
                }
                return removed;
            }
            public new void Clear() {
                if (Count > 0) return;
                base.Clear();
                OnChange?.Invoke();
            }
        }
        private class ObservableGate : IObservableGate {
            public event Action OnChange;
            private readonly Func<bool> _enableWhen;
            public bool Enabled => _enableWhen();
            public ObservableGate(IObservable observable, Func<bool> enableWhen) {
                _enableWhen = enableWhen;
                observable.OnChange += HandleChange;
            }
            private void HandleChange() {
                OnChange?.Invoke();
            }
        }
        private class MultiObservableGate : IObservableGate {
            public event Action OnChange;
            private readonly IObservableGate[] _gates;
            public bool Enabled {
                get {
                    foreach (var gate in _gates) {
                        if (gate.Enabled) return true;
                    }
                    return false;
                }
            }
            public MultiObservableGate(params IObservableGate[] gates) {
                _gates = gates;
                var handleChange = new Action(HandleChange);
                foreach (var gate in gates) {
                    gate.OnChange += handleChange;
                }
            }
            private void HandleChange() {
                OnChange?.Invoke();
            }
        }
        private class GatedHookCollection {
            public bool IsSubscribed {
                get;
                private set;
            }
            = true;
            private readonly RidableDrones _plugin;
            private readonly IObservableGate _gate;
            private readonly string[] _hookNames;
            public GatedHookCollection(RidableDrones plugin, IObservableGate gate, params string[] hookNames) {
                _plugin = plugin;
                _gate = gate;
                _hookNames = hookNames;
                gate.OnChange += Refresh;
            }
            public void Subscribe() {
                foreach (var hookName in _hookNames) {
                    _plugin.Subscribe(hookName);
                }
                IsSubscribed = true;
            }
            public void Unsubscribe() {
                foreach (var hookName in _hookNames) {
                    _plugin.Unsubscribe(hookName);
                }
                IsSubscribed = false;
            }
            public void Refresh(bool shouldSubscribe) {
                if (shouldSubscribe) {
                    if (!IsSubscribed) {
                        Subscribe();
                    }
                }
                else if (IsSubscribed) {
                    Unsubscribe();
                }
            }
            public void Refresh() {
                Refresh(_gate.Enabled);
            }
        }
#endregion
#region Slot Config
        private class SlotConfig {
            public readonly BaseEntity.Slot[] Slots;
            public SlotConfig(params BaseEntity.Slot[] slots) {
                if (slots.Length == 0) throw new ArgumentOutOfRangeException(nameof(slots), "Must not be empty");
                Slots = slots;
            }
            public BaseEntity GetOccupant(BaseEntity host) {
                return host.GetSlot(Slots[0]);
            }
            public bool IsCompatibleWithHost(BaseEntity host) {
                foreach (var slot in Slots) {
                    if (host.GetSlot(slot) != null) return false;
                }
                return true;
            }
            public void OccupyHost(BaseEntity host, BaseEntity occupant) {
                foreach (var slot in Slots) {
                    host.SetSlot(slot, occupant);
                }
            }
        }
#endregion
#region Parent Trigger
        private class SignTriggerParentEnclosed : TriggerParentEnclosed {
            public static SignTriggerParentEnclosed AddToDrone(Drone drone, GameObject host) {
                var component = host.gameObject.AddComponent<SignTriggerParentEnclosed>();
                component._drone = drone;
                return component;
            }
            private Drone _drone;
            public override bool ShouldParent(BaseEntity entity, bool bypassOtherTriggerCheck = false) {
                // This avoids the drone trying to parent itself when using the Targetable Drones plugin.// Targetable Drones uses a child object with the player layer, which the parent trigger is interested in.// This also avoids other drones being parented which can create some problems such as recursive parenting.if (entity is Drone)
                return false;
                var player = entity as BasePlayer;
                if ((object)player != null && Vector3.Dot(Vector3.up, _drone.transform.up) < 0.8f) return false;
                return base.ShouldParent(entity, bypassOtherTriggerCheck);
            }
        }
        private class SignComponent : EntityComponent<BaseEntity> {
            public static void AddToDrone(RidableDrones plugin, Drone drone, Signage sign) {
                var component = sign.gameObject.AddComponent<SignComponent>();
                component._plugin = plugin;
                component._drone = drone;
                component.CreateParentTrigger(drone, sign);
                plugin._signDrones.Add(drone);
            }
            public static void RemoveFromSign(Signage sign) {
                DestroyImmediate(sign.gameObject.GetComponent<SignComponent>());
            }
            private const float ColliderHeight = 1.8f;
            private RidableDrones _plugin;
            private Drone _drone;
            private GameObject _triggerHost;
            private void CreateParentTrigger(Drone drone, Signage sign) {
                var signExtents = sign.bounds.extents;
                var colliderExtents = new Vector3(signExtents.x, ColliderHeight / 2f, signExtents.y);
                _triggerHost = drone.gameObject.CreateChild();
                _triggerHost.transform.localPosition += new Vector3(0, colliderExtents.y, 0);
                // Without this hack, the drone's sweep test can collide with other entities using the
                // parent trigger collider, causing the drone to occasionally reduce altitude._triggerHost.GetOrAddComponent<Rigidbody>().isKinematic = true;
                var triggerCollider = _triggerHost.gameObject.AddComponent<BoxCollider>();
                triggerCollider.isTrigger = true;
                triggerCollider.gameObject.layer = (int)Rust.Layer.Trigger;
                triggerCollider.size = 2 * colliderExtents;
                var triggerParent = SignTriggerParentEnclosed.AddToDrone(drone, _triggerHost);
                triggerParent.intersectionMode = TriggerParentEnclosed.TriggerMode.PivotPoint;
                triggerParent.interestLayers = Rust.Layers.Mask.Player_Server;
            }
            private void OnDestroy() {
                if (_triggerHost != null) {
                    Destroy(_triggerHost);
                }
                _plugin._signDrones.Remove(_drone);
            }
        }
#endregion
#region DroneController
        private class DroneController : FacepunchBehaviour {
            public static bool Exists(BasePlayer player) {
                return player.GetComponent<DroneController>() != null;
            }
            public static void Mount(RidableDrones plugin, BasePlayer player, Drone drone, bool isPilotChair) {
                var component = player.GetComponent<DroneController>();
                var alreadyExists = component != null;
                if (!alreadyExists) {
                    component = player.gameObject.AddComponent<DroneController>();
                }
                component.OnMount(player, drone, isPilotChair);
                if (!alreadyExists) {
                    ExposedHooks.OnDroneControlStarted(drone, player);
                }
                plugin._mountedChairDrones.Add(drone);
            }
            public static void Dismount(RidableDrones plugin, BasePlayer player, Drone drone) {
                player.GetComponent<DroneController>()?.OnDismount();
                plugin._mountedChairDrones.Remove(drone);
            }
            public static void RemoveFromPlayer(BasePlayer player) {
                DestroyImmediate(player.GetComponent<DroneController>());
            }
            private Drone _drone;
            private BasePlayer _controller;
            private CameraViewerId _viewerId;
            private bool _isPilotChair;
            private void DelayedDestroy() => DestroyImmediate(this);
            private void OnMount(BasePlayer controller, Drone drone, bool isPilotChair) {
                // If they were swapping chairs, cancel destroying this component.CancelInvoke(DelayedDestroy);
                _drone = drone;
                _controller = controller;
                _viewerId = new CameraViewerId(controller.userID, 0);
                _isPilotChair = isPilotChair;
                if (drone.ControllingViewerId.HasValue) {
                    drone.StopControl(drone.ControllingViewerId.Value);
                }
                drone.InitializeControl(_viewerId);
                drone.playerCheckRadius = 0;
            }
            // Don't destroy the component immediately, in case the player is swapping chairs.private void OnDismount() => Invoke(DelayedDestroy, 0);
            private void Update() {
                if (_drone == null || _drone.IsDestroyed) {
                    DestroyImmediate(this);
                    return;
                }
                // Optimization: Skip if there was no user input this frame.if (_controller.lastTickTime < Time.time)
                return;
                _drone.UserInput(_controller.serverInput, _viewerId);
                if (!_isPilotChair) {
                    // In hybrid mode, move relative to the direction the player is facing, instead of relative to the direction the drone is facing.var worldDirection = _drone.transform.InverseTransformVector(_drone.currentInput.movement);
                    var playerRotation = Quaternion.Euler(0, _controller.viewAngles.y, 0);
                    _drone.currentInput.movement = playerRotation * worldDirection;
                }
            }
            private void OnDestroy() {
                if (_drone != null && !_drone.IsDestroyed) {
                    if (_drone.ControllingViewerId.HasValue) {
                        _drone.StopControl(_viewerId);
                    }
                    ExposedHooks.OnDroneControlEnded(_drone, _controller);
                }
            }
        }
#endregion
#region Configuration
        private class Configuration : BaseConfiguration {
            [JsonProperty("Chair tip chance")]
            public int ChairTipChance = 25;
            [JsonProperty("Sign tip chance")]
            public int SignTipChance = 25;
            [JsonProperty("TipChance")]
            private int DeprecatedTipChance {
                set {
                    ChairTipChance = SignTipChance = value;
                }
            }
        }
        private Configuration GetDefaultConfig() => new();
#endregion
#region Configuration Helpers
        private class BaseConfiguration {
            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(BaseConfiguration 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) {
// ... [Code preview truncated due to length. Full source in attached file] ...

Автор
15FPS
Скачиваний
0
Просмотры
1
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок
Сверху