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

FREE CannonsOnBases : 3.0.1 3.0.1

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

namespace Oxide.Plugins
{
    [Info("CannonsOnBases", "wGw", "3.0.1")]
    [Description("Place real boat cannons on base floors/foundations like auto-turrets. Left-click while holding the cannon item. E to mount / long-press E to pick up.")]
    public class CannonsOnBases : RustPlugin
    {
        private Configuration _config;
        private StoredData _data;

        private readonly Dictionary<ulong, float> _lastPlaceTime = new Dictionary<ulong, float>();
        private readonly Dictionary<ulong, float> _useHoldStart = new Dictionary<ulong, float>();
        private readonly Dictionary<ulong, BaseEntity> _useHoldTarget = new Dictionary<ulong, BaseEntity>();

        private const string PrefabCannon = "assets/prefabs/deployable/boatbuilding/cannon/cannon.deployed.prefab";
        private const string ItemShortname = "cannon";
        private const string DeployedShortname = "cannon.deployed";
        private const string PermPlace = "cannonsonbases.place";
        private const string PermAdmin = "cannonsonbases.admin";
        private const int BuildLayerMask = 1219701497;
        private const float PlaceDistance = 4f;
        private const float MountDistance = 5f;
        private const float FlatNormalThreshold = 0.85f;
        private const float LongPressSeconds = 0.75f;

        private class Configuration
        {
            [JsonProperty("Max Cannons Per Tool Cupboard")]
            public int MaxCannonsPerTC { get; set; } = 8;

            [JsonProperty("Only On Owned Blocks")]
            public bool OnlyOnOwnedBlocks { get; set; } = true;

            [JsonProperty("Place Cooldown Seconds")]
            public float PlaceCooldownSeconds { get; set; } = 3f;
        }

        protected override void LoadDefaultConfig()
        {
            _config = new Configuration();
        }

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

            if (_config.MaxCannonsPerTC < 1) _config.MaxCannonsPerTC = 8;
            if (_config.PlaceCooldownSeconds < 0.5f) _config.PlaceCooldownSeconds = 3f;

            SaveConfig();
        }

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

        private class StoredData
        {
            public Dictionary<ulong, int> TcCannonCount { get; set; } = new Dictionary<ulong, int>();
            public Dictionary<ulong, ulong> CannonToTc { get; set; } = new Dictionary<ulong, ulong>();
        }

        private void LoadData()
        {
            _data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name) ?? new StoredData();
        }

        private void SaveData()
        {
            if (_data == null) return;
            Interface.Oxide.DataFileSystem.WriteObject(Name, _data);
        }

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["NoPermission"] = "No permission.",
                ["ReloadSuccess"] = "CannonsOnBases config & data reloaded.",
                ["RecountDone"] = "Recount complete. {0} cannons tracked."
            }, this, "en");
        }

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

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

        private void OnServerInitialized()
        {
            ReconcileCannons();
        }

        private void OnServerSave() => SaveData();

        private void Unload()
        {
            SaveData();
            _lastPlaceTime.Clear();
            _useHoldStart.Clear();
            _useHoldTarget.Clear();
            _data = null;
            _config = null;
        }

        private void OnPlayerDisconnected(BasePlayer player)
        {
            if (player == null) return;
            _lastPlaceTime.Remove(player.userID);
            _useHoldStart.Remove(player.userID);
            _useHoldTarget.Remove(player.userID);
        }

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

            if (input.WasJustPressed(BUTTON.FIRE_PRIMARY))
            {
                var held = player.GetHeldEntity() as HeldEntity;
                if (held == null) return;

                var item = held.GetItem();
                if (item == null || item.info == null || item.info.shortname != ItemShortname || item.amount < 1)
                    return;

                TryPlaceCannon(player, item);
                return;
            }

            if (input.WasJustPressed(BUTTON.USE))
            {
                HandleUsePressed(player);
            }
            else if (input.IsDown(BUTTON.USE))
            {
                HandleUseHeld(player);
            }
            else if (input.WasJustReleased(BUTTON.USE))
            {
                HandleUseReleased(player);
            }
        }

        private object CanPickupEntity(BasePlayer player, BaseEntity entity)
        {
            // Own the pickup so tracking stays correct. Engine "failed to pickup" log is boat-prefab noise.
            if (entity != null && entity.ShortPrefabName == DeployedShortname)
                return false;
            return null;
        }

        private void OnEntityKill(BaseNetworkable entity)
        {
            if (entity == null || entity.net == null) return;
            if (entity.ShortPrefabName != DeployedShortname) return;

            ulong cannonId = entity.net.ID.Value;
            if (_data.CannonToTc.TryGetValue(cannonId, out ulong tcId))
            {
                if (_data.TcCannonCount.TryGetValue(tcId, out int count) && count > 0)
                {
                    count--;
                    if (count <= 0)
                        _data.TcCannonCount.Remove(tcId);
                    else
                        _data.TcCannonCount[tcId] = count;
                }
                _data.CannonToTc.Remove(cannonId);
            }
        }

        private void HandleUsePressed(BasePlayer player)
        {
            var entity = GetLookedAtCannon(player);
            if (entity == null)
            {
                ClearUseHold(player);
                return;
            }

            _useHoldStart[player.userID] = Time.realtimeSinceStartup;
            _useHoldTarget[player.userID] = entity;
        }

        private void HandleUseHeld(BasePlayer player)
        {
            if (!_useHoldStart.TryGetValue(player.userID, out float start))
                return;

            if (Time.realtimeSinceStartup - start < LongPressSeconds)
                return;

            if (!_useHoldTarget.TryGetValue(player.userID, out BaseEntity target) || target == null || target.IsDestroyed)
            {
                ClearUseHold(player);
                return;
            }

            var current = GetLookedAtCannon(player);
            if (current == null || current.net == null || target.net == null || current.net.ID != target.net.ID)
            {
                ClearUseHold(player);
                return;
            }

            bool canPickup = target.OwnerID == player.userID ||
                             player.IsAdmin ||
                             permission.UserHasPermission(player.UserIDString, PermAdmin);

            if (!canPickup)
            {
                ClearUseHold(player);
                return;
            }

            DoPickupCannon(player, target);
            ClearUseHold(player);
        }

        private void HandleUseReleased(BasePlayer player)
        {
            if (!_useHoldStart.TryGetValue(player.userID, out float start))
            {
                ClearUseHold(player);
                return;
            }

            float held = Time.realtimeSinceStartup - start;
            ClearUseHold(player);

            if (held < LongPressSeconds)
                TryForceMount(player);
        }

        private void ClearUseHold(BasePlayer player)
        {
            if (player == null) return;
            _useHoldStart.Remove(player.userID);
            _useHoldTarget.Remove(player.userID);
        }

        private BaseEntity GetLookedAtCannon(BasePlayer player)
        {
            Ray ray = player.eyes.HeadRay();
            RaycastHit hit;
            if (!Physics.Raycast(ray, out hit, MountDistance)) return null;

            var entity = hit.GetEntity();
            if (entity == null || entity.ShortPrefabName != DeployedShortname) return null;
            return entity;
        }

        private void DoPickupCannon(BasePlayer player, BaseEntity entity)
        {
            if (player == null || entity == null || entity.IsDestroyed) return;

            Item item = ItemManager.CreateByName(ItemShortname, 1);
            if (item == null)
            {
                PrintWarning("Failed to create cannon item on pickup.");
                return;
            }

            player.GiveItem(item);
            entity.Kill();

            Puts($"{player.displayName} ({player.userID}) picked up a base cannon.");
        }

        private void TryForceMount(BasePlayer player)
        {
            var entity = GetLookedAtCannon(player);
            if (entity == null) return;

            var mountable = entity as BaseMountable;
            if (mountable == null || mountable.IsMounted()) return;

            var block = entity.GetParentEntity() as BuildingBlock;
            if (block == null)
                block = entity.GetComponentInParent<BuildingBlock>();

            if (block != null)
            {
                var tc = block.GetBuildingPrivilege();
                if (tc != null && !tc.IsAuthed(player) && !player.IsAdmin)
                    return;
            }

            mountable.MountPlayer(player);
        }

        private void TryPlaceCannon(BasePlayer player, Item item)
        {
            float now = Time.realtimeSinceStartup;
            if (_lastPlaceTime.TryGetValue(player.userID, out float last) && now - last < _config.PlaceCooldownSeconds)
                return;
            _lastPlaceTime[player.userID] = now;

            if (!permission.UserHasPermission(player.UserIDString, PermPlace) &&
                !permission.UserHasPermission(player.UserIDString, PermAdmin))
                return;

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

            var block = hit.GetEntity() as BuildingBlock;
            if (block == null || block.IsDestroyed) return;

            if (hit.normal.y < FlatNormalThreshold)
                return;

            string shortName = block.ShortPrefabName ?? string.Empty;
            if (!shortName.Contains("floor") && !shortName.Contains("foundation") &&
                !shortName.Contains("floor.triangle") && !shortName.Contains("foundation.triangle"))
                return;

            var tc = block.GetBuildingPrivilege();
            if (tc == null) return;

            if (_config.OnlyOnOwnedBlocks && !tc.IsAuthed(player) && !player.IsAdmin)
                return;

            ulong tcId = tc.net.ID.Value;

            if (!permission.UserHasPermission(player.UserIDString, PermAdmin))
            {
                if (_data.TcCannonCount.TryGetValue(tcId, out int count) && count >= _config.MaxCannonsPerTC)
                    return;
            }

            Vector3 pos = hit.point + hit.normal * 0.05f;
            Vector3 aimDir = Vector3.ProjectOnPlane(player.eyes.BodyForward(), hit.normal).normalized;
            if (aimDir.sqrMagnitude < 0.01f)
                aimDir = -hit.normal;
            Quaternion rot = Quaternion.LookRotation(aimDir, hit.normal);

            var cannonEntity = GameManager.server.CreateEntity(PrefabCannon, pos, rot);
            if (cannonEntity == null)
            {
                PrintWarning("Failed to create cannon entity - prefab missing?");
                return;
            }

            cannonEntity.OwnerID = player.userID;
            cannonEntity.SetParent(block, true, true);
            cannonEntity.Spawn();

            NextTick(() =>
            {
                if (cannonEntity != null && !cannonEntity.IsDestroyed)
                    cannonEntity.SendNetworkUpdateImmediate();
            });

            if (!_data.TcCannonCount.ContainsKey(tcId))
                _data.TcCannonCount[tcId] = 0;
            _data.TcCannonCount[tcId]++;
            _data.CannonToTc[cannonEntity.net.ID.Value] = tcId;

            item.UseItem(1);

            Puts($"{player.displayName} ({player.userID}) placed cannon on TC {tcId}");
        }

        private void ReconcileCannons()
        {
            if (_data == null) return;

            _data.TcCannonCount.Clear();
            _data.CannonToTc.Clear();

            int found = 0;
            foreach (var ent in BaseNetworkable.serverEntities)
            {
                if (ent == null || ent.IsDestroyed || ent.ShortPrefabName != DeployedShortname) continue;
                if (ent.net == null) continue;

                var block = ent.GetParentEntity() as BuildingBlock;
                if (block == null) continue;

                var tc = block.GetBuildingPrivilege();
                if (tc == null || tc.net == null) continue;

                ulong cannonId = ent.net.ID.Value;
                ulong tcId = tc.net.ID.Value;

                _data.CannonToTc[cannonId] = tcId;
                if (!_data.TcCannonCount.ContainsKey(tcId))
                    _data.TcCannonCount[tcId] = 0;
                _data.TcCannonCount[tcId]++;
                found++;
            }

            SaveData();
            Puts($"Reconciliation complete - {found} cannons tracked.");
        }

        [ChatCommand("cob")]
        private void CmdCob(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;

            if (!permission.UserHasPermission(player.UserIDString, PermAdmin) && !player.IsAdmin)
                return;

            if (args == null || args.Length == 0)
            {
                player.ChatMessage("Usage: /cob reload | recount");
                return;
            }

            switch (args[0].ToLower())
            {
                case "reload":
                    LoadConfig();
                    LoadData();
                    player.ChatMessage(Lang("ReloadSuccess", player.UserIDString));
                    break;

                case "recount":
                    ReconcileCannons();
                    player.ChatMessage(Lang("RecountDone", player.UserIDString, _data.CannonToTc.Count));
                    break;

                default:
                    player.ChatMessage("Usage: /cob reload | recount");
                    break;
            }
        }

        [ConsoleCommand("cannonsonbases.reload")]
        private void CmdReload(ConsoleSystem.Arg arg)
        {
            if (arg.Player() != null && !arg.Player().IsAdmin &&
                !permission.UserHasPermission(arg.Player().UserIDString, PermAdmin))
                return;

            LoadConfig();
            LoadData();
            Puts("CannonsOnBases reloaded.");
        }
    }
}

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