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

FREE HeliSignals : 1.2.32 1.2.32

Нет прав для скачивания
C#:
using Facepunch;
using HarmonyLib;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Rust;
using Rust.Ai;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using System.Text.RegularExpressions;
using System.Reflection;
using Time = UnityEngine.Time;
using Random = UnityEngine.Random;
#region Changelog
/* Changelog

1.2.32
 - Fixed: For Sep 4th 2025 Rust update
 - Fixed: Invalid item shortnames in loot tables causing server crash

1.2.31
 - Fixed: Hackable locked crates not spawning when set in config

1.2.30
 - Fixed: Wave discord announcements not being correct in some cases
 - Fixed: Heli Signals interfering with Hackable crates from other plugins
 - Fixed: Heli fleeing from player in some cases when shouldn't
 - Fixed: Occasional Harmony patching error
 - Fixed: Heli sometimes getting stuck in low aggro orbit state

1.2.29
 - Fixed: Wave helicopters spawning before previous wave ended
 - Fixed: Cooldowns not working properly in some cases
 - Fixed: Discord embed not having Heli HP info on called
 - Fixed: Heli moving away from calling player and not returning to normal behaviour
 - Fixed: If moving away, heli will now return when hit by auth'd player
 - Added: Heli crate despawn timer config option

1.2.28
 - Fixed: Refunding waves giving incorrect non-wave signal

1.2.27
 - Fixed: Loot item dupes in certain cases when dupes allowed set to false
 - Fixed: Support for BetterNPC now fixed (when false now prevents Better NPC spawn)
 - Fixed: HackableLockedCrate config hack time not working in certain cases
 - Fixed: Heli crates falling on Unloadable Train wagons not being lootable
 - Fixed: Heli targeting players under water, now they do not!
 - Fixed: Heli glitching under terrain and ocean
 - Fixed: Player calling location/heli killed grid position accuracy
 - Fixed: Team kill messages only going to the called player
 - Fixed: Players exploiting base protection by fighting on non-team mebers bases when auth'd on TC
 - Added: Config option to allow/prevent Epic Loot adding items to crates
 - Added: Option to make heli and hackable crates float if fall in water
 - Added: Config option for minimum heli altitude
 - Added: Config option for maximum heli altitude (prevents raised flight path with 7th June FP update)
 - Changed: Code clean up & performance/efficiency improvements++
 - Added: New Discord embed announcements with language entries
 
*/
#endregion Changelog
namespace Oxide.Plugins {
    [Info("Heli Signals", "ZEODE", "1.2.32")]
    [Description("Call Patrol Helicopters to your location with custom supply signals.")]
    public class HeliSignals : RustPlugin {
#region Plugin References
        [PluginReference]
        Plugin Friends, Clans, FancyDrop, ServerRewards, Economics, Vanish, NoEscape, BotReSpawn, BetterNPC, DynamicPVP, XPerience, SkillTree, NpcSpawn;
#endregion Plugin References
#region Constants
        private bool DEBUG = true;
        private static HeliSignals Instance;
        private static Harmony harmony = new Harmony("HeliSignals");
        public static Dictionary<PatrolHelicopterAI, HeliComponent> heliCompCache = new Dictionary<PatrolHelicopterAI, HeliComponent>();
        private static System.Random random = new System.Random();
        private const string permAdmin = "helisignals.admin";
        private const string permBuy = "helisignals.buy";
        private const string permBypasscooldown = "helisignals.bypasscooldown";
        private const string heliPrefab = "assets/prefabs/npc/patrol helicopter/patrolhelicopter.prefab";
        private const string hackableCrate = "assets/prefabs/deployable/chinooklockedcrate/codelockedhackablecrate.prefab";
        private const string shockSound = "assets/prefabs/locks/keypad/effects/lock.code.shock.prefab";
        private const string deniedSound = "assets/prefabs/locks/keypad/effects/lock.code.denied.prefab";
        private const string steamProfileUrl = "https://steamcommunity.com/profiles/";
        private const string HELI_AVATAR = "https://zeode.io/images/HeliSignalsBot.png";
        private const string zeodeFooterUrl = "https://zeode.io";
        private const string defaultWebhookUrl = "https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks";
        private const int supplySignalId = 1397052267;
        private const int scrapId = -932201673;
        private const uint ENGINE_COL = 224139191;
        private const uint MAIN_ROTOR_COL = 1460989848;
        private const uint TAIL_COL = 2699525250;
        // Default config item skins
        // Single
        private const ulong easySkinID = 2920175997;
        private const ulong medSkinID = 2920176079;
        private const ulong hardSkinID = 2920176050;
        private const ulong eliteSkinID = 2920176024;
        private const ulong expertSkinID = 3099117081;
        private const ulong nightmareSkinID = 3099117372;
        // Multi
        private const ulong easyMultiSkinID = 3083234542;
        private const ulong medMultiSkinID = 3083234833;
        private const ulong hardMultiSkinID = 3083234755;
        private const ulong eliteMultiSkinID = 3083234647;
        private const ulong elxpertMultiSkinID = 3099124338;
        private const ulong nightmareMultiSkinID = 3099124426;
        // Wave
        private const ulong normalWaveSkinID = 3104667036;
        private const ulong hardWaveSkinID = 3104666951;
        // Default config item names
        // Single
        private const string easyHeli = "Heli Signal (Easy)";
        private const string medHeli = "Heli Signal (Medium)";
        private const string hardHeli = "Heli Signal (Hard)";
        private const string expertHeli = "Heli Signal (Expert)";
        private const string nightmareHeli = "Heli Signal (Nightmare)";
        private const string eliteHeli = "Heli Signal (Elite)";
        // Multi
        private const string easyMulti = "Multi Heli (Easy)";
        private const string medMulti = "Multi Heli (Medium)";
        private const string hardMulti = "Multi Heli (Hard)";
        private const string eliteMulti = "Multi Heli (Elite)";
        // Wave
        private const string normalWave = "Heli Wave Signal (Normal)";
        private const string hardWave = "Heli Wave Signal (Hard)";
#endregion Constants
#region Language
        protected override void LoadDefaultMessages() {
            lang.RegisterMessages(new Dictionary<string, string> {
                ["SyntaxPlayer"]
                = "<color=red>Invalid syntax</color>, use:\n\n<color=green>/hsgive <type> <SteamID/PlayerName> <amount></color>", ["SyntaxConsole"] = "Invalid syntax, use: hsgive <type> <SteamID/PlayerName> <amount>", ["ClearSyntaxPlayer"] = "<color=red>Invalid syntax</color>, use:\n\n<color=green>/hsclearcd</color> (clears all cooldowns)\n\nOr\n\n<color=green>/hsclearcd <SteamID/PlayerName></color>", ["ClearSyntaxConsole"] = "Invalid syntax, use: \"hsclearcd\" (clears all cooldowns) or \"hsclearcd <SteamID/PlayerName>\"", ["Receive"] = "You received <color=orange>{0}</color> x <color=orange>{1}</color>!", ["PlayerReceive"] = "Player {0} ({1}) received {2} x {3}!", ["Permission"] = "You do not have permission to use <color=orange>{0}</color>!", ["RaidBlocked"] = "You cannot use <color=orange>{0}</color> while raid blocked!", ["CombatBlocked"] = "You cannot use <color=orange>{0}</color> while combat blocked!", ["CooldownTime"] = "Cooldown active! You can call another in {0}!", ["TeamCooldownTime"] = "Team cooldown active! You can call another in {0}!", ["GlobalLimit"] = "Global limit of {0} active helicopters is reached, please try again later", ["PlayerLimit"] = "Player limit of {0} active helicopters is reached, please try again later", ["NotAdmin"] = "You do not have permission to use that command!", ["PlayerNotFound"] = "Can't find a player with the name or ID: {0}", ["InGameOnly"] = "Error: This command is only for use in game!", ["PlayerDead"] = "Player with name or ID {0} is dead, try again when they have respawned", ["InNamedMonument"] = "You cannot call <color=orange>{0}</color> in or near <color=red>{1}</color>, signal refunded, check inventory.", ["InSafeZone"] = "<color=orange>{0}</color> was thrown in a <color=green>Safe Zone</color> and was refunded, check inventory.", ["InvalidDrop"] = "Signal type \"{0}\" not recognised, please check and try again!", ["CannotLoot"] = "You cannot loot this because it is not yours!", ["CannotHack"] = "You cannot hack this because it is not yours!", ["CannotHarvest"] = "You cannot harvest this because it is not yours!", ["BuyCmdSyntax"] = "Buy Command Usage (prefix / for chat):\n\n{0}{1}", ["NoBuy"] = "Buy Command for <color=orange>{0}</color> Is Not Enabled!", ["BuyPermission"] = "You do not have permission to buy Heli Signal \"<color=orange>{0}</color>\".", ["PriceList"] = "Heli Signal Prices:\n\n{0}", ["HeliKilledTime"] = "<color=orange>{0}</color> killed by <color=green>{1}</color> in grid <color=green>{2}</color> (Time Taken: {3})", ["HeliCalled"] = "<color=green>{0}</color> just called in <color=orange>{1}</color> to their location in grid <color=green>{2}</color>", ["PointsGiven"] = "<color=green>{0} {1}</color> received for destroying <color=orange>{2}</color>!", ["XPGiven"] = "<color=green>{0} XP</color> received for destroying <color=orange>{1}</color>!", ["ScrapGiven"] = "<color=green>{0} Scrap</color> received for destroying <color=orange>{1}</color>!", ["CustomGiven"] = "<color=green>{0} {1}</color> received for destroying <color=orange>{2}</color>!", ["CannotDamage"] = "You <color=red>Cannot</color> damage this <color=orange>{0}</color>!", ["NoTurret"] = "You <color=red>Cannot</color> damage this <color=orange>{0}</color> with remote turrets!", ["TooFarAway"] = "You are <color=red>Too Far</color> away to engage this <color=orange>{0}</color> ({1} m)", ["CantAfford"] = "You <color=red>Cannot</color> afford this! Cost: <color=orange>{0}</color> Required: <color=orange>{1}</color>", ["FullInventory"] = "<color=green>{0}</color> <color=red>NOT</color> purchased - no inventory space.You have not been charged.", ["PlayerFull"] = "<color=green>{0}</color> <color=red>NOT</color> given to {1} - No inventory space.", ["HeliReportTitle"] = "There are currently <color=orange>{0}</color> active Helicopters", ["HeliReportItem"] = "<size=9><color=orange>{0}/{1}</color> - <color=green>{2}</color> (Owner: <color=orange>{3}</color> Grid: <color=orange>{4}</color> Health: <color=orange>{5}</color> Rotors: <color=orange>{6}/{7}</color> State: <color=orange>{8}</color>)\n</size>", ["HeliReportTitleCon"] = "There are currently {0} active Helicopters", ["HeliReportItemCon"] = "{0}/{1} - {2} (Owner: {3} Grid: {4} Health: {5} Rotors: {6}/{7} State: {8})\n", ["HeliReportList"] = "{0}", ["RetireHeli"] = "<color=orange>{0}</color> is retiring, you were warned! <color=red>{1}</color>/<color=red>{2}</color>", ["RetireWarn"] = "<color=red>Damage Blocked!</color> You may only attack from a base with TC auth.If you continue, the <color=orange>{0}</color> will retire.Warning Level: <color=red>{1}</color>/<color=red>{2}</color>", ["DmgReport"] = "<color=orange>Damage</color> / <color=orange>Rotors</color> / <color=orange>Turret Damage</color>\n\n{0}\n{1}", ["DmgReportOwner"] = "<size=11>Type: <color=orange>{0}</color> Owner: <color=green>{1}</color> Status: <color=red>{2}</color>\n</size>", ["DmgReportIndex"] = "<size=11>{0}.<color=green>{1}</color> -> {2} HP / <color=green>{3}%</color> / <color=red>{4}%</color>\n</size>", ["DiscordCall"] = "**{0}** just called a **{1}** to their location in grid **{2}**", ["DiscordKill"] = "**{0}** just took down a **{1}** in grid **{2}**", ["DiscordRetire"] = "**{0}** called by **{1}** just retired from grid **{2}**", ["RetiredHelis"] = "You have retired ALL your (your teams) called Patrol Helicopters", ["NoRetiredHelis"] = "You have no active Patrol Helicopters to retire.", ["CooldownsCleared"] = "All player cooldowns have been cleared!", ["PlayerCooldownCleared"] = "Cooldown cleared for player {0} ({1})", ["PlayerNoCooldown"] = "No active cooldown for player {0} ({1})", ["NoWaveProfiles"] = "There are no wave heli profiles set up for <color=orange>{0}</color>, please report to an Admin.", ["WaveProfileError"] = "There is an error in the plugin config <color=orange>{0}</color>, please report to an Admin.", ["FirstHeliCalled"] = "Stand by, {0} <color=red>{1}</color> on route to your location!", ["NextHeliDelayed"] = "<color=green>{0}</color> destroyed! Stand by, a <color=red>{1}</color> is enroute to your location, ETA {2} mins", ["NextHeliInbound"] = "Look sharp, a <color=red>{0}</color> is closing in on your location!", ["WaveFinished"] = "<color=green>{0}</color> destroyed! <color=red>Hostile forces have no more airbourne assets to send to your location.</color> Well Done!", ["HeliRetired"] = "<color=green>{0}</color> is low on fuel and is breaking off engagement to return to base.", ["WaveRetired"] = "<color=green>{0}</color> finished operations in the area and is retiring.Hostile airbourne forces are returning to base.", ["DiscordEmbedTitleCalled"] = "🚁  {0} Called  🚁", ["DiscordEmbedTitleKilled"] = "💀  {0} Destroyed  💀", ["DiscordEmbedTitleRetire"] = "🚫  {0} Retired  🚫", ["DiscordEmbedOwner"] = "Owner: {0} SteamID: {1}", ["DiscordEmbedFooter"] = "Heli Signals by ZEODE", ["DiscordCalledLocation"] = "Location 📍", ["DiscordCalledHealth"] = "Health ❤️", ["DiscordCalledDespawn"] = "Despawn Time 🕐", ["DiscordKilledLocation"] = "Location 💥", ["DiscordKilledTime"] = "Time Taken ⏱️", ["DiscordKilledKiller"] = "Killer ⚔️", ["DiscordKilledLeaderboard"] = "Damage Leaderboard 🏆", ["DiscordRetireLocation"] = "Location 📍", ["DiscordRetireTime"] = "Retired After ⏱️", ["DiscordRetirePlayer"] = "Retired By 🧑", ["DiscordRetireHealth"] = "Health ❤️", ["DiscordRetireReport"] = "Damage Report 📋", ["DiscordEmbedDamageReportIndex"] = "{0}.{1} Total: {2} HP Rotors: {3} % Turret: {4} %\n", ["DiscordRetireNoReport"] = "No Damage", ["DiscordEmbedTitleKilledWave"] = "💀  {0} Destroyed (Wave: {1} of {2})  💀", ["DiscordWaveTotal"] = "Total Waves #️⃣", ["DiscordWaveHealthInfo"] = " - **HP:** {0}  **Rotors:** {1} / {2}", ["DiscordRotorHealthInfo"] = "Rotor HP ❤️", ["DiscordWaveHelicopters"] = "Wave Helicopters 🌊", ["DiscordEmbedTitleWaveComplete"] = "🏁  {0} Complete  🏁", ["DiscordWaveCompleteStatus"] = "Wave Status 🌊", ["DiscordWaveCompleteHelicopters"] = "Wave Helicopters 🚁", ["DiscordWaveCompleteTotal"] = "Total Destroyed 💀", ["DiscordWaveCompleteAllDestroyed"] = "All Helicopters Destroyed", ["DiscordEmbedTitleWaveRetired"] = "🚫  {0} Retired  🚫", ["DiscordMultiOrWaveRetiredBy"] = "Retired by: [{0}]({1})", ["DiscordMultiOrWaveRetiredTimeout"] = "Ran Out Of Time", ["DiscordWaveRetiredHelicopters"] = "Wave Helicopters 🚁", ["DiscordEmbedTitleKilledMulti"] = "💀  {0} Destroyed (Multi: {1} of {2})  💀", ["DiscordEmbedTitleMultiRetired"] = "🚫  {0} Retired (Multi Event)  🚫", ["DiscordWaveNotSpawned"] = "Not Yet Spawned", ["DiscordWavePartiallyDestroyed"] = "Partially Destroyed", ["DiscordMultiCompleteStatus"] = "Multi Status 🚁+", ["DiscordMultiCompleteHelicopters"] = "Multi Helicopters 🚁",
            }
            , this);
        }
        private string Lang(string messageKey, string arg) {
            return string.Format(lang.GetMessage(messageKey, this, null), arg);
        }
        private string Lang(string messageKey, params object[] args) {
            return string.Format(lang.GetMessage(messageKey, this, null), args);
        }
        private string Lang(string messageKey, string playerId, params object[] args) {
            return string.Format(lang.GetMessage(messageKey, this, playerId), args);
        }
        private void Message(IPlayer player, string messageKey, params object[] args) {
            if (player == null || !player.IsConnected) return;
            var message = Lang(messageKey, player.Id, args);
            if (config.options.usePrefix && config.options.chatPrefix != string.Empty) {
                if (player.IsServer) {
                    Regex regex = new Regex(@"(\[.*?\])");
                    Match match = regex.Match(config.options.chatPrefix);
                    player.Reply($"{match}: {message}");
                }
                else {
                    player.Reply($"{config.options.chatPrefix}: {message}");
                }
            }
            else {
                player.Reply(message);
            }
        }
        private void Message(BasePlayer player, string messageKey, params object[] args) {
            if (player == null || player is NPCPlayer || !player.IPlayer.IsConnected) return;
            var message = Lang(messageKey, player.UserIDString, args);
            Player.Message(player, message, config.options.usePrefix ? config.options.chatPrefix : null, config.options.chatIcon);
        }
#endregion Language
#region Plugin Load/Unload
        private void Init() {
            Instance = this;
            permission.RegisterPermission(permAdmin, this);
            permission.RegisterPermission(permBuy, this);
            permission.RegisterPermission(permBypasscooldown, this);
            config.rewards.RegisterPermissions(permission, this);
            config.heli.RegisterPermissions(permission, this);
            AddCovalenceCommand(config.options.reportCommand, nameof(CmdReport));
            AddCovalenceCommand(config.purchasing.buyCommand, nameof(CmdBuySignal));
            AddCovalenceCommand(config.heli.retireCommand, nameof(CmdRetireHeli));
            AddCovalenceCommand("hsgive", nameof(CmdGiveSignal));
            AddCovalenceCommand("hsclearcd", nameof(CmdClearCooldown));
        }
        private void OnServerInitialized() {
            ApplyHarmonyPatches();
            CheckRewardPlugins();
            NextTick(() => {
                LoadProfileCache();
                if (!config.options.useStacking) {
                    Unsubscribe(nameof(CanStackItem));
                    Unsubscribe(nameof(CanCombineDroppedItem));
                }
                if (config.options.noVanillaHeli) PrintWarning($"INFO: Vanilla patrol Helicopter server event is disabled");
                if (!config.heli.allowMonuments) {
                    foreach (var monument in TerrainMeta.Path.Monuments) {
                        if (config.heli.blockedMonuments.Contains(monument.name)) Monuments.Add(monument);
                    }
                    if (Monuments.Count == 0) {
                        PrintWarning($"WARNING: No monument info found.Config options relating to 'Allow Players to Call Helis at Monuments' will not function.");
                    }
                }
                if (config.heli.allowFlee) {
                    RunSilentCommand("PatrolHelicopterAI.use_danger_zones", "True");
                    RunSilentCommand("PatrolHelicopterAI.flee_damage_percentage", $"{config.heli.fleePercent}");
                }
                else if (!config.heli.allowFlee) {
                    RunSilentCommand("PatrolHelicopterAI.use_danger_zones", "False");
                    RunSilentCommand("PatrolHelicopterAI.flee_damage_percentage", "0.35");
                }
                if (config.heli.canMonumentCrash) RunSilentCommand("PatrolHelicopterAI.monument_crash", "True");
                else if (!config.heli.canMonumentCrash) RunSilentCommand("PatrolHelicopterAI.monument_crash", "False");
            }
            );
        }
        private void Unload() {
            if (heliCompCache != null && heliCompCache.Count > 0) {
                foreach (var kvp in heliCompCache.ToList()) {
                    var component = kvp.Value;
                    if (component != null) {
                        component.enabled = false;
                        component.CancelInvoke();
                        component.StopAllCoroutines();
                    }
                }
                heliCompCache.Clear();
            }
            if (config.options.noVanillaHeli) PrintWarning($"INFO: Vanilla patrol Helicopter server event has been re-enabled");
            var heliDataCopy = HeliSignalData.ToDictionary(k => k.Key, v => v.Value);
            foreach (var netId in heliDataCopy.Keys) {
                try {
                    var heli = BaseNetworkable.serverEntities.Find(new NetworkableId(netId)) as PatrolHelicopter;
                    if (heli != null) {
                        HeliComponent heliComp = heli.GetComponent<HeliComponent>();
                        if (heliComp != null) {
                            heliComp.enabled = false;
                            UnityEngine.Object.Destroy(heliComp);
                        }
                        heli.Kill(BaseNetworkable.DestroyMode.None);
                    }
                }
                catch (Exception ex) {
                    PrintError($"Error cleaning up Heli: {ex.Message}");
                }
            }
            heliDataCopy.Clear();
            HeliSignalData.Clear();
            PlayerCooldowns.Clear();
            TierCooldowns.Clear();
            LockedCrates.Clear();
            HeliProfiles.Clear();
            WaveProfiles.Clear();
            Monuments.Clear();
            HeliProfileCache.Clear();
            SpawnedWaveProfiles.Clear();
            AutoRetiringWaveHelis.Clear();
            AutoRetiringMultiHelis.Clear();
            try {
                harmony?.UnpatchAll("HeliSignals");
                PrintWarning("INFO: Harmony patches removed and cache cleared successfully!");
            }
            catch (System.Exception ex) {
                PrintError($"ERROR during removing HeliSignals Harmony patches!");
            }
            Instance = null;
        }
#endregion Plugin Load/Unload
#region Misc Oxide Hooks
        private object OnEventTrigger(TriggeredEventPrefab eventPrefab) {
            if (eventPrefab.name.Contains("event_helicopter") && config.options.noVanillaHeli) {
                timer.Once(1f, () => {
                    if (eventPrefab != null) eventPrefab.Kill();
                }
                );
                return true;
            }
            return null;
        }
        private void OnItemAddedToContainer(ItemContainer container, Item item) {
            // Re-Adds supply signal names to items added via kits/loadouts by plugins
            // which don't specify a custom item display name.if (item == null || container == null)
            return;
            if (item.info?.itemid == supplySignalId) NextTick(() => CheckAndFixSignal(item));
        }
        private object OnExplosiveThrown(BasePlayer player, SupplySignal entity, ThrownWeapon item) {
            var signal = item?.GetItem();
            if (signal == null) return null;
            if (HeliProfileCache.ContainsKey(signal.skin)) {
                entity.EntityToCreate = null;
                entity.CancelInvoke(entity.Explode);
                entity.skinID = signal.skin;
                HeliSignalThrown(player, entity, signal);
            }
            return null;
        }
        private void OnExplosiveDropped(BasePlayer player, SupplySignal entity, ThrownWeapon item) => OnExplosiveThrown(player, entity, item);
        private object CanStackItem(Item item, Item targetItem) {
            if (item == null || targetItem == null) return null;
            if (HeliProfileCache.ContainsKey(item.skin)) {
                // Only act on Heli Signal supply drops
                if (item.info.itemid == targetItem.info.itemid && item.skin != targetItem.skin) return false;
            }
            return null;
        }
        private object CanCombineDroppedItem(DroppedItem droppedItem, DroppedItem targetItem) {
            if (droppedItem == null || targetItem == null) return null;
            if (HeliProfileCache.ContainsKey(droppedItem.item.skin)) {
                // Only act on Heli Signal supply drops
                if (droppedItem.item.info.itemid == targetItem.item.info.itemid && droppedItem.item.skin != targetItem.item.skin) return true;
            }
            return null;
        }
        private void OnEntitySpawned(BaseNetworkable entity) {
            if (entity == null) return;
            if (entity.ShortPrefabName.Equals("napalm")) {
                // Set the owner ID & name of fire/napalm from heli rockets to stop NRE which occurs in
                // OnEntityTakeDamage from left over napalm fire after heli is destroyed.NextTick(() =>
                {
                    FireBall napalm = entity as FireBall;
                    if (napalm == null || napalm.creatorEntity == null) return;
                    if (!HeliProfileCache.ContainsKey(napalm.skinID)) return;
                    napalm.OwnerID = napalm.creatorEntity.OwnerID;
                    napalm._name = napalm.creatorEntity._name;
                    napalm.skinID = napalm.creatorEntity.skinID;
                }
                );
            }
            else if (entity.ShortPrefabName.Equals("heli_crate")) {
                timer.Once(0.25f, () => {
                    LockedByEntCrate crate = entity as LockedByEntCrate;
                    if (crate == null) return;
                    // Fix for crates that fall on Unloadable train cars from being unlootable
                    // This issue also affects vanilla heli, so also fixes that.Hackable crates are not affected.CrateComp crateComp = crate.gameObject.AddComponent<CrateComp>();
                    crateComp.crate = crate;
                    bool isHeliSignalCrate = crate.skinID != 0;
                    bool isVanillaHeliCrate = crate.skinID == 0;
                    if (isHeliSignalCrate && (!config.heli.buoyantHeliCrates || !HeliProfileCache.ContainsKey(crate.skinID))) return;
                    if (isVanillaHeliCrate && !config.heli.buoyantVanillaCrates) return;
                    Buoyancy buoyancy = crate.gameObject.AddComponent<Buoyancy>();
                    buoyancy.buoyancyScale = 1;
                    buoyancy.rigidBody = crate.gameObject.GetComponent<Rigidbody>();
                    buoyancy.SavePointData(true);
                }
                );
            }
            else if (entity.ShortPrefabName.Equals("codelockedhackablecrate")) {
                timer.Once(0.25f, () => {
                    var crate = entity as HackableLockedCrate;
                    if (crate == null) return;
                    if (!config.heli.buoyantHackableCrates) return;
                    if (!HeliProfileCache.ContainsKey(crate.skinID) || !LockedCrates.ContainsKey(crate.net.ID.Value)) return;
                    Buoyancy buoyancy = crate.gameObject.AddComponent<Buoyancy>();
                    buoyancy.buoyancyScale = 1;
                    buoyancy.rigidBody = crate.gameObject.GetComponent<Rigidbody>();
                    buoyancy.SavePointData(true);
                }
                );
            }
        }
#endregion Misc Oxide Hooks
#region Damage/Kill Hooks
        private object OnEntityTakeDamage(BasePlayer player, HitInfo info) {
            if (player == null || info?.Initiator == null || info.damageTypes == null) return null;
            var Initiator = info.Initiator;
            if (Initiator is PatrolHelicopter) {
                PatrolHelicopter heli = Initiator as PatrolHelicopter;
                if (heli == null || heli.IsDestroyed) return null;
                if (heli.net?.ID == null) return null;
                if (!HeliSignalData.ContainsKey(heli.net.ID.Value)) return null;
                if (!HeliProfileCache.TryGetValue(heli.skinID, out string heliProfile) || string.IsNullOrEmpty(heliProfile)) return null;
                var heliOwnerId = heli.OwnerID;
                if (config.heli.heliConfig.TryGetValue(heliProfile, out HeliData? heliData) && heliData != null) {
                    if (heliData.BlockPlayerDamage && !IsOwnerOrFriend(heliOwnerId, player.userID)) {
                        info.damageTypes.Clear();
                        return true;
                    }
                    if (info.damageTypes.GetMajorityDamageType() == DamageType.Bullet) {
                        float hitProb = (float)random.Next(0, 100);
                        if (heliData.BulletAccuracy < hitProb) {
                            info.damageTypes.Clear();
                            return true;
                        }
                    }
                }
            }
            else if (!string.IsNullOrEmpty(Initiator.ShortPrefabName) && Initiator.ShortPrefabName.Equals("napalm")) {
                if (!HeliProfileCache.TryGetValue(Initiator.skinID, out string heliProfile) || string.IsNullOrEmpty(heliProfile)) return null;
                if (config.heli.heliConfig.TryGetValue(heliProfile, out HeliData? heliData) && heliData != null) {
                    var heliOwnerId = Initiator.OwnerID;
                    if (heliData.BlockPlayerDamage && !IsOwnerOrFriend(heliOwnerId, player.userID)) {
                        info.damageTypes.Clear();
                        return true;
                    }
                }
            }
            return null;
        }
        private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info) {
            if (info?.Initiator == null || entity == null || info.damageTypes == null) return null;
            var initiator = info.Initiator;
            var ownerId = entity.OwnerID;
            if (!ownerId.IsSteamId()) return null;
            if (initiator is PatrolHelicopter heli) {
                if (heli.net?.ID == null || !HeliSignalData.ContainsKey(heli.net.ID.Value)) return null;
                if (!HeliProfileCache.TryGetValue(heli.skinID, out string heliProfile) || string.IsNullOrEmpty(heliProfile)) return null;
                if (config?.heli?.heliConfig?.TryGetValue(heliProfile, out HeliData heliData) != true || heliData == null) return null;
                if (heliData.BlockProtectedList && config?.heli?.protectedPrefabs?.Contains(entity.name) == true) {
                    info.damageTypes.Clear();
                    return true;
                }
                if (heliData.BlockOwnerDamage && heliData.BlockOtherDamage) {
                    info.damageTypes.Clear();
                    return true;
                }
                bool isOwnerOrFriend = IsOwnerOrFriend(heli.OwnerID, ownerId);
                if (heliData.BlockOtherDamage && !isOwnerOrFriend && !IsDamageAuthorized(heli, entity)) {
                    info.damageTypes.Clear();
                    return true;
                }
                if (heliData.BlockOwnerDamage && isOwnerOrFriend) {
                    info.damageTypes.Clear();
                    return true;
                }
            }
            else if (initiator.ShortPrefabName?.Equals("napalm", StringComparison.Ordinal) == true) {
                if (!HeliProfileCache.TryGetValue(initiator.skinID, out string heliProfile) || string.IsNullOrEmpty(heliProfile)) return null;
                if (config?.heli?.heliConfig?.TryGetValue(heliProfile, out HeliData heliData) != true || heliData == null) return null;
                if (heliData.BlockProtectedList && config?.heli?.protectedPrefabs?.Contains(entity.name) == true) {
                    info.damageTypes.Clear();
                    return true;
                }
                bool isOwnerOrFriend = IsOwnerOrFriend(initiator.OwnerID, ownerId);
                if (heliData.BlockOtherDamage && !isOwnerOrFriend) {
                    info.damageTypes.Clear();
                    return true;
                }
                if (heliData.BlockOwnerDamage && isOwnerOrFriend) {
                    info.damageTypes.Clear();
                    return true;
                }
            }
            return null;
        }
        private object OnPatrolHelicopterTakeDamage(PatrolHelicopter heli, HitInfo info) {
            if (heli?.net?.ID == null || info?.Initiator == null || info.damageTypes == null || info.Initiator == heli) return null;
            var initiator = info.Initiator;
            var heliAI = heli.myAI;
            var heliId = heli.net.ID.Value;
            var ownerId = heli.OwnerID;
            bool isVanilla = false;
            string heliDisplayName = string.Empty;
            if (!HeliProfileCache.TryGetValue(heli.skinID, out string heliProfile)) heliProfile = null;
            if ((string.IsNullOrEmpty(heliProfile) || heliProfile.Contains("patrolhelicopter")) && heli.skinID == 0) {
                if (config?.announce?.reportVanilla != true) return null;
                isVanilla = true;
                heliDisplayName = config.announce.vanillaName ?? "Patrol Helicopter";
            }
            else if (string.IsNullOrEmpty(heliProfile) && heli.skinID != 0) return null;
            HeliComponent heliComp = heli.GetComponent<HeliComponent>();
            if (!isVanilla && heliComp == null) return null;
            if (heliComp?.isRetiring == true) {
                info.damageTypes.Clear();
                return null;
            }
            ulong attackerId = 0;
            BasePlayer attacker = null;
            AutoTurret turret = null;
            if (initiator is BasePlayer player) {
                attacker = player;
                attackerId = attacker.userID;
            }
            else if (initiator is AutoTurret autoTurret) {
                turret = autoTurret;
                var turretPlayerId = turret.ControllingViewerId?.SteamId;
                if (turretPlayerId == null || turretPlayerId == 0) return null;
                attacker = BasePlayer.FindByID(turretPlayerId.Value);
                if (attacker == null) return null;
                attackerId = attacker.userID;
                if (config?.heli?.allowTurretDamage != true) {
                    info.damageTypes.Clear();
                    var station = attacker.GetMounted() as ComputerStation;
                    if (station != null) {
                        timer?.Once(0.25f, () => Effect.server.Run(shockSound, station.transform.position));
                        timer?.Once(0.5f, () => Effect.server.Run(deniedSound, station.transform.position));
                        attacker.EnsureDismounted();
                        Message(attacker, "NoTurret", heliDisplayName);
                    }
                    return true;
                }
            }
            else {
                return null;
            }
            if (attacker == null) return null;
            if (isVanilla && heliAI != null && (heliAI.isRetiring || heliAI.isDead)) return null;
            if (!isVanilla) {
                if (heliComp == null) return null;
                heliDisplayName = heliComp.heliProfile;
                if (config.heli.heliConfig[heliProfile].OwnerDamage && !IsOwnerOrFriend(attackerId, ownerId)) {
                    info.damageTypes.Clear();
                    Message(attacker, "CannotDamage", heliDisplayName);
                    return true;
                }
                heliComp.MaybeMoveToAttacker(attacker);
            }
            if (!HeliSignalData.ContainsKey(heliId)) {
                HeliSignalData[heliId] = new HeliStats {
                    OwnerID = ownerId, OwnerName = isVanilla ? (config.announce.vanillaOwner ?? "Unknown") : (heliComp.owner.displayName ?? "Unknown"), Attackers = new Dictionary<ulong, AttackersStats>()
                }
                ;
            }
            if (!HeliSignalData[heliId].Attackers.ContainsKey(attackerId)) {
                HeliSignalData[heliId].Attackers[attackerId] = new AttackersStats {
                    Name = attacker.displayName ?? "Unknown Player"
                }
                ;
            }
            if (!isVanilla && heliComp != null) {
                if (heliComp.isDying) {
                    info.damageTypes.Clear();
                    return true;
                }
                var damageAmount = info.damageTypes.Total();
                if (initiator is AutoTurret && turret != null) {
                    HeliSignalData[heliId].LastTurretAttacker = turret;
                    HeliSignalData[heliId].TurretDamage += damageAmount;
                    HeliSignalData[heliId].Attackers[attackerId].TurretDamage += damageAmount;
                    if (config?.heli?.heliTargetTurret == true && heliAI != null && config.heli.turretCooldown > 0 && (Time.realtimeSinceStartup - heliComp.turretCooldown > config.heli.turretCooldown)) {
                        if (!heliComp.isStrafingTurret && HeliSignalData[heliId].TurretDamage > HeliSignalData[heliId].PlayerDamage) {
                            heliComp.turretCooldown = Time.realtimeSinceStartup;
                            heliComp.isStrafing = true;
                            heliComp.isStrafingTurret = true;
                            heliAI.ExitCurrentState();
                            heliAI.State_Strafe_Enter(attacker, false);
                        }
                    }
                }
                else {
                    HeliSignalData[heliId].PlayerDamage += damageAmount;
                }
                var maxDist = config?.heli?.maxHitDistance ?? 0;
                if (maxDist > 0) {
                    var dist = Vector3.Distance(heli.transform.position, attacker.transform.position);
                    if (dist > maxDist) {
                        info.damageTypes.Clear();
                        Message(attacker, "TooFarAway", heliDisplayName, maxDist);
                        return true;
                    }
                }
                if (config?.heli?.RetireWarning == true && attacker.IsBuildingBlocked() && IsOwnerOrFriend(attackerId, ownerId)) {
                    HeliSignalData[heliId].WarningLevel++;
                    var warningThreshold = config.heli.WarningThreshold;
                    if (!heliComp.isRetiring && HeliSignalData[heliId].WarningLevel >= warningThreshold) {
                        heliComp.isRetiring = true;
                        info.damageTypes.Clear();
                        RetireHeli(heliAI, heliId, 0f);
                        Message(attacker, "RetireHeli", heliDisplayName, HeliSignalData[heliId].WarningLevel, warningThreshold);
                        return true;
                    }
                    info.damageTypes.Clear();
                    if (!heliComp.isRetiring) Message(attacker, "RetireWarn", heliDisplayName, HeliSignalData[heliId].WarningLevel, warningThreshold);
                    return true;
                }
            }
            if (HeliSignalData[heliId].FirstHitTime == 0) HeliSignalData[heliId].FirstHitTime = Time.realtimeSinceStartup;
            HeliSignalData[heliId].LastAttacker = attacker;
            var totalDamage = info.damageTypes.Total();
            var actualDamage = totalDamage > heli._health ? heli._health : totalDamage;
            HeliSignalData[heliId].Attackers[attackerId].DamageDealt += actualDamage;
            HeliSignalData[heliId].Attackers[attackerId].TotalHits++;
            if (info.HitMaterial == 2306822461) {
                HeliSignalData[heliId].Attackers[attackerId].RotorHits++;
                if (heli.weakspots?.Length >= 2) {
                    PatrolHelicopter.weakspot weakspot = null;
                    if (info.HitBone == MAIN_ROTOR_COL || info.HitBone == ENGINE_COL) weakspot = heli.weakspots[0];
                    else if (info.HitBone == TAIL_COL) weakspot = heli.weakspots[1];
                }
            }
            return null;
        }
        private object OnPatrolHelicopterKill(PatrolHelicopter heli, HitInfo info) {
            HeliComponent heliComp = heli?.GetComponent<HeliComponent>();
            if (heliComp == null) return null;
            var heliProfile = heliComp.heliProfile;
            if (config.heli.heliConfig.ContainsKey(heliProfile)) {
                if (info.damageTypes.Total() >= heli.health && !heli.myAI.isDead) {
                    // Prevent heli blowing up in mid air before crash spiral under certain conditions
                    heli.health = config.heli.heliConfig[heliProfile].Health;
                    heli.myAI.forceTerrainPushback = false;
                    heli.myAI.CriticalDamage();
                    return true;
                }
            }
            return null;
        }
        private object OnEntityKill(PatrolHelicopter heli) {
            if (heli?.net?.ID == null || heli.myAI?.isRetiring == true || !HeliSignalData.ContainsKey(heli.net.ID.Value)) return null;
            bool isVanilla = false;
            var skinId = heli.skinID;
            var heliId = heli.net.ID.Value;
            var position = heli.transform.position;
            var ownerId = heli.OwnerID;
            string heliProfile = heli._name;
            if (HeliProfileCache.TryGetValue(skinId, out string cachedProfile)) heliProfile = cachedProfile;
            if ((heliProfile == null && skinId == 0) || (!string.IsNullOrEmpty(heliProfile) && heliProfile.Contains("patrolhelicopter") && skinId == 0)) isVanilla = true;
            else if (heliProfile == null && skinId != 0) return null;
            string heliDisplayName = null;
            if (isVanilla) {
                heliDisplayName = config.announce.vanillaName;
            }
            else if (!string.IsNullOrEmpty(heliProfile) && config.heli.heliConfig.ContainsKey(heliProfile)) {
                heliDisplayName = config.heli.heliConfig[heliProfile].HeliName;
            }
            if (string.IsNullOrEmpty(heliDisplayName)) heliDisplayName = "Patrol helicopter";
            var gridPos = PositionToGrid(position);
            if (string.IsNullOrEmpty(gridPos)) gridPos = "Unknown";
            if (!HeliSignalData.TryGetValue(heliId, out var _heliSignalData) || _heliSignalData?.LastAttacker == null) return null;
            BasePlayer lastAttacker = _heliSignalData.LastAttacker;
            if (config.announce.killChat) {
                if (!isVanilla || (isVanilla && config.announce.killVanilla)) {
                    var firstHitTime = _heliSignalData.FirstHitTime;
                    if (firstHitTime > 0) {
                        TimeSpan timeSpan = TimeSpan.FromSeconds(Time.realtimeSinceStartup - firstHitTime);
                        string time = timeSpan.ToString(@"hh\:mm\:ss");
                        var message = string.Format(lang.GetMessage("HeliKilledTime", this, lastAttacker.UserIDString), heliDisplayName, lastAttacker.displayName ?? "Unknown", gridPos, time);
                        AnnounceToChat(lastAttacker, message);
                    }
                }
            }
            var heliComp = heli.GetComponent<HeliComponent>();
            // Damage report
            if (config.announce.reportChat) {
                if (!isVanilla || (isVanilla && config.announce.reportVanilla)) {
                    var heliOwnerName = config.announce.vanillaOwner ?? "Unknown";
                    if (!isVanilla && heliComp.owner.displayName != null) heliOwnerName = heliComp.owner.displayName;
                    var _heliAttackers = _heliSignalData.Attackers;
                    if (_heliAttackers != null && _heliAttackers.Count > 0) {
                        string topReport = string.Empty;
                        string ownerReport = string.Format(lang.GetMessage("DmgReportOwner", this, lastAttacker.UserIDString), heliDisplayName, heliOwnerName, "Killed");
                        int count = 1;
                        int maxReported = config.announce.maxReported;
                        foreach (var key in _heliAttackers.Keys.OrderByDescending(x => _heliAttackers[x]?.DamageDealt ?? 0)) {
                            if (count >= maxReported) break;
                            var attackerStats = _heliAttackers[key];
                            if (attackerStats == null) continue;
                            string playerName = attackerStats.Name ?? "Unknown";
                            float damageDealt = attackerStats.DamageDealt;
                            int totalHits = attackerStats.TotalHits;
                            int rotorHits = attackerStats.RotorHits;
                            float turretDamage = attackerStats.TurretDamage;
                            double rotorAccuracy = totalHits > 0 ? ((double)rotorHits / (double)totalHits) * 100 : 0;
                            double damageRatio = damageDealt > 0 ? ((double)turretDamage / (double)damageDealt) * 100 : 0;
                            topReport += string.Format(lang.GetMessage("DmgReportIndex", this, lastAttacker.UserIDString), count, playerName, Math.Round(damageDealt, 2), Math.Round(rotorAccuracy, 2), Math.Round(damageRatio, 2));
                            count++;
                        }
                        var dmgReport = string.Format(lang.GetMessage("DmgReport", this, lastAttacker.UserIDString), ownerReport, topReport);
                        AnnounceToChat(lastAttacker, dmgReport);
                    }
                }
            }
            if (!isVanilla) {
                // Discord notification
                if (config.discord.sendHeliKill) {
                    var firstHitTime = _heliSignalData.FirstHitTime;
                    if (firstHitTime > 0) {
                        TimeSpan timeSpan = TimeSpan.FromSeconds(Time.realtimeSinceStartup - firstHitTime);
                        string time = timeSpan.ToString(@"hh\:mm\:ss");
                        // Check if this is part of a wave or multi
                        bool isWaveKill = false;
                        bool isMultiKill = false;
                        int wavePosition = 0;
                        int waveTotalCount = 0;
                        if (heliComp?.isWaveHeli == true && !string.IsNullOrEmpty(heliComp.waveProfile)) {
                            isWaveKill = true;
                            // Calculate wave position based on how many helis have been destroyed
                            foreach (var waveKey in WavesCalled.Keys) {
                                if (WavesCalled[waveKey]?.Contains(heliId) == true) {
                                    var waveHelis = WavesCalled[waveKey];
                                    if (config.heli.waveConfig.TryGetValue(heliComp.waveProfile, out var waveConfig) == true && waveConfig.WaveProfiles != null) {
                                        waveTotalCount = waveConfig.WaveProfiles.Count;
                                        wavePosition = waveTotalCount - (heliComp.waveProfileCache?.Count ?? 0) + 1;
                                    }
                                    break;
                                }
                            }
                        }
                        else if (_heliSignalData.IsMultiHeli) {
                            isMultiKill = true;
                            // Use the actual position from HeliSignalData
                            wavePosition = _heliSignalData.MultiPosition;
                            waveTotalCount = _heliSignalData.MultiTotalCount;
                        }
                        SendDiscordKillReport(heli, _heliSignalData, heliDisplayName, position, gridPos, lastAt
// ... [Code preview truncated due to length. Full source in attached file] ...

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

Другие ресурсы пользователя 15FPS

Сверху