C#:
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Rust;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using System.Text.RegularExpressions;
using System.Reflection;
/* Changelog
1.1.11
- Fixed: For 7th Sep 2023 Rust update
- Fixed: Hackable crates not giving correct loot in some cases when enabled in the config
- Fixed: Players being able to kill retiring heli and only getting vanilla loot
1.1.10
- Fixed: Hook conflict on CanCombineDroppedItem returning false instead of true
- Fixed: Added missing config option to allow compatibility with Dynamic PVP plugin
- Updated: Changed some code to be more performant
1.1.9
- Fixed: OnEntityKill NRE when one or more heli kill team members are offline
- Fixed: Bug where rewards were not received in cases where team members offline
- Fixed: Bug where no rewards earned if players take heli with auto turret
- Fixed: AlphaLoot not being able to populate hackable crates
- Added: Custom chat icon config option
1.1.8
- Fixed: Final fix for language defaulting to English (hopefully)
1.1.7
- Fixed: Language defaulting always to English, users who use their own lang files should now be able to
- Fixed: Better compatibility with NextGenPVE/TruePVE (Any plugins using CanEntityTakeDamage hook)
- Fixed: Compatibility with Dynamic PVP now works properly
- Fixed: BotReSpawn vanilla heli profile triggering as well as bot profile set in HeliSignals config
- Added: BP chance & max BPs to loot table config
- Added: Config option to announce to player/team only or global chat
- Added: Config option to force return heli to original called position instead of calling player
- Added: Check to allow players with admin perm to override crate and gibs protection
1.1.6:
- Fixed: Unlootable crate bug, re-introduced in error by last update
1.1.5
- Fixed: "KeyNotFoundException" error when clear cooldown command used
- Fixed: Bug which prevented players looting own boxes after crate lock expired
- Fixed: Bug which prevented harvesting gibs when unlock gibs timer expired
- Fixed: Retire command retiring other peoples helis in some cases in error
1.1.4
- Fixed: Typo in OnHelicopterRetire hook causing "ArgumentNullException" error
1.1.3
- Fixed: report command not using custom command if edited in the config
- Fixed: Vanilla helicopter event not despawning correctly
- Added: Clear cooldown admin command (hsclearcd) requires admin perm
- Added: Separate cooldowns for each Heli tier/profile
- Added: Config option to enable per tier cooldown or not
- Added: Player heli limit config option
1.1.2
- Fixed: Bug where players were prevented from looting crates from other Bradley or Heli in certain cases
1.1.1
- Fixed: Bullet accuracy option not working
- Fixed: Return to called position height not using the config value correctly
- Fixed: Bug where in certain cases players would get kill rewards after heli retired
- Fixed: NRE when players shoot helis with remote Auto Turrets
- Added: Config option to allow/disallow damage to helis by remote Auto Turret
- Added: Report command is now usable by players for their owned helis (admin perm allows all helis)
- Added: Config option to specify custom report command (default = hsreport)
- Updated: Re-write of some methods for improvements in damage handling and code efficiency
- Info: Code cleanup
1.0.26
- Added example items to loot tables
1.0.25
- Fixed: Hackable locked crate not despawning at time specified in config
- Fixed: Custom loot not populating after 1.0.22 update (sorry)
- Fixed: Occasional NullReferenceException error on CanStackItem
- Added: Hackable locked crate loot table options
*/
namespace Oxide.Plugins {
[Info("Heli Signals", "ZEODE", "1.1.11")]
[Description("Call Patrol Helicopters to your location with custom supply signals.")]
public class HeliSignals : RustPlugin {
#region Plugin References
[PluginReference]
Plugin Friends, Clans, FancyDrop, RewardPlugin, Vanish, NoEscape, BotReSpawn, DynamicPVP;
#endregion Plugin References
#region Constants
private static HeliSignals Instance;
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 heliExplosion = "assets/prefabs/npc/patrol helicopter/effects/heli_explosion.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 defaultWebhookUrl = "https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks";
private const int supplySignalId = 1397052267;
private const int scrapId = -932201673;
// Default config item skins
private const ulong easySkinID = 2920175997;
private const ulong medSkinID = 2920176079;
private const ulong hardSkinID = 2920176050;
private const ulong eliteSkinID = 2920176024;
// Default config item names
private const string easyHeli = "Heli Signal (Easy)";
private const string medHeli = "Heli Signal (Medium)";
private const string hardHeli = "Heli Signal (Hard)";
private const string eliteHeli = "Heli Signal (Elite)";
#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", ["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!", ["BuySyntaxCmd"] = "Usage:\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 a <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>!", ["ScrapGiven"] = "<color=green>{0}</color> Scrap received for destroying <color=orange>{1}</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>", ["DamageReport"] = "<color=orange>Damage Report</color> / <color=orange>Rotor Accuracy</color>\n\n{0}", ["DamageReportIndex"] = "<size=11>{0}. <color=green>{1}</color> -> {2} HP / {3}%\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", ["CooldownsCleared"] = "All player cooldowns have been cleared!", ["PlayerCooldownCleared"] = "Cooldown cleared for player {0} ({1})", ["PlayerNoCooldown"] = "No active cooldown for player {0} ({1})"
}
, this);
}
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 Oxide Hooks
private void OnServerInitialized() {
Instance = this;
CheckRewardPlugin();
if (!config.options.useStacking) {
Unsubscribe(nameof(CanStackItem));
Unsubscribe(nameof(CanCombineDroppedItem));
}
if (config.options.noVanillaHeli) Puts($"INFO: Vanilla patrol Helicopter server event is disabled");
}
private void Init() {
permission.RegisterPermission(permAdmin, this);
permission.RegisterPermission(permBuy, this);
permission.RegisterPermission(permBypasscooldown, this);
config.rewards.RegisterPermissions(permission, this);
config.heli.RegisterPermissions(permission, this);
foreach (var key in config.heli.heliConfig.Keys) {
var permSuffix = config.heli.heliConfig[key].GiveItemCommand.ToLower();
var perm = $"{Name.ToLower()}.{permSuffix}";
permission.RegisterPermission(perm, this);
HeliTypes.Add(config.heli.heliConfig[key].GiveItemCommand);
HeliSkins.Add(config.heli.heliConfig[key].SignalSkinID);
}
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 Unload() {
foreach (var netId in HeliSignalData.Keys) {
var heli = BaseNetworkable.serverEntities.Find(new NetworkableId(netId)) as PatrolHelicopter;
if (heli != null) {
var heliComp = heli.gameObject.GetComponent<HeliComponent>();
if (heliComp != null) UnityEngine.Object.DestroyImmediate(heliComp);
var strafeHeli = heli.gameObject.GetComponent<StrafeHeliAI>();
if (strafeHeli != null) UnityEngine.Object.DestroyImmediate(strafeHeli);
heli.Kill();
}
}
if (config.options.noVanillaHeli) Puts($"INFO: Vanilla patrol Helicopter server event has been re-enabled");
HeliSignalData.Clear();
PlayerCooldowns.Clear();
TierCooldowns.Clear();
LockedCrates.Clear();
HeliTypes.Clear();
HeliSkins.Clear();
RewardPlugin = null;
Instance = null;
}
private object OnEventTrigger(TriggeredEventPrefab eventPrefab) {
if (eventPrefab.name.Contains("event_helicopter") && config.options.noVanillaHeli) return true;
return null;
}
// Re-Adds supply signal names to items added via kits/loadouts by plugins
// which don't specify a custom item display name.
private void OnItemAddedToContainer(ItemContainer container, Item item) {
if (item.info.itemid == supplySignalId) if (HeliSkins.Contains(item.skin)) NextTick(()=> FixSignalInfo(item));
}
private void OnExplosiveThrown(BasePlayer player, SupplySignal entity, ThrownWeapon item) {
var signal = item.GetItem();
if (signal == null) return;
if (HeliSkins.Contains(signal.skin)) {
entity.EntityToCreate = null;
entity.CancelInvoke(entity.Explode);
entity.skinID = signal.skin;
NextTick(()=> HeliSignalThrown(player, entity, signal));
}
}
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 (item.info.itemid == targetItem.info.itemid && item.skin != targetItem.skin) return false;
return null;
}
private object CanCombineDroppedItem(DroppedItem item, DroppedItem targetItem) {
if (item == null || targetItem == null) return null;
if (item.item.info.itemid == targetItem.item.info.itemid && item.item.skin != targetItem.item.skin) return true;
return null;
}
private void OnEntitySpawned(BaseNetworkable entity) {
if (entity.ShortPrefabName.Contains("rocket_heli")) {
NextTick(()=> {
if (HeliSignalData.Count == 0 || entity == null) return;
var heliList = Facepunch.Pool.GetList<PatrolHelicopter>();
var rocketEnt = entity.GetComponent<BaseEntity>();
Vis.Entities(rocketEnt.transform.position, 15f, heliList);
foreach (var ent in heliList) {
if (ent.IsDestroyed) continue;
PatrolHelicopter heli = ent as PatrolHelicopter;
var heliProfile = heli?._name;
if (heliProfile == null) continue;
if (!config.heli.heliConfig.ContainsKey(heliProfile)) continue;
rocketEnt.creatorEntity = heli;
rocketEnt.OwnerID = heli.OwnerID;
SetDamageScale((rocketEnt as TimedExplosive), config.heli.heliConfig[heliProfile].RocketDamageScale);
break;
}
Facepunch.Pool.FreeList(ref heliList);
}
);
}
else if (entity.ShortPrefabName.Equals("napalm")) {
// Set the owner ID & name of fire/napalm from heli rockets to stop NRE which occurs in
// OnEntityTakeDamage from leftover napalm fire after heli is destroyed.
NextTick(()=> {
BaseEntity baseEnt = entity as BaseEntity;
if (baseEnt.creatorEntity != null) {
baseEnt.OwnerID = baseEnt.creatorEntity.OwnerID;
baseEnt._name = baseEnt.creatorEntity._name;
}
}
);
}
}
private object OnEntityTakeDamage(PatrolHelicopter heli, HitInfo info) {
var Initiator = info?.Initiator;
if (Initiator == null || Initiator == heli) return null;
var heliProfile = heli._name;
if (heliProfile == null) return null;
if (!config.heli.heliConfig.ContainsKey(heliProfile)) return null;
HeliComponent heliComp = heli.GetComponent<HeliComponent>();
if (heliComp == null) return null;
if (heliComp.isRetiring) {
info.damageTypes.ScaleAll(0);
return true;
}
var heliDisplayName = config.heli.heliConfig[heliProfile].HeliName;
BasePlayer attacker = Initiator as BasePlayer;
if (Initiator is BasePlayer) {
attacker = Initiator as BasePlayer;
}
else if (Initiator is AutoTurret) {
AutoTurret turret = Initiator as AutoTurret;
ulong playerId = turret.ControllingViewerId.GetValueOrDefault().SteamId;
attacker = BasePlayer.FindByID(playerId);
if (attacker == null) return null;
if (!config.heli.allowTurretDamage) {
info.damageTypes.ScaleAll(0);
ComputerStation 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;
}
}
var heliAI = heli.myAI;
if (heliAI == null) return null;
if (config.heli.heliConfig[heliProfile].OwnerDamage && !IsOwnerOrFriend(attacker.userID, heli.OwnerID)) {
info.damageTypes.ScaleAll(0);
Message(attacker, "CannotDamage", heliDisplayName);
return true;
}
var dist = Vector3.Distance(heli.transform.position, attacker.transform.position);
var maxDist = config.heli.maxHitDistance;
if (maxDist > 0 && dist > maxDist) {
info.damageTypes.ScaleAll(0);
Message(attacker, "TooFarAway", heliDisplayName, maxDist);
return true;
}
var heliId = heli.net.ID.Value;
if (config.heli.RetireWarning && attacker.IsBuildingBlocked() && IsOwnerOrFriend(attacker.userID, heli.OwnerID)) {
HeliSignalData[heliId].WarningLevel++;
if (!heliComp.isRetiring && HeliSignalData[heliId].WarningLevel >= config.heli.WarningThreshold) {
heliComp.isRetiring = true;
info.damageTypes.ScaleAll(0);
RetireHeli(heliAI, heliId);
Message(attacker, "RetireHeli", heliDisplayName, HeliSignalData[heliId].WarningLevel, config.heli.WarningThreshold);
return true;
}
info.damageTypes.ScaleAll(0);
Message(attacker, "RetireWarn", heliDisplayName, HeliSignalData[heliId].WarningLevel, config.heli.WarningThreshold);
return true;
}
if (heliAI._currentState != PatrolHelicopterAI.aiState.DEATH) {
if (!HeliSignalData.ContainsKey(heliId)) HeliSignalData.Add(heliId, new HeliStats());
if (HeliSignalData[heliId].FirstHitTime == 0) HeliSignalData[heliId].FirstHitTime = UnityEngine.Time.realtimeSinceStartup;
if (!HeliSignalData[heliId].Attackers.ContainsKey(attacker.userID)) {
HeliSignalData[heliId].Attackers.Add(attacker.userID, new AttackersStats());
HeliSignalData[heliId].Attackers[attacker.userID].Name = attacker.displayName;
}
HeliSignalData[heliId].Attackers[attacker.userID].DamageDealt += info.damageTypes.Total();
HeliSignalData[heliId].Attackers[attacker.userID].TotalHits++;
if (info.HitMaterial == 2306822461) HeliSignalData[heliId].Attackers[attacker.userID].RotorHits++;
if (heliAI._currentState != PatrolHelicopterAI.aiState.DEATH) HeliSignalData[heliId].LastAttacker = attacker;
}
else if (heliAI._currentState == PatrolHelicopterAI.aiState.DEATH && !heliComp.isDying) {
// Give rewards as soon as heli enters death state, otherwise if
// players die before it crashes they might not get rewards
heliComp.isDying = true;
var ownerId = heli.OwnerID;
var totalReward = config.heli.heliConfig[heliProfile].RewardPoints;
var totalScrap = config.heli.heliConfig[heliProfile].ScrapReward;
float damageThreshold = config.heli.heliConfig[heliProfile].DamageThreshold;
if (config.rewards.enableRewards && totalReward > 0) {
if (plugins.Find(config.rewards.rewardPlugin)) {
if (config.rewards.shareRewards) {
foreach (var playerId in HeliSignalData[heliId].Attackers.Keys) {
float damageDealt = HeliSignalData[heliId].Attackers[playerId].DamageDealt;
if (damageDealt >= damageThreshold) {
var amount = totalReward / HeliSignalData[heliId].Attackers.Count(key => key.Value.DamageDealt >= damageThreshold);
NextTick(()=> GiveReward(playerId, amount, heliProfile));
}
}
}
else {
NextTick(()=> GiveReward(ownerId, totalReward, heliProfile));
}
}
}
if (config.rewards.enableScrap && totalScrap > 0) {
if (config.rewards.shareScrap) {
foreach (var playerId in HeliSignalData[heliId].Attackers.Keys) {
float damageDealt = HeliSignalData[heliId].Attackers[playerId].DamageDealt;
if (damageDealt >= damageThreshold) {
var amount = totalScrap / HeliSignalData[heliId].Attackers.Count(key => key.Value.DamageDealt >= damageThreshold);
if (playerId.IsSteamId()) {
NextTick(()=> GiveScrap(playerId, amount, heliProfile));
}
}
}
}
else {
NextTick(()=> GiveScrap(ownerId, totalScrap, heliProfile));
}
}
}
return null;
}
private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info) {
var Initiator = info?.Initiator;
if (Initiator == null) return null;
if (Initiator is PatrolHelicopter) {
PatrolHelicopter heli = Initiator as PatrolHelicopter;
if (heli == null) return null;
if (!HeliSignalData.ContainsKey(heli.net.ID.Value)) return null;
string heliProfile = heli?._name;
if (heliProfile == null) return null;
if (config.heli.heliConfig.ContainsKey(heliProfile)) {
if (entity is BasePlayer) {
BasePlayer player = entity as BasePlayer;
if (player == null) return null;
if (config.heli.heliConfig[heliProfile].BlockPlayerDamage && !IsOwnerOrFriend(heli.OwnerID, player.userID)) {
info.damageTypes.ScaleAll(0);
return true;
}
else if (info.damageTypes.GetMajorityDamageType() == DamageType.Bullet) {
float hitProb = (float)random.Next(1, 100);
if (config.heli.heliConfig[heliProfile].BulletAccuracy < hitProb) {
info.damageTypes.ScaleAll(0);
return true;
}
}
}
else if (entity.OwnerID.IsSteamId()) {
if (config.heli.heliConfig[heliProfile].BlockOtherDamage && !IsOwnerOrFriend(heli.OwnerID, entity.OwnerID)) {
info.damageTypes.ScaleAll(0);
return true;
}
else if (config.heli.heliConfig[heliProfile].BlockOwnerDamage && IsOwnerOrFriend(heli.OwnerID, entity.OwnerID)) {
info.damageTypes.ScaleAll(0);
return true;
}
}
}
}
else if (Initiator.ShortPrefabName.Equals("napalm")) {
string heliProfile = Initiator?._name;
if (heliProfile == null) return null;
if (!config.heli.heliConfig.ContainsKey(heliProfile)) return null;
var heliOwner = Initiator.OwnerID;
if (heliOwner == null) return null;
if (entity is BasePlayer) {
BasePlayer player = entity as BasePlayer;
if (player == null) return null;
if (config.heli.heliConfig[heliProfile].BlockPlayerDamage && !IsOwnerOrFriend(heliOwner, player.userID)) {
info.damageTypes.ScaleAll(0);
return true;
}
}
else if (entity.OwnerID.IsSteamId()) {
if (config.heli.heliConfig[heliProfile].BlockOtherDamage && !IsOwnerOrFriend(heliOwner, entity.OwnerID)) {
info.damageTypes.ScaleAll(0);
return true;
}
else if (config.heli.heliConfig[heliProfile].BlockOwnerDamage && IsOwnerOrFriend(heliOwner, entity.OwnerID)) {
info.damageTypes.ScaleAll(0);
return true;
}
}
}
return null;
}
private object OnEntityKill(PatrolHelicopter heli) {
if (heli == null || !HeliSignalData.ContainsKey(heli.net.ID.Value) || heli.myAI.isRetiring) return null;
var skinId = heli.skinID;
var position = heli.transform.position;
var ownerId = heli.OwnerID;
var heliProfile = heli._name;
var heliId = heli.net.ID.Value;
var gridPos = PositionToGrid(position);
if (gridPos == null) gridPos = "Unknown";
BasePlayer lastAttacker = HeliSignalData[heliId].LastAttacker;
if (lastAttacker == null) return null;
if (config.announce.killChat) {
TimeSpan timeSpan = TimeSpan.FromSeconds(UnityEngine.Time.realtimeSinceStartup - HeliSignalData[heliId].FirstHitTime);
string time = timeSpan.ToString(@"hh\:mm\:ss");
var heliDisplayName = config.heli.heliConfig[heliProfile].HeliName;
var message = string.Format(lang.GetMessage("HeliKilledTime", this, lastAttacker.UserIDString), heliDisplayName, lastAttacker.displayName, gridPos, time);
AnnounceToChat(lastAttacker, message);
}
if (config.announce.reportChat) {
string topReport = string.Empty;
int count = 1;
foreach (var key in HeliSignalData[heliId].Attackers.Keys.OrderByDescending(x => HeliSignalData[heliId].Attackers[x].DamageDealt)) {
if (count >= config.announce.maxReported) break;
string playerName = HeliSignalData[heliId].Attackers[key].Name;
float damageDealt = HeliSignalData[heliId].Attackers[key].DamageDealt;
int totalHits = HeliSignalData[heliId].Attackers[key].TotalHits;
int rotorHits = HeliSignalData[heliId].Attackers[key].RotorHits;
double rotorAccuracy = ((double)rotorHits / (double)totalHits) * 100;
topReport += string.Format(lang.GetMessage("DamageReportIndex", this, lastAttacker.UserIDString), count, playerName, Math.Round(damageDealt, 2), Math.Round(rotorAccuracy, 2));
count++;
}
var dmgReport = string.Format(lang.GetMessage("DamageReport", this, lastAttacker.UserIDString), topReport);
AnnounceToChat(lastAttacker, dmgReport);
}
if (config.discord.sendHeliKill) SendToDiscord(string.Format(lang.GetMessage("DiscordKill", this, lastAttacker.UserIDString), lastAttacker.displayName, heliProfile, gridPos));
if (config.heli.heliConfig[heliProfile].LockedCratesToSpawn > 0) SpawnLockedCrates(ownerId, skinId, heliProfile, position);
NextTick(() => {
List<BaseEntity> ents = new List<BaseEntity>();
Vis.Entities(position, 15f, ents);
foreach (var ent in ents) {
if ((ent is HelicopterDebris) || (ent is LockedByEntCrate) || (ent is FireBall)) {
ent.OwnerID = ownerId;
ent.skinID = skinId;
ent._name = heliProfile;
ProcessHeliEnt(ent);
}
}
}
);
if (BotReSpawn && BotReSpawn.IsLoaded) {
var botReSpawnProfile = config.heli.heliConfig[heliProfile].BotReSpawnProfile;
if (botReSpawnProfile != string.Empty) BotReSpawn?.Call("AddGroupSpawn", position, botReSpawnProfile, $"{botReSpawnProfile}Group", 0);
}
timer.Once(5f, () => {
if (HeliSignalData.ContainsKey(heliId)) HeliSignalData.Remove(heliId);
}
);
return null;
}
private object CanHelicopterStrafeTarget(PatrolHelicopterAI heliAI, BasePlayer player) {
PatrolHelicopter heli = heliAI?.helicopterBase;
string heliProfile = heli._name;
if (heliProfile == null) return null;
if (!config.heli.heliConfig.ContainsKey(heliProfile)) return null;
if (!config.heli.heliConfig[heliProfile].TargetOtherPlayers && !IsOwnerOrFriend(player.userID, heli.OwnerID)) {
heliAI.ExitCurrentState();
for (int i = 0; i < heliAI._targetList.Count; i++) {
if (heliAI._targetList[i].ply == player) {
heliAI._targetList.Remove(heliAI._targetList[i]);
continue;
}
}
return false;
}
return null;
}
private object CanHelicopterUseNapalm(PatrolHelicopterAI heliAI) => CanHelicopterStrafe(heliAI);
private object CanHelicopterStrafe(PatrolHelicopterAI heliAI) {
PatrolHelicopter heli = heliAI?.helicopterBase;
if (heli == null) return null;
HeliComponent heliComp = heli.GetComponent<HeliComponent>();
if (heliComp != null) {
if (heliComp.isRetiring) return false;
}
return null;
}
private object OnHelicopterTarget(HelicopterTurret turret, BasePlayer player) {
PatrolHelicopter heli = turret._heliAI?.helicopterBase;
PatrolHelicopterAI heliAI = turret?._heliAI;
if (heli == null || heliAI == null) return null;
string heliProfile = heli._name;
if (heliProfile == null) return null;
if (config.heli.heliConfig.ContainsKey(heliProfile)) return CanCustomHeliTarget(heliAI, player, heliProfile);
return null;
}
private object CanHelicopterTarget(PatrolHelicopterAI heliAI, BasePlayer player) {
PatrolHelicopter heli = heliAI?.helicopterBase;
string heliProfile = heli._name;
if (heliProfile == null) return null;
if (config.heli.heliConfig.ContainsKey(heliProfile)) return CanCustomHeliTarget(heliAI, player, heliProfile);
return null;
}
private object CanCustomHeliTarget(PatrolHelicopterAI heliAI, BasePlayer player, string heliProfile) {
if (heliAI == null || player == null || heliProfile == null) return null;
// Don't target players in Vanish
if (Vanish && (bool)Vanish?.Call("IsInvisible", player)) return null;
PatrolHelicopter heli = heliAI?.helicopterBase;
if (config.heli.heliConfig.ContainsKey(heliProfile)) {
if (!config.heli.heliConfig[heliProfile].TargetOtherPlayers && !IsOwnerOrFriend(player.userID, heli.OwnerID)) {
HeliComponent heliComp = heli.GetComponent<HeliComponent>();
if (heliComp != null) {
heliAI.interestZoneOrigin = heliComp.calledPosition;
heliAI.hasInterestZone = true;
}
return false;
}
}
if (config.heli.maxHitDistance > 0 && (Vector3.Distance(heli.transform.position, player.transform.position) > config.heli.maxHitDistance)) return false;
return null;
}
private object OnHelicopterRetire(PatrolHelicopterAI heliAI) {
PatrolHelicopter heli = heliAI?.helicopterBase;
string heliProfile = heli?._name;
if (heliProfile == null) return null;
if (config.heli.heliConfig.ContainsKey(heliProfile) && heliAI.IsAlive()) {
HeliComponent heliComp = heli.GetComponent<HeliComponent>();
if (heliComp == null) return null;
heliComp.isRetiring = true;
heliAI.leftGun.maxTargetRange = 0f;
heliAI.rightGun.maxTargetRange = 0f;
if (config.discord.sendHeliRetire) {
var gridPos = PositionToGrid(heli.transform.position);
if (gridPos == null) gridPos = "Unknown";
BasePlayer player = FindPlayer(heli.OwnerID.ToString())?.Object as BasePlayer;
if (player == null) return null;
string discordMsg = string.Format(lang.GetMessage("DiscordRetire", this, player.UserIDString), heli._name, player.displayName, gridPos);
SendToDiscord(discordMsg);
}
timer.Once(2f, ()=> {
if (HeliSignalData.ContainsKey(heli.net.ID.Value)) HeliSignalData.Remove(heli.net.ID.Value);
}
);
}
return null;
}
private object CanLootEntity(BasePlayer player, LockedByEntCrate entity) {
if (entity.OwnerID == 0) return null;
string heliProfile = entity?._name;
if (heliProfile == null) return null;
if (config.heli.heliConfig.ContainsKey(heliProfile)) {
if (config.heli.heliConfig[heliProfile].ProtectCrates) {
if (permission.UserHasPermission(player.UserIDString, permAdmin)) return null;
if (!IsOwnerOrFriend(player.userID, entity.OwnerID)) {
Message(player, "CannotLoot");
return false;
}
}
}
return null;
}
private object OnPlayerAttack(BasePlayer attacker, HitInfo info) {
BaseEntity entity = info?.HitEntity;
if (entity == null) return null;
if (entity.OwnerID == 0) return null;
if (entity is ServerGib) {
var heliProfile = entity._name;
if (heliProfile == null) return null;
if (config.heli.heliConfig.ContainsKey(heliProfile)) {
if (config.heli.heliConfig[heliProfile].ProtectGibs) {
if (permission.UserHasPermission(attacker.UserIDString, permAdmin)) return null;
if (!IsOwnerOrFriend(attacker.userID, entity.OwnerID)) {
info.damageTypes.ScaleAll(0);
Message(attacker, "CannotHarvest");
return true;
}
}
}
}
return null;
}
private void OnLootSpawn(LootContainer lootContainer) {
timer.Once(2f, () => {
var heliProfile = lootContainer?._name;
if (heliProfile == null) return;
if (config.heli.heliConfig.ContainsKey(heliProfile)) {
if (lootContainer.ShortPrefabName.Contains("heli_crate") && config.heli.heliConfig[heliProfile].Loot.UseCustomLoot) SpawnHeliCrateLoot(lootContainer, heliProfile);
else if (lootContainer.ShortPrefabName.Contains("heli_crate") && config.heli.heliConfig[heliProfile].ExtraLoot.UseExtraLoot) AddExtraHeliCrateLoot(lootContainer, heliProfile);
else if (lootContainer.ShortPrefabName.Contains("codelockedhackablecrate") && config.heli.heliConfig[heliProfile].LockedCrateLoot.UseLockedCrateLoot) SpawnLockedCrateLoot(lootContainer, heliProfile);
}
}
);
}
private string PositionToGrid(Vector3 position) => PhoneController.PositionToGridCoord(position);
#endregion Oxide Hooks
#region Core
private void HeliSignalThrown(BasePlayer player, SupplySignal entity, Item signal) {
if (signal.name == null || !config.heli.heliConfig.ContainsKey(signal.name)) FixSignalInfo(signal);
var skinId = signal.skin;
var heliProfile = signal.name;
var permSuffix = config.heli.heliConfig[heliProfile].GiveItemCommand.ToLower();
var perm = $"{Name.ToLower()}.{permSuffix}";
if (!permission.UserHasPermission(player.UserIDString, perm)) {
Message(player, "Permission", heliProfile);
entity.Kill();
GiveHeliSignal(player, skinId, heliProfile, 1, "refund");
return;
}
if (config.heli.UseNoEscape && NoEscape) {
if ((bool)NoEscape.CallHook("IsRaidBlocked", player)) {
Message(player, "RaidBlocked", heliProfile);
entity.Kill();
GiveHeliSignal(player, skinId, heliProfile, 1, "refund");
return;
}
else if ((bool)NoEscape.CallHook("IsCombatBlocked", player)) {
Message(player, "CombatBlocked", heliProfile);
entity.Kill();
GiveHeliSignal(player, skinId, heliProfile, 1, "refund");
return;
}
}
if (config.heli.playerCooldown > 0f && !permission.UserHasPermission(player.UserIDString, permBypasscooldown)) {
float cooldown;
ulong userId = player.userID;
if (!config.heli.tierCooldowns) {
if (PlayerCooldowns.TryGetValue(userId, out cooldown)) {
TimeSpan time = TimeSpan.FromSeconds(cooldown - UnityEngine.Time.time);
Message(player, "CooldownTime", time.ToString(@"hh\:mm\:ss"));
entity.Kill();
GiveHeliSignal(player, skinId, heliProfile, 1, "refund");
return;
}
else if (config.heli.teamCooldown) {
foreach (var playerId in PlayerCooldowns.Keys) {
if (PlayerCooldowns.TryGetValue(playerId, out cooldown) && IsOwnerOrFriend(userId, playerId)) {
TimeSpan time = TimeSpan.FromSeconds(cooldown - UnityEngine.Time.time);
Message(player, "TeamCooldownTime", time.ToString(@"hh\:mm\:ss"));
entity.Kill();
GiveHeliSignal(player, skinId, heliProfile, 1, "refund");
return;
}
}
}
}
else {
if (TierCooldowns.ContainsKey(userId)) {
if (TierCooldowns[userId].TryGetValue(heliProfile, out cooldown)) {
TimeSpan time = TimeSpan.FromSeconds(cooldown - UnityEngine.Time.time);
Message(player, "CooldownTime", time.ToString(@"hh\:mm\:ss"));
entity.Kill();
GiveHeliSignal(player, skinId, heliProfile, 1, "refund");
return;
}
else if (config.heli.teamCooldown) {
foreach (var playerId in TierCooldowns.Keys) {
if (TierCooldowns[userId].TryGetValue(heliProfile, out cooldown) && IsOwnerOrFriend(userId, playerId)) {
TimeSpan time = TimeSpan.FromSeconds(cooldown - UnityEngine.Time.time);
Message(player, "TeamCooldownTime", time.ToString(@"hh\:mm\:ss"));
entity.Kill();
GiveHeliSignal(player, skinId, heliProfile, 1, "refund");
return;
}
}
}
}
}
cooldown = config.heli.playerCooldown;
foreach (KeyValuePair<string, float> keyPair in config.heli.vipCooldowns) {
if (permission.UserHasPermission(player.UserIDString, keyPair.Key)) {
cooldown = keyPair.Value;
break;
}
}
if (!config.heli.tierCooldowns) {
if (!PlayerCooldowns.ContainsKey(userId)) PlayerCooldowns.Add(userId, UnityEngine.Time.time + cooldown);
else PlayerCooldowns[userId] = UnityEngine.Time.time + cooldown;
timer.Once(cooldown, () => {
if (PlayerCooldowns.ContainsKey(userId)) PlayerCooldowns.Remove(userId);
}
);
}
else {
if (!TierCooldowns.ContainsKey(userId)) TierCooldowns.Add(userId, new Dictionary<string, float>());
if (!TierCooldowns[userId].ContainsKey(heliProfile)) TierCooldowns[userId].Add(heliProfile, UnityEngine.Time.time + cooldown);
else TierCooldowns[userId][heliProfile] = UnityEngine.Time.time + cooldown;
timer.Once(cooldown, () => {
if (!TierCooldowns.ContainsKey(userId)) return;
if (TierCooldowns[userId].ContainsKey(heliProfile)) TierCooldowns[userId].Remove(heliProfile);
}
);
}
}
HeliSignalComponent signalComponent = entity.gameObject.AddComponent<HeliSignalComponent>();
if (signalComponent != null) {
signalComponent.signal = entity;
signalComponent.player = player;
signalComponent.skinId = skinId;
signalComponent.heliProfile = heliProfile;
}
}
private void ProcessHeliEnt (BaseEntity entity) {
if (entity != null) {
var heliProfile = entity._name;
if (heliProfile != null) {
if (entity is HelicopterDebris) {
var debris = entity?.GetComponent<HelicopterDebris>() ?? null;
if (debris != null) {
debris.InitializeHealth(config.heli.heliConfig[heliProfile].GibsHealth, config.heli.heliConfig[heliProfile].GibsHealth);
de
// ... [Code preview truncated due to length. Full source in attached file] ...