C#:
using System.Collections.Generic;
using Object = System.Object;
using System.Collections;
using System;
using System.Reflection;
using Rust;
using HarmonyLib;
using System.Linq;
using Oxide.Core;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using UnityEngine;
namespace Oxide.Plugins {
[Info("IQLoot", "Scrooge", "1.3.2")]
[Description("Cпасибо скруджу")]
public class IQLoot : RustPlugin {
private void LoadContainers(String prefabName, LootContainer lootContainer) {
if (dataContainers.ContainsKey(prefabName)) return;
ContainerPreset preset = PresetGenerated(lootContainer);
dataContainers.Add(prefabName, preset);
WriteData(prefabName, preset, false);
}
private ContainerPreset GeneratePreset(List<ItemAmountRanged> allItems, (Int32 minLoot, Int32 maxLoot) lootSlots) {
ContainerPreset preset = new ContainerPreset {
enabled = true, amountLoot = new ContainerPreset.AmountController {
minAmount = lootSlots.minLoot, maxAmount = lootSlots.maxLoot
}
}
;
foreach (ItemAmountRanged item in allItems) {
ItemDefinition itemDef = item.itemDef;
TypeLootContents typeLootContents = itemDef.category == ItemCategory.Weapon ? TypeLootContents.Weapon : TypeLootContents.Other;
preset.lootPresets.Add(new ContainerPreset.LootPreset {
shortname = itemDef.shortname, conditionMin = itemDef.condition.foundCondition.fractionMin * 100f, conditionMax = itemDef.condition.foundCondition.fractionMax * 100f, displayName = string.Empty, skinID = 0, isBplueprint = false, rareDrop = rareDropDefault[itemDef.rarity], amount = new ContainerPreset.AmountController {
minAmount = (Int32)item.startAmount, maxAmount = (Int32)(item.maxAmount <= 0 ? item.startAmount : item.maxAmount)
}
, ammoWeapon = new ContainerPreset.LootPreset.AmmoWeapon {
shortname = "", amount = 0
}
, typeContents = typeLootContents, contents = new List<ContainerPreset.LootPreset.ItemContent>()
}
);
}
return preset;
}
private Dictionary<String, ContainerPreset> npcContainers = new Dictionary<String, ContainerPreset>();
public class ContainerPreset {
public AmountController amountLoot = new AmountController();
public class LootPreset {
public AmmoWeapon ammoWeapon;
public String shortname;
public Int32 rareDrop;
public Single conditionMin;
public String displayName;
public class AmmoWeapon {
public Int32 amount;
public String shortname;
}
public AmountController amount;
public Boolean isBplueprint;
public Single conditionMax;
public class ItemContent {
public String shortname;
public UInt64 skinID;
public Int32 amount;
public Item GetContentItem() {
Item contentItem = ItemManager.CreateByName(shortname);
return contentItem;
}
}
public List<ItemContent> contents = new List<ItemContent>();
public UInt64 skinID;
public TypeLootContents typeContents;
public Item BuildItem() {
Item buildedItem;
if (isBplueprint) {
ItemDefinition definition = ItemManager.FindItemDefinition(shortname);
buildedItem = ItemManager.Create(ItemManager.blueprintBaseDef);
buildedItem.instanceData ??= new ProtoBuf.Item.InstanceData();
buildedItem.instanceData.ShouldPool = false;
buildedItem.instanceData.blueprintAmount = 1;
buildedItem.instanceData.blueprintTarget = definition.itemid;
buildedItem.MarkDirty();
return buildedItem;
}
buildedItem = ItemManager.CreateByName(shortname, amount.GetAmount(), skinID);
if(!String.IsNullOrWhiteSpace(displayName)) buildedItem.name = displayName;
if (buildedItem.hasCondition) {
Int32 currentCondition = Mathf.RoundToInt(buildedItem.maxCondition * (Oxide.Core.Random.Range(conditionMin, conditionMax) / 100f));
buildedItem.maxCondition = buildedItem.maxCondition;
buildedItem.condition = Math.Min(currentCondition, buildedItem.maxCondition);
buildedItem.busyTime = buildedItem.busyTime;
}
if (typeContents == TypeLootContents.Weapon) {
if (!String.IsNullOrWhiteSpace(ammoWeapon.shortname) && ammoWeapon.amount > 0) {
BaseProjectile weaponItem = buildedItem.GetHeldEntity() as BaseProjectile;
if (weaponItem) {
ItemDefinition ammoDef = ItemManager.FindItemDefinition(ammoWeapon.shortname);
if (ammoDef) {
weaponItem.primaryMagazine.ammoType = ammoDef;
weaponItem.primaryMagazine.contents = ammoWeapon.amount;
}
}
}
}
if (contents != null && contents.Count != 0) {
foreach (ItemContent mod in contents) {
Item modItem = mod.GetContentItem();
if(modItem == null) continue;
try {
buildedItem.contents.AddItem(mod.GetContentItem().info, mod.amount, mod.skinID);
}
catch (Exception e) {
modItem.Remove();
throw;
}
}
}
return buildedItem;
}
}
public readonly List<LootPreset> lootPresets = new List<LootPreset>();
public List<LootPreset> GetRandomLoots(Int32 requestedCount) {
if (lootPresets == null || lootPresets.Count == 0 || requestedCount <= 0) {
// Debug.LogError($"Ref slot random list : {requestedCount}");
return null;
}
List<LootPreset> uniqueLoots = new ();
HashSet<String> seenShortnames = new ();
foreach (LootPreset loot in lootPresets) {
if(loot.shortname == "scrap") continue;
if (seenShortnames.Add(loot.shortname)) uniqueLoots.Add(loot);
}
Int32 count = Math.Min(requestedCount, uniqueLoots.Count);
Single totalWeight = uniqueLoots.Sum(loot => Math.Clamp(loot.rareDrop, 0f, 100f));
List<LootPreset> selectedLoots = new List<LootPreset>();
for (Int32 i = 0; i < count; i++) {
Single randomValue = Core.Random.Range(0f, totalWeight);
Single cumulativeWeight = 0f;
foreach (LootPreset loot in uniqueLoots) {
Single weight = Math.Clamp(loot.rareDrop, 0f, 100f);
cumulativeWeight += weight;
if (randomValue <= cumulativeWeight) {
selectedLoots.Add(loot);
uniqueLoots.Remove(loot);
totalWeight -= weight;
break;
}
}
}
return selectedLoots;
}
public Boolean enabled;
public Item GetScrap() {
Int32 scrapIndex = lootPresets.FindIndex(x => x.shortname == "scrap");
if (scrapIndex == -1) return null;
Int32 amountScrap = lootPresets[scrapIndex].amount.GetAmount();
if (amountScrap <= 0) return null;
return Core.Random.Range(0, 100) < lootPresets[scrapIndex].rareDrop ? ItemManager.CreateByName("scrap", amountScrap) : null;
}
public class AmountController {
public Int32 minAmount;
public Int32 maxAmount;
public Int32 GetAmount() {
Int32 min = Math.Min(minAmount, maxAmount);
Int32 max = Math.Max(minAmount, maxAmount) + 1;
Int32 amount = Core.Random.Range(min, max);
return Math.Max(amount, 1);
}
}
}
private void Unload() {
if (instance == null) return;
HarmonyInstance?.UnpatchAll(HarmonyInstance.Id);
dataContainers?.Clear();
npcContainers?.Clear();
if (routinePrefabs != null) {
ServerMgr.Instance.StopCoroutine(routinePrefabs);
routinePrefabs = null;
}
instance = null;
}
private Boolean finalWarningShown = false;
[HarmonyPatch(typeof(LootContainer), nameof(LootContainer.SpawnLoot))]
public class Patch_LootContainer_SpawnLoot {
[HarmonyPostfix]
public static void SpawnLoot_Postfix(LootContainer __instance) {
ApplyLootContainer(__instance);
}
}
public enum TypeLootContents {
Weapon, Other
}
// - Улучшить на сайте настройку % (сделать не только ползунок)
// - Превью крейта с которым сейчас работаешь (на сайте фотку)
//
// - Вагоны
// - Подарки яички
// - У прочности тоже разбросик
// - Пак предметов (то есть создать арбалет и к нему со 100 шансом падает стрелы)
//Update :
// Обновление плагина :
// - Исправлена проблема, заставляющая мусорки на земле (junkiles) переспавниваться лишний раз.// - Заменен метод получения префаба, который мог вызывать варнинги/ошибки при загрузке из-за требования сцены для проверки определенного префаба
private const Boolean LanguageEn = false;
private static String FormatTime(Single secondsTotal) {
Int32 minutes = (Int32)(secondsTotal / 60);
Int32 seconds = (Int32)(secondsTotal % 60);
return LanguageEn ?$"{minutes} min {seconds} sec" : $"{minutes} мин {seconds} сек";
}
private void ReadData() {
ReadDataFolder("IQSystem/IQLoot/Containers/", dataContainers);
ReadDataFolder("IQSystem/IQLoot/NPCs/", npcContainers);
}
private void LoadHarmonyPatches() {
Type[] types = GetType().GetNestedTypes(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
foreach (Type type in types) {
if (type.GetCustomAttributes(typeof(HarmonyPatch), false).Length == 0) continue;
List<MethodInfo> harmonyMethods = HarmonyInstance.CreateClassProcessor(type)?.Patch();
if (harmonyMethods == null || harmonyMethods.Count == 0) {
Interface.Oxide.LogWarning($"[{Title}] AutoPatch attribute found on '{type.Name}' but no HarmonyPatch methods found.Skipping.");
continue;
}
foreach (MethodInfo method in harmonyMethods) {
Interface.Oxide.LogInfo($"[{Title}] Automatically Harmony patched '{method.Name}' method.({type.Name})");
}
}
}
private static Coroutine routinePrefabs = null;
private void RegenerateLootServer() {
foreach (LootContainer container in BaseNetworkable.serverEntities.Where(p => p is LootContainer && p)) ApplyLootContainer(container);
}
private void OnServerInitialized() {
if (LanguageEn) {
PrintWarning("Link to configure settings via the website: https://iqloot.skyplugins.ru/");
PrintWarning($"Web access code: {codeOthers}");
}
else {
if (isAlkad) {
PrintWarning("Ссылка для настройки конфигураций через сайт - https://iqloot.skyplugins.ru/");
PrintWarning($"Web access code: {codeOthers}");
}
else PrintWarning("Ссылка для настройки конфигураций через сайт - https://iqloot.skyplugins.ru/");
}
routinePrefabs = ServerMgr.Instance.StartCoroutine(InitPrefabManifest());
}
private void ReadDataFolder(String baseFolder, Dictionary<String, ContainerPreset> targetDict) {
try {
String[] files = Interface.Oxide.DataFileSystem.GetFiles(baseFolder, "*.json");
foreach (String fullPath in files) {
String fileKey = fullPath.Substring(0, fullPath.Length - 5);
try {
Object rawObj = Interface.Oxide.DataFileSystem.ReadObject<Object>(fileKey);
String rawJson = JsonConvert.SerializeObject(rawObj);
JObject jObject = JObject.Parse(rawJson);
Boolean isLegacy = false;
if (jObject["lootPresets"] is JArray lootPresets) {
foreach (JToken loot in lootPresets) {
JToken conditionToken = loot["condition"];
if (conditionToken != null && loot["conditionMin"] == null && loot["conditionMax"] == null) {
Single conditionValue = conditionToken.ToObject<Single>();
loot["conditionMin"] = conditionValue;
loot["conditionMax"] = conditionValue;
((JObject)loot).Remove("condition");
isLegacy = true;
}
}
}
ContainerPreset preset = jObject.ToObject<ContainerPreset>();
if (preset == null) continue;
String prefabName = System.IO.Path.GetFileNameWithoutExtension(fileKey);
targetDict.TryAdd(prefabName, preset);
if (isLegacy) {
Interface.Oxide.DataFileSystem.WriteObject(fileKey, preset, true);
Puts(LanguageEn ? $"→ Updated legacy preset: {prefabName}.json" : $"→ Обновлён устаревший пресет: {prefabName}.json");
}
}
catch (Exception ex) {
PrintError(LanguageEn ? $"Error reading {fullPath}: {ex.Message}" : $"Ошибка при чтении {fullPath}: {ex.Message}");
}
}
}
catch (Exception ex) {
PrintError(LanguageEn ? $"Error scanning folder {baseFolder}: {ex.Message}" : $"Ошибка при сканировании папки {baseFolder}: {ex.Message}");
}
}
private readonly HashSet<String> lootPrefabKeysIgnore = new(StringComparer.OrdinalIgnoreCase) {
"test", "mission", "invisible", "fx", "ui", "sounds", "spawners", "misc", "client", "preview", "invisible", "marker"
}
;
private (Int32 minLoot, Int32 maxLoot) CalculateLootRange(LootContainer container) {
if (!container.name.Contains("road")) return EstimateMinMaxLoot(container);
Int32 count = container.inventory?.itemList?.Count ?? 2;
return (1, count);
}
private (Int32 minLoot, Int32 maxLoot) CalculateLootRange(HumanNPC npc) => EstimateMinMaxLootSpawnSloot(npc.LootSpawnSlots);
private IEnumerator InitPrefabManifest() {
PrintWarning(LanguageEn ? "Prefab manifest processing started.Please wait..." : "Запущен обработчик манифеста префабов, этот процесс ресурсоемкий и может вызвать некоторую задержку.\nНе перезагружайте плагин пока не увидите сообщение о завершении процесса");
Int32 count = GameManifest.Current.prefabProperties.Length;
const Single delay = 0.01f;
Single totalSeconds = count * delay;
Int32 minutes = (Int32)(totalSeconds / 60);
Int32 seconds = (Int32)(totalSeconds % 60);
PrintWarning(LanguageEn ? $"Estimated processing time: {minutes}m {seconds}s.It usually happens faster." : $"Примерное время обработки: {minutes} мин {seconds} сек.Обычно это происходит быстрее");
Single halfTime = totalSeconds / 2f;
const Single finalReminderThreshold = 30f;
Single elapsedTime = 0f;
for (Int32 i = 0; i < count; i++) {
GameManifest.PrefabProperties prefabInManifest = GameManifest.Current.prefabProperties[i];
if (prefabInManifest == null) continue;
if (!prefabInManifest.name.EndsWith(".prefab")) continue;
GameObject prefab = FindPrefab(prefabInManifest.name);
if (!prefab) continue;
String prefabName = prefab.name.ToLower();
if (!IsValidLootPrefab(prefabName) || IsIgnoreLootPrefab(prefabName)) continue;
if (prefab.TryGetComponent(out LootContainer lootContainer)) LoadContainers(prefabName, lootContainer);
else if (prefab.TryGetComponent(out HumanNPC npcPlayer)) LoadNpcs(prefabName, npcPlayer);
elapsedTime += delay;
if (!halfWarningShown && elapsedTime >= halfTime) {
halfWarningShown = true;
PrintWarning(LanguageEn ? $"~50% complete.Remaining: {FormatTime(totalSeconds - elapsedTime)}" : $"~50% завершено.Осталось примерно: {FormatTime(totalSeconds - elapsedTime)}");
}
if (!finalWarningShown && totalSeconds - elapsedTime <= finalReminderThreshold) {
finalWarningShown = true;
PrintWarning(LanguageEn ? $"Almost done.Remaining: {FormatTime(totalSeconds - elapsedTime)}" : $"Почти готово.Осталось: {FormatTime(totalSeconds - elapsedTime)}");
}
yield return CoroutineEx.waitForSeconds(delay);
}
PrintWarning(LanguageEn ? "Prefab manifest processing complete." : "Обработка манифестов завершена");
LoadHarmonyPatches();
RegenerateLootServer();
yield return null;
}
private List<ItemAmountRanged> GetAllLootItems(LootContainer container) {
var items = new List<ItemAmountRanged>();
if (container.lootDefinition) items.AddRange(ProcessLootSpawn(container.lootDefinition));
if (items.Count <= 3 && container.LootSpawnSlots != null) {
foreach (LootContainer.LootSpawnSlot slot in container.LootSpawnSlots) {
if (slot.definition) items.AddRange(ProcessLootSpawn(slot.definition));
}
}
if (container.scrapAmount > 0) {
items.Add(new ItemAmountRanged(ItemManager.FindItemDefinition("scrap"), container.scrapAmount, container.scrapAmount));
}
List<ItemAmountRanged> uniqueItems = items.GroupBy(i => i.itemDef.shortname).Select(g => g.First()).ToList();
return uniqueItems;
}
private static IQLoot instance;
private static GameObject FindPrefab(String strPrefab) {
GameObject prefab = GameManager.server.preProcessed.Find(strPrefab);
return prefab ? prefab : null;
}
private readonly Dictionary<Rarity, Int32> rareDropDefault = new Dictionary<Rarity, Int32>() {
[Rarity.None]
= 100, [Rarity.Common] = 100, [Rarity.Uncommon] = 75, [Rarity.Rare] = 50, [Rarity.VeryRare] = 25,
}
;
private const String codeOthers = "";
private Dictionary<String, ContainerPreset> dataContainers = new Dictionary<String, ContainerPreset>();
private readonly HashSet<String> lootPrefabKeys = new(StringComparer.OrdinalIgnoreCase) {
// Ящики и контейнеры
"crate", "loot", "barrel", "trash-pile", "dm ", "foodbox", "food_cache", "tech_parts", "heli_crate", "bradley_crate", "minecart", "wagon_crate", "oil_barrel", "presentdrop", "supply_drop", "giftbox_loot", "beehive", "codelockedhackablecrate", "roadsign", "vehicle_parts", // NPC (Scientist, Tunnel Dweller, и т.д.)
"npc", "scientist", "tunneldweller", "underwaterdweller", "gingerbread"
}
;
private List<ItemAmountRanged> ProcessLootSpawn(LootSpawn lootSpawn) {
List<ItemAmountRanged> items = new();
if (!lootSpawn) return items;
if (lootSpawn.subSpawn is {
Length : > 0
}
) {
foreach (LootSpawn.Entry subSpawn in lootSpawn.subSpawn) {
if (subSpawn.category) items.AddRange(ProcessLootSpawn(subSpawn.category));
}
}
if (lootSpawn.items is {
Length : > 0
}
) items.AddRange(lootSpawn.items);
return items;
}
private ContainerPreset PresetGenerated(LootContainer lootContainer) {
List<ItemAmountRanged> allItems = GetAllLootItems(lootContainer);
(Int32 minLoot, Int32 maxLoot) lootSlots = CalculateLootRange(lootContainer);
return GeneratePreset(allItems, lootSlots);
}
private static void ApplyLootContainer(LootContainer container) {
if (!container || !instance.dataContainers.TryGetValue(container.ShortPrefabName, out ContainerPreset dataContainer) || !dataContainer.enabled) return;
if (container.skinID != 0) return;
if (instance.ignoredOwnerIdsOtherPlugins.Contains(container.OwnerID)) return;
if (container.inventory is null) {
container.CreateInventory(true);
container.OnInventoryFirstCreated(container.inventory);
}
if (container.inventory.itemList.Count > 0) {
container.inventory.Clear();
ItemManager.DoRemoves();
container.HasBeenLooted = false;
}
Int32 slotAmount = dataContainer.amountLoot.GetAmount();
Item scrap = dataContainer.GetScrap();
if (scrap != null) {
if (!scrap.MoveToContainer(container.inventory)) scrap.Remove();
else slotAmount--;
}
List<ContainerPreset.LootPreset> presets = dataContainer.GetRandomLoots(slotAmount);
if (presets == null && scrap == null) {
Interface.Oxide.LogWarning($"WARNING: Loot container nulled.Prefab: {container.ShortPrefabName}");
container.Kill();
return;
}
if (presets == null) return;
foreach (ContainerPreset.LootPreset loot in presets) {
Item item = loot.BuildItem();
if (item == null) continue;
if (!item.MoveToContainer(container.inventory)) item.Remove();
else item.OnVirginSpawn();
}
container.inventory.MarkDirty();
}
private ContainerPreset PresetGenerated(HumanNPC npc) {
List<ItemAmountRanged> allItems = GetAllLootItems(npc);
(Int32 minLoot, Int32 maxLoot) lootSlots = CalculateLootRange(npc);
return GeneratePreset(allItems, lootSlots);
}
private static (Int32 min, Int32 max) EstimateMinMaxLootSpawnSloot(LootContainer.LootSpawnSlot[] slots) {
Int32 minTotal = 0;
Int32 maxTotal = 0;
foreach (LootContainer.LootSpawnSlot slot in slots) {
if (slot.eras is {
Length : > 0
}
&& Array.IndexOf(slot.eras, ConVar.Server.Era) == -1) continue;
Int32 attempts = slot.numberToSpawn;
maxTotal += attempts;
Int32 minSlot = (Int32)Math.Floor(attempts * slot.probability);
minTotal += minSlot;
}
return (minTotal, maxTotal);
}
private const Boolean isAlkad = false;
private void Init() {
instance = this;
ReadData();
}
private void LoadNpcs(String prefabName, HumanNPC npc) {
if (npcContainers.ContainsKey(prefabName)) return;
ContainerPreset preset = PresetGenerated(npc);
npcContainers.Add(prefabName, preset);
WriteData(prefabName, preset, true);
}
private void WriteData(String prefabName, ContainerPreset preset, Boolean isNpc) {
Dictionary<String, ContainerPreset> dict = isNpc ? npcContainers : dataContainers;
if (!dict.ContainsKey(prefabName)) return;
String folder = isNpc ? "NPCs" : "Containers";
String path = $"IQSystem/IQLoot/{folder}/{prefabName}";
try {
Interface.Oxide.DataFileSystem.WriteObject(path, preset);
Puts(LanguageEn ? $"A new container ({folder}) '{prefabName}' has been detected.It has been added to {path} and is disabled by default." : $"Обнаружен новый контейнер ({folder}) `{prefabName}`, он добавлен в {path} и по стандарту выключен");
}
catch (Exception ex) {
PrintError(LanguageEn ? $"Failed to write file {prefabName}: {ex.Message}" : $"Ошибка при записи файла {prefabName}: {ex.Message}");
}
}
private Boolean halfWarningShown = false;
private Boolean IsValidLootPrefab(String prefabName) => lootPrefabKeys.Any(validKey => prefabName.IndexOf(validKey, StringComparison.OrdinalIgnoreCase) >= 0);
private static (Int32 min, Int32 max) EstimateMinMaxLoot(LootContainer container) {
Int32 minTotal = 0;
Int32 maxTotal = 0;
if (container.LootSpawnSlots is {
Length : > 0
}
) {
(Int32 min, Int32 max) slotsInfo = EstimateMinMaxLootSpawnSloot(container.LootSpawnSlots);
minTotal = Math.Max(1, slotsInfo.min - GetOffset(slotsInfo.min));
maxTotal = slotsInfo.max;
}
else if (container.lootDefinition) {
minTotal = Math.Max(1, container.maxDefinitionsToSpawn - GetOffset(container.maxDefinitionsToSpawn));
maxTotal += container.maxDefinitionsToSpawn;
}
return (minTotal, maxTotal);
Int32 GetOffset(Int32 total) => minTotal <= 3 ? 0 : Math.Min(3, minTotal / 2);
}
private Boolean IsIgnoreLootPrefab(String prefabName) => lootPrefabKeysIgnore.Any(validKey => prefabName.IndexOf(validKey, StringComparison.OrdinalIgnoreCase) >= 0);
[HarmonyPatch(typeof(NPCPlayer), nameof(NPCPlayer.CreateCorpse))]
public class Patch_NPCPlayer_CreateCorpse {
[HarmonyPostfix]
public static void CreateCorpse_Postfix(BaseCorpse __result, NPCPlayer __instance) {
if (__result is not NPCPlayerCorpse corpse) return;
if (!instance.npcContainers.TryGetValue(__instance.ShortPrefabName, out ContainerPreset dataContainer) || !dataContainer.enabled) return;
if (corpse.containers.Length == 0 || !(corpse.containers[0] is ItemContainer inv)) return;
if (__instance.skinID != 0) return;
if (instance.ignoredOwnerIdsOtherPlugins.Contains(__instance.OwnerID)) return;
if (inv.itemList.Count > 0) {
inv.Clear();
ItemManager.DoRemoves();
}
Int32 slotAmount = dataContainer.amountLoot.GetAmount();
Item scrap = dataContainer.GetScrap();
if (scrap != null) {
if (!scrap.MoveToContainer(inv)) scrap.Remove();
else slotAmount--;
}
var presets = dataContainer.GetRandomLoots(slotAmount);
if (presets == null && scrap == null) {
Interface.Oxide.LogWarning($"WARNING: NPC loot nulled.Prefab: {corpse.ShortPrefabName}");
corpse.Kill();
return;
}
if (presets == null) return;
foreach (ContainerPreset.LootPreset loot in presets) {
Item item = loot.BuildItem();
if (item == null) continue;
if (!item.MoveToContainer(inv)) item.Remove();
}
}
}
private List<ItemAmountRanged> GetAllLootItems(HumanNPC npc) {
var items = new List<ItemAmountRanged>();
if (npc.LootSpawnSlots == null) return items;
foreach (LootContainer.LootSpawnSlot slot in npc.LootSpawnSlots) {
if (slot.definition) items.AddRange(ProcessLootSpawn(slot.definition));
}
return items;
}
private readonly HashSet<UInt64> ignoredOwnerIdsOtherPlugins = new HashSet<UInt64>() {
1235847, 8856355, 999073677, 577365, 3766520, 488855, 7655463
}
;
}
}