C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
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.RegularExpressions;
using UnityEngine;
/* Changelog
1.1.10
- Fixed: Locked crates not giving correct loot in some cases when enabled in the config
1.1.9
- Fixed: Bradley Signals are now blocked in monuments
1.1.8
- 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.7
- Fixed: OnEntityDestroy NRE when one or more bradley 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 bradley with auto turret
- Fixed: AlphaLoot not being able to populate hackable crates
- Added: Custom chat icon config option
1.1.6
- Fixed: Language defaulting always to English (Fixed this time)
1.1.5
- Fixed: Player limit not resetting after player using despawn command
- Fixed: Occasional OnEntityTakeDamage NRE spam in console (hopefully)
- Added: Config option to destroy/remove entities placed in the landing zone after signal thrown
1.1.4
- 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 Bradley profile triggering as well as bot profile set in BradleyDrops config
- Added: BP chance & max BPs to loot table config
- Added: Config option to announce to player/team only or global chat
- Added: Check to allow players with admin perm to override crate and gibs protection
1.1.3
- Fixed: report command not using custom command if edited in the config
- Added: Clear cooldown admin command (bdclearcd) requires admin perm
- Added: Seperate cooldowns for each Bradley tier/profile
- Added: Config option to enable per tier cooldown or not
- Added: Player Bradley 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: NRE when bradley receives damage from fireball
- Fixed: NRE when players shoot bradley with remote Auto Turrets
- Added: Config option to allow/disallow damage to bradley by remote Auto Turret
- Added: Report command is now usable by players for their owned bradleys (admin perm allows all bradleys)
- Added: Config option to specify custom report command (default = bdreport)
- Updated: Re-write of some methods for improvements in damage handling and code efficiency
- Info: Code cleanup
1.0.32
- Added example items to loot tables
-
1.0.31
- Fixed: Hackable locked crate not despawning at time specified in config
- Fixed: Occasional NullReferenceException error on CanStackItem
- Added: Hackable locked crate loot table options
*/
namespace Oxide.Plugins
{
[Info("Bradley Drops", "ZEODE", "1.1.10")]
[Description("Call a Bradley APC to your location with custom supply signals.")]
public class BradleyDrops: RustPlugin
{
#region Plugin References
[PluginReference] Plugin Friends, Clans, FancyDrop, RewardPlugin, Vanish, NoEscape, BotReSpawn, DynamicPVP;
#endregion Plugin References
#region Constants
private static BradleyDrops Instance;
private static System.Random random = new System.Random();
private const string permAdmin = "bradleydrops.admin";
private const string permBuy = "bradleydrops.buy";
private const string permBypasscooldown = "bradleydrops.bypasscooldown";
private const string bradleyPrefab = "assets/prefabs/npc/m2bradley/bradleyapc.prefab";
private const string chutePrefab = "assets/prefabs/misc/parachute/parachute.prefab";
private const string planePrefab = "assets/prefabs/npc/cargo plane/cargo_plane.prefab";
private const string bradleyExplosion = "assets/prefabs/npc/m2bradley/effects/bradley_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 = 2905355269;
private const ulong medSkinID = 2905355312;
private const ulong hardSkinID = 2905355296;
private const ulong eliteSkinID = 2911864795;
// Default config item names
private const string easyDrop = "Bradley Drop (Easy)";
private const string medDrop = "Bradley Drop (Medium)";
private const string hardDrop = "Bradley Drop (Hard)";
private const string eliteDrop = "Bradley Drop (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>/bdgive <type> <SteamID/PlayerName> <amount></color>",
["SyntaxConsole"] = "Invalid syntax, use: bdgive <type> <SteamID/PlayerName> <amount>",
["ClearSyntaxPlayer"] = "<color=red>Invalid syntax</color>, use:\n\n<color=green>/bdclearcd</color> (clears all cooldowns)\n\nOr\n\n<color=green>/bdclearcd <SteamID/PlayerName></color>",
["ClearSyntaxConsole"] = "Invalid syntax, use: \"bdclearcd\" (clears all cooldowns) or \"bdclearcd <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>!",
["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 Bradley APCs is reached, please try again later",
["PlayerLimit"] = "Player limit of {0} active Bradley APCs 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",
["NotOnGround"] = "<color=orange>{0}</color> must be thrown onto the floor, signal refunded, check inventory.",
["InMonument"] = "You cannot call <color=orange>{0}</color> in a <color=red>monument</color>, signal refunded, check inventory.",
["UnderWater"] = "<color=orange>{0}</color> was thrown too near <color=blue>water</color> and was refunded, check inventory.",
["IntoWater"] = "<color=orange>{0}</color> went into deep <color=blue>water</color> and was destroyed.",
["InSafeZone"] = "<color=orange>{0}</color> was thrown in a <color=green>Safe Zone</color> and was refunded, check inventory.",
["IntoSafeZone"] = "<color=orange>{0}</color> moved into a <color=green>Safe Zone</color> and was destroyed.",
["BuildingPriv"] = "<color=orange>{0}</color> was thrown too close to a players base and was refunded, check inventory.",
["NearCollider"] = "<color=orange>{0}</color> was thrown too close to a object, please throw in more open ground.",
["LZCleared"] = "Entities placed near the landing zone of <color=orange>{0}</color> have been destroyed.",
["Inside"] = "<color=orange>{0}</color> was thrown inside and was refunded, check inventory.",
["InvalidDrop"] = "Drop 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!",
["CannotMine"] = "You cannot mine this because it is not yours!",
["BuyCmdSyntax"] = "Useage:\n\n/{0} {1}",
["NoBuy"] = "Buy Command for <color=orange>{0}</color> Is Not Enabled!",
["BuyPermission"] = "You do not have permission to buy Bradley Drop \"<color=orange>{0}</color>\".",
["PriceList"] = "Bradley Drop Prices:\n\n{0}",
["BradleyKilledTime"] = "<color=orange>{0}</color> killed by <color=green>{1}</color> in grid <color=green>{2}</color> (Time Taken: {3})",
["BradleyCalled"] = "<color=green>{0}</color> just called in a <color=orange>{1}</color> to their location in grid <color=green>{2}</color>",
["RewardGiven"] = "<color=green>{0} {1}</color> points 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> given! No inventory space!",
["NotLanded"] = "You <color=red>Cannot</color> damage <color=orange>{0}</color> until it's landed!",
["ApcReportTitle"] = "There are currently <color=orange>{0}</color> active dropped Bradley APCs",
["ApcReportItem"] = "<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>)\n</size>",
["ApcReportTitleCon"] = "There are currently {0} active dropped Bradley APCs",
["ApcReportItemCon"] = "{0}/{1} - {2} (Owner: {3} Grid: {4} Health: {5})\n",
["ApcReportList"] = "{0}",
["DespawnApc"] = "<color=orange>{0}</color> is despawning, you were warned! <color=red>{1}</color>/<color=red>{2}</color>",
["DespawnWarn"] = "<color=red>Damage Blocked!</color> You may only attack from a base with TC auth.If you continue, the <color=orange>{0}</color> will despawn.Warning Level: <color=red>{1}</color>/<color=red>{2}</color>",
["DamageReport"] = "<color=orange>Damage Report</color>\n\n{0}",
["DamageReportIndex"] = "<size=11>{0}.<color=green>{1}</color> -> {2} HP\n</size>",
["DiscordCall"] = "**{0}** just called a **{1}** to their location in grid **{2}**",
["DiscordKill"] = "**{0}** just destroyed a **{1}** in grid **{2}**",
["DiscordDespawn"] = "**{0}** called by **{1}** just despawned at grid **{2}**",
["DespawnedBradleys"] = "You have retired ALL your (your teams) called Bradley APCs",
["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.noVanillaApc)
{
ConVar.Bradley.enabled = false;
Puts($"INFO: Vanilla Bradley APC server event at Launch Site is disabled");
}
}
private void Init()
{
permission.RegisterPermission(permAdmin, this);
permission.RegisterPermission(permBuy, this);
permission.RegisterPermission(permBypasscooldown, this);
config.rewards.RegisterPermissions(permission, this);
config.bradley.RegisterPermissions(permission, this);
foreach (var key in config.bradley.apcConfig.Keys)
{
var permSuffix = config.bradley.apcConfig[key].GiveItemCommand.ToLower();
var perm = $"{Name.ToLower()}.{permSuffix}";
permission.RegisterPermission(perm, this);
DropTypes.Add(config.bradley.apcConfig[key].GiveItemCommand);
BradleySkins.Add(config.bradley.apcConfig[key].SignalSkinID);
}
AddCovalenceCommand(config.options.reportCommand, nameof(CmdReport));
AddCovalenceCommand(config.purchasing.buyCommand, nameof(CmdBuyDrop));
AddCovalenceCommand(config.bradley.despawnCommand, nameof(CmdDespawnApc));
AddCovalenceCommand("bdgive", nameof(CmdGiveDrop));
AddCovalenceCommand("bdclearcd", nameof(CmdClearCooldown));
}
private void Unload()
{
foreach (var netId in BradleyDropData.Keys)
{
var apc = BaseNetworkable.serverEntities.Find(new NetworkableId(netId)) as BradleyAPC;
if (apc != null)
{
var bradleyComp = apc.gameObject.GetComponent<BradleyDrop>();
if (bradleyComp != null)
UnityEngine.Object.DestroyImmediate(bradleyComp);
apc.Kill();
}
}
foreach (var ent in CargoPlaneList)
{
if (ent != null)
{
var planeComp = ent.gameObject.GetComponent<BradleyDropPlane>();
if (planeComp != null)
UnityEngine.Object.DestroyImmediate(planeComp);
ent.Kill();
}
}
if (config.options.noVanillaApc)
{
ConVar.Bradley.enabled = true;
Puts($"Vanilla Bradley APC event at Launch Site has been re-enabled");
}
BradleyDropData.Clear();
CargoPlaneList.Clear();
PlayerCooldowns.Clear();
TierCooldowns.Clear();
DropTypes.Clear();
BradleySkins.Clear();
RewardPlugin = null;
Instance = 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 (BradleySkins.Contains(item.skin)) NextTick(()=> FixSignalInfo(item));
}
private void OnExplosiveThrown(BasePlayer player, SupplySignal entity, ThrownWeapon item)
{
var signal = item.GetItem();
if (signal == null) return;
if (BradleySkins.Contains(signal.skin))
{
entity.EntityToCreate = null;
entity.CancelInvoke(entity.Explode);
entity.skinID = signal.skin;
NextTick(()=> BradleySignalThrown(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("maincannonshell"))
{
NextTick(()=>
{
if (BradleyDropData.Count == 0)
return;
var apcList = Facepunch.Pool.GetList<BradleyAPC>();
var shellEnt = entity as BaseEntity;
Vis.Entities(shellEnt.transform.position, 10f, apcList);
foreach (var ent in apcList)
{
if (ent.IsDestroyed)
continue;
BradleyAPC bradley = ent as BradleyAPC;
var apcProfile = bradley._name;
if (apcProfile == null)
continue;
if (!config.bradley.apcConfig.ContainsKey(apcProfile))
continue;
shellEnt.creatorEntity = bradley;
shellEnt.OwnerID = bradley.OwnerID;
SetDamageScale((shellEnt as TimedExplosive), config.bradley.apcConfig[apcProfile].ShellDamageScale);
break;
}
Facepunch.Pool.FreeList(ref apcList);
});
}
else if (entity.ShortPrefabName.Equals("oilfireballsmall"))
{
// Set the owner ID & name of fire on Bradley crates to stop NRE from
// OnEntityTakeDamage since LockedEnt crate fires arent assigned this.NextTick(()=>
{
BaseEntity parentEnt = entity.GetParentEntity();
if (parentEnt != null)
{
BaseEntity baseEnt = entity as BaseEntity;
baseEnt.OwnerID = parentEnt.OwnerID;
baseEnt._name = parentEnt._name;
}
});
}
}
private object OnEntityTakeDamage(BradleyAPC bradley, HitInfo info)
{
var Initiator = info?.Initiator;
if (Initiator == null || Initiator == bradley)
return null;
var apcProfile = bradley._name;
if (apcProfile == null)
return null;
if (!config.bradley.apcConfig.ContainsKey(apcProfile))
return null;
// If bradley touches fireball, allow normal damage and stop NRE
if (Initiator.ShortPrefabName.Equals("fireball_small"))
return null;
BradleyDrop bradComp = bradley.GetComponent<BradleyDrop>();
if (bradComp == null)
return null;
if (bradComp.isDespawning)
{
info.damageTypes.ScaleAll(0);
return true;
}
var apcName = config.bradley.apcConfig[apcProfile].APCName;
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.bradley.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", apcName);
return true;
}
}
if (!bradComp.hasLanded && config.bradley.apcConfig[apcProfile].ChuteProtected)
{
info.damageTypes.ScaleAll(0);
Message(attacker, "NotLanded", apcName);
return true;
}
if (config.bradley.apcConfig[apcProfile].OwnerDamage && !IsOwnerOrFriend(attacker.userID, bradley.OwnerID))
{
info.damageTypes.ScaleAll(0);
Message(attacker, "CannotDamage", apcName);
return true;
}
var dist = Vector3.Distance(bradley.transform.position, attacker.transform.position);
var maxDist = config.bradley.maxHitDistance;
if (maxDist > 0 && dist > maxDist)
{
info.damageTypes.ScaleAll(0);
Message(attacker, "TooFarAway", apcName, maxDist);
return true;
}
var bradleyId = bradley.net.ID.Value;
if (config.bradley.DespawnWarning && attacker.IsBuildingBlocked() && IsOwnerOrFriend(attacker.userID, bradley.OwnerID))
{
BradleyDropData[bradleyId].WarningLevel++;
if (!bradComp.isDespawning && BradleyDropData[bradleyId].WarningLevel >= config.bradley.WarningThreshold)
{
bradComp.isDespawning = true;
info.damageTypes.ScaleAll(0);
DespawnAPC(bradley);
Message(attacker, "DespawnApc", apcName, BradleyDropData[bradleyId].WarningLevel, config.bradley.WarningThreshold);
return true;
}
info.damageTypes.ScaleAll(0);
Message(attacker, "DespawnWarn", apcName, BradleyDropData[bradleyId].WarningLevel, config.bradley.WarningThreshold);
return true;
}
if (!BradleyDropData.ContainsKey(bradleyId))
BradleyDropData.Add(bradleyId, new ApcStats());
if (!BradleyDropData[bradleyId].Attackers.ContainsKey(attacker.userID))
{
BradleyDropData[bradleyId].Attackers.Add(attacker.userID, new AttackersStats());
BradleyDropData[bradleyId].Attackers[attacker.userID].Name = attacker.displayName;
}
BradleyDropData[bradleyId].Attackers[attacker.userID].DamageDealt += info.damageTypes.Total();
BradleyDropData[bradleyId].LastAttacker = attacker;
return null;
}
private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info)
{
var Initiator = info?.Initiator;
if (Initiator == null)
return null;
if (Initiator is BradleyAPC)
{
BradleyAPC bradley = Initiator as BradleyAPC;
if (bradley == null)
return null;
if (!BradleyDropData.ContainsKey(bradley.net.ID.Value))
return null;
string apcProfile = bradley._name;
if (apcProfile == null)
return null;
if (config.bradley.apcConfig.ContainsKey(apcProfile))
{
if (entity is BasePlayer)
{
BasePlayer player = entity as BasePlayer;
if (player == null)
return null;
if (config.bradley.apcConfig[apcProfile].BlockPlayerDamage && !IsOwnerOrFriend(bradley.OwnerID, player.userID))
{
info.damageTypes.ScaleAll(0);
return true;
}
else if (info.damageTypes.GetMajorityDamageType() == DamageType.Bullet)
{
info.damageTypes.ScaleAll(config.bradley.apcConfig[apcProfile].GunDamage);
float hitProb = (float)random.Next(1, 100);
if (config.bradley.apcConfig[apcProfile].GunAccuracy < hitProb)
{
info.damageTypes.ScaleAll(0);
return true;
}
}
}
else if (entity.OwnerID.IsSteamId())
{
if (config.bradley.apcConfig[apcProfile].BlockOtherDamage && !IsOwnerOrFriend(bradley.OwnerID, entity.OwnerID))
{
info.damageTypes.ScaleAll(0);
return true;
}
if (config.bradley.apcConfig[apcProfile].BlockOwnerDamage && IsOwnerOrFriend(bradley.OwnerID, entity.OwnerID))
{
info.damageTypes.ScaleAll(0);
return true;
}
}
}
}
else if (Initiator.ShortPrefabName.Equals("oilfireballsmall"))
{
string apcProfile = Initiator?._name;
if (apcProfile == null)
return null;
if (!config.bradley.apcConfig.ContainsKey(apcProfile))
return null;
var apcOwner = Initiator.OwnerID;
if (apcOwner == null)
return null;
if (entity is BasePlayer)
{
BasePlayer player = entity as BasePlayer;
if (player == null)
return null;
if (config.bradley.apcConfig[apcProfile].BlockPlayerDamage && !IsOwnerOrFriend(apcOwner, player.userID))
{
info.damageTypes.ScaleAll(0);
return true;
}
}
else if (entity.OwnerID.IsSteamId())
{
if (config.bradley.apcConfig[apcProfile].BlockOtherDamage && !IsOwnerOrFriend(apcOwner, entity.OwnerID))
{
info.damageTypes.ScaleAll(0);
return true;
}
else if (config.bradley.apcConfig[apcProfile].BlockOwnerDamage && IsOwnerOrFriend(apcOwner, entity.OwnerID))
{
info.damageTypes.ScaleAll(0);
return true;
}
}
}
return null;
}
private object OnEntityDestroy(BradleyAPC bradley)
{
if (bradley == null || !BradleyDropData.ContainsKey(bradley.net.ID.Value))
return null;
var skinId = bradley.skinID;
var position = bradley.transform.position;
var ownerId = bradley.OwnerID;
var apcProfile = bradley._name;
var bradleyId = bradley.net.ID.Value;
var gridPos = PositionToGrid(position);
if (gridPos == null)
gridPos = "Unknown";
BasePlayer lastAttacker = BradleyDropData[bradleyId].LastAttacker;
if (lastAttacker == null)
return null;
var totalReward = config.bradley.apcConfig[apcProfile].RewardPoints;
var totalScrap = config.bradley.apcConfig[apcProfile].ScrapReward;
float damageThreshold = config.bradley.apcConfig[apcProfile].DamageThreshold;
if (config.rewards.enableRewards && totalReward > 0)
{
if (plugins.Find(config.rewards.rewardPlugin))
{
if (config.rewards.shareRewards)
{
foreach (var playerId in BradleyDropData[bradleyId].Attackers.Keys)
{
float damageDealt = BradleyDropData[bradleyId].Attackers[playerId].DamageDealt;
if (damageDealt >= damageThreshold)
{
var amount = totalReward / BradleyDropData[bradleyId].Attackers.Count(key => key.Value.DamageDealt >= damageThreshold);
NextTick(()=> GiveReward(playerId, amount, apcProfile));
}
}
}
else
{
NextTick(()=> GiveReward(ownerId, totalReward, apcProfile));
}
}
}
if (config.rewards.enableScrap && totalScrap > 0)
{
if (config.rewards.shareScrap)
{
foreach (var playerId in BradleyDropData[bradleyId].Attackers.Keys)
{
float damageDealt = BradleyDropData[bradleyId].Attackers[playerId].DamageDealt;
if (damageDealt >= damageThreshold)
{
var amount = totalScrap / BradleyDropData[bradleyId].Attackers.Count(key => key.Value.DamageDealt >= damageThreshold);
NextTick(()=> GiveScrap(playerId, amount, apcProfile));
}
}
}
else
{
NextTick(()=> GiveScrap(ownerId, totalScrap, apcProfile));
}
}
if (config.announce.killChat)
{
TimeSpan timeSpan = TimeSpan.FromSeconds(UnityEngine.Time.realtimeSinceStartup - BradleyDropData[bradleyId].StartTime);
string time = timeSpan.ToString(@"hh\:mm\:ss");
var apcDisplayName = config.bradley.apcConfig[apcProfile].APCName;
var message = string.Format(lang.GetMessage("BradleyKilledTime", this), apcDisplayName, lastAttacker.displayName, gridPos, time);
AnnounceToChat(lastAttacker, message);
}
if (config.announce.reportChat)
{
string topReport = string.Empty;
int count = 1;
foreach (var key in BradleyDropData[bradleyId].Attackers.Keys.OrderByDescending(x => BradleyDropData[bradleyId].Attackers[x].DamageDealt))
{
if (count >= config.announce.maxReported)
break;
string playerName = BradleyDropData[bradleyId].Attackers[key].Name;
float damageDealt = BradleyDropData[bradleyId].Attackers[key].DamageDealt;
topReport += string.Format(lang.GetMessage("DamageReportIndex", this, lastAttacker.UserIDString), count, playerName, Math.Round(damageDealt, 2));
count++;
}
var dmgReport = string.Format(lang.GetMessage("DamageReport", this, lastAttacker.UserIDString), topReport);
AnnounceToChat(lastAttacker, dmgReport);
}
if (config.discord.sendApcKill)
{
string discordMsg = string.Format(lang.GetMessage("DiscordKill", this, lastAttacker.UserIDString), lastAttacker.displayName, apcProfile, gridPos);
SendToDiscord(discordMsg);
}
if (config.bradley.apcConfig[apcProfile].LockedCratesToSpawn > 0)
SpawnLockedCrates(ownerId, skinId, apcProfile, 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 = apcProfile;
ProcessBradleyEnt(ent);
}
}
});
if (BotReSpawn && BotReSpawn.IsLoaded)
{
var botReSpawnProfile = config.bradley.apcConfig[apcProfile].BotReSpawnProfile;
if (botReSpawnProfile != string.Empty)
BotReSpawn?.Call("AddGroupSpawn", position, botReSpawnProfile, $"{botReSpawnProfile}Group", 1);
}
timer.Once(5f, () =>
{
if (BradleyDropData.ContainsKey(bradleyId))
BradleyDropData.Remove(bradleyId);
});
return null;
}
private object CanLootEntity(BasePlayer player, LockedByEntCrate entity)
{
if (entity.OwnerID == 0)
return null;
string apcProfile = entity?._name;
if (apcProfile == null)
return null;
if (config.bradley.apcConfig.ContainsKey(apcProfile))
{
if (config.bradley.apcConfig[apcProfile].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 apcProfile = entity._name;
if (apcProfile == null)
return null;
if (config.bradley.apcConfig.ContainsKey(apcProfile))
{
if (config.bradley.apcConfig[apcProfile].ProtectGibs)
{
if (permission.UserHasPermission(attacker.UserIDString, permAdmin))
return null;
if (!IsOwnerOrFriend(attacker.userID, entity.OwnerID))
{
info.damageTypes.ScaleAll(0);
Message(attacker, "CannotMine");
return true;
}
}
}
}
return null;
}
private void OnLootSpawn(LootContainer lootContainer)
{
timer.Once(2f, () =>
{
var apcProfile = lootContainer?._name;
if (apcProfile == null)
return;
if (config.bradley.apcConfig.ContainsKey(apcProfile))
{
if (lootContainer.ShortPrefabName.Contains("bradley_crate") && config.bradley.apcConfig[apcProfile].Loot.UseCustomLoot)
SpawnBradleyCrateLoot(lootContainer, apcProfile);
else if (lootContainer.ShortPrefabName.Contains("bradley_crate") && config.bradley.apcConfig[apcProfile].ExtraLoot.UseExtraLoot)
AddExtraBradleyCrateLoot(lootContainer, apcProfile);
else if (lootContainer.ShortPrefabName.Contains("codelockedhackablecrate") && config.bradley.apcConfig[apcProfile].LockedCrateLoot.UseLockedCrateLoot)
SpawnLockedCrateLoot(lootContainer, apcProfile);
}
});
}
private string PositionToGrid(Vector3 position) => PhoneController.PositionToGridCoord(position);
private void OnSupplyDropDropped(SupplyDrop supplyDrop, CargoPlane plane)
{
if (plane != null)
{
var planeComp = plane.gameObject.GetComponent<BradleyDropPlane>();
if (planeComp != null)
{
var position = supplyDrop.transform.position;
supplyDrop.Kill();
SpawnBradleyDrop(position, plane.transform.eulerAngles, planeComp.player, planeComp.skinId, planeComp.apcProfile);
GameManager.DestroyImmediate(planeComp);
if (CargoPlaneList.Contains(plane))
CargoPlaneList.Remove(plane);
}
}
}
private object CanBradleyApcTarget(BradleyAPC bradley, BaseEntity target)
{
var apcProfile = bradley._name;
if (apcProfile == null)
return null;
if (!config.bradley.apcConfig.ContainsKey(apcProfile))
return null;
if (target.IsNpc)
return false;
BasePlayer player = target as BasePlayer;
if (player != null)
{
if (Vanish && (bool)Vanish?.Call("IsInvisible", player))
return false;
if (!config.bradley.apcConfig[apcProfile].TargetSleepers && player.IsSleeping())
return false;
if (!config.bradley.apcConfig[apcProfile].AttackOwner && IsOwnerOrFriend(player.userID, bradley.OwnerID))
return false;
if (!config.bradley.apcConfig[apcProfile].TargetOtherPlayers && !IsOwnerOrFriend(player.userID, bradley.OwnerID))
return false;
var mainTurretPosition = bradley.mainTurret.transform.position;
if (!(player.IsVisible(mainTurretPosition, bradley.CenterPoint()) || player.IsVisible(mainTurretPosition, player.eyes.position) || player.IsVisible(mainTurretPosition, player.transform.position)))
return false;
}
return null;
}
private object OnBradleyApcInitialize(BradleyAPC bradley)
{
var apcProfile = bradley._name;
if (apcProfile == null)
return null;
if (config.bradley.apcConfig.ContainsKey(apcProfile))
{
bradley._maxHealth = config.bradley.apcConfig[apcProfile].Health;
bradley.health = bradley._maxHealth;
bradley.viewDistance = 0f; // Set to 0 so APC doesn't target while parachuting
bradley.searchRange = 0f; // Set to 0 so APC doesn't target while parachuting
}
return null;
}
#endregion Oxide Hooks
#region Core
private void BradleySignalThrown(BasePlayer player, SupplySignal entity, Item signal)
{
if (signal.name == null || !config.bradley.apcConfig.ContainsKey(signal.name))
FixSignalInfo(signal);
var skinId = signal.skin;
var apcProfile = signal.name;
var permSuffix = config.bradley.apcConfig[apcProfile].GiveItemCommand.ToLower();
var perm = $"{Name.ToLower()}.{permSuffix}";
if (!permission.UserHasPermission(player.UserIDString, perm))
{
Message(player, "Permission", apcProfile);
entity.Kill();
GiveBradleyDrop(player, skinId, apcProfile, 1, "refund");
return;
}
if (config.bradley.useNoEscape && NoEscape)
{
if ((bool)NoEscape.CallHook("IsRaidBlocked", player))
{
Message(player, "RaidBlocked", apcProfile);
entity.Kill();
GiveBradleyDrop(player, skinId, apcProfile, 1, "refund");
return;
}
else if ((bool)NoEscape.CallHook("IsCombatBlocked", player))
{
Message(player, "CombatBlocked", apcProfile);
entity.Kill();
GiveBradleyDrop(player, skinId, apcProfile, 1, "refund");
return;
}
}
if (config.bradley.playerCooldown > 0f && !permission.UserHasPermission(player.UserIDString, permBypasscooldown))
{
float cooldown;
ulong userId = player.userID;
if (!config.bradley.tierCooldowns)
{
if (PlayerCooldowns.TryGetValue(player.userID, out cooldown))
{
TimeSpan time = TimeSpan.FromSeconds(cooldown - UnityEngine.Time.time);
Message(player, "CooldownTime", time.ToString(@"hh\:mm\:ss"));
entity.Kill();
GiveBradleyDrop(player, skinId, apcProfile, 1, "refund");
return;
}
else if (config.bradley.teamCooldown)
{
foreach (var playerId in PlayerCooldowns.Keys)
{
if (PlayerCooldowns.TryGetValue(playerId, out cooldown) && IsOwnerOrFriend(player.userID, playerId))
{
TimeSpan time = TimeSpan.FromSeconds(cooldown - UnityEngine.Time.time);
Message(player, "TeamCooldownTime", time.ToString(@"hh\:mm\:ss"));
entity.Kill();
GiveBradleyDrop(player, skinId, apcProfile, 1, "refund");
return;
}
}
}
}
else
{
if (TierCooldowns.ContainsKey(userId))
{
if (TierCooldowns[userId].TryGetValue(apcProfile, out cooldown))
{
TimeSpan time = TimeSpan.FromSeconds(cooldown - UnityEngine.Time.time);
Message(player, "CooldownTime", time.ToString(@"hh\:mm\:ss"));
entity.Kill();
GiveBradleyDrop(player, skinId, apcProfile, 1, "refund");
return;
}
else if (config.bradley.teamCooldown)
{
foreach (var playerId in TierCooldowns.Keys)
{
if (TierCooldowns[userId].TryGetValue(apcProfile, out cooldown) && IsOwnerOrFriend(userId, playerId))
{
TimeSpan time = TimeSpan.FromSeconds(cooldown - UnityEngine.Time.time);
Message(player, "TeamCooldownTime", time.ToString(@"hh\:mm\:ss"));
entity.Kill();
GiveBradleyDrop(player, skinId, apcProfile, 1, "refund");
return;
}
}
}
}
}
cooldown = config.bradley.playerCooldown;
foreach (KeyValuePair<string, float> keyPair in config.bradley.vipCooldowns)
{
if (permission.UserHasPermission(player.UserIDString, keyPair.Key))
{
cooldown = keyPair.Value;
break;
}
}
if (!config.bradley.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(apcProfile))
TierCooldowns[userId].Add(apcProfile, UnityEngine.Time.time + cooldown);
else
TierCooldowns[userId][apcProfile]
// ... [Code preview truncated due to length. Full source in attached file] ...