/* * Copyright (c) 2023 Bazz3l * * Bradley Guards cannot be copied, edited and/or (re)distributed without the express permission of Bazz3l. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ using System.Collections.Generic; using System.Collections; using System.Diagnostics; using System; using System.Linq; using System.Text; using Oxide.Plugins.BradleyGuardsExtensionMethods; using Oxide.Core.Plugins; using Oxide.Core; using Rust; using UnityEngine; using Newtonsoft.Json.Linq; using Newtonsoft.Json; using Facepunch; using Network; namespace Oxide.Plugins { [Info("Bradley Guards", "Bazz3l", "1.6.2")] [Description("Spawn reinforcements for bradley when destroyed at configured monuments.")] internal class BradleyGuards : RustPlugin { [PluginReference] private Plugin NpcSpawn, GUIAnnouncements; #region Fields private const string PERM_USE = "bradleyguards.use"; private const float INITIALIZE_DELAY = 10f; private StoredData _storedData; private ConfigData _configData; private Coroutine _setupRoutine; private enum CurrentState { Waiting, Started } private static BradleyGuards Instance; #endregion #region Local protected override void LoadDefaultMessages() { lang.RegisterMessages(new Dictionary { { MessageKeys.NoPermission, "Sorry you don't have permission to do that." }, { MessageKeys.Prefix, "Bradley Guards:\n" }, { MessageKeys.EventStart, "Armed reinforcements en route to {0} eliminate the guards to gain access to high-value loot." }, { MessageKeys.EventEnded, "Armed reinforcements have been eliminated at {0}." }, { MessageKeys.EventUpdated, "Event updated, please reload the plugin to take effect." }, { MessageKeys.EventNotFound, "Event not found, please make sure you have typed the correct name." }, { MessageKeys.DisplayNameEmpty, "Invalid event name provided." }, { MessageKeys.InvalidGuardAmount, "Invalid guard amount must be between {0} - {1}." }, { MessageKeys.InvalidBooleanValue, "Invalid boolean value provided, must be true or false." }, { MessageKeys.HelpEventEnable, "/{0} \"\" enable \n" }, { MessageKeys.HelpEventName, "/{0} \"\" display \"\"\n" }, { MessageKeys.HelpGuardAmount, "/{0} \"\" amount \n" }, { MessageKeys.HelpGuardLoadout, "/{0} \"\" loadout" }, }, this); } private class MessageKeys { public static readonly string Prefix = "Prefix"; public static readonly string NoPermission = "NoPermission"; public static readonly string EventStart = "EventStart"; public static readonly string EventEnded = "EventEnded"; public static readonly string EventNotFound = "EventNotFound"; public static readonly string EventUpdated = "EventUpdated"; public static readonly string DisplayNameEmpty = "InvalidDisplayName"; public static readonly string InvalidGuardAmount = "InvalidGuardAmount"; public static readonly string InvalidBooleanValue = "InvalidBooleanValue"; public static readonly string HelpEventEnable = "HelpEventEnable"; public static readonly string HelpEventName = "HelpEventName"; public static readonly string HelpGuardAmount = "HelpGuardAmount"; public static readonly string HelpGuardLoadout = "HelpGuardLoadout"; } private void MessagePlayer(BasePlayer player, string langKey, params object[] args) { if (player == null || !player.IsConnected) return; var message = lang.GetMessage(langKey, this); player.ChatMessage(args?.Length > 0 ? string.Format(message, args) : message); } private void MessagePlayers(string langKey, params object[] args) { var message = lang.GetMessage(langKey, this); message = args?.Length > 0 ? string.Format(message, args) : message; if (_configData.MessageSettings.EnableChat) ConsoleNetwork.BroadcastToAllClients("chat.add", 2, _configData.MessageSettings.ChatIcon, _configData.MessageSettings.EnableChatPrefix ? (lang.GetMessage(MessageKeys.Prefix, this) + message) : message); if (_configData.MessageSettings.EnableToast) ConsoleNetwork.BroadcastToAllClients("gametip.showtoast_translated", 2, null, message); if (_configData.MessageSettings.EnableGuiAnnouncements && GUIAnnouncements.IsReady()) GUIAnnouncements?.Call("CreateAnnouncement", message, _configData.MessageSettings.GuiAnnouncementsBgColor, _configData.MessageSettings.GuiAnnouncementsTextColor, null, 0.03f); } #endregion #region Config protected override void LoadDefaultConfig() { _configData = ConfigData.DefaultConfig(); PrintWarning("Loaded default config."); } protected override void LoadConfig() { base.LoadConfig(); try { _configData = Config.ReadObject(); if (_configData == null) throw new JsonException(); if (_configData.CommandName == null || _configData.MessageSettings == null) { PrintWarning("Updated config."); LoadDefaultConfig(); SaveConfig(); } } catch(Exception e) { PrintWarning(e.Message); LoadDefaultConfig(); } } protected override void SaveConfig() => Config.WriteObject(_configData, true); private class ConfigData { [JsonProperty("command name")] public string CommandName = "bguard"; [JsonProperty("enable auto unlock crates when guards are eliminated")] public bool EnableAutoUnlock; [JsonProperty("enable auto extinguish crates when guards are eliminated")] public bool EnableAutoExtinguish; [JsonProperty("bradley starting health")] public float BradleyHealth; [JsonProperty("crate spawn amount")] public int CrateSpawnAmount; [JsonProperty("message notification settings")] public MessageSettings MessageSettings; public static ConfigData DefaultConfig() { return new ConfigData { CommandName = "bguard", EnableAutoUnlock = true, EnableAutoExtinguish = true, BradleyHealth = 1000f, CrateSpawnAmount = 4, MessageSettings = new MessageSettings() { EnableToast = false, EnableChat = true, EnableChatPrefix = true, ChatIcon = 76561199542550973, EnableGuiAnnouncements = false, GuiAnnouncementsBgColor = "Purple", GuiAnnouncementsTextColor = "White" } }; } } private class MessageSettings { [JsonProperty("enable toast message")] public bool EnableToast; [JsonProperty("enable chat message")] public bool EnableChat; [JsonProperty("enable chat prefix")] public bool EnableChatPrefix; [JsonProperty("custom chat message icon (steam64)")] public ulong ChatIcon; [JsonProperty("enable gui announcements plugin from umod.org")] public bool EnableGuiAnnouncements; [JsonProperty("gui announcements text color")] public string GuiAnnouncementsTextColor; [JsonProperty("gui announcements background color")] public string GuiAnnouncementsBgColor; } #endregion #region Storage private void LoadDefaultData() { _storedData = new StoredData { BradleyEventEntries = new Dictionary { ["assets/bundled/prefabs/autospawn/monument/xlarge/launch_site_1.prefab"] = new EventEntry { DisplayName = "Launch Site", EnabledEvent = true, BoundsPosition = new Vector3(0f, 0f, 0f), BoundsSize = new Vector3(580f, 280f, 300f), LandingPosition = new Vector3(152.3f, 3f, 0f), LandingRotation = new Vector3(0f, 90f, 0f), ChinookPosition = new Vector3(-195f, 150f, 25f), GuardAmount = 10, GuardConfig = new GuardConfig { Name = "Launch Site Guard", WearItems = new List { new GuardConfig.WearEntry { ShortName = "hazmatsuit_scientist_peacekeeper", SkinID = 0UL } }, BeltItems = new List { new GuardConfig.BeltEntry { ShortName = "smg.mp5", Amount = 1, SkinID = 0UL, Mods = new List() }, new GuardConfig.BeltEntry { ShortName = "syringe.medical", Amount = 10, SkinID = 0UL, Mods = new List() }, }, Kit = "", Health = 250f, RoamRange = 25f, ChaseRange = 40f, SenseRange = 150f, ListenRange = 150f / 2, AttackRangeMultiplier = 8f, CheckVisionCone = false, VisionCone = 180f, DamageScale = 1f, TurretDamageScale = 0.25f, AimConeScale = 0.35f, DisableRadio = false, CanRunAwayWater = true, CanSleep = false, Speed = 8.5f, AreaMask = 1, AgentTypeID = -1372625422, HomePosition = string.Empty, MemoryDuration = 30f, States = new HashSet { "RoamState", "ChaseState", "CombatState", "CombatStationaryState" } } }, ["assets/bundled/prefabs/autospawn/monument/large/airfield_1.prefab"] = new EventEntry { DisplayName = "Airfield Guard", EnabledEvent = false, BoundsPosition = new Vector3(0f, 0f, 0f), BoundsSize = new Vector3(340f, 260f, 300f), LandingPosition = new Vector3(0f, 0f, -28f), LandingRotation = new Vector3(0f, 0f, 0f), ChinookPosition = new Vector3(-195f, 150f, 25f), GuardAmount = 10, GuardConfig = new GuardConfig { Name = "Guarded Crate", WearItems = new List { new GuardConfig.WearEntry { ShortName = "hazmatsuit_scientist_peacekeeper", SkinID = 0UL } }, BeltItems = new List { new GuardConfig.BeltEntry { ShortName = "smg.mp5", Amount = 1, SkinID = 0UL, Mods = new List() }, new GuardConfig.BeltEntry { ShortName = "syringe.medical", Amount = 10, SkinID = 0UL, Mods = new List() }, }, Kit = "", Health = 250f, RoamRange = 25f, ChaseRange = 40f, SenseRange = 150f, ListenRange = 150f / 2, AttackRangeMultiplier = 8f, CheckVisionCone = false, VisionCone = 180f, DamageScale = 1f, TurretDamageScale = 0.25f, AimConeScale = 0.35f, DisableRadio = false, CanRunAwayWater = true, CanSleep = false, Speed = 8.5f, AreaMask = 1, AgentTypeID = -1372625422, HomePosition = string.Empty, MemoryDuration = 30f, States = new HashSet { "RoamState", "ChaseState", "CombatState", "CombatStationaryState" } } } } }; SaveData(); } private void LoadData() { try { _storedData = Interface.Oxide.DataFileSystem.ReadObject(Name); if (_storedData == null || !_storedData.IsValid) throw new Exception(); } catch { PrintWarning("Loaded default data."); LoadDefaultData(); } } private void SaveData() { if (_storedData != null && _storedData.IsValid) Interface.Oxide.DataFileSystem.WriteObject(Name, _storedData); } private class StoredData { public Dictionary BradleyEventEntries = new Dictionary(StringComparer.OrdinalIgnoreCase); [JsonIgnore] public bool IsValid => BradleyEventEntries != null && BradleyEventEntries.Count > 0; public EventEntry FindEntryByName(string monumentName) { EventEntry eventEntry; return BradleyEventEntries.TryGetValue(monumentName, out eventEntry) ? eventEntry : null; } } private class EventEntry { [JsonProperty("display name")] public string DisplayName; [JsonProperty("enabled")] public bool EnabledEvent; [JsonProperty("bounds center")] public Vector3 BoundsPosition; [JsonProperty("bounds size")] public Vector3 BoundsSize; [JsonProperty("landing position")] public Vector3 LandingPosition; [JsonProperty("landing rotation")] public Vector3 LandingRotation; [JsonProperty("chinook position")] public Vector3 ChinookPosition; [JsonProperty("guard spawn amount")] public int GuardAmount; [JsonProperty("guard spawn profile")] public GuardConfig GuardConfig; public IEnumerator Create(Transform transform, bool enableAutoExtinguish, bool enableAutoUnlock) { var landingRotation = transform.TransformDirection(transform.rotation.eulerAngles - LandingRotation); var landingPosition = transform.TransformPoint(LandingPosition); var chinookPosition = transform.TransformPoint(ChinookPosition); var component = Utils.CreateObjectWithComponent(landingPosition, Quaternion.Euler(landingRotation), "Bradley_Guards_Event"); component.bounds = new OBB(transform.position, transform.rotation, new Bounds(BoundsPosition, BoundsSize)); component.chinookPosition = chinookPosition; component.guardConfig = GuardConfig; component.guardAmount = GuardAmount; component.displayName = DisplayName; component.enableAutoExtinguish = enableAutoExtinguish; component.enableAutoUnlock = enableAutoUnlock; component.CreateLandingZone(); component.DisplayInfo(); yield return CoroutineEx.waitForEndOfFrame; } } private class GuardConfig { public string Name; public List WearItems; public List BeltItems; public string Kit; public float Health; public float RoamRange; public float ChaseRange; public float SenseRange; public float ListenRange; public float AttackRangeMultiplier; public bool CheckVisionCone; public float VisionCone; public float DamageScale; public float TurretDamageScale; public float AimConeScale; public bool DisableRadio; public bool CanRunAwayWater; public bool CanSleep; public float Speed; public int AreaMask; public int AgentTypeID; public string HomePosition; public float MemoryDuration; public HashSet States; public class BeltEntry { public string ShortName; public ulong SkinID; public int Amount; public string Ammo; public List Mods; public static List SaveItems(ItemContainer container) { var items = new List(); foreach (var item in container.itemList) { var beltEntry = new BeltEntry { ShortName = item.info.shortname, SkinID = item.skin, Amount = item.amount, Mods = new List() }; var projectile = item.GetHeldEntity() as BaseProjectile; if (projectile?.primaryMagazine != null && projectile.primaryMagazine.ammoType != null) beltEntry.Ammo = projectile.primaryMagazine.ammoType.shortname; if (item?.contents?.itemList != null) { foreach (var itemContent in item.contents.itemList) beltEntry.Mods.Add(itemContent.info.shortname); } items.Add(beltEntry); } return items; } } public class WearEntry { public string ShortName; public ulong SkinID; public static List SaveItems(ItemContainer container) { var items = new List(); foreach (var item in container.itemList) { var wearEntry = new WearEntry { ShortName = item.info.shortname, SkinID = item.skin }; items.Add(wearEntry); } return items; } } public JObject ToJObject() => JObject.FromObject(this); } #endregion #region Oxide Hooks private void OnServerInitialized() { CustomProtection.Instance = new CustomProtection(); CustomProtection.Instance?.Initialize(); EntitiesLookup.Initialize(); PerformSetupRoutine(); if (!string.IsNullOrEmpty(_configData.CommandName)) cmd.AddChatCommand(_configData.CommandName, this, nameof(EventCommandCommands)); } private void Init() { permission.RegisterPermission(PERM_USE, this); Instance = this; LoadData(); } private void Unload() { try { DestroySetupRoutine(); BradleyGuardsEvent.DestroyAll(); } finally { CustomProtection.Instance?.Dispose(); CustomProtection.Instance = null; EntitiesLookup.Dispose(); Instance = null; } } private void OnEntityKill(ScientistNPC npc) { EntitiesLookup.FindEventByEntity(npc) ?.OnGuardDeath(npc, null); } private void OnEntitySpawned(BradleyAPC apc) { if (apc == null || apc.IsDestroyed) return; var position = apc.transform.position; if (position == Vector3.zero) return; var component = BradleyGuardsEvent.GetClosest(position); if (component == null) return; component.ResetEvent(); apc.maxCratesToSpawn = _configData.CrateSpawnAmount; apc._maxHealth = _configData.BradleyHealth; apc.InitializeHealth(_configData.BradleyHealth, _configData.BradleyHealth); } private void OnEntityDeath(BradleyAPC apc, HitInfo info) { if (apc == null || apc.IsDestroyed || info?.InitiatorPlayer == null) return; var position = apc.transform.position; if (position == Vector3.zero) return; var component = BradleyGuardsEvent.GetClosest(position); if (component == null || component.IsStarted()) return; if (!NpcSpawn.IsReady()) { PrintWarning("Missing dependency [NpcSpawn v2.4.8] this can be found over at codefling.com thanks to KpucTaJl"); return; } component.StartEvent(position); } private void OnEntityDeath(ScientistNPC npc, HitInfo hitInfo) { EntitiesLookup.FindEventByEntity(npc) ?.OnGuardDeath(npc, hitInfo?.InitiatorPlayer); } private void OnEntityDismounted(BaseMountable mountable, ScientistNPC npc) { var chinook = mountable.GetParentEntity() as CH47HelicopterAIController; if (chinook == null || chinook.OwnerID != 0UL || chinook.IsDestroyed) return; if (npc == null || EntitiesLookup.FindEventByEntity(npc) == null) return; npc.Brain.Navigator.ForceToGround(); if (chinook.AnyMounted()) return; ForceChinookLeave(chinook); } #endregion #region Event private void PerformSetupRoutine() { if (_setupRoutine == null) _setupRoutine = ServerMgr.Instance.StartCoroutine(InitializeEvents()); } private void DestroySetupRoutine() { if (_setupRoutine == null) return; ServerMgr.Instance.StopCoroutine(_setupRoutine); _setupRoutine = null; } private IEnumerator InitializeEvents() { Puts("Initializing events in ({0})s.", INITIALIZE_DELAY); yield return CoroutineEx.waitForSeconds(INITIALIZE_DELAY); var stopwatch = new Stopwatch(); stopwatch.Start(); foreach (var monumentInfo in TerrainMeta.Path.Monuments) { var monumentName = monumentInfo.name; if (monumentName.IsNullOrEmpty()) continue; var monumentEntry = _storedData.FindEntryByName(monumentName); if (monumentEntry == null || !monumentEntry.EnabledEvent) continue; yield return monumentEntry.Create(monumentInfo.transform, _configData.EnableAutoExtinguish, _configData.EnableAutoUnlock); } stopwatch.Stop(); Puts($"Finished initializing in ({stopwatch.ElapsedMilliseconds})ms."); _setupRoutine = null; } private class BradleyGuardsEvent : FacepunchBehaviour { public static List EventComponents = new List(); private List _guardEntities = new List(); private CH47LandingZone _eventLandingZone; private CurrentState _eventState; private Vector3 _eventPosition; private GameObject _go; public GuardConfig guardConfig; public Vector3 chinookPosition; public string displayName; public int guardAmount; public bool enableAutoExtinguish; public bool enableAutoUnlock; public OBB bounds; public BasePlayer winningPlayer; public static BradleyGuardsEvent GetClosest(Vector3 position) { foreach (var component in BradleyGuardsEvent.EventComponents) { if (component.bounds.Contains(position)) return component; } return (BradleyGuardsEvent)null; } public static void DestroyAll() { if (Rust.Application.isQuitting) return; for (var i = BradleyGuardsEvent.EventComponents.Count - 1; i >= 0; i--) BradleyGuardsEvent.EventComponents[i]?.DestroyMe(); } #region Unity public void Awake() { _go = gameObject; BradleyGuardsEvent.EventComponents.Add(this); } public void OnDestroy() { if (BradleyGuardsEvent.EventComponents.Contains(this)) BradleyGuardsEvent.EventComponents.Remove(this); } public void DestroyMe() { ClearGuards(); GameObject.Destroy(_go); Destroy(this); } #endregion #region Event Management public bool IsStarted() => _eventState == CurrentState.Started; public void StartEvent(Vector3 deathPosition) { if (IsStarted()) return; winningPlayer = null; _eventPosition = deathPosition; _eventState = CurrentState.Started; SpawnChinook(); RemoveDamage(); Instance?.HookSubscribe(); Instance?.MessagePlayers(MessageKeys.EventStart, displayName); } private void StopEvent() { _eventState = CurrentState.Waiting; Instance?.HookUnsubscribe(); Instance?.MessagePlayers(MessageKeys.EventEnded, displayName); } public void ResetEvent() { ClearGuards(); _eventState = CurrentState.Waiting; } public void CheckEvent() { if (enableAutoExtinguish) RemoveFlames(); if (enableAutoUnlock) UnlockCrates(); if (winningPlayer != null) Interface.CallHook("OnBradleyGuardsEventEnded", winningPlayer); StopEvent(); } private bool HasGuards() { return _guardEntities != null && _guardEntities.Count > 0; } private void UnlockCrates() { var entities = Pool.GetList(); Vis.Entities(_eventPosition, 25f, entities); foreach (var entCrate in entities) { if (!entCrate.IsValid() || entCrate.IsDestroyed) continue; entCrate.SetLocked(false); if (entCrate.lockingEnt == null) continue; var entity = entCrate.lockingEnt.GetComponent(); if (entity != null && !entity.IsDestroyed) entity.Kill(); } Pool.FreeList(ref entities); } private void RemoveFlames() { var entities = Pool.GetList(); Vis.Entities(_eventPosition, 25f, entities); foreach (var fireball in entities) { if (fireball.IsValid() && !fireball.IsDestroyed) fireball.Extinguish(); } Pool.FreeList(ref entities); } private void RemoveDamage() { var entities = Pool.GetList(); Vis.Entities(_eventPosition, 25f, entities); foreach (var fireball in entities) { if (fireball.IsValid() && !fireball.IsDestroyed) fireball.ignoreNPC = true; } Pool.FreeList(ref entities); } #endregion #region Chinook private void SpawnChinook() { var chinook = (CH47HelicopterAIController)GameManager.server.CreateEntity("assets/prefabs/npc/ch47/ch47scientists.entity.prefab", chinookPosition, Quaternion.identity); chinook.SetLandingTarget(_eventLandingZone.transform.position); chinook.Spawn(); chinook.CancelInvoke(chinook.SpawnScientists); chinook.Invoke(() => SpawnGuards(chinook, guardAmount), 0.25f); chinook.SetMinHoverHeight(0.0f); CustomProtection.Instance?.ModifyProtection(chinook); } #endregion #region Landing public void CreateLandingZone() { _eventLandingZone = gameObject.AddComponent(); _eventLandingZone.enabled = true; } #endregion #region Guard private void SpawnGuards(CH47HelicopterAIController chinook, int numToSpawn) { var num = Math.Min(numToSpawn, 10); for (var i = 0; i < num; i++) SpawnGuard(chinook, chinook.transform.position + chinook.transform.forward * 10f); } private void SpawnGuard(CH47HelicopterAIController chinook, Vector3 position) { var homePosition = _eventPosition; guardConfig.HomePosition = homePosition.GetPointAround(2f).ToString(); var scientist = (ScientistNPC)Instance?.NpcSpawn.Call("SpawnNpc", position, guardConfig?.ToJObject()); if (scientist == null || scientist.IsDestroyed) return; scientist.SetDestination(homePosition); chinook.AttemptMount(scientist, false); CacheGuardEntity(scientist); } public void ClearGuards() { for (var i = _guardEntities.Count - 1; i >= 0; i--) { var baseEntity = _guardEntities[i]; if (baseEntity != null && !baseEntity.IsDestroyed) baseEntity.Kill(); } } #endregion #region Oxide Hooks public void OnGuardDeath(ScientistNPC npc, BasePlayer player) { CacheGuardRemove(npc); if (HasGuards()) return; if (player != null) winningPlayer = player; CheckEvent(); } #endregion #region Cache Guard Entity private void CacheGuardEntity(BaseEntity entity) { _guardEntities.Add(entity); EntitiesLookup.CreateEntity(entity, this); } private void CacheGuardRemove(BaseEntity entity) { _guardEntities.Remove(entity); EntitiesLookup.RemoveEntity(entity); } #endregion #region Debug Info public void DisplayInfo() { var connections = Facepunch.Pool.GetList(); try { connections.AddRange(Net.sv.connections.Where(x => x.connected && x.authLevel > 0)); Utils.DrawText(connections, transform.position, "Landing Position", Color.magenta, 30f); Utils.DrawText(connections, chinookPosition, "Chinook Position", Color.green, 30f); Utils.DrawText(connections, bounds.position, "Bounds Position", Color.cyan, 30f); Utils.DrawCube(connections, bounds.position, bounds.rotation, bounds.extents, Color.red, 30f, 1.5f); } catch (Exception e) { // } Facepunch.Pool.FreeList(ref connections); } #endregion } private void ForceChinookLeave(CH47HelicopterAIController chinook) { if (chinook == null || chinook.IsDestroyed) return; chinook.Invoke(() => { chinook.EnableFacingOverride(true); chinook.InitiateAnger(); chinook.SetMinHoverHeight(150f); chinook.SetMoveTarget(Vector3.right * 6000f); }, 10f); } #endregion #region Rust Edit private object OnNpcRustEdit(ScientistNPC npc) { return EntitiesLookup.FindEventByEntity(npc) != null ? (object)true : null; } #endregion #region Hook Subscribe private void HookUnsubscribe() { Unsubscribe("OnEntityDismounted"); //Puts("Unsubscribed from hooks!"); } private void HookSubscribe() { Subscribe("OnEntityDismounted"); //Puts("Subscribed to hooks!"); } #endregion #region Custom Protection private class CustomProtection : IDisposable { private ProtectionProperties _protectionProperties; public static CustomProtection Instance; public void Initialize() { _protectionProperties = ScriptableObject.CreateInstance(); _protectionProperties.name = "Bradley_Guards_Protection"; _protectionProperties.Add(1); } public void Dispose() { if (_protectionProperties == null) return; ScriptableObject.Destroy(_protectionProperties); _protectionProperties = null; } public void ModifyProtection(BaseCombatEntity combatEntity) { if (combatEntity != null && !combatEntity.IsDestroyed) combatEntity.baseProtection = _protectionProperties; } } #endregion #region Entities Lookup private static class EntitiesLookup { public static Dictionary Entities; public static BradleyGuardsEvent FindEventByEntity(BaseEntity entity) { BradleyGuardsEvent component; return entity != null && Entities.TryGetValue(entity, out component) ? component : null; } public static void Initialize() { Entities = new Dictionary(); } public static void Dispose() { EntitiesLookup.Entities.Clear(); EntitiesLookup.Entities = null; } public static void CreateEntity(BaseEntity entity, BradleyGuardsEvent component) { if (Entities != null) Entities.Add(entity, component); } public static void RemoveEntity(BaseEntity entity) { if (Entities != null) Entities.Remove(entity); } } #endregion #region Chat Command private void EventCommandCommands(BasePlayer player, string command, string[] args) { if (!permission.UserHasPermission(player.UserIDString, PERM_USE)) { MessagePlayer(player, MessageKeys.NoPermission); return; } if (args.Length < 2) { DisplayHelpText(player); return; } EventEntry eventEntry; if (!_storedData.BradleyEventEntries.TryGetValue(args[0], out eventEntry)) { MessagePlayer(player, MessageKeys.EventNotFound); return; } var option = args[1]; if (option.Equals("enable")) { if (args.Length != 3) { DisplayHelpText(player); return; } bool enabled; if (bool.TryParse(args[2], out enabled)) { MessagePlayer(player, MessageKeys.InvalidBooleanValue); return; } eventEntry.EnabledEvent = enabled; SaveData(); MessagePlayer(player, MessageKeys.EventUpdated); return; } if (option.Equals("display")) { if (args.Length != 3) { DisplayHelpText(player); return; } var displayName = string.Join(" ", args.Skip(2)); if (string.IsNullOrEmpty(displayName)) { MessagePlayer(player, MessageKeys.DisplayNameEmpty); return; } eventEntry.DisplayName = displayName; SaveData(); MessagePlayer(player, MessageKeys.EventUpdated); return; } if (option.Equals("amount")) { if (args.Length != 3) { DisplayHelpText(player); return; } int amount; if (!int.TryParse(args[2], out amount) || (amount < 2 || amount > 10)) { MessagePlayer(player, MessageKeys.InvalidGuardAmount); return; } eventEntry.GuardAmount = amount; SaveData(); MessagePlayer(player, MessageKeys.EventUpdated); return; } if (option.Equals("loadout")) { if (player.inventory == null) return; eventEntry.GuardConfig.BeltItems = GuardConfig.BeltEntry.SaveItems(player.inventory.containerBelt); eventEntry.GuardConfig.WearItems = GuardConfig.WearEntry.SaveItems(player.inventory.containerWear); SaveData(); MessagePlayer(player, MessageKeys.EventUpdated); return; } DisplayHelpText(player); } private void DisplayHelpText(BasePlayer player) { var sb = Facepunch.Pool.Get(); try { sb.AppendFormat(lang.GetMessage(MessageKeys.Prefix, this, player.UserIDString)) .AppendFormat(lang.GetMessage(MessageKeys.HelpEventEnable, this, player.UserIDString), _configData.CommandName) .AppendFormat(lang.GetMessage(MessageKeys.HelpEventName, this, player.UserIDString), _configData.CommandName) .AppendFormat(lang.GetMessage(MessageKeys.HelpGuardAmount, this, player.UserIDString), _configData.CommandName) .AppendFormat(lang.GetMessage(MessageKeys.HelpGuardLoadout, this, player.UserIDString), _configData.CommandName); player.ChatMessage(sb.ToString()); } catch (Exception e) { // } sb.Clear(); Facepunch.Pool.Free(ref sb); } #endregion } } namespace Oxide.Plugins.BradleyGuardsExtensionMethods { public static class Utils { public static T CreateObjectWithComponent(Vector3 position, Quaternion rotation, string name) where T : MonoBehaviour { return new GameObject(name) { layer = (int)Layer.Reserved1, transform = { position = position, rotation = rotation } }.AddComponent(); } public static string GetGrid(Vector3 position) => PhoneController.PositionToGridCoord(position); public static Color GetColor(string hex) { Color color; return ColorUtility.TryParseHtmlString(hex, out color) ? color : Color.yellow; } public static string GetTime(int seconds) { var time = TimeSpan.FromSeconds(seconds); return $"{time.Hours:D2}h:{time.Minutes:D2}m:{time.Seconds:D2}s"; } public static void DrawSphere(List connections, Vector3 origin, float radius, Color color, float duration) { if (connections != null && connections.Count > 0) ConsoleNetwork.SendClientCommand(connections, "ddraw.sphere", duration, color, origin, radius); } public static void DrawText(List connections, Vector3 origin, string text, Color color, float duration) { if (connections != null && connections.Count > 0) ConsoleNetwork.SendClientCommand(connections, "ddraw.text", duration, color, origin, text); } public static void DrawCube(List connections, Vector3 center, Quaternion rotation, Vector3 extents, Color color, float duration, float sphereRadius = 0.5f) { if (connections == null || connections.Count == 0) return; var forwardUpperLeft = center + rotation * extents.WithX(-extents.x); var forwardUpperRight = center + rotation * extents; var forwardLowerLeft = center + rotation * extents.WithX(-extents.x).WithY(-extents.y); var forwardLowerRight = center + rotation * extents.WithY(-extents.y); var backLowerRight = center + rotation * -extents.WithX(-extents.x); var backLowerLeft = center + rotation * -extents; var backUpperRight = center + rotation * -extents.WithX(-extents.x).WithY(-extents.y); var backUpperLeft = center + rotation * -extents.WithY(-extents.y); Utils.DrawSphere(connections, forwardUpperLeft, sphereRadius, color, duration); Utils.DrawSphere(connections, forwardUpperRight, sphereRadius, color, duration); Utils.DrawSphere(connections, forwardLowerLeft, sphereRadius, color, duration); Utils.DrawSphere(connections, forwardLowerRight, sphereRadius, color, duration); Utils.DrawSphere(connections, backLowerRight, sphereRadius, color, duration); Utils.DrawSphere(connections, backLowerLeft, sphereRadius, color, duration); Utils.DrawSphere(connections, backUpperRight, sphereRadius, color, duration); Utils.DrawSphere(connections, backUpperLeft, sphereRadius, color, duration); } } public static class ExtensionMethods { public static bool IsNullOrEmpty(this string value) => string.IsNullOrEmpty(value); public static bool IsReady(this Plugin plugin) => plugin != null && plugin.IsLoaded; public static bool IsPlayer(this BasePlayer player) => player != null && player.userID.IsSteamId(); public static void SafeKill(this BaseEntity entity) { if (entity != null && !entity.IsDestroyed) entity.Kill(); } public static void SafeClear(this ItemContainer container) { for (var index = container.itemList.Count - 1; index >= 0; index--) { var item = container.itemList[index]; item.RemoveFromContainer(); item.Remove(); } } public static Vector3 GetPointAround(this Vector3 position, float radius) { var angle = UnityEngine.Random.value * 360f; var pointAround = position; pointAround.x = position.x + radius * Mathf.Sin(angle * Mathf.Deg2Rad); pointAround.z = position.z + radius * Mathf.Cos(angle * Mathf.Deg2Rad); pointAround.y = position.y; pointAround.y = TerrainMeta.HeightMap.GetHeight(pointAround); return pointAround; } } }