C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Facepunch;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Plugins;
using Steamworks;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace Oxide.Plugins {
[Info("AlphaLoot", "k1lly0u", "3.1.19")]
class AlphaLoot : RustPlugin {
#region Fields
[PluginReference]
Plugin CustomLootSpawns, EventLoot, FancyDrop;
private StoredData storedData;
private StoredData bradleyData;
private StoredData heliData;
private DynamicConfigFile data, bradley, heli, skinData;
private bool updateContainerCapacities = false;
private static Hash<string, HashSet<SkinEntry>> weightedSkinIds;
private static Hash<string, List<ulong>> importedSkinIds;
private static Hash<string, int> defaultScrapAmounts;
private readonly Hash<string, string> shortnameReplacements = new Hash<string, string> {
["chocholate"]
= "chocolate"
}
;
private const string ADMIN_PERMISSION = "alphaloot.admin";
private const string HELI_CRATE = "heli_crate";
private const string BRADLEY_CRATE = "bradley_crate";
#endregion
#region Oxide Hooks
private void Loaded() {
importedSkinIds = new Hash<string, List<ulong>>();
defaultScrapAmounts = new Hash<string, int>();
permission.RegisterPermission(ADMIN_PERMISSION, this);
LoadData();
if (updateContainerCapacities) SetCapacityLimits();
Unsubscribe(nameof(OnLootSpawn));
}
private void OnServerInitialized() {
PopulateContainerDefinitions(ref storedData, ref heliData, ref bradleyData);
SaveData();
Puts($"Loaded {storedData.loot_advanced.Count + storedData.loot_simple.Count} loot container definitions and {storedData.npcs_advanced.Count + storedData.npcs_simple.Count} npc loot definitions");
Puts($"Loaded {heliData.loot_advanced.Count + heliData.loot_simple.Count} heli loot profiles");
Puts($"Loaded {bradleyData.loot_advanced.Count + bradleyData.loot_simple.Count} bradley loot profiles");
if (configData.AutoUpdate) AutoUpdateItemLists();
FindAndRemoveInvalidItems();
Subscribe(nameof(OnLootSpawn));
if (configData.UseSkinboxSkins || configData.UseApprovedSkins) {
if ((Steamworks.SteamInventory.Definitions?.Length ?? 0) == 0) {
PrintWarning("Waiting for Steamworks to initialize to load item skins");
Steamworks.SteamInventory.OnDefinitionsUpdated += LoadSkins;
}
else LoadSkins();
}
else RefreshLootContents();
}
private void OnEntitySpawned(BradleyAPC bradleyApc) {
if (bradleyApc != null && configData.BradleyCrates > 0) bradleyApc.maxCratesToSpawn = configData.BradleyCrates;
}
private void OnEntitySpawned(BaseHelicopter baseHelicopter) {
if (baseHelicopter != null && configData.HelicopterCrates > 0) baseHelicopter.maxCratesToSpawn = configData.HelicopterCrates;
}
private object OnCorpsePopulate(BaseEntity entity, LootableCorpse corpse) {
if (entity == null || corpse == null) return null;
object obj = Interface.CallHook("CanPopulateLoot", entity, corpse);
if (obj != null) return null;
return PopulateLoot(entity, corpse) ? corpse : null;
}
private object OnLootSpawn(LootContainer container) {
if (container == null) return null;
if (CustomLootSpawns && (bool)CustomLootSpawns.Call("IsLootBox", container as BaseEntity)) return null;
if (EventLoot && (bool)EventLoot.Call("IsEventLootContainer", container as BaseEntity)) return null;
if (FancyDrop && container is SupplyDrop && !configData.OverrideFancyDrop) return null;
object obj = Interface.CallHook("CanPopulateLoot", container);
if (obj != null) return null;
if (PopulateLoot(container)) return true;
return null;
}
private object OnItemUnwrap(Item item, BasePlayer player, ItemModUnwrap itemModUnwrap) {
if (PopulateLoot(itemModUnwrap, item, player)) {
item.UseItem(1);
if (itemModUnwrap.successEffect.isValid) Effect.server.Run(itemModUnwrap.successEffect.resourcePath, player.eyes.position, new Vector3(), null, false);
return true;
}
return null;
}
private void OnHammerHit(BasePlayer player, HitInfo info) {
if (player == null || info == null || !info.HitEntity) return;
if (!permission.UserHasPermission(player.UserIDString, ADMIN_PERMISSION)) return;
LootContainer lootContainer = info.HitEntity.GetComponent<LootContainer>();
if (lootContainer == null) return;
player.ChatMessage($"Viewing loot generation for: <color=#ffff00>{lootContainer.ShortPrefabName}</color>");
lootContainer.gameObject.AddComponent<LootCycler>();
player.inventory.loot.StartLootingEntity(lootContainer, false);
player.inventory.loot.AddContainer(lootContainer.inventory);
player.inventory.loot.SendImmediate();
player.ClientRPCPlayer(null, player, "RPC_OpenLootPanel", lootContainer.panelName);
}
private void OnLootEntityEnd(BasePlayer player, StorageContainer container) {
if (!container) return;
LootCycler lootCycler = container.GetComponent<LootCycler>();
if (lootCycler != null) UnityEngine.Object.Destroy(lootCycler);
}
private void Unload() {
RefreshLootContents(null, true);
LootCycler[] lootCyclers = UnityEngine.Object.FindObjectsOfType<LootCycler>();
for (int i = 0; i < lootCyclers?.Length; i++) UnityEngine.Object.Destroy(lootCyclers[i]);
configData = null;
weightedSkinIds = null;
importedSkinIds = null;
defaultScrapAmounts = null;
}
#endregion
#region Skins
private void LoadSkins() {
Steamworks.SteamInventory.OnDefinitionsUpdated -= LoadSkins;
if (configData.UseApprovedSkins) PopulateSkinListFromApproved();
RefreshLootContents();
}
private void OnSkinBoxSkinsLoaded(Hash<string, HashSet<ulong>> skinList) {
if (!configData.UseSkinboxSkins) return;
foreach (KeyValuePair<string, HashSet<ulong>> kvp in skinList) {
List<ulong> list;
if (!importedSkinIds.TryGetValue(kvp.Key, out list)) list = importedSkinIds[kvp.Key] = new List<ulong>();
foreach(ulong skinId in kvp.Value) {
if (!list.Contains(skinId)) list.Add(skinId);
}
}
}
private void OnPlayerSkinsSkinsLoaded(Hash<string, HashSet<ulong>> skinList) {
if (!configData.UsePlayerSkinsSkins) return;
foreach (KeyValuePair<string, HashSet<ulong>> kvp in skinList) {
List<ulong> list;
if (!importedSkinIds.TryGetValue(kvp.Key, out list)) list = importedSkinIds[kvp.Key] = new List<ulong>();
foreach (ulong skinId in kvp.Value) {
if (!list.Contains(skinId)) list.Add(skinId);
}
}
}
private void PopulateSkinListFromApproved() {
if (importedSkinIds == null) importedSkinIds = new Hash<string, List<ulong>>();
List<int> itemSkinDirectory = Pool.GetList<int>();
itemSkinDirectory.AddRange(ItemSkinDirectory.Instance.skins.Select(x => x.id));
int count = 0;
foreach (InventoryDef item in Steamworks.SteamInventory.Definitions) {
string shortname = item.GetProperty("itemshortname");
if (string.IsNullOrEmpty(shortname) || item.Id < 100) continue;
ulong wsid;
if (itemSkinDirectory.Contains(item.Id)) wsid = (ulong)item.Id;
else {
if (!ulong.TryParse(item.GetProperty("workshopid"), out wsid)) continue;
}
if (!importedSkinIds.ContainsKey(shortname)) importedSkinIds[shortname] = new List<ulong>();
if (!importedSkinIds[shortname].Contains(wsid)) {
importedSkinIds[shortname].Add(wsid);
count++;
}
}
Puts($"Imported {count} approved skins that weren't already in the list");
Pool.FreeList(ref itemSkinDirectory);
}
#endregion
#region Container Population
private void RefreshLootContents(ConsoleSystem.Arg arg = null, bool setDefaultScrap = false) {
LootContainer[] lootContainers = UnityEngine.Object.FindObjectsOfType<LootContainer>();
if (arg != null) SendReply(arg, $"Repopulating loot for {lootContainers?.Length} containers");
else Puts($"Repopulating loot for {lootContainers?.Length} containers");
for (int i = 0; i < lootContainers?.Length; i++) {
LootContainer lootContainer = lootContainers[i];
if (lootContainer != null && !lootContainer.IsDestroyed) {
if (lootContainer.inventory == null) {
lootContainer.CreateInventory(true);
lootContainer.OnInventoryFirstCreated(lootContainer.inventory);
}
if (setDefaultScrap) {
int scrapAmount;
if (defaultScrapAmounts.TryGetValue(lootContainer.ShortPrefabName, out scrapAmount)) lootContainer.scrapAmount = scrapAmount;
}
lootContainer.inventory.capacity = lootContainer.inventorySlots;
lootContainer.CancelInvoke(lootContainer.SpawnLoot);
lootContainer.Invoke(lootContainer.SpawnLoot, UnityEngine.Random.Range(1f, 20f));
}
}
}
private void CreateLootDefinitionFor(LootContainer lootContainer, ref StoredData storedData, ref StoredData heliData, ref StoredData bradleyData) {
if (!defaultScrapAmounts.ContainsKey(lootContainer.ShortPrefabName)) defaultScrapAmounts[lootContainer.ShortPrefabName] = lootContainer.scrapAmount;
if (lootContainer.ShortPrefabName.Equals(HELI_CRATE)) {
BaseLootContainerProfile lootContainerProfile;
if (storedData.TryGetLootProfile(HELI_CRATE, out lootContainerProfile)) {
heliData.CloneLootProfile(HELI_CRATE, lootContainerProfile);
storedData.RemoveProfile(HELI_CRATE);
Debug.LogWarning($"Helicopter loot profiles have been removed from your loot table and placed in its own data file.(/data/AlphaLoot/LootProfiles/{configData.HeliProfileName}.json)");
}
else {
if (!heliData.HasAnyProfiles) heliData.CreateDefaultLootProfile(lootContainer);
}
}
else if (lootContainer.ShortPrefabName.Equals(BRADLEY_CRATE)) {
BaseLootContainerProfile lootContainerProfile;
if (storedData.TryGetLootProfile(BRADLEY_CRATE, out lootContainerProfile)) {
bradleyData.CloneLootProfile(BRADLEY_CRATE, lootContainerProfile);
storedData.RemoveProfile(BRADLEY_CRATE);
Debug.LogWarning($"Bradley loot profiles have been removed from your loot table and placed in its own data file.(/data/AlphaLoot/LootProfiles/{configData.BradleyProfileName}.json)");
}
else {
if (!bradleyData.HasAnyProfiles) bradleyData.CreateDefaultLootProfile(lootContainer);
}
}
else storedData.CreateDefaultLootProfile(lootContainer);
}
private void CreateLootDefinitionFor(NPCPlayer npcPlayer, ref StoredData storedData) {
global : : HumanNPC humanNPC = npcPlayer as global : : HumanNPC;
if (humanNPC != null) {
storedData.CreateDefaultLootProfile(npcPlayer.ShortPrefabName, humanNPC.LootSpawnSlots);
return;
}
ScarecrowNPC scarecrowNPC = npcPlayer as ScarecrowNPC;
if (scarecrowNPC != null) {
storedData.CreateDefaultLootProfile(npcPlayer.ShortPrefabName, scarecrowNPC.LootSpawnSlots);
return;
}
}
private void PopulateContainerDefinitions(ref StoredData storedData, ref StoredData heliData, ref StoredData bradleyData) {
storedData.IsBaseLootTable = true;
int loot = 0;
int npc = 0;
int item = 0;
foreach (KeyValuePair<string, Object> kvp in FileSystem.Backend.cache) {
if (kvp.Value is GameObject) {
LootContainer lootContainer = (kvp.Value as GameObject).GetComponent<LootContainer>();
if (lootContainer != null) {
loot++;
CreateLootDefinitionFor(lootContainer, ref storedData, ref heliData, ref bradleyData);
continue;
}
NPCPlayer npcPlayer = (kvp.Value as GameObject).GetComponent<NPCPlayer>();
if (npcPlayer != null) {
npc++;
CreateLootDefinitionFor(npcPlayer, ref storedData);
continue;
}
}
}
foreach(ItemDefinition itemDefinition in ItemManager.itemList) {
ItemModUnwrap itemModUnwrap = itemDefinition.GetComponentInChildren<ItemModUnwrap>();
if (itemModUnwrap != null) {
storedData.CreateDefaultLootProfile(itemDefinition, itemModUnwrap);
item++;
}
}
Debug.Log($"Found {loot} loot containers, {npc} NPC prefabs and {item} unwrapable items in bundles");
}
private bool PopulateLoot(LootContainer container) {
BaseLootContainerProfile lootProfile;
if (container.ShortPrefabName.Equals(HELI_CRATE)) {
if (heliData.GetRandomLootProfile(out lootProfile)) {
PopulateLootContainer(container, lootProfile);
return true;
}
}
else if (container.ShortPrefabName.Equals(BRADLEY_CRATE)) {
if (bradleyData.GetRandomLootProfile(out lootProfile)) {
PopulateLootContainer(container, lootProfile);
return true;
}
}
else {
if (storedData.TryGetLootProfile(container.ShortPrefabName, out lootProfile) && lootProfile.Enabled) {
PopulateLootContainer(container, lootProfile);
return true;
}
}
return false;
}
private bool PopulateLoot(ItemModUnwrap itemModUnwrap, Item item, BasePlayer player) {
BaseLootContainerProfile lootProfile;
if (storedData.TryGetLootProfile(item.info.shortname, out lootProfile) && lootProfile.Enabled) {
int attempts = UnityEngine.Random.Range(itemModUnwrap.minTries, itemModUnwrap.maxTries + 1);
for (int i = 0; i < attempts; i++) lootProfile.PopulateLoot(player.inventory.containerMain);
return true;
}
return false;
}
private bool PopulateLoot(BaseEntity entity, LootableCorpse corpse) {
BaseLootProfile lootProfile;
if (storedData.TryGetNPCProfile(entity.ShortPrefabName, out lootProfile)) {
if (!lootProfile.Enabled) return false;
lootProfile.PopulateLoot(corpse.containers[0]);
return true;
}
return false;
}
private void PopulateLootContainer(LootContainer container, BaseLootContainerProfile lootProfile) {
container.destroyOnEmpty = lootProfile.DestroyOnEmpty;
lootProfile.PopulateLoot(container.inventory);
container.CancelInvoke(container.SpawnLoot);
if (lootProfile.ShouldRefreshContents) container.Invoke(container.SpawnLoot, UnityEngine.Random.Range(lootProfile.MinSecondsBetweenRefresh, lootProfile.MaxSecondsBetweenRefresh));
}
#endregion
#region Functions
private string ToShortName(string name) {
return name.Split('/').Last().Replace(".prefab", "");
}
private LootContainer FindContainer(BasePlayer player) {
RaycastHit raycastHit;
if (Physics.Raycast(player.eyes.HeadRay(), out raycastHit, 20f)) {
LootContainer lootContainer = raycastHit.GetEntity() as LootContainer;
return lootContainer;
}
return null;
}
private object WantsToHandleFancyDropLoot() => configData.OverrideFancyDrop ? (object)true : null;
#endregion
#region Auto-Updater
private void AutoUpdateItemLists() {
const string lastDefaultTable = "AlphaLoot/AutoUpdater/do_not_edit_this_file";
ItemList lastItemList;
if (Interface.Oxide.DataFileSystem.ExistsDatafile(lastDefaultTable)) {
lastItemList = Interface.Oxide.DataFileSystem.GetFile(lastDefaultTable).ReadObject<ItemList>();
if (lastItemList != null) {
if (lastItemList.protocol == Rust.Protocol.printable) {
Debug.Log("[AlphaLoot Auto Updater] - Last item list protocol matches current protocol.No new items added");
return;
}
List<int> newItems = Pool.GetList<int>();
ItemManager.itemList.ForEach(x => {
if (!lastItemList.itemIds.Contains(x.itemid)) newItems.Add(x.itemid);
}
);
if (newItems.Count > 0) {
int additions = 0;
Debug.Log($"[AlphaLoot Auto Updater] - Found {newItems.Count} new game items.Adding them to your loot table");
AddItemsToLootTable(newItems, out additions);
if (additions > 0) {
Debug.Log($"[AlphaLoot Auto Updater] - Added {additions} new loot definitions to the loot table");
SaveData();
Interface.Oxide.DataFileSystem.WriteObject<ItemList>(lastDefaultTable, new ItemList() {
itemIds = ItemManager.itemDictionary.Keys.ToList(), protocol = Rust.Protocol.printable
}
);
}
}
else Debug.Log("[AlphaLoot Auto Updater] - No new items in game");
Pool.FreeList(ref newItems);
}
}
else {
Debug.Log("[AlphaLoot Auto Updater] - Generating item list for auto-updater.Future game updates will automatically add new items to your loot table.You can disable this feature in the config");
Interface.Oxide.DataFileSystem.WriteObject<ItemList>(lastDefaultTable, new ItemList() {
itemIds = ItemManager.itemDictionary.Keys.ToList(), protocol = Rust.Protocol.printable
}
);
}
}
private void AddSpecifiedItemsToLootTable(params string[] args) {
int additions = 0;
List<int> items = Pool.GetList<int>();
foreach(string str in args) {
ItemDefinition itemDefinition = ItemManager.FindItemDefinition(str);
if (itemDefinition != null) items.Add(itemDefinition.itemid);
}
if (items.Count > 0) {
Debug.Log($"[AlphaLoot] - Adding {items.Count} specified items to your loot table");
AddItemsToLootTable(items, out additions);
if (additions > 0) {
Debug.Log($"[AlphaLoot] - Successfully added {additions} new loot definitions to the loot table");
SaveData();
}
}
else Debug.Log("[AlphaLoot] - Failed to find item definitions for the shortname's supplied");
Pool.FreeList(ref items);
}
private void AddItemsToLootTable(List<int> items, out int additions) {
additions = 0;
StoredData defaultLootTable = new StoredData();
StoredData defaultHeliLootTable = new StoredData();
StoredData defaultBradleyLootTable = new StoredData();
PopulateContainerDefinitions(ref defaultLootTable, ref defaultHeliLootTable, ref defaultBradleyLootTable);
foreach (KeyValuePair<string, AdvancedLootContainerProfile> kvp in defaultLootTable.loot_advanced) {
foreach (int itemid in items) {
string shortname = ItemManager.itemDictionary[itemid].shortname;
NewLootItem newLootItem = new NewLootItem();
float score = 0;
int multiplier = 1;
foreach (LootSpawnSlot lootSpawnSlot in kvp.Value.LootSpawnSlots) {
FindItemAndCalculateScoreRecursive(lootSpawnSlot.LootDefinition, shortname, ref newLootItem, ref multiplier);
score += (lootSpawnSlot.Probability * newLootItem.Score) * lootSpawnSlot.NumberToSpawn;
}
if (score > 0) {
AdvancedLootContainerProfile advancedLootProfile;
SimpleLootContainerProfile simpleLootContainerProfile;
if (storedData.loot_advanced.TryGetValue(kvp.Key, out advancedLootProfile)) {
LootSpawnSlot newLootSpawnSlot = new LootSpawnSlot {
LootDefinition = new LootSpawn() {
Items = new ItemAmountRanged[] {
newLootItem.Item
}
, SubSpawn = new LootSpawn.Entry[0]
}
, NumberToSpawn = 1, Probability = Mathf.Clamp01(score * multiplier)
}
;
int index = advancedLootProfile.LootSpawnSlots.Length;
System.Array.Resize(ref advancedLootProfile.LootSpawnSlots, index + 1);
advancedLootProfile.LootSpawnSlots[index] = newLootSpawnSlot;
additions++;
Debug.Log($"[AlphaLoot] - Added {shortname} to advanced loot profile ({kvp.Key}) with a calculated probability of {newLootSpawnSlot.Probability}");
}
else if (storedData.loot_simple.TryGetValue(kvp.Key, out simpleLootContainerProfile)) {
ItemAmountWeighted itemAmountWeighted = new ItemAmountWeighted() {
BlueprintChance = newLootItem.Item.BlueprintChance, Condition = new ItemAmount.ConditionItem() {
MinCondition = newLootItem.Item.Condition.MinCondition, MaxCondition = newLootItem.Item.Condition.MaxCondition
}
, MaxAmount = newLootItem.Item.MaxAmount, MinAmount = newLootItem.Item.MinAmount, Shortname = shortname, Weight = Mathf.Max(Mathf.RoundToInt(((float)simpleLootContainerProfile.Items.Sum(x => x.Weight) * score) * multiplier), 1)
}
;
int index = simpleLootContainerProfile.Items.Length;
System.Array.Resize(ref simpleLootContainerProfile.Items, index + 1);
simpleLootContainerProfile.Items[index] = itemAmountWeighted;
additions++;
Debug.Log($"[AlphaLoot] - Added {shortname} to simple loot profile ({kvp.Key}) with a calculated weight of {itemAmountWeighted.Weight}");
}
continue;
}
}
}
foreach (KeyValuePair<string, AdvancedNPCLootProfile> kvp in defaultLootTable.npcs_advanced) {
foreach (int itemid in items) {
string shortname = ItemManager.itemDictionary[itemid].shortname;
NewLootItem newLootItem = new NewLootItem();
float score = 0;
int multiplier = 1;
foreach (LootSpawnSlot lootSpawnSlot in kvp.Value.LootSpawnSlots) {
FindItemAndCalculateScoreRecursive(lootSpawnSlot.LootDefinition, shortname, ref newLootItem, ref multiplier);
score += (lootSpawnSlot.Probability * newLootItem.Score) * lootSpawnSlot.NumberToSpawn;
}
if (score > 0) {
AdvancedNPCLootProfile advancedNPCLootProfile;
SimpleNPCLootProfile simpleNPCLootProfile;
if (storedData.npcs_advanced.TryGetValue(kvp.Key, out advancedNPCLootProfile)) {
LootSpawnSlot newLootSpawnSlot = new LootSpawnSlot {
LootDefinition = new LootSpawn() {
Items = new ItemAmountRanged[] {
newLootItem.Item
}
, SubSpawn = new LootSpawn.Entry[0]
}
, NumberToSpawn = 1, Probability = Mathf.Clamp01(score * multiplier)
}
;
int index = advancedNPCLootProfile.LootSpawnSlots.Length;
System.Array.Resize(ref advancedNPCLootProfile.LootSpawnSlots, index + 1);
advancedNPCLootProfile.LootSpawnSlots[index] = newLootSpawnSlot;
additions++;
Debug.Log($"[AlphaLoot] - Added {shortname} to advanced NPC profile ({kvp.Key}) with a calculated probability of {newLootSpawnSlot.Probability}");
}
else if (storedData.npcs_simple.TryGetValue(kvp.Key, out simpleNPCLootProfile)) {
ItemAmountWeighted itemAmountWeighted = new ItemAmountWeighted() {
BlueprintChance = newLootItem.Item.BlueprintChance, Condition = new ItemAmount.ConditionItem() {
MinCondition = newLootItem.Item.Condition.MinCondition, MaxCondition = newLootItem.Item.Condition.MaxCondition
}
, MaxAmount = newLootItem.Item.MaxAmount, MinAmount = newLootItem.Item.MinAmount, Shortname = shortname, Weight = Mathf.Max(Mathf.RoundToInt(((float)simpleNPCLootProfile.Items.Sum(x => x.Weight) * score) * multiplier), 1)
}
;
int index = simpleNPCLootProfile.Items.Length;
System.Array.Resize(ref simpleNPCLootProfile.Items, index + 1);
simpleNPCLootProfile.Items[index] = itemAmountWeighted;
additions++;
Debug.Log($"[AlphaLoot] - Added {shortname} to simple NPC profile ({kvp.Key}) with a calculated weight of {itemAmountWeighted.Weight}");
}
continue;
}
}
}
foreach (KeyValuePair<string, AdvancedLootContainerProfile> kvp in defaultHeliLootTable.loot_advanced) {
foreach (int itemid in items) {
string shortname = ItemManager.itemDictionary[itemid].shortname;
NewLootItem newLootItem = new NewLootItem();
float score = 0;
int multiplier = 1;
foreach (LootSpawnSlot lootSpawnSlot in kvp.Value.LootSpawnSlots) {
FindItemAndCalculateScoreRecursive(lootSpawnSlot.LootDefinition, shortname, ref newLootItem, ref multiplier);
score += (lootSpawnSlot.Probability * newLootItem.Score) * lootSpawnSlot.NumberToSpawn;
}
if (score > 0) {
AdvancedLootContainerProfile advancedLootProfile;
if (heliData.loot_advanced.TryGetValue(kvp.Key, out advancedLootProfile)) {
LootSpawnSlot newLootSpawnSlot = new LootSpawnSlot {
LootDefinition = new LootSpawn() {
Items = new ItemAmountRanged[] {
newLootItem.Item
}
, SubSpawn = new LootSpawn.Entry[0]
}
, NumberToSpawn = 1, Probability = Mathf.Clamp01(score * multiplier)
}
;
int index = advancedLootProfile.LootSpawnSlots.Length;
System.Array.Resize(ref advancedLootProfile.LootSpawnSlots, index + 1);
advancedLootProfile.LootSpawnSlots[index] = newLootSpawnSlot;
additions++;
Debug.Log($"[AlphaLoot] - Added {shortname} to advanced heli loot profile ({kvp.Key}) with a calculated probability of {newLootSpawnSlot.Probability}");
}
else {
SimpleLootContainerProfile simpleLootContainerProfile;
if (heliData.loot_simple.TryGetValue(kvp.Key, out simpleLootContainerProfile)) {
ItemAmountWeighted itemAmountWeighted = new ItemAmountWeighted() {
BlueprintChance = newLootItem.Item.BlueprintChance, Condition = new ItemAmount.ConditionItem() {
MinCondition = newLootItem.Item.Condition.MinCondition, MaxCondition = newLootItem.Item.Condition.MaxCondition
}
, MaxAmount = newLootItem.Item.MaxAmount, MinAmount = newLootItem.Item.MinAmount, Shortname = shortname, Weight = Mathf.Max(Mathf.RoundToInt(((float)simpleLootContainerProfile.Items.Sum(x => x.Weight) * score) * multiplier), 1)
}
;
int index = simpleLootContainerProfile.Items.Length;
System.Array.Resize(ref simpleLootContainerProfile.Items, index + 1);
simpleLootContainerProfile.Items[index] = itemAmountWeighted;
additions++;
Debug.Log($"[AlphaLoot] - Added {shortname} to simple heli loot profile ({kvp.Key}) with a calculated weight of {itemAmountWeighted.Weight}");
}
}
continue;
}
}
}
foreach (KeyValuePair<string, AdvancedLootContainerProfile> kvp in defaultBradleyLootTable.loot_advanced) {
foreach (int itemid in items) {
string shortname = ItemManager.itemDictionary[itemid].shortname;
NewLootItem newLootItem = new NewLootItem();
float score = 0;
int multiplier = 1;
foreach (LootSpawnSlot lootSpawnSlot in kvp.Value.LootSpawnSlots) {
FindItemAndCalculateScoreRecursive(lootSpawnSlot.LootDefinition, shortname, ref newLootItem, ref multiplier);
score += (lootSpawnSlot.Probability * newLootItem.Score) * lootSpawnSlot.NumberToSpawn;
}
if (score > 0) {
AdvancedLootContainerProfile advancedLootProfile;
if (bradleyData.loot_advanced.TryGetValue(kvp.Key, out advancedLootProfile)) {
LootSpawnSlot newLootSpawnSlot = new LootSpawnSlot {
LootDefinition = new LootSpawn() {
Items = new ItemAmountRanged[] {
newLootItem.Item
}
, SubSpawn = new LootSpawn.Entry[0]
}
, NumberToSpawn = 1, Probability = Mathf.Clamp01(score * multiplier)
}
;
int index = advancedLootProfile.LootSpawnSlots.Length;
System.Array.Resize(ref advancedLootProfile.LootSpawnSlots, index + 1);
advancedLootProfile.LootSpawnSlots[index] = newLootSpawnSlot;
additions++;
Debug.Log($"[AlphaLoot] - Added {shortname} to advanced bradley loot profile ({kvp.Key}) with a calculated probability of {newLootSpawnSlot.Probability}");
}
else {
SimpleLootContainerProfile simpleLootContainerProfile;
if (bradleyData.loot_simple.TryGetValue(kvp.Key, out simpleLootContainerProfile)) {
ItemAmountWeighted itemAmountWeighted = new ItemAmountWeighted() {
BlueprintChance = newLootItem.Item.BlueprintChance, Condition = new ItemAmount.ConditionItem() {
MinCondition = newLootItem.Item.Condition.MinCondition, MaxCondition = newLootItem.Item.Condition.MaxCondition
}
, MaxAmount = newLootItem.Item.MaxAmount, MinAmount = newLootItem.Item.MinAmount, Shortname = shortname, Weight = Mathf.Max(Mathf.RoundToInt(((float)simpleLootContainerProfile.Items.Sum(x => x.Weight) * score) * multiplier), 1)
}
;
int index = simpleLootContainerProfile.Items.Length;
System.Array.Resize(ref simpleLootContainerProfile.Items, index + 1);
simpleLootContainerProfile.Items[index] = itemAmountWeighted;
additions++;
Debug.Log($"[AlphaLoot] - Added {shortname} to simple bradley loot profile ({kvp.Key}) with a calculated weight of {itemAmountWeighted.Weight}");
}
}
continue;
}
}
}
}
private void FindItemAndCalculateScoreRecursive(LootSpawn lootSpawn, string shortname, ref NewLootItem newLootItem, ref int multiplier) {
if (lootSpawn.SubSpawn.Length > 0) {
foreach(LootSpawn.Entry lootSpawnEntry in lootSpawn.SubSpawn) {
FindItemAndCalculateScoreRecursive(lootSpawnEntry.Category, shortname, ref newLootItem, ref multiplier);
if (newLootItem.HasItem) {
if (newLootItem.Score == 0) newLootItem.Score = (float)lootSpawnEntry.Weight / (float)lootSpawn.SubSpawn.Sum(x => x.Weight);
else newLootItem.Score *= (float)lootSpawnEntry.Weight / (float)lootSpawn.SubSpawn.Sum(x => x.Weight);
return;
}
}
}
else if (lootSpawn.Items.Length > 0) {
foreach(ItemAmountRanged itemAmountRanged in lootSpawn.Items) {
if (itemAmountRanged.Shortname == shortname) {
if (newLootItem.HasItem) multiplier++;
newLootItem.Item = itemAmountRanged;
if (lootSpawn.Items.Length == 1 && newLootItem.Score == 0) newLootItem.Score = 1;
}
}
}
}
private class NewLootItem {
public ItemAmountRanged Item;
public float Score;
public bool HasItem => Item != null;
public void Clear() {
Item = null;
Score = 0;
}
}
private class ItemList {
public List<int> itemIds = new List<int>();
public string protocol;
}
#endregion
#region Removed Item Scan
private List<ItemAmountRanged> keepItemsRanged = new List<ItemAmountRanged>();
private List<ItemAmountWeighted> keepItemsWeighted = new List<ItemAmountWeighted>();
private void FindAndRemoveInvalidItems() {
Puts("Scanning loot tables for removed items...");
FindAndRemoveInvalidItems(data, storedData, "Loot Table");
FindAndRemoveInvalidItems(heli, heliData, "Heli Loot Table");
FindAndRemoveInvalidItems(bradley, bradleyData, "Bradley Loot Table");
Puts("Removed item scan completed!");
}
private void FindAndRemoveInvalidItems(DynamicConfigFile dynamicConfigFile, StoredData data, string table) {
Puts($"Scanning {table}...");
bool shouldSave = false;
foreach (KeyValuePair<string, AdvancedLootContainerProfile> kvp in data.loot_advanced) {
foreach (LootSpawnSlot lootSpawnSlot in kvp.Value.LootSpawnSlots) FindItemRemoveInvalidItemRecursive(kvp.Key, ref lootSpawnSlot.LootDefinition, ref shouldSave);
}
foreach (KeyValuePair<string, SimpleLootContainerProfile> kvp in storedData.loot_simple) FindItemRemoveInvalidItemRecursive(kvp.Key, ref kvp.Value.Items, ref shouldSave);
foreach (KeyValuePair<string, AdvancedNPCLootProfile> kvp in data.npcs_advanced) {
foreach (LootSpawnSlot lootSpawnSlot in kvp.Value.LootSpawnSlots) FindItemRemoveInvalidItemRecursive(kvp.Key, ref lootSpawnSlot.LootDefinition, ref shouldSave);
}
foreach (KeyValuePair<string, SimpleNPCLootProfile> kvp in storedData.npcs_simple) FindItemRemoveInvalidItemRecursive(kvp.Key, ref kvp.Value.Items, ref shouldSave);
if (shouldSave) dynamicConfigFile.WriteObject(data);
}
private void FindItemRemoveInvalidItemRecursive(string container, ref LootSpawn lootSpawn, ref bool shouldSave) {
if (lootSpawn.SubSpawn.Length > 0) {
foreach (LootSpawn.Entry lootSpawnEntry in lootSpawn.SubSpawn) {
FindItemRemoveInvalidItemRecursive(container, ref lootSpawnEntry.Category, ref shouldSave);
}
}
else if (lootSpawn.Items.Length > 0) {
bool shouldUpdate = false;
for (int i = 0; i < lootSpawn.Items.Length; i++) {
ItemAmountRanged itemAmountRanged = lootSpawn.Items[i];
if (ItemManager.itemDictionaryByName.ContainsKey(itemAmountRanged.Shortname)) keepItemsRanged.Add(itemAmountRanged);
else {
string shortname;
if (shortnameReplacements.TryGetValue(itemAmountRanged.Shortname, out shortname) && ItemManager.itemDictionaryByName.ContainsKey(shortname)) {
Puts($"Replacing invalid shortname {itemAmountRanged.Shortname} with {shortname} in {container}");
itemAmountRanged.Shortname = shortname;
keepItemsRanged.Add(itemAmountRanged);
shouldSave = true;
}
else {
shouldSave = true;
shouldUpdate = true;
Puts($"Removing {itemAmountRanged.Shortname} from {container}");
}
}
}
if (shouldUpdate) lootSpawn.Items = keepItemsRanged.ToArray();
keepItemsRanged.Clear();
}
}
private void FindItemRemoveInvalidItemRecursive(string container, ref ItemAmountWeighted[] items, ref bool shouldSave) {
bool shouldUpdate = false;
for (int i = 0; i < items.Length; i++) {
ItemAmountWeighted itemAmountWeighted = items[i];
if (ItemManager.itemDictionaryByName.ContainsKey(itemAmountWeighted.Shortname)) keepItemsWeighted.Add(itemAmountWeighted);
else {
shouldSave = true;
shouldUpdate = true;
Puts($"Removing '{itemAmountWeighted.Shortname}' from container '{container}'");
}
}
if (shouldUpdate) items = keepItemsWeighted.ToArray();
keepItemsWeighted.Clear();
}
#endregion
#region Components
private class LootCycler : MonoBehaviour {
private LootContainer lootContainer;
private void Awake() {
lootContainer = GetComponent<LootContainer>();
InvokeHandler.InvokeRepeating(this, lootContainer.SpawnLoot, 1f, 1f);
}
private void OnDestroy() {
InvokeHandler.CancelInvoke(this, lootContainer.SpawnLoot);
}
}
#endregion
#region Commands
[ChatCommand("aloot")]
private void cmdRepopulateTarget(BasePlayer player, string command, string[] args) {
if (!permission.UserHasPermission(player.UserIDString, ADMIN_PERMISSION)) {
SendReply(player, "You do not have permission to use this command");
return;
}
if (args == null || args.Length == 0) {
SendReply(player, "/aloot repopulate - Repopulate the container you are looking at");
SendReply(player, "/aloot view - List the contents of the container you are looking at");
SendReply(player, "/aloot repopulateall - Repopulate every loot container on the map (can take upto 20 seconds)");
return;
}
switch (args[0].ToLower()) {
case "repopulate" : {
LootContainer lootContainer = FindContainer(player);
if (lootContainer != null) {
lootContainer.CancelInvoke(lootContainer.SpawnLoot);
lootContainer.SpawnLoot();
SendReply(player, $"Refreshed loot contents for {lootContainer.ShortPrefabName}");
}
else SendReply(player, "No loot container found");
}
return;
case "view" : {
LootContainer lootContainer = FindContainer(player);
if (lootContainer != null) {
SendReply(player, $"Loot contents for {lootContainer.ShortPrefabName};");
SendReply(player, lootContainer.inventory.itemList.Select(x => $"{x.info.displayName.english} x{x.amount}").ToSentence());
}
else SendReply(player, "No loot container found");
}
return;
case "repopulateall" : {
SendReply(player, "Refreshing all loot containers...");
RefreshLootContents();
}
return;
default : SendReply(player, "Invalid syntax!");
break;
}
}
[ConsoleCommand("al.repopulateall")]
private void ccmdRepopulateAll(ConsoleSystem.Arg arg) {
if (arg.Connection != null) {
BasePlayer player = arg.Player();
if (player != null && !permission.UserHasPermission(player.UserIDString, ADMIN_PERMISSION)) {
SendReply(arg, "You do not have permission to use this command");
return;
}
}
RefreshLootContents(arg);
}
[ConsoleCommand("al.additems")]
private void ccmdAddItemsl(ConsoleSystem.Arg arg) {
if (arg.Connection != null) {
BasePlayer player = arg.Player();
if (player != null && !permission.UserHasPermission(player.UserIDString, ADMIN_PERMISSION)) {
SendReply(arg, "You do not have permission to use this command");
return;
}
}
if (arg.Args == null || arg.Args.Length == 0) {
SendReply(arg, "al.additems <shortname> <opt:shortname> <opt:shortname>...- Add the specified item(s) to your loot table.\nThis finds the containers the items are in from the default loot table, calculates a score and adds it to your existing loot table.\nYou can enter as many shortnames as you like");
return;
}
AddSpecifiedItemsToLootTable(arg.Args);
}
[ConsoleCommand("al.search")]
private void ccmdSearchItem(ConsoleSystem.Arg arg) {
if (arg.Connection != null) {
BasePlayer player = arg.Player();
if (player != null && !permission.UserHasPermission(player.UserIDString, ADMIN_PERMISSION)) {
SendReply(arg, "You do not have permission to use this command");
return;
}
}
string shortname = arg.GetString(0);
if (string.IsNullOrEmpty(shortname) || !ItemManager.itemDictionaryByName.ContainsKey(shortname)) {
SendReply(arg, "You must enter a valid item shortname to search");
return;
}
Hash<string, int> containers = new Hash<string, int>();
foreach (KeyValuePair<string, AdvancedLootContainerProfile> profile in storedData.loot_advanced) {
int count = 0;
foreach (LootSpawnSlot lootSpawnSlot in profile.Value.LootSpawnSlots) {
FindItemCountRecursive(lootSpawnSlot.LootDefinition, shortname, ref count);
}
if (count > 0) containers[profile.Key] = count;
}
foreach (KeyValuePair<string, AdvancedNPCLootProfile> profile in storedData.npcs_advanced) {
int count =
// ... [Code preview truncated due to length. Full source in attached file] ...