using System; using System.Collections.Generic; using Facepunch; using Newtonsoft.Json; using Oxide.Core; using Rust; namespace Oxide.Plugins { [Info("Magic Loot", "collect_vood", "1.0.5")] [Description("Simple components multiplier and loot system")] public class MagicLoot : CovalencePlugin { #region Fields private bool _initialized = false; private readonly int _maxContainerSlots = 18; private int _scrapStack; private Dictionary> _sortedRarities; private Dictionary> _skinsCache = new Dictionary>(); private Dictionary _originItemAmountRange = new Dictionary(); #endregion #region Configuration private ConfigurationFile _configuration; public class ConfigurationFile { [JsonProperty(PropertyName = "General Settings")] public SettingsFile Settings = new SettingsFile(); [JsonProperty(PropertyName = "Extra Loot")] public ExtraLootFile ExtraLoot = new ExtraLootFile(); [JsonProperty(PropertyName = "Blacklisted Items (Item-Shortnames)", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List BlacklistedItems = new List() { "ammo.rocket.smoke" }; [JsonProperty(PropertyName = "Blacklisted Workshop Skins (Workshop Ids)", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List BlacklistSkins = new List() { 10180 }; [JsonProperty(PropertyName = "Manual Item Multipliers (Key: Item-Shortname, Value: Multiplier)", ObjectCreationHandling = ObjectCreationHandling.Replace)] public Dictionary ManualItemMultipliers = new Dictionary(); [JsonProperty(PropertyName = "Containers Data (Key: Container-Shortname, Value: Container Settings)", ObjectCreationHandling = ObjectCreationHandling.Replace)] public Dictionary ContainersData = new Dictionary(); [JsonProperty(PropertyName = "Debug")] public bool Debug = false; } public class SettingsFile { [JsonProperty(PropertyName = "General Item List Multiplier (All items in the 'Manual Item Multipliers' List)")] public float ItemListMultiplier = 1f; [JsonProperty(PropertyName = "Non Item List Multiplier (All items not listed in the 'Manual Item Multipliers' List)")] public float NonItemListMultiplier = 1f; [JsonProperty(PropertyName = "Limit Multipliers to Stacksizes")] public bool LimitToStacksizes = true; [JsonProperty(PropertyName = "Multiply Blueprints")] public bool BlueprintDuplication = false; [JsonProperty(PropertyName = "Disable Blueprint Drops")] public bool DisableBlueprints = false; [JsonProperty(PropertyName = "Random Workshop Skins")] public bool RandomWorkshopSkins = false; [JsonProperty(PropertyName = "Multiply Tea Buffs")] public bool MultiplyTeaBuff = false; [JsonProperty(PropertyName = "Force Custom Loot Tables for Default Loot on all Containers")] public bool ForceCustomLootTables = false; } public class ExtraLootFile { [JsonProperty(PropertyName = "Enabled")] public bool Enabled = false; [JsonProperty(PropertyName = "Extra Items Min")] public int ExtraItemsMin = 0; [JsonProperty(PropertyName = "Extra Items Max")] public int ExtraItemsMax = 0; [JsonProperty(PropertyName = "Prevent Duplicates")] public bool PreventDuplicates = true; [JsonProperty(PropertyName = "Prevent Duplicates Retries")] public int PreventDuplicatesRetries = 10; [JsonProperty(PropertyName = "Force Custom Loot Tables for Extra Loot on all Containers")] public bool ForceCustomLootTables = false; } public class ContainerData { [JsonProperty(PropertyName = "Enabled")] public bool Enabled = true; [JsonProperty(PropertyName = "Extra Items Min")] public int ExtraItemsMin = 0; [JsonProperty(PropertyName = "Extra Items Max")] public int ExtraItemsMax = 0; [JsonProperty(PropertyName = "Loot Multiplier")] public float Multiplier = 1f; [JsonProperty(PropertyName = "Utilize Vanilla Loot Tables on Default Loot")] public bool VanillaLootTablesDefault = true; [JsonProperty(PropertyName = "Utilize Vanilla Loot Tables on Extra Loot")] public bool VanillaLootTablesExtra = true; [JsonProperty(PropertyName = "Utilize Random Rarity (depending on Items ALREADY in the container)")] public bool RandomRarities = true; [JsonProperty(PropertyName = "Rarity To Use (ONLY if 'Utilize Vanilla Loot Tables' is FALSE & 'Utilize Random Rarity' is FALSE | 0 = None, 1 = Common, 2 = Uncommon, 3 = Rare, 4 = Very Rare)", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List Rarities = new List() { Rarity.Common, Rarity.Uncommon, Rarity.Rare, Rarity.VeryRare }; } protected override void LoadDefaultConfig() { PrintWarning("Creating a new configuration file"); _configuration = new ConfigurationFile(); foreach (var itemDef in ItemManager.itemList) { if (itemDef.category != ItemCategory.Component) { continue; } _configuration.ManualItemMultipliers.Add(itemDef.shortname, 1f); } _configuration.ManualItemMultipliers.Add("scrap", 1f); } protected override void LoadConfig() { base.LoadConfig(); _configuration = Config.ReadObject(); SaveConfig(); } protected override void SaveConfig() => Config.WriteObject(_configuration); #endregion #region Data private void LoadData() { _data = Interface.Oxide.DataFileSystem.ReadObject(Name); int count = 0; foreach (var item in ItemManager.itemList) { if (_data.Items.ContainsKey(item.shortname)) { continue; } _data.Items.Add(item.shortname, item.stackable); count++; } SaveData(); if (count > 0) { Puts($"Added {count} new items to the item list!"); } //Determine if rarity system is needed if (_configuration.Settings.ForceCustomLootTables || _configuration.ExtraLoot.ForceCustomLootTables) { _sortedRarities = GetSortedRarities(); } else { foreach (var container in _configuration.ContainersData) { if (!container.Value.VanillaLootTablesDefault || !container.Value.VanillaLootTablesExtra) { _sortedRarities = GetSortedRarities(); break; } } } _scrapStack = _data.Items["scrap"]; } private void SaveData() { Interface.Oxide.DataFileSystem.WriteObject(Name, _data); } private DataFile _data; public class DataFile { [JsonProperty(PropertyName = "Item Data (Key: Item Shortname, Value: Stacksize)", ObjectCreationHandling = ObjectCreationHandling.Replace)] public Dictionary Items = new Dictionary(); } #endregion #region Hooks private void OnServerInitialized() { _initialized = true; if (!_configuration.Settings.MultiplyTeaBuff) { Unsubscribe(nameof(OnBonusItemDrop)); } LoadData(); ModifyItemAmountRanges(); AddMissingContainers(); Puts($"Loaded at x{_configuration.Settings.NonItemListMultiplier} vanilla rate" + $" | Manual Item List at x{_configuration.Settings.ItemListMultiplier} rate [Extra Loot: {_configuration.ExtraLoot.Enabled}, Multiply Tea Buffs: {_configuration.Settings.MultiplyTeaBuff}]"); RepopulateContainers(); } private object OnLootSpawn(LootContainer container) { if (!_initialized || container?.inventory?.itemList == null) { return null; } ContainerData containerData; if (!_configuration.ContainersData.TryGetValue(container.ShortPrefabName, out containerData)) { _configuration.ContainersData.Add(container.ShortPrefabName, containerData = new ContainerData()); SaveConfig(); } if (IgnoreContainer(containerData)) { return null; } PopulateContainer(container, containerData); if (container.shouldRefreshContents && container.isLootable) { container.Invoke(new Action(container.SpawnLoot), UnityEngine.Random.Range( container.minSecondsBetweenRefresh, container.maxSecondsBetweenRefresh)); } return container; } private void OnBonusItemDrop(Item item, BasePlayer player) { ReinforceRules(item, null); } private void Unload() { _initialized = false; RestoreItemAmountRanges(); foreach (var container in UnityEngine.Object.FindObjectsOfType()) { if (container?.inventory == null) { continue; } container.SpawnLoot(); } } #endregion #region Methods /// /// Adds missing containers to the configuration file /// private void AddMissingContainers() { int count = 0; foreach (var container in UnityEngine.Resources.FindObjectsOfTypeAll()) { if (!container.enabled) { continue; } ContainerData containerData; if (_configuration.ContainersData.TryGetValue(container.ShortPrefabName, out containerData)) { continue; } _configuration.ContainersData.Add(container.ShortPrefabName, containerData = new ContainerData()); count++; } if (count > 0) { Puts($"Added {count} missing containers to the config file."); } } /// /// Repopulates all LootContainers /// private void RepopulateContainers() { int count = 0; foreach (var container in UnityEngine.Object.FindObjectsOfType()) { var containerData = _configuration.ContainersData[container.ShortPrefabName]; if (IgnoreContainer(containerData)) { continue; } container.inventory.Clear(); ItemManager.DoRemoves(); PopulateContainer(container, containerData); count++; } SaveConfig(); Puts("Repopulated " + count.ToString() + " loot containers."); } /// /// Populates the Container /// /// /// private void PopulateContainer(LootContainer container, ContainerData containerData) { container.inventory.capacity = _maxContainerSlots; AddDefaultLoot(container, containerData); AddExtraLoot(container, containerData); RandomizeDurability(container); //Generate scrap late so it is in the last slot if (_scrapStack > 0) { container.GenerateScrap(); } ReinforceRules(container, containerData); } /// /// Removes blacklisted items, applies item skins and applies multipliers /// /// private void ReinforceRules(LootContainer container, ContainerData containerData) { for (int i = 0; i < container.inventory.itemList.Count; i++) { var item = container.inventory.itemList[i]; ReinforceRules(item, containerData); } } /// /// Removes blacklisted item, applies item skin and applies multipliers /// /// /// private void ReinforceRules(Item item, ContainerData containerData = null) { if (_configuration.Settings.DisableBlueprints && item.IsBlueprint()) { item.info = ItemManager.FindItemDefinition(item.blueprintTarget); item.blueprintTarget = 0; item.maxCondition = item.info.condition.max; item.condition = item.info.condition.max; item.OnItemCreated(); } if (!_configuration.Settings.BlueprintDuplication && item.IsBlueprint()) { return; } if (_configuration.Settings.RandomWorkshopSkins && item.info.shortname != "hazmatsuit") { var skinId = GetRandomSkin(item.info); ApplySkinToItem(item, skinId); } float multiplier = 1f; var inItemList = _configuration.ManualItemMultipliers.TryGetValue( item.info.shortname, out multiplier); if (!inItemList) { multiplier = 1f; } //Do not multiply if (multiplier == 0f) { return; } multiplier *= inItemList ? _configuration.Settings.ItemListMultiplier : _configuration.Settings.NonItemListMultiplier; if (containerData != null) { multiplier *= containerData.Multiplier; } var maxAmount = _data.Items[item.info.shortname]; if (maxAmount <= 0) { PrintWarning($"Item '{item.info.shortname}' spawned with 0 amount..."); } item.amount = _configuration.Settings.LimitToStacksizes ? (int)Math.Min(item.amount * multiplier, maxAmount) : (int)(item.amount * multiplier); } /// /// Returns a list of items where duplicated items are found /// /// /// /// private void FindDuplicates(int itemId, List items, ref List duplicateItems) { foreach (var cItem in items) { if (cItem.info.itemid != itemId) { continue; } duplicateItems.Add(cItem); } } private bool IsValid(ItemDefinition itemDefintion) { if (!_configuration.Settings.LimitToStacksizes) { return true; } else if (_configuration.BlacklistedItems.Contains(itemDefintion.shortname)) { return false; } else if (_data.Items[itemDefintion.shortname] <= 0) { return false; } return true; } /// /// Validates all Loot Spawns found /// private void ModifyItemAmountRanges() { if (_configuration.Settings.ForceCustomLootTables && (_configuration.ExtraLoot.Enabled && _configuration.ExtraLoot.ForceCustomLootTables)) { Puts("Skipping modifying default loot tables..."); return; } var lootSpawns = UnityEngine.Resources.FindObjectsOfTypeAll(); foreach (var lootSpawn in lootSpawns) { if (lootSpawn?.items == null) { PrintDebug("Loot spawn data is null"); continue; } ModifyItemAmountRange(lootSpawn); } if (_originItemAmountRange.Count > 0) { Puts($"Modified '{_originItemAmountRange.Count}' loot data item ranges out of a total of '{lootSpawns.Length}' ..."); } } /// /// Validates given item amount ranged array and modifies/removes items if needed /// /// private void ModifyItemAmountRange(LootSpawn origin) { var tempItemsToSpawn = Pool.GetList(); for (int i = 0; i < origin.items.Length; i++) { var itemRangeToSpawn = origin.items[i]; if (!IsValid(itemRangeToSpawn.itemDef)) { continue; } tempItemsToSpawn.Add(itemRangeToSpawn); } if (tempItemsToSpawn.Count != origin.items.Length) { _originItemAmountRange.Add(origin, origin.items.Clone() as ItemAmountRanged[]); origin.items = tempItemsToSpawn.ToArray(); PrintDebug($"Modified loot data of '{origin.name}'..."); } Pool.FreeList(ref tempItemsToSpawn); } /// /// Resets all initially stored loot spawns to their default item ranges /// private void RestoreItemAmountRanges() { foreach (var lootSpawn in _originItemAmountRange) { lootSpawn.Key.items = lootSpawn.Value; } Puts($"Restored '{_originItemAmountRange.Count}' loot data item ranges to default..."); } /// /// Adds default loot to the container /// /// /// private void AddDefaultLoot(LootContainer container, ContainerData containerData) { var useDefaultLootTables = !(containerData.Enabled && !containerData.VanillaLootTablesDefault); if (useDefaultLootTables) { useDefaultLootTables = !_configuration.Settings.ForceCustomLootTables; } if (container.LootSpawnSlots.Length != 0) { for (int i = 0; i < container.LootSpawnSlots.Length; i++) { var lootSpawnSlot = container.LootSpawnSlots[i]; for (int j = 0; j < lootSpawnSlot.numberToSpawn; j++) { if (UnityEngine.Random.Range(0f, 1f) <= lootSpawnSlot.probability) { if (useDefaultLootTables) { lootSpawnSlot.definition.SpawnIntoContainer(container.inventory); continue; } AddRandomItem(container, containerData); } } } } else if (container.lootDefinition != null) { for (int k = 0; k < container.maxDefinitionsToSpawn; k++) { if (useDefaultLootTables) { container.lootDefinition.SpawnIntoContainer(container.inventory); continue; } AddRandomItem(container, containerData); } } } /// /// Adds extra loot to the container, depending on both the Extra Loot and Specific Container Data /// /// private void AddExtraLoot(LootContainer container, ContainerData containerData) { var useDefaultLootTables = !(containerData.Enabled && !containerData.VanillaLootTablesExtra); if (useDefaultLootTables) { useDefaultLootTables = !_configuration.ExtraLoot.ForceCustomLootTables; } var additionalItemsMin = 0; var additionalItemsMax = 0; if (_configuration.ExtraLoot.Enabled) { additionalItemsMin += _configuration.ExtraLoot.ExtraItemsMin; additionalItemsMax += _configuration.ExtraLoot.ExtraItemsMax; } if (containerData.Enabled) { additionalItemsMin += containerData.ExtraItemsMin; additionalItemsMax += containerData.ExtraItemsMax; } var additionalItems = UnityEngine.Random.Range(additionalItemsMin, additionalItemsMax + 1); for (int i = 0; i < additionalItems; i++) { if (useDefaultLootTables) { AddRandomVanillaItem(container); } else { AddRandomItem(container, containerData); } } } /// /// Adds a random Vanilla-LootTable Item to the Container /// /// private void AddRandomVanillaItem(LootContainer container) { if (container.lootDefinition != null) { container.lootDefinition.SpawnIntoContainer(container.inventory); } } /// /// Adds a random Item to the Container /// /// private void AddRandomItem(LootContainer container, ContainerData containerData) { if (containerData.Rarities.Count == 0) { PrintWarning("Using Non-Vanilla-LootTables but no set Rarity found in list for container : " + container.ShortPrefabName); containerData.Rarities.Add(Rarity.Common); SaveConfig(); } var randomContainerRarity = Rarity.Common; if (containerData.RandomRarities && container.inventory.itemList.Count > 0) { randomContainerRarity = container.inventory.itemList[UnityEngine.Random.Range(0, container.inventory.itemList.Count)].info.rarity; } else { randomContainerRarity = containerData.Rarities[UnityEngine.Random.Range(0, containerData.Rarities.Count)]; } var rarities = _sortedRarities[randomContainerRarity]; var randomItemShortname = rarities[UnityEngine.Random.Range(0, rarities.Count)]; var randomItem = ItemManager.itemDictionaryByName[randomItemShortname]; if (_configuration.ExtraLoot.PreventDuplicates) { var duplicateItems = Pool.GetList(); for (int i = 0; i < _configuration.ExtraLoot.PreventDuplicatesRetries; i++) { FindDuplicates(randomItem.itemid, container.inventory.itemList, ref duplicateItems); if (duplicateItems.Count > 0) { randomItemShortname = rarities[UnityEngine.Random.Range(0, rarities.Count)]; randomItem = ItemManager.itemDictionaryByName[randomItemShortname]; duplicateItems.Clear(); if (i >= _configuration.ExtraLoot.PreventDuplicatesRetries - 1) { PrintDebug("Unable to solve duplicate conflict with " + container.ShortPrefabName + " " + container.transform.position); } } else { break; } } Pool.FreeList(ref duplicateItems); } var maxAmount = _data.Items[randomItemShortname]; if (maxAmount > 0) { container.inventory.AddItem(randomItem, UnityEngine.Random.Range(1, maxAmount)); } } /// /// Randomizes the durability of the Items in the Container if ROADSIDE or TOWN Container /// /// private void RandomizeDurability(LootContainer container) { if (container.SpawnType == LootContainer.spawnType.ROADSIDE || container.SpawnType == LootContainer.spawnType.TOWN) { foreach (Item item in container.inventory.itemList) { if (!item.hasCondition) { continue; } item.condition = UnityEngine.Random.Range(item.info.condition.foundCondition.fractionMin, item.info.condition.foundCondition.fractionMax) * item.info.condition.max; } } } /// /// Generates a Rarity Dictionary containing only Item Definitions that are not Blacklisted /// /// private Dictionary> GetSortedRarities() { var sortedRarities = new Dictionary>(); foreach (var itemDef in ItemManager.itemList) { if (_configuration.BlacklistedItems.Contains(itemDef.shortname)) { PrintDebug($"Filtering out '{itemDef.shortname}'"); continue; } if (_data.Items[itemDef.shortname] <= 0) { continue; } if (!sortedRarities.ContainsKey(itemDef.rarity)) { sortedRarities.Add(itemDef.rarity, new List() { itemDef.shortname }); continue; } sortedRarities[itemDef.rarity].Add(itemDef.shortname); } Puts("Loaded Rarities..."); return sortedRarities; } /// /// Returns if the container is NOT needed to be custom populated /// /// /// private bool IgnoreContainer(ContainerData containerData) { return !containerData.Enabled; } private ulong GetRandomSkin(ItemDefinition itemDef) { List possibleSkins; if (_skinsCache.TryGetValue(itemDef.itemid, out possibleSkins)) { return possibleSkins[UnityEngine.Random.Range(0, possibleSkins.Count)]; } possibleSkins = new List() { 0 }; foreach (var skin in ItemSkinDirectory.ForItem(itemDef)) { var skinId = (ulong)skin.id; if (_configuration.BlacklistSkins.Contains(skinId)) { continue; } possibleSkins.Add(skinId); } foreach (var skin in Rust.Workshop.Approved.All.Values) { if (skin.Skinnable.ItemName != itemDef.shortname) { continue; } if (_configuration.BlacklistSkins.Contains(skin.WorkshopdId)) { continue; } possibleSkins.Add(skin.WorkshopdId); } _skinsCache.Add(itemDef.itemid, possibleSkins); return possibleSkins[UnityEngine.Random.Range(0, possibleSkins.Count)]; } private void ApplySkinToItem(Item item, ulong skinId) { if (skinId == 0) { return; } item.skin = skinId; item.MarkDirty(); BaseEntity heldEntity = item.GetHeldEntity(); if (heldEntity != null) { heldEntity.skinID = skinId; heldEntity.SendNetworkUpdate(BasePlayer.NetworkQueue.Update); } } private void PrintDebug(object message) { if (!_configuration.Debug) { return; } Puts(message.ToString()); } #endregion } }