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

FREE TurretsOnBoat : 2.0.1 2.0.1

Нет прав для скачивания
C#:
#region Using
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;
using UnityEngine;
using Rust;
#endregion

namespace Oxide.Plugins
{
    [Info("TurretsOnBoat", "wGw", "2.0.1")]
    [Description("Place AutoTurrets on boats. Left-click while holding the turret. Hardened against collision & stability cascade.")]
    public class TurretsOnBoat : RustPlugin
    {
        #region Fields

        private Configuration _config;

        private const string PermUse = "turretsonboat.use";
        private const string PermAdmin = "turretsonboat.admin";

        private const string TurretItemShortname = "autoturret";
        private const string TurretDeployedShortname = "autoturret_deployed";
        private const string TurretDeployedPrefab = "assets/prefabs/npc/autoturret/autoturret_deployed.prefab";

        private const int BuildLayerMask = 1219701497;
        private const float PlaceDistance = 6f;
        private const float PlaceCooldown = 0.35f;

        // boat net ID -> set of live turret entities parented to it
        private readonly Dictionary<ulong, HashSet<BaseEntity>> _boatTurrets = new Dictionary<ulong, HashSet<BaseEntity>>();
        private readonly Dictionary<ulong, float> _lastPlaceTime = new Dictionary<ulong, float>();

        #endregion

        #region Config

        private class Configuration
        {
            [JsonProperty("Version (do not edit)")]
            public VersionNumber Version { get; set; } = new VersionNumber(2, 0, 1);

            [JsonProperty("Max turrets per boat")]
            public int MaxPerBoat { get; set; } = 6;

            [JsonProperty("Minimum distance between turrets on boat (meters)")]
            public float MinDistanceBetweenTurrets { get; set; } = 3.0f;

            [JsonProperty("Enable collision protection")]
            public bool EnableCollisionProtection { get; set; } = true;

            [JsonProperty("Require ownership or be on the boat")]
            public bool RequireOwnershipOrOnBoat { get; set; } = true;
        }

        protected override void LoadDefaultConfig()
        {
            _config = new Configuration();
            Puts("Created fresh default config for TurretsOnBoat.");
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                _config = Config.ReadObject<Configuration>();
                if (_config == null) throw new Exception("Config is null");
            }
            catch
            {
                PrintError("Config corrupted or missing — loading defaults.");
                LoadDefaultConfig();
            }

            // Version migration
            if (_config.Version < new VersionNumber(2, 0, 1))
                _config.Version = new VersionNumber(2, 0, 1);

            // Clamp values
            if (_config.MaxPerBoat < 1) _config.MaxPerBoat = 1;
            if (_config.MaxPerBoat > 20) _config.MaxPerBoat = 20;
            if (_config.MinDistanceBetweenTurrets < 1f) _config.MinDistanceBetweenTurrets = 1f;
            if (_config.MinDistanceBetweenTurrets > 15f) _config.MinDistanceBetweenTurrets = 15f;

            SaveConfig();
        }

        protected override void SaveConfig() => Config.WriteObject(_config, true);

        #endregion

        #region Localization

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["NoPermission"] = "You do not have permission to place turrets on boats.",
                ["NotAuthorized"] = "You must own this boat or be standing on it!",
                ["MaxReached"] = "Maximum {0} turrets per boat!",
                ["TooClose"] = "<color=#FF5555>Turrets must be at least {0:F1}m apart on boats to prevent stability issues!</color>",
                ["Placed"] = "<color=#55FF55>AutoTurret placed on boat.</color>",
                ["HoldTurret"] = "Hold an Auto Turret in your hands first.",
                ["ReloadSuccess"] = "TurretsOnBoat config reloaded.",
                ["ScanDone"] = "Scan complete. Fixed {0} boat-mounted turrets."
            }, this, "en");
        }

        private string Lang(string key, string userId = null, params object[] args)
        {
            return string.Format(lang.GetMessage(key, this, userId), args);
        }

        #endregion

        #region Oxide Hooks

        private void Init()
        {
            permission.RegisterPermission(PermUse, this);
            permission.RegisterPermission(PermAdmin, this);
        }

        private void OnServerInitialized()
        {
            Puts($"TurretsOnBoat v{Version} loaded — Collision protection: {(_config.EnableCollisionProtection ? "ENABLED" : "DISABLED")}");
            Puts($"Min turret spacing: {_config.MinDistanceBetweenTurrets:F1}m | Max per boat: {_config.MaxPerBoat}");

            // Full scan + harden every existing boat-mounted turret
            int fixedCount = ScanAndFixAllBoatTurrets();
            if (fixedCount > 0)
                Puts($"Hardened {fixedCount} existing boat turrets on load.");
        }

        private void Unload()
        {
            _boatTurrets.Clear();
            _lastPlaceTime.Clear();
            Puts("TurretsOnBoat unloaded — existing turrets remain parented to boats.");
        }

        private void OnPlayerInput(BasePlayer player, InputState input)
        {
            if (player == null || !player.IsConnected || player.IsDead() || player.IsSleeping())
                return;

            if (!input.WasJustPressed(BUTTON.FIRE_PRIMARY))
                return;

            // Prefer GetActiveItem — more reliable across Build Mode / creative / privilege states
            Item item = player.GetActiveItem();
            if (item == null || item.info == null || item.info.shortname != TurretItemShortname || item.amount < 1)
            {
                // Fallback to HeldEntity path (some modes still expose it this way)
                var held = player.GetHeldEntity() as HeldEntity;
                if (held == null) return;
                item = held.GetItem();
                if (item == null || item.info == null || item.info.shortname != TurretItemShortname || item.amount < 1)
                    return;
            }

            // Only now check permission so we don't silently eat other left-clicks
            if (!permission.UserHasPermission(player.UserIDString, PermUse) &&
                !permission.UserHasPermission(player.UserIDString, PermAdmin))
            {
                player.ChatMessage(Lang("NoPermission", player.UserIDString));
                return;
            }

            TryPlaceTurret(player, item);
        }

        private void TryPlaceTurret(BasePlayer player, Item item)
        {
            if (player == null || item == null || item.amount < 1) return;

            // Simple per-player cooldown so rapid clicks don't spam
            float now = Time.realtimeSinceStartup;
            if (_lastPlaceTime.TryGetValue(player.userID, out float last) && now - last < PlaceCooldown)
                return;
            _lastPlaceTime[player.userID] = now;

            Ray ray = player.eyes.HeadRay();
            RaycastHit hit;
            if (!GamePhysics.Trace(ray, 0f, out hit, PlaceDistance, BuildLayerMask, QueryTriggerInteraction.Collide))
                return;

            var hitEntity = hit.GetEntity();
            if (hitEntity == null) return;

            var boat = GetRootBoat(hitEntity);
            if (boat == null) return;

            if (_config.RequireOwnershipOrOnBoat && !IsAuthorizedOnBoat(boat, player))
            {
                player.ChatMessage(Lang("NotAuthorized", player.UserIDString));
                return;
            }

            ulong boatId = boat.net.ID.Value;

            if (!_boatTurrets.TryGetValue(boatId, out var existingTurrets))
            {
                existingTurrets = new HashSet<BaseEntity>();
                _boatTurrets[boatId] = existingTurrets;
            }

            // Prune dead entries first
            existingTurrets.RemoveWhere(t => t == null || t.IsDestroyed);

            if (existingTurrets.Count >= _config.MaxPerBoat)
            {
                player.ChatMessage(Lang("MaxReached", player.UserIDString, _config.MaxPerBoat));
                return;
            }

            Vector3 newPos = hit.point + hit.normal * 0.05f;

            // Proximity block — stops the classic stability cascade
            foreach (var existing in existingTurrets)
            {
                if (existing == null || existing.IsDestroyed) continue;
                if (Vector3.Distance(newPos, existing.transform.position) < _config.MinDistanceBetweenTurrets)
                {
                    player.ChatMessage(Lang("TooClose", player.UserIDString, _config.MinDistanceBetweenTurrets));
                    return;
                }
            }

            // Orientation — flat to the hit surface, facing player forward projected onto plane
            Vector3 forward = Vector3.ProjectOnPlane(player.eyes.BodyForward(), hit.normal).normalized;
            if (forward.sqrMagnitude < 0.01f)
                forward = Vector3.Cross(hit.normal, Vector3.right).normalized;

            Quaternion rot = Quaternion.LookRotation(forward, hit.normal);

            var turret = GameManager.server.CreateEntity(TurretDeployedPrefab, newPos, rot) as BaseEntity;
            if (turret == null) return;

            turret.OwnerID = player.userID;
            turret.SetParent(boat, true, true);

            // Harden BEFORE Spawn so physics never sees a dynamic + concave combination
            ApplyTurretFix(turret);

            turret.Spawn();

            // Second pass after Spawn in case any components initialise late
            NextTick(() =>
            {
                if (turret != null && !turret.IsDestroyed)
                    ApplyTurretFix(turret);
            });

            existingTurrets.Add(turret);

            // Consume the item
            item.amount--;
            item.MarkDirty();
            if (item.amount <= 0)
                item.RemoveFromContainer();

            player.ChatMessage(Lang("Placed", player.UserIDString));
            Puts($"{player.displayName} placed AutoTurret on boat {boatId}");
        }

        object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info)
        {
            if (!_config.EnableCollisionProtection)
                return null;

            if (entity == null || info == null)
                return null;

            if (entity.ShortPrefabName != TurretDeployedShortname)
                return null;

            var parent = entity.GetParentEntity();
            if (parent == null || !(parent is BaseVehicle))
                return null;

            // Aggressive filter for boat physics transfer
            // Collision / Fall are the obvious ones.
            // Null initiator + any damage is almost always world / physics impulse.
            // Generic with no initiator is also common on frontal junk-pile hits.
            float collision = info.damageTypes.Get(DamageType.Collision);
            float fall = info.damageTypes.Get(DamageType.Fall);
            float generic = info.damageTypes.Get(DamageType.Generic);

            bool isPhysicsDamage =
                collision > 0f ||
                fall > 0f ||
                (info.Initiator == null && info.damageTypes.Total() > 0f) ||
                (generic > 0f && info.Initiator == null) ||
                (info.Initiator is BaseVehicle);

            if (isPhysicsDamage)
            {
                // Cancel the damage
                return true;
            }

            return null;
        }

        private void OnEntityKill(BaseNetworkable entity)
        {
            if (entity == null) return;

            // Turret died
            if (entity.ShortPrefabName == TurretDeployedShortname)
            {
                var turret = entity as BaseEntity;
                if (turret == null) return;

                var boat = turret.GetParentEntity();
                if (boat == null || !(boat is BaseVehicle)) return;

                ulong boatId = boat.net.ID.Value;
                if (_boatTurrets.TryGetValue(boatId, out var turrets))
                {
                    turrets.Remove(turret);
                    if (turrets.Count == 0)
                        _boatTurrets.Remove(boatId);
                    else
                    {
                        // Re-harden remaining siblings to stop cascade
                        NextTick(() =>
                        {
                            if (boat == null || boat.IsDestroyed) return;
                            foreach (var remaining in turrets)
                            {
                                if (remaining != null && !remaining.IsDestroyed)
                                    ApplyTurretFix(remaining);
                            }
                        });
                    }
                }
                return;
            }

            // Boat itself died — clean the tracking
            if (entity is BaseVehicle)
            {
                ulong boatId = entity.net.ID.Value;
                _boatTurrets.Remove(boatId);
            }
        }

        #endregion

        #region Commands

        // Optional alternative placement command (works on both Oxide and Carbon)
        [ChatCommand("boatturret")]
        private void CmdPlaceTurret(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;

            if (!permission.UserHasPermission(player.UserIDString, PermUse) &&
                !permission.UserHasPermission(player.UserIDString, PermAdmin))
            {
                player.ChatMessage(Lang("NoPermission", player.UserIDString));
                return;
            }

            Item item = player.GetActiveItem();
            if (item == null || item.info == null || item.info.shortname != TurretItemShortname || item.amount < 1)
            {
                player.ChatMessage(Lang("HoldTurret", player.UserIDString));
                return;
            }

            TryPlaceTurret(player, item);
        }

        [ChatCommand("turretsonboat.reload")]
        [ConsoleCommand("turretsonboat.reload")]
        private void CmdReload(BasePlayer player, string command, string[] args)
        {
            if (player != null && !permission.UserHasPermission(player.UserIDString, PermAdmin))
            {
                player.ChatMessage(Lang("NoPermission", player.UserIDString));
                return;
            }

            LoadConfig();
            int fixedCount = ScanAndFixAllBoatTurrets();

            string msg = Lang("ReloadSuccess") + " " + Lang("ScanDone", null, fixedCount);
            if (player != null)
                player.ChatMessage(msg);
            else
                Puts(msg);
        }

        #endregion

        #region Core Methods

        private void ApplyTurretFix(BaseEntity turret)
        {
            if (turret == null || turret.IsDestroyed) return;

            // Force every rigidbody kinematic FIRST — this must happen before physics wakes up
            var rigidbodies = turret.GetComponentsInChildren<Rigidbody>(true);
            for (int i = 0; i < rigidbodies.Length; i++)
            {
                var rb = rigidbodies[i];
                if (rb == null) continue;
                rb.isKinematic = true;
                rb.interpolation = RigidbodyInterpolation.None;
                rb.collisionDetectionMode = CollisionDetectionMode.Discrete;
            }

            // Kill the concave mesh collider warning when parented to a dynamic boat
            // Making them convex is the Unity-recommended fix and is fine for mounted turrets
            var meshColliders = turret.GetComponentsInChildren<MeshCollider>(true);
            for (int i = 0; i < meshColliders.Length; i++)
            {
                var mc = meshColliders[i];
                if (mc == null) continue;
                if (!mc.convex)
                    mc.convex = true;
            }

            // StabilityEntity is the real cascade source
            var stability = turret.GetComponent<StabilityEntity>();
            if (stability != null)
            {
                stability.grounded = true;
                stability.cachedStability = 1f;
            }

            // Also force any child StabilityEntities
            var childStabilities = turret.GetComponentsInChildren<StabilityEntity>(true);
            for (int i = 0; i < childStabilities.Length; i++)
            {
                var s = childStabilities[i];
                if (s == null) continue;
                s.grounded = true;
                s.cachedStability = 1f;
            }

            turret.SendNetworkUpdateImmediate();
        }

        private int ScanAndFixAllBoatTurrets()
        {
            _boatTurrets.Clear();
            int fixedCount = 0;

            foreach (var ent in BaseNetworkable.serverEntities)
            {
                if (ent == null || ent.IsDestroyed) continue;
                if (ent.ShortPrefabName != TurretDeployedShortname) continue;

                var turret = ent as BaseEntity;
                if (turret == null) continue;

                var parent = turret.GetParentEntity();
                if (parent == null || !(parent is BaseVehicle)) continue;

                ulong boatId = parent.net.ID.Value;
                if (!_boatTurrets.TryGetValue(boatId, out var set))
                {
                    set = new HashSet<BaseEntity>();
                    _boatTurrets[boatId] = set;
                }

                set.Add(turret);
                ApplyTurretFix(turret);
                fixedCount++;
            }

            return fixedCount;
        }

        private BaseEntity GetRootBoat(BaseEntity entity)
        {
            BaseEntity current = entity;
            int safety = 0;
            while (current != null && safety++ < 12)
            {
                if (current is BaseVehicle)
                    return current;
                current = current.GetParentEntity();
            }
            return null;
        }

        private bool IsAuthorizedOnBoat(BaseEntity boat, BasePlayer player)
        {
            if (boat == null || player == null) return false;

            // Owner
            if (boat.OwnerID == player.userID) return true;

            // Currently mounted on the boat
            var mounted = player.GetMounted();
            if (mounted != null)
            {
                var mountParent = mounted.GetParentEntity();
                if (mountParent == boat || mounted == boat)
                    return true;
            }

            // Standing on / very close to the boat
            if (Vector3.Distance(player.transform.position, boat.transform.position) < 10f)
                return true;

            return false;
        }

        #endregion
    }
}

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