using Newtonsoft.Json; using Oxide.Core; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; using UnityEngine; namespace Oxide.Plugins { [Info("SimpleSupply", "0xF", "1.0.0")] public class SimpleSupply : RustPlugin { #region Consts private static readonly ItemDefinition SUPPLY_SIGNAL_DEF = ItemManager.FindItemDefinition("supply.signal"); private static readonly FieldInfo m_CachedPtrField = typeof(UnityEngine.Object).GetField("m_CachedPtr", (BindingFlags)(-1)); private const BaseEntity.Flags SUPPLYDROP_FLAG = BaseEntity.Flags.Reserved12; private const string SUPPLY_NOTE = "populated_supply"; private const string SUPPLY_ITEM_NAME_LOCKED = "LOCKED SUPPLY DROP"; #endregion #region Variables private static SimpleSupply Instance; #endregion #region Hooks void Init() { Instance = this; } void OnServerInitialized(bool initial) { if (BaseNetworkable.serverEntities.OfType().Count() == 0) UnsubscribeHooks(); } void OnCargoPlaneSignaled(CargoPlane cargoPlane, SupplySignal supplySignal) { if (cargoPlane == null || supplySignal == null) return; if (supplySignal.skinID != 0 && supplySignal.skinID != 234501) return; cargoPlane.Kill(); supplySignal.Invoke(supplySignal.KillMessage, 0f); Vector3 position = supplySignal.transform.position; bool IsRequiredSpawn = true; List list = Facepunch.Pool.GetList(); global::GamePhysics.TraceAll(new Ray(position, Vector3.one), config.Range, list, config.Range, -10, QueryTriggerInteraction.UseGlobal, null); foreach (SupplyDrop supplyDrop in list.Select(hit => hit.GetEntity()).OfType().OrderBy(supply => supply.Distance(position))) { ContainedSupplySignal containedSupply = ContainedSupplySignal.CreateNew(supplyDrop); if (containedSupply.MoveToContainer(supplyDrop.inventory)) { containedSupply.PopulateLoot(supplyDrop); IsRequiredSpawn = false; Effect.server.Run("assets/prefabs/missions/portal/proceduraldungeon/effects/disappear.prefab", position); Effect.server.Run("assets/prefabs/missions/portal/proceduraldungeon/effects/appear.prefab", supplyDrop.transform.position + supplyDrop.transform.forward); Effect.server.Run("assets/prefabs/missions/portal/proceduraldungeon/effects/appear.prefab", supplyDrop.transform.position - supplyDrop.transform.forward); Effect.server.Run("assets/prefabs/missions/portal/proceduraldungeon/effects/appear.prefab", supplyDrop.transform.position + supplyDrop.transform.right); Effect.server.Run("assets/prefabs/missions/portal/proceduraldungeon/effects/appear.prefab", supplyDrop.transform.position - supplyDrop.transform.right); break; } else { containedSupply.DoRemove(); } } Facepunch.Pool.FreeList(ref list); if (IsRequiredSpawn) { SupplyDrop supplyDrop = global::GameManager.server.CreateEntity("assets/prefabs/misc/supply drop/supply_drop.prefab", position, default(Quaternion), true) as SupplyDrop; if (supplyDrop) { SubscribeHooks(); supplyDrop.globalBroadcast = true; supplyDrop.SetFlag(BaseEntity.Flags.Reserved12, true, false, false); supplyDrop.Spawn(); supplyDrop.RemoveParachute(); supplyDrop.inventory.capacity = config.SupplyCapacity; supplyDrop.panelName = "generic_large"; supplyDrop.SendNetworkUpdate(); Interface.CallHook("OnSupplyDropDropped", supplyDrop, supplySignal); } } } void OnEntityKill(SupplyDrop supplyDrop) { if (supplyDrop == null) return; if (!supplyDrop.HasFlag(SUPPLYDROP_FLAG)) return; if (BaseNetworkable.serverEntities.OfType().Count() == 0) UnsubscribeHooks(); } object OnLootSpawn(SupplyDrop supplyDrop) { if (supplyDrop == null) return null; if (!supplyDrop.HasFlag(SUPPLYDROP_FLAG)) return null; ContainedSupplySignal containedSupply = ContainedSupplySignal.CreateNew(supplyDrop); if (containedSupply.MoveToContainer(supplyDrop.inventory, -1, true, false, null, true)) containedSupply.PopulateLoot(supplyDrop); else containedSupply.DoRemove(); return false; } object OnItemAction(Item item, string action, BasePlayer player) { if (item == null || item.text != SUPPLY_NOTE) return null; if (action == "open") { if (item.IsLocked()) return false; PlayerLoot playerLoot = player.inventory.loot; playerLoot.Clear(); playerLoot.PositionChecks = true; playerLoot.containers.Add(item.contents); item.contents.onDirty += playerLoot.MarkDirty; playerLoot.itemSource = item; playerLoot.entitySource = item.GetEntityOwner(); Interface.CallHook("OnLootItem", player, item); playerLoot.MarkDirty(); } else if (action == "drop") { item.Remove(0); } return false; } void OnItemAddedToContainer(ItemContainer container, Item item) { if (!(container.entityOwner as SupplyDrop)) return; ContainedSupplySignal containedSupplySignal = item as ContainedSupplySignal; if (containedSupplySignal == null) return; if (containedSupplySignal.IsBusy()) container.entityOwner.InvokeRepeating(containedSupplySignal.Update, 0, 1f); } void OnItemRemovedFromContainer(ItemContainer container, Item item) { Item parentItem = container.parent; if (parentItem == null || parentItem.text != SUPPLY_NOTE) return; if (container.itemList.Count == 0) { foreach (BasePlayer player in BasePlayer.activePlayerList) { PlayerLoot playerLoot = player.inventory.loot; if (playerLoot.IsLooting() && playerLoot.itemSource == parentItem) playerLoot.Clear(); } parentItem.Remove(2f); } } void OnPlayerLootEnd(PlayerLoot playerLoot) { SupplyDrop supplyDrop = playerLoot.entitySource as SupplyDrop; Item itemSource = playerLoot.itemSource; if (supplyDrop != null && itemSource != null && itemSource.text == SUPPLY_NOTE) NextFrame(() => supplyDrop.PlayerOpenLoot(playerLoot.baseEntity)); } object CanMoveItem(Item item, PlayerInventory playerLoot, ItemContainerId targetContainer, int targetSlot, int amount) { if (item.text != SUPPLY_NOTE) return null; OnItemAction(item, "open", playerLoot.baseEntity); return false; } ItemContainer.CanAcceptResult? CanAcceptItem(ItemContainer container, Item item, int targetPos) { if (item.text == SUPPLY_NOTE && !(container.entityOwner is SupplyDrop)) return ItemContainer.CanAcceptResult.CannotAccept; return null; } object CanStackItem(Item item, Item targetItem) { if (item.text == SUPPLY_NOTE || targetItem.text == SUPPLY_NOTE) return false; return null; } object OnContainerPopulate(SupplyDrop supplyDrop) => CanPopulateLoot(supplyDrop); object CanPopulateLoot(SupplyDrop supplyDrop) { if (supplyDrop == null) return null; if (!supplyDrop.HasFlag(SUPPLYDROP_FLAG)) return null; return false; } #endregion #region Classes public class ContainedSupplySignal : Item { public void PopulateLoot(LootContainer lootContainer) { SpawnLootInContainer(lootContainer, this.contents); Instance.NextTick(() => this.contents.capacity = this.contents.itemList.Count); } public void Update() { bool isBusy = this.IsBusy(); this.LockUnlock(isBusy); string name; if (isBusy) name = $"{SUPPLY_ITEM_NAME_LOCKED} ({Mathf.RoundToInt(this.busyTime - UnityEngine.Time.time)}s)"; else name = null; if (this.name != name) { this.name = name; this.MarkDirty(); } if (!isBusy) this.parent?.entityOwner?.CancelInvoke(Update); } public static ContainedSupplySignal CreateNew(ItemContainer.Flag containerFlags) { ContainedSupplySignal item = new ContainedSupplySignal(); item.isServer = true; item.info = SUPPLY_SIGNAL_DEF; item.amount = 1; item.name = config.TimeToUnlock > 0 ? SUPPLY_ITEM_NAME_LOCKED : null; item.text = SUPPLY_NOTE; item.Initialize(SUPPLY_SIGNAL_DEF); item.contents = new global::ItemContainer(); item.contents.flags = containerFlags; item.contents.allowedContents = ItemContainer.ContentsType.Generic; item.contents.ServerInitialize(item, 18); item.contents.GiveUID(); item.BusyFor(config.TimeToUnlock); item.LockUnlock(item.IsBusy()); return item; } public static ContainedSupplySignal CreateNew(SupplyDrop supplyDrop) { return ContainedSupplySignal.CreateNew(supplyDrop.inventory.flags); } } #endregion #region Methods void SubscribeHooks() { Subscribe(nameof(OnItemAddedToContainer)); Subscribe(nameof(OnItemRemovedFromContainer)); Subscribe(nameof(CanStackItem)); Subscribe(nameof(OnLootSpawn)); Subscribe(nameof(OnItemAction)); Subscribe(nameof(CanMoveItem)); Subscribe(nameof(OnPlayerLootEnd)); Subscribe(nameof(OnContainerPopulate)); Subscribe(nameof(CanPopulateLoot)); } void UnsubscribeHooks() { Unsubscribe(nameof(OnItemAddedToContainer)); Unsubscribe(nameof(OnItemRemovedFromContainer)); Unsubscribe(nameof(CanStackItem)); Unsubscribe(nameof(OnLootSpawn)); Unsubscribe(nameof(OnItemAction)); Unsubscribe(nameof(CanMoveItem)); Unsubscribe(nameof(OnPlayerLootEnd)); Unsubscribe(nameof(OnContainerPopulate)); Unsubscribe(nameof(CanPopulateLoot)); } public static void SpawnLootInContainer(LootContainer lootContainer, ItemContainer container) { BaseEntity.Flags flags = lootContainer.flags; if ((flags & SUPPLYDROP_FLAG) == SUPPLYDROP_FLAG) flags &= ~SUPPLYDROP_FLAG; LootContainer fakeLootContainer = Activator.CreateInstance(lootContainer.GetType()) as LootContainer; fakeLootContainer.inventory = container; fakeLootContainer.prefabID = lootContainer.prefabID; fakeLootContainer.skinID = lootContainer.skinID; fakeLootContainer.flags = flags; fakeLootContainer.lootDefinition = lootContainer.lootDefinition; fakeLootContainer.maxDefinitionsToSpawn = lootContainer.maxDefinitionsToSpawn; fakeLootContainer.SpawnType = lootContainer.SpawnType; fakeLootContainer.LootSpawnSlots = lootContainer.LootSpawnSlots; fakeLootContainer.scrapAmount = lootContainer.scrapAmount; fakeLootContainer.net = new Network.Networkable() { ID = new NetworkableId((ulong)UnityEngine.Random.Range(345536, 5678934)) }; m_CachedPtrField.SetValue(fakeLootContainer, (IntPtr)(1)); fakeLootContainer.SpawnLoot(); } #endregion #region Config static Configuration config; public class Configuration { [JsonProperty(PropertyName = "Supply capacity (1-42)")] public int SupplyCapacity = 42; [JsonProperty(PropertyName = "Range")] public float Range = 15f; [JsonProperty(PropertyName = "Time to unlock supply item")] public float TimeToUnlock = 120f; public static Configuration DefaultConfig() => new Configuration(); } protected override void LoadConfig() { base.LoadConfig(); try { config = Config.ReadObject(); if (config == null) LoadDefaultConfig(); SaveConfig(); } catch (Exception e) { Debug.LogException(e); PrintWarning("Creating new config file."); LoadDefaultConfig(); } } protected override void LoadDefaultConfig() => config = Configuration.DefaultConfig(); protected override void SaveConfig() => Config.WriteObject(config); #endregion } }