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

FREE BradleyTiers : 2.0.6 2.0.6

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

#region Changelogs and ToDo
/**********************************************************************
*
* 2.0.0 :
* 
* -   Major rework of the entire plugin
* -   Added custom tiers through cfg
* -   Added spawn probability
* -   Optimised economics handouts
* -   Optimised ServerRewards handouts
* -   Re-added SimpleKillFeed message
* -   Reworked the crate lootinjections and checks
* -   The amount of unique items are now consistant
* -   Custom item names are supported
* -   DLC/Skin Check built in
* -   Removed Linq dependency
* 
* 2.0.1 :
* 
* - Fixed issue with custom naming.
* - Some code cleanup.
* - New lang file entries (remove current lang files and let the plugin generate a fresh one.
* - Reward messages have been added
* - Adressed a message always to be in english rather then the assigned language
* 
* 2.0.2 :
* - Adressed boxes being empty when skiping plugin lootsystem and added extra checks.
* - Added string GetBradleyTier(BradleyAPC bradley) to the API
* 
* 2.0.3 :
* - Bradleys should only attack Players now.
* - Added support for QueuedPopups plugin
* - Fix for vanilla or other plugin loot system
* 
* 2.0.4 :
* - Added several QueuedPopups settings to the cfg.
* - Exposed and added several API's
* - Added console commands for spawning a tiered road bradley (adding player id to the bradley)
*   Usefull when adding a purchasable bradley to a GUI shop by command.
* - Added chat command to spawn a tiered bradley on a road near the admin
* - 3 new lang file entries (spawn commands info)
* - Added admin permission (for spawning road bradleys)
* - Readded and fixed RoadBradley, Satdish/harbor Events, Convoy, ArmoredTrain bradleys to get updated or not
* - Readded Notify and UINotify message support
* - Added a catch inside the chat command when for some reason the cfg is not working or tiers where not generated.
* 
* 2.0.5 :
* - Optimised and changed the CanUpdateToBradleyTier hook into an object.
* - Fixed Alphaloot call which has been brought to my attention.
* - Added SkillTree rewards.
* - Added XPerience rewards.
* - Added lang file entries for the SkillTree and XPerience plugins.
* 
* 2.0.6 :
* - Fixed Popup spams on spawns Thx to Kap for reporting the issue.
* - Added cfg - Annouce spawned to chat
* - Added cfg - Annouce destroyed to chat
* 
*   API List :
*   
*   object CanUpdateToBradleyTier(BradleyAPC bradley)
*   void OnBradleyTiersSpawned(BradleyAPC apc , string tierName)
*   void OnTieredBradleyDeath(BradleyAPC bradley , HitInfo info , string tierName)
*   object IsTieredBradley(BradleyAPC bradley)
*   
*   string GetBradleyTier(BradleyAPC bradley)
*   ulong GetBradleyPurchaserId(BradleyAPC bradley)
*   bool IsPurchasedBradley(BradleyAPC bradley)
*   Dictionary<NetworkableId , ulong> GetAllPurchasedBradleys()
*   Dictionary<string , object> GetBradleyTierData()
*   void OnBradleyTierPurchased(player , bradley , tier.Name)
*   
**********************************************************************/
#endregion

namespace Oxide.Plugins
{
    [Info("Bradley Tiers" , "Krungh Crow" , "2.0.6")]
    [Description("Bradley with configurable difficulties")]
    class BradleyTiers : RustPlugin
    {
        [PluginReference]//Other plugins
        Plugin AlphaLoot, Economics, QueuedPopups, GUIAnnouncements, Notify, UINotify, ServerRewards, SimpleKillFeed, SkillTree, XPerience;

        [PluginReference]//Bradley plugins
        Plugin ArmoredTrain, Convoy, HarborEvent, RoadBadley, SatDishEvent;

        #region Variables
        ulong Chaticon = 0;
        const string Use_Perm = "BradleyTiers.use";
        const string Admin_Perm = "BradleyTiers.admin";
        string Prefix;
        string BTVersion;

        Dictionary<NetworkableId , BradleyAPC> bradleyPool = new Dictionary<NetworkableId , BradleyAPC>();
        private readonly object _bradleyLock = new object();
        private readonly object _cooldownsLock = new object();

        private readonly HashSet<NetworkableId> _processedBradleyCrates = new HashSet<NetworkableId>();
        private readonly HashSet<NetworkableId> _skipInitTier = new HashSet<NetworkableId>();
        private readonly Dictionary<ulong , PendingBuy> _pendingBuys = new Dictionary<ulong , PendingBuy>();
        private readonly Dictionary<NetworkableId , ulong> _purchasedBradleys = new Dictionary<NetworkableId , ulong>();
        private readonly object _purchasedLock = new object();

        private bool Debug;
        #endregion

        #region Configuration
        void Init()
        {
            if (!LoadConfigVariables())
            {
                Puts("Config file issue detected. Please delete file, or check syntax and fix.");
                return;
            }
            permission.RegisterPermission(Use_Perm , this);
            permission.RegisterPermission(Admin_Perm , this);
            Prefix = configData.Prefix;
            Chaticon = configData.Chaticon;
            Debug = configData.Debug;
            BTVersion = this.Version.ToString();
            Debugmsg($"Debug mode enabled for {this.Name} v{BTVersion}");

            Debugmsg("Starting DLC skin definition check.");
            CheckDefsUpdated(false);
            timer.Every(1800f , () => CheckDefsUpdated(false));

            foreach (var entity in BaseNetworkable.serverEntities)
            {
                BradleyAPC bradley = entity as BradleyAPC;
                if (bradley == null) continue;

                if (!bradleyPool.ContainsKey(bradley.net.ID))
                {
                    foreach (var tier in configData.Tiers.Values)
                    {
                        if (bradley.name.Contains(tier.Name))
                        {
                            bradley._maxHealth = tier.BradleyHealth;
                            bradley.health = tier.BradleyHealth;
                            bradley.bulletDamage = tier.BradleyBDamage;
                            bradley.viewDistance = tier.BradleyMaxFireRange;
                            bradley.searchRange = tier.BradleyMaxFireRange;
                            bradley.throttle = tier.BradleySpeed;
                            bradley.leftThrottle = bradley.throttle;
                            bradley.rightThrottle = bradley.throttle;
                            bradley.maxCratesToSpawn = tier.BradleyCratesAmount;
                            bradley.name = $"{tier.Name} Bradley";

                            bradleyPool[bradley.net.ID] = bradley;

                            Debugmsg($"Added existing {tier.Name} BradleyApc[{bradley.net.ID}] to the pool.");
                            break;
                        }
                    }
                }
            }
        }

        private ConfigData configData;

        class ConfigData
        {
            [JsonProperty(PropertyName = "Prefix")]
            public string Prefix = "[Bradley Tiers] ";
            [JsonProperty(PropertyName = "Chat Steam64ID")]
            public ulong Chaticon = 76561199183246772;
            [JsonProperty(PropertyName = "Debug")]
            public bool Debug = true;
            [JsonProperty(PropertyName = "QueuedPopups (when installed)")]
            public Popups Popup = new Popups();
            [JsonProperty(PropertyName = "Notifications")]
            public Notifications Notes = new Notifications();
            [JsonProperty(PropertyName = "Other Plugins")]
            public OtherPlugins OPlugins = new OtherPlugins();
            [JsonProperty(PropertyName = "Loot Settings")]
            public SettingsLoot Loot = new SettingsLoot();
            [JsonProperty(PropertyName = "Tiers")]
            public Dictionary<string , TierSettings> Tiers = new Dictionary<string , TierSettings>();
        }

        class Popups
        {
            [JsonProperty(PropertyName = "Background Color (rgba)")]
            public string Color = "0 0 0.65 0.6";
            [JsonProperty(PropertyName = "Height (Pixels)")]
            public float? Height = 34;
        }

        class OtherPlugins
        {
            [JsonProperty(PropertyName = "Include ArmoredTrain Bradley")]
            public bool TrainUse = false;
            [JsonProperty(PropertyName = "Include Convoy Bradley")]
            public bool ConvoyUse = false;
            [JsonProperty(PropertyName = "Include RoadBradley")]
            public bool RoadBradleyUse = false;
            [JsonProperty(PropertyName = "Include SatDish/Harbor Event Bradley")]
            public bool SatdishUse = false;
        }

        class Notifications
        {
            [JsonProperty(PropertyName = "Annouce spawned to chat")]
            public bool ChatSpawnUse = true;
            [JsonProperty(PropertyName = "Annouce destroyed to chat")]
            public bool ChatDestroyUse = true;
            [JsonProperty(PropertyName = "Use GUIAnnouncement")]
            public bool GUIAUse = false;
            [JsonProperty(PropertyName = "Use SimpleKillFeed")]
            public bool SKFUse = true;
            [JsonProperty(PropertyName = "Use Notify")]
            public bool UseNotify = false;
            [JsonProperty(PropertyName = "Notify profile ID")]
            public int PiD = 0;
        }

        class TierSettings
        {
            [JsonProperty(PropertyName = "Name")]
            public string Name;
            [JsonProperty(PropertyName = "BradleyHealth")]
            public float BradleyHealth = 1000;
            [JsonProperty(PropertyName = "BradleyBDamage")]
            public float BradleyBDamage = 1.0f;
            [JsonProperty(PropertyName = "BradleyMaxFireRange")]
            public float BradleyMaxFireRange = 100.0f;
            [JsonProperty(PropertyName = "BradleySpeed")]
            public float BradleySpeed = 10.0f;
            [JsonProperty(PropertyName = "BradleyCratesAmount")]
            public int BradleyCratesAmount = 3;
            [JsonProperty(PropertyName = "Color")]
            public string Color = "green";
            [JsonProperty(PropertyName = "SpawnChance")]
            public float SpawnChance = 25.0f;
            [JsonProperty(PropertyName = "EconomicsReward")]
            public double EconomicsReward = 0;
            [JsonProperty(PropertyName = "ServerRewards")]
            public int ServerRewards = 0;
            [JsonProperty(PropertyName = "SkillTree Points")]
            public int SkillTreePoints = 0;
            [JsonProperty(PropertyName = "Xperience XP")]
            public double XperienceXP = 0d;
            [JsonProperty(PropertyName = "AlphaLoot Profile")]
            public string AlphaLootProfile { get; set; } = "";
            [JsonProperty(PropertyName = "Spawn Min Amount Items")]
            public int MinAmount { get; set; } = 2;
            [JsonProperty(PropertyName = "Spawn Max Amount Items")]
            public int MaxAmount { get; set; } = 6;
            [JsonProperty(PropertyName = "Loot Table" , ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<BradleyItem> Loot { get; set; } = DefaultLoot;
        }

        class SettingsLoot
        {
            [JsonProperty(PropertyName = "Use lootsystem (internal)")]
            public bool UseLoot = true;
            [JsonProperty(PropertyName = "Use Random Skins (internal)")]
            public bool RandomSkins { get; set; } = true;
            [JsonProperty(PropertyName = "Use AlphaLoot")]
            public bool UseAlphaLoot { get; set; } = false;
        }

        private bool LoadConfigVariables()
        {
            try
            {
                configData = Config.ReadObject<ConfigData>();
            }
            catch (Exception ex)
            {
                Puts($"[Error] Failed to load config: {ex.Message}");
                return false;
            }
            SaveConf();
            return true;
        }

        protected override void LoadDefaultConfig()
        {
            Puts("Fresh install detected. Creating a new config file.");

            configData = new ConfigData();

            configData.Tiers.Add("0" , new TierSettings
            {
                Name = "Easy" ,
                BradleyHealth = 1000 ,
                BradleyBDamage = 1.0f ,
                BradleyMaxFireRange = 100.0f ,
                BradleySpeed = 10.0f ,
                BradleyCratesAmount = 3 ,
                Color = "green" ,
                SpawnChance = 25.0f ,
                EconomicsReward = 100 ,
                ServerRewards = 10 ,
                SkillTreePoints = 2 ,
                XperienceXP = 25d ,
                AlphaLootProfile = ""
            });

            configData.Tiers.Add("1" , new TierSettings
            {
                Name = "Medium" ,
                BradleyHealth = 2000 ,
                BradleyBDamage = 1.0f ,
                BradleyMaxFireRange = 100.0f ,
                BradleySpeed = 10.0f ,
                BradleyCratesAmount = 3 ,
                Color = "purple" ,
                SpawnChance = 25.0f ,
                EconomicsReward = 200 ,
                ServerRewards = 20 ,
                SkillTreePoints = 4 ,
                XperienceXP = 25d ,
                AlphaLootProfile = ""
            });

            configData.Tiers.Add("2" , new TierSettings
            {
                Name = "Hard" ,
                BradleyHealth = 3000 ,
                BradleyBDamage = 1.0f ,
                BradleyMaxFireRange = 100.0f ,
                BradleySpeed = 10.0f ,
                BradleyCratesAmount = 3 ,
                Color = "red" ,
                SpawnChance = 25.0f ,
                EconomicsReward = 300 ,
                ServerRewards = 30 ,
                SkillTreePoints = 6 ,
                XperienceXP = 25d ,
                AlphaLootProfile = ""
            });

            configData.Tiers.Add("3" , new TierSettings
            {
                Name = "Nightmare" ,
                BradleyHealth = 4000 ,
                BradleyBDamage = 1.0f ,
                BradleyMaxFireRange = 100.0f ,
                BradleySpeed = 10.0f ,
                BradleyCratesAmount = 3 ,
                Color = "yellow" ,
                SpawnChance = 25.0f ,
                EconomicsReward = 400 ,
                ServerRewards = 40 ,
                SkillTreePoints = 10 ,
                XperienceXP = 25d ,
                AlphaLootProfile = ""
            });

            SaveConf();
        }

        void SaveConf() => Config.WriteObject(configData , true);
        #endregion

        #region LanguageAPI
        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string , string>
            {
                ["BradleyInitiate"] = "Careful! This is a {tierName} Bradley APC!" ,
                ["InvalidInput"] = "<color=red>Please enter a valid command!</color>" ,
                ["Info"] = "<color=green>Bradley Tiers v{0}</color>\nWe have the following Tiers :\n" ,
                ["KillNotification"] = "<color=orange>{0}</color> Destroyed a {1} BradleyApc" ,
                ["NoPermission"] = "<color=green>You do not have permission to use that command!</color>" ,
                ["NoRoadPoint"] = "<color=red>No valid road point found within range.</color>" ,
                ["SpawnMessage"] = "A {0} Bradley has spawned around {1}" ,
                ["KillRewardMain"] = "You destroyed a {0} BradleyApc and received: {1}" ,
                ["RewardEcon"] = "<color=green>${0} Economics</color>" ,
                ["RewardSR"] = "<color=orange>{0} RP</color>" ,
                ["RewardSkillPoints"] = "<color=orange>{0} skill points</color>" ,
                ["RewardXperience"] = "<color=orange>{0} exp</color>" ,
                ["RewardSeparator"] = " and " ,
                ["NoRewards"] = "no extra rewards.",
                ["SpawnHeader"] = "Spawn a Bradley on the nearest road <color=orange>(Admin permission)</color>\n" ,
                ["SpawnCommands"] = "/bt spawn {0}",
                ["ConfigNotLoaded"] = "<color=red>Config not loaded. Check server console for errors.</color>"
            } , this);
        }
        #endregion

        #region Commands
        [ChatCommand("bt")]
        private void BTCmd(BasePlayer player , string command , string[] args)
        {
            if (args.Length == 0)
                return;

            if (configData == null || configData.Tiers == null)
            {
                Player.Message(player , Prefix + msg("ConfigNotLoaded" , player.UserIDString) , Chaticon);
                return;
            }

            string sub = args[0].ToLower();

            if (sub == "info")
            {
                if (!HasPerm(player , Use_Perm))
                    return;

                var messageBuilder = new System.Text.StringBuilder();

                string originalInfoMessage = string.Format(
                    lang.GetMessage("Info" , this , player.UserIDString) ,
                    BTVersion
                );

                messageBuilder.AppendLine(originalInfoMessage);

                // Tier list
                foreach (var tierEntry in configData.Tiers)
                {
                    string name = tierEntry.Value.Name;
                    string color = tierEntry.Value.Color;

                    messageBuilder.AppendLine(
                        $"<color={color}>{name}</color> - <color=white>{tierEntry.Value.BradleyHealth}</color> HP"
                    );
                }

                if (HasPerm(player , Admin_Perm))
                {
                    // Spawn header
                    messageBuilder.AppendLine();
                    messageBuilder.AppendLine(
                        lang.GetMessage("SpawnHeader" , this , player.UserIDString)
                    );

                    // Spawn command lines per tier
                    foreach (var tierEntry in configData.Tiers)
                    {
                        string name = tierEntry.Value.Name.ToLower();

                        messageBuilder.AppendLine(
                            string.Format(
                                lang.GetMessage("SpawnCommands" , this , player.UserIDString) ,
                                name
                            )
                        );
                    }
                }

                Player.Message(player , messageBuilder.ToString() , Chaticon);
                return;
            }

            if (sub == "spawn")
            {
                if (!HasPerm(player , Admin_Perm))
                    return;
                HandleBuyBradley(player , args);
                return;
            }
        }

        [ConsoleCommand("bt.spawn")]
        private void CCmdBuyId(ConsoleSystem.Arg arg)
        {
            // bt.spawn <steamId> <tiername>
            if (arg.Args == null || arg.Args.Length < 2) return;

            var idStr = arg.GetString(0);
            var tierArg = arg.GetString(1);

            if (string.IsNullOrEmpty(idStr) || string.IsNullOrEmpty(tierArg)) return;

            if (!ulong.TryParse(idStr , out var userId)) return;

            var player = BasePlayer.FindByID(userId) ?? BasePlayer.FindSleeping(userId);
            if (player == null) return;

            HandleBuyBradley(player , new[] { "spawn" , tierArg });
        }

        #endregion

        #region Hooks

        void Unload()
        {
            bradleyPool?.Clear();
            CoolDowns?.Clear();
            bradleyPool = null;
            CoolDowns = null;

            _processedBradleyCrates?.Clear();

            _dlcSkins?.Clear();
            _wsToDefId?.Clear();
            _idToSkin?.Clear();
            _dlcSkins = null;
            _skipInitTier?.Clear();
            lock (_purchasedLock)
            {
                _purchasedBradleys.Clear();
            }
            _roadBradleys?.Clear();
        }

        private void OnBradleyApcInitialize(BradleyAPC bradley)
        {
            if (bradley == null) return;

            float now = Time.realtimeSinceStartup;

            foreach (var kvp in _pendingBuys)
            {
                var p = kvp.Value;
                if (p == null) continue;

                if (now - p.createdAt > 3f) continue;

                if (Vector3.Distance(bradley.transform.position , p.spawnPos) <= 25f)
                    return;
            }

            var hook = Interface.CallHook("CanUpdateToBradleyTier" , bradley);
            if (hook is bool b && b == false) return;

            NextTick(() =>
            {
                if (bradley == null || bradley.IsDestroyed) return;

                if (!CanUseAPC(bradley))
                    return;

                var tier = RollRandomTier();
                if (tier == null)
                    return;

                ApplyBradleyTier(bradley , tier , announceToAll: true , sendHook: true);
            });
        }

        private TierSettings RollRandomTier()
        {
            float totalSpawnChance = 0f;
            foreach (var t in configData.Tiers.Values)
                totalSpawnChance += t.SpawnChance;

            if (totalSpawnChance <= 0f)
                return null;

            float roll = UnityEngine.Random.Range(0f , totalSpawnChance);

            float cumulative = 0f;
            foreach (var t in configData.Tiers.Values)
            {
                cumulative += t.SpawnChance;
                if (roll <= cumulative)
                    return t;
            }

            foreach (var t in configData.Tiers.Values)
                return t;

            return null;
        }

        private void ApplyBradleyTier(BradleyAPC bradley , TierSettings tier , bool announceToAll , bool sendHook)
        {
            if (bradley == null || bradley.IsDestroyed || tier == null)
                return;

            var dropPosition = bradley.transform.position;
            string grid = GetGrid(dropPosition);

            timer.Once(0.5f , () =>
            {
                if (bradley == null || bradley.IsDestroyed)
                    return;

                bradley._maxHealth = tier.BradleyHealth;
                bradley.health = tier.BradleyHealth;
                bradley.bulletDamage = tier.BradleyBDamage;
                bradley.viewDistance = tier.BradleyMaxFireRange;
                bradley.searchRange = tier.BradleyMaxFireRange;
                bradley.throttle = tier.BradleySpeed;
                bradley.leftThrottle = bradley.throttle;
                bradley.rightThrottle = bradley.throttle;
                bradley.maxCratesToSpawn = tier.BradleyCratesAmount;

                bradley.name = $"{tier.Name}";

                bradleyPool[bradley.net.ID] = bradley;

                if (sendHook)
                    Interface.CallHook("OnBradleyTiersSpawned" , bradley , tier.Name);

                bradley.SendNetworkUpdate();

                if (!announceToAll)
                    return;

                var bradleyType = $"<color={tier.Color}>{tier.Name}</color>";
                var gridColor = $"<color={tier.Color}>{grid}</color>";

                string rawMsg = lang.GetMessage("SpawnMessage" , this);
                string broadcastMsg = string.Format(rawMsg , bradleyType , gridColor);

                foreach (BasePlayer player in BasePlayer.activePlayerList)
                {
                    if (QueuedPopups != null)
                    {
                        QueuedPopups.Call("AddPopup" ,
                            player ,
                            broadcastMsg ,
                            2 ,
                            "assets/prefabs/npc/m2bradley/BradleyAvatar.png" ,
                            configData.Popup.Color ,
                            null ,
                            null ,
                            configData.Popup.Height
                        );
                    }
                    else
                    {
                        if (configData.Notes.ChatSpawnUse)
                            Player.Message(player , Prefix + broadcastMsg , Chaticon);
                    }
                }

                SendPopups(broadcastMsg);

                Puts(tier.Name.ToLower() != "vanilla"
                    ? $"Upgraded a BradleyApc to [{tier.Name}] Tier"
                    : "Allowed a Vanilla BradleyApc to spawn");
            });
        }

        private void OnEntityDeath(BaseCombatEntity entity , HitInfo info)
        {
            BradleyAPC bradley = entity as BradleyAPC;
            if (bradley == null) return;

            if (bradleyPool.ContainsKey(bradley.net.ID))
            {
                var tierName = bradley.name;
                var killer = info?.InitiatorPlayer?.displayName ?? "unknown";
                var killerID = info?.InitiatorPlayer?.UserIDString ?? "unknown";

                Debugmsg($"Tiered Bradley '{tierName}' got destroyed, removing from pool.");

                var message = string.Format(msg("KillNotification") , killer , tierName);
                Debugmsg($"{tierName} BradleyAPC at {GetGrid(bradley.transform.position)} was destroyed by {killer}");

                if (configData.Notes.ChatDestroyUse) Server.Broadcast(Prefix + message , Chaticon);

                SendPopups(message);

                TierSettings tierSettings = null;
                foreach (var t in configData.Tiers.Values)
                {
                    if (t.Name == tierName)
                    {
                        tierSettings = t;
                        break;
                    }
                }

                if (killerID != "unknown" && tierSettings != null)
                {
                    BasePlayer killerPlayer = BasePlayer.FindByID(ulong.Parse(killerID));
                    if (killerPlayer != null && killerPlayer.IsConnected)
                    {
                        List<string> rewardParts = new List<string>();

                        if (Economics != null && tierSettings.EconomicsReward > 0)
                            rewardParts.Add(string.Format(lang.GetMessage("RewardEcon" , this , killerID) , tierSettings.EconomicsReward));

                        if (ServerRewards != null && tierSettings.ServerRewards > 0)
                            rewardParts.Add(string.Format(lang.GetMessage("RewardSR" , this , killerID) , tierSettings.ServerRewards));

                        if (SkillTree != null && tierSettings.SkillTreePoints > 0)
                            rewardParts.Add(string.Format(lang.GetMessage("RewardSkillPoints" , this , killerID) , tierSettings.SkillTreePoints));

                        if (XPerience != null && tierSettings.XperienceXP > 0d)
                            rewardParts.Add(string.Format(lang.GetMessage("RewardXperience" , this , killerID) , tierSettings.XperienceXP));

                        string rewardSummary = rewardParts.Count > 0
                            ? string.Join(lang.GetMessage("RewardSeparator" , this , killerID) , rewardParts)
                            : lang.GetMessage("NoRewards" , this , killerID);

                        string mainMsg = string.Format(lang.GetMessage("KillRewardMain" , this , killerID) , tierSettings.Name , rewardSummary);
                        PrintToChat(killerPlayer , Prefix + mainMsg);

                        GiveTierRewards(killerPlayer , tierSettings);
                    }
                }

                Interface.CallHook("OnTieredBradleyDeath" , bradley , info , tierName);
                lock (_purchasedLock)
                {
                    _purchasedBradleys.Remove(bradley.net.ID);
                }
                bradleyPool.Remove(bradley.net.ID);
            }
        }

        private void OnEntityKill(BaseNetworkable entity)
        {
            var bradley = entity as BradleyAPC;
            if (bradley == null) return;
            _roadBradleys.Remove(bradley.net.ID);

            lock (_purchasedLock)
            {
                _purchasedBradleys.Remove(bradley.net.ID);
            }
        }

        void OnEntityTakeDamage(BradleyAPC bradley , HitInfo info)
        {
            if (!ContainsBradley(bradley.net.ID))
            {
                Debugmsg($"OnEntityTakeDamage: Bradley {bradley.net.ID} is not registered as tiered.");
                return;
            }

            if (!TryGetBradley(bradley.net.ID , out var tieredBradley))
            {
                Debugmsg($"OnEntityTakeDamage: Failed to retrieve tiered Bradley {bradley.net.ID} from pool.");
                return;
            }

            Debugmsg($"Tiered Bradley '{tieredBradley.name}' took damage.");

            var attacker = info?.InitiatorPlayer;
            if (attacker == null)
            {
                Debugmsg("OnEntityTakeDamage: attacker is null or not a player.");
                return;
            }

            string playerID = attacker.UserIDString;

            if (TryStartCooldown(playerID , 30f))
            {
                string chatMessage = msg("BradleyInitiate" , attacker.UserIDString);
                chatMessage = chatMessage.Replace("{tierName}" , tieredBradley.name);

                Player.Message(attacker , Prefix + chatMessage , Chaticon);
                QueuedPopups?.Call("AddPopup" , attacker , chatMessage , 2 , "assets/prefabs/npc/m2bradley/BradleyAvatar.png" , configData.Popup.Color , null , null , configData.Popup.Height);

                Debugmsg($"Sent chat message to {attacker.displayName}: {chatMessage}");
            }
            else
            {
                Debugmsg($"{attacker.displayName} is still on cooldown for messages.");
            }
        }

        object CanBradleyApcTarget(BradleyAPC bradley , BaseEntity target)
        {
            if (bradleyPool.ContainsKey(bradley.net.ID))
            {
                if (!(target is BasePlayer))
                {
                    return false;
                }

                BasePlayer player = target as BasePlayer;
                if (player != null && player.IsNpc)
                {
                    return false;
                }
            }
            return null;
        }

        #endregion

        #region Timers

        Dictionary<string , float> CoolDowns = new Dictionary<string , float>();

        private bool TryStartCooldown(string playerID , float durationSeconds)
        {
            float lastUsed;
            float currentTime = Time.realtimeSinceStartup;

            if (CoolDowns.TryGetValue(playerID , out lastUsed))
            {
                if (currentTime < lastUsed + durationSeconds)
                    return false;
            }

            CoolDowns[playerID] = currentTime;
            return true;
        }

        public class CooldownTimer
        {
            public Timer timer;
            public float start;
            public float countdown;
        }

        #endregion

        #region API

        object IsTieredBradley(BradleyAPC bradley)
        {
            return bradleyPool.ContainsKey(bradley.net.ID);
        }

        string GetBradleyTier(BradleyAPC bradley)
        {
            if (bradley == null || !bradleyPool.ContainsKey(bradley.net.ID))
                return "Vanilla";

            return bradley.name;
        }

        ulong GetBradleyPurchaserId(BradleyAPC bradley)
        {
            if (bradley == null) return 0;

            lock (_purchasedLock)
            {
                ulong buyer;
                return _purchasedBradleys.TryGetValue(bradley.net.ID , out buyer) ? buyer : 0;
            }
        }

        bool IsPurchasedBradley(BradleyAPC bradley)
        {
            if (bradley == null) return false;

            lock (_purchasedLock)
            {
                return _purchasedBradleys.ContainsKey(bradley.net.ID);
            }
        }

        Dictionary<NetworkableId , ulong> GetAllPurchasedBradleys()
        {
            lock (_purchasedLock)
            {
                return new Dictionary<NetworkableId , ulong>(_purchasedBradleys);
            }
        }

        #endregion

        #region API CFG

        public class BradleyTierInfo
        {
            public string Key;
            public string Name;
            public float BradleyHealth;
            public float BradleyBDamage;
            public float BradleyMaxFireRange;
            public float BradleySpeed;
            public int BradleyCratesAmount;
            public string Color;
            public float SpawnChance;
            public double EconomicsReward;
            public int ServerRewards;
        }

        private BradleyTierInfo ToDto(string key , TierSettings t)
        {
            return new BradleyTierInfo
            {
                Key = key ,
                Name = t.Name ,
                BradleyHealth = t.BradleyHealth ,
                BradleyBDamage = t.BradleyBDamage ,
                BradleyMaxFireRange = t.BradleyMaxFireRange ,
                BradleySpeed = t.BradleySpeed ,
                BradleyCratesAmount = t.BradleyCratesAmount ,
                Color = t.Color ,
                SpawnChance = t.SpawnChance ,
                EconomicsReward = t.EconomicsReward ,
                ServerRewards = t.ServerRewards
            };
        }

        Dictionary<string , object> GetBradleyTierData()
        {
            var result = new Dictionary<string , object>();

            foreach (var kv in configData.Tiers)
                result[kv.Key] = ToDto(kv.Key , kv.Value);

            return result;
        }

        #endregion

        #region Helpers

        private readonly HashSet<ulong> _satDishClaimed = new HashSet<ulong>();

        private static ulong GetNetId(BaseNetworkable ent)
        {
            return ent?.net?.ID.Value ?? 0UL;
        }

        private void CacheSatDishClaimNextTick(BradleyAPC bradley)
        {
            if (bradley == null || bradley.net == null) return;

            NextTick(() =>
            {
                var res = Interface.CallHook("CanBradleyTiersEdit" , bradley);
                if (res != null)
                {
                    var id = GetNetId(bradley);
                    if (id != 0UL)
                    {
                        _satDishClaimed.Add(id);
                        Debugmsg($"Cached SatDish claim for {bradley} netID={id} res={res}");
                    }
                }
            });
        }

        private bool IsSatDishBradleyCached(BradleyAPC bradley)
        {
            var id = GetNetId(bradley);
            return id != 0UL && _satDishClaimed.Contains(id);
        }

        private void OnRoadBradleySpawned(BasePlayer owner , BradleyAPC apc , string profile , string targetRoad)
        {
            if (apc == null || apc.net == null) return;

            _roadBradleys.Add(apc.net.ID);

            Debugmsg($"RoadBradley detected: owner={owner?.UserIDString ?? "null"} apc={apc} id={apc.net.ID} profile={profile} road={targetRoad}");
        }

        private readonly HashSet<NetworkableId> _roadBradleys = new HashSet<NetworkableId>();

        private bool CanUseAPC(BradleyAPC bradley)
        {
            Debugmsg($"[CanUseAPC] called for {bradley} id={bradley?.net?.ID}");

            CacheSatDishClaimNextTick(bradley);

            bool isSatDishOrHarbor =
                Interface.CallHook("CanBradleyTiersEdit" , bradley) != null ||
                IsSatDishBradleyCached(bradley);

            if (isSatDishOrHarbor)
            {
                Debugmsg($"Detected SatDish/Harbor Event Bradley {bradley}");

                if (!configData.OPlugins.SatdishUse)
                {
                    Debugmsg($"Blocked Tiered APC for SatDish/Harbor Event {bradley}");
                    return false;
                }

                return true;
            }

            var convoyHook = Interface.CallHook("IsConvoyVehicle" , (BaseEntity)bradley);
            if (convoyHook is bool isConvoy && isConvoy)
            {
                Debugmsg($"Detected Convoy Bradley {bradley}");

                if (!configData.OPlugins.ConvoyUse)
                {
                    Debugmsg($"Blocked Tiered APC for Convoy {bradley}");
                    return false;
                }

                return true;
            }

            ulong idUlong = bradley?.net?.ID.Value ?? 0UL;
            uint idUint = (uint)idUlong;

            var trainHook = Interface.CallHook("IsTrainBradley" , idUint);

            if (!(trainHook is bool trainBool && trainBool))
                trainHook = Interface.CallHook("IsTrainBradley" , idUlong);

            if (trainHook is bool isTrain && isTrain)
            {
                Debugmsg($"Detected ArmoredTrain Bradley {bradley}");

                if (!configData.OPlugins.TrainUse)
                {
                    Debugmsg($"Blocked Tiered APC for ArmoredTrain {bradley}");
                    return false;
                }

                return true;
            }

            if (_roadBradleys.Contains(bradley.net.ID))
            {
                Debugmsg($"Detected RoadBradley {bradley}");

                if (!configData.OPlugins.RoadBradleyUse) return false;
                return true;
            }

            if (Interface.CallHook("IsBradleyDrop" , bradley) != null)
            {
                Debugmsg($"Blocked Tiered APC for BradleyDrops {bradley}");
                return false;
            }

            return true;
        }

        void OnTieredBradleyDeath(BradleyAPC bradley , HitInfo info , string tierName)
        {
            var killer = info?.InitiatorPlayer?.displayName ?? "unknown";
            Vector3 position = bradley.transform.position;
            int count = 0;
            string grid = GetGrid(position);

            timer.Once(1f , () =>
            {
                float distance = 10f;
                List<LootContainer> crates = new List<LootContainer>();
                Vis.Entities<LootContainer>(position , distance , crates);

                TierSettings tierSettings = null;
                foreach (var t in configData.Tiers.Values)
                {
                    if (t.Name == tierName)
                    {
                        tierSettings = t;
                        break;
                    }
                }

                foreach (LootContainer lootContainer in crates)
                {
                    if (lootContainer == null) continue;

                    if (!lootContainer.PrefabName.Contains("bradley_crate"))
                        continue;

                    if (_processedBradleyCrates.Contains(lootContainer.net.ID))
                        continue;

                    bool allowAlphaLoot =
                        configData.Loot.UseAlphaLoot &&
                        tierSettings != null &&
                        !string.IsNullOrEmpty(tierSettings.AlphaLootProfile);

                    bool allowInternalLoot =
                        configData.Loot.UseLoot && 
                        tierSettings != null &&
                        tierSettings.Loot != null &&
                        tierSettings.Loot.Count > 0;

                    if (!allowAlphaLoot && !allowInternalLoot)
                    {
                        Debugmsg($"Loot disabled for {tierName}. Leaving crate {lootContainer.net.ID} untouched for other plugins.");
                        continue;
                    }

                    lootContainer.inventory.Clear();
                    count++;

                    if (allowAlphaLoot)
                    {
                        AlphaLoot?.Call("PopulateLoot" , lootContainer.inventory , tierSettings.AlphaLootProfile);
                        Debugmsg($"Populated crate {lootContainer.net.ID} with AlphaLoot profile: {tierSettings.AlphaLootProfile}");
                    }
                    else
                    {
                        SpawnLoot(lootContainer.inventory , tierSettings.Loot , tierSettings.MinAmount , tierSettings.MaxAmount);
                        Debugmsg($"Populated crate {lootContainer.net.ID} with internal loot.");
                    }

                    _processedBradleyCrates.Add(lootContainer.net.ID);
                }

                Debugmsg($"{count} bradley crates populated with {tierName} loot. Others left untouched/vanilla.");
            });
        }

        private bool TryGetBradley(NetworkableId id , out BradleyAPC bradley)
        {
            lock (_bradleyLock)
            {
                return bradleyPool.TryGetValue(id , out bradley);
            }
        }

        private bool ContainsBradley(NetworkableId id)
        {
            lock (_bradleyLock)
            {
                return bradleyPool.ContainsKey(id);
            }
        }

        bool HasPerm(BasePlayer player , string perm) => permission.UserHasPermission(player.UserIDString , perm);

        private static string GetGrid(Vector3? position)
        {
            if (position.HasValue)
            {
                return MapHelper.PositionToString(position.Value);
            }
            else
            {
                return "Unknown";
            }
        }

        #endregion

        #region Exp and Reward systems

        private void GiveTierRewards(BasePlayer player , TierSettings tier)
        {
            if (player == null || !player.IsConnected) return;
            if (tier == null) return;

            string killerID = player.UserIDString;

            if (tier.EconomicsReward > 0)
            {
                Economics?.Call("Deposit" , killerID , tier.EconomicsReward);
                Debugmsg($"[Rewards] Economics Deposit {tier.EconomicsReward} -> {player.displayName} ({killerID})");
            }

            if (tier.ServerRewards > 0)
            {
                ServerRewards?.Call("AddPoints" , killerID , tier.ServerRewards);
                Debugmsg($"[Rewards] ServerRewards AddPoints {tier.ServerRewards} -> {player.displayName} ({killerID})");
            }

            int skillPoints = tier.SkillTreePoints;
            if (skillPoints > 0)
            {
                var hookResult = Interface.CallHook("OnBradleyTiersGiveSkillPoints" , player , skillPoints);
                if (hookResult is int modified)
                    skillPoints = modified;

                if (skillPoints > 0)
                {
                    SkillTree?.Call("GiveSkillPoints" , player , skillPoints);
                    Debugmsg($"[Rewards] SkillTree GiveSkillPoints {skillPoints} -> {player.displayName}");
                }
                else Debugmsg($"[Rewards] SkillTree blocked/modified to 0 for {player.displayName}");
            }

            double xp = tier.XperienceXP;
            if (xp > 0d)
            {
                XPerience?.Call("GiveXPBasic" , player , xp);
                Debugmsg($"[Rewards] Xperience GiveXPBasic {xp} -> {player.displayName} (plugin={(XPerience != null ? "loaded" : "null")})");
            }
        }

        #endregion

        #region Lootsystem

        private static List<BradleyItem> DefaultLoot
        {
            get
            {
                return new List<BradleyItem>
                {
                    new BradleyItem { probability = 1f, shortname = "ammo.pistol", name = "", amountMin = 128, amount = 256, skin = 0 },
                    new BradleyItem { probability = 1f, shortname = "ammo.pistol.fire", name = "", amountMin = 128, amount = 256, skin = 0 },
                    new BradleyItem { probability = 1f, shortname = "ammo.rifle.explosive", name = "", amountMin = 128, amount = 256, skin = 0 },
                    new BradleyItem { probability = 1f, shortname = "ammo.rifle.hv", name = "", amountMin = 128, amount = 256, skin = 0 },
                    new BradleyItem { probability = 1f, shortname = "ammo.rifle.incendiary", name = "", amountMin = 128, amount = 256, skin = 0 },
                    new BradleyItem { probability = 1f, shortname = "ammo.shotgun", name = "", amountMin = 128, amount = 256, skin = 0 },
                    new BradleyItem { probability = 1f, shortname = "ammo.shotgun", name = "", amountMin = 128, amount = 256, skin = 0 },
                    new BradleyItem { probability = 1f, shortname = "explosive.timed", name = "", amountMin = 1, amount = 5, skin = 0 },
                    new BradleyItem { probability = 1f, shortname = "explosives", name = "", amountMin = 200, amount = 200, skin = 0 },
                    new BradleyItem { probability = 1f, shortname = "pistol.m92", name = "", amountMin = 1, amount = 1, skin = 0 },
                    new BradleyItem { probability = 1f, shortname = "shotgun.spas12", name = "", amountMin = 1, amount = 1, skin = 0 },
                    new BradleyItem { probability = 1f, shortname = "pickaxe", name = "", amountMin = 1, amount = 1, skin = 0 },
                    new BradleyItem { probability = 1f, shortname = "hatchet", name = "", amountMin = 1, amount = 1, skin = 0 },
                    new BradleyItem { probability = 1f, shortname = "can.beans", name = "", amountMin = 3, amount = 5, skin = 0 },
                    new BradleyItem { probability = 1f, shortname = "can.tuna", name = "", amountMin = 3, amount = 5, skin = 0 },
                    new BradleyItem { probability = 1f, shortname = "black.raspberries", name = "", amountMin = 3, amount = 5, skin = 0 },
                    new BradleyItem { probability = 0.6f, shortname = "hmlmg", name = "", amountMin = 1, amount = 1, skin = 0 },
                    new BradleyItem { probability = 0.6f, shortname = "supply.signal", name = "", amountMin = 1, amount = 1, skin = 0 },
                };
            }
        }

        public class BradleyItem
        {
            public float probability { get; set; } = 1f;
            public string shortname { get; set; }
            public string name { get; set; } = "";
            public ulong skin { get; set; }
            public int amountMin { get; set; }
            public int amount { get; set; }
        }

        #region DlcCheckerIntegration
        private readonly Hash<ulong , int> _wsToDefId = new Hash<ulong , int>();
        private readonly Hash<int , ItemSkinDirectory.Skin> _idToSkin = new Hash<int , ItemSkinDirectory.Skin>();
        private float _wst;
        private bool _isInitialized;
        private int _lastSteamDefCount = 0;
        private List<ulong> _dlcSkins = new List<ulong>();
        private System.R
// ... [Code preview truncated due to length. Full source in attached file] ...

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