Untitled diff
549 lines
// #define DEBUG
// #define DEBUG
using Rust;
using Rust;
using System;
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Linq;
using System.Text;
using System.Text;
using System.Text.RegularExpressions;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine;
using Oxide.Core.Plugins;
using Oxide.Core.Plugins;
using Random = System.Random;
using Random = System.Random;
namespace Oxide.Plugins
namespace Oxide.Plugins
{
{
[Info("BetterLoot", "playrust.io / dcode", "1.9.7", ResourceId = 828)]
[Info("BetterLoot", "playrust.io / dcode", "1.9.7", ResourceId = 828)]
public class BetterLoot : RustPlugin
public class BetterLoot : RustPlugin
{
{
// To configure, use config/BetterLoot.json instead
// To configure, use config/BetterLoot.json instead
#region Configuration
#region Configuration
private double defaultBlueprintProbability = 0.11;
private double defaultBlueprintProbability = 0.11;
private int defaultMinItemsPerBarrel = 1;
private int defaultMinItemsPerBarrel = 1;
private int defaultMaxItemsPerBarrel = 3;
private int defaultMaxItemsPerBarrel = 3;
private int defaultMinItemsPerCrate = 3;
private int defaultMinItemsPerCrate = 3;
private int defaultMaxItemsPerCrate = 6;
private int defaultMaxItemsPerCrate = 6;
private double defaultBaseItemRarity = 2;
private double defaultBaseItemRarity = 2;
private double defaultBaseBlueprintRarity = 2;
private double defaultBaseBlueprintRarity = 2;
private int defaultRefreshMinutes = 15;
private int defaultRefreshMinutes = 15;
private bool defaultEnforceBlacklist = false;
private bool defaultEnforceBlacklist = false;
private bool defaultDropWeaponsWithAmmo = true;
private bool defaultDropWeaponsWithAmmo = true;
private Dictionary<string, int> defaultDropLimits = new Dictionary<string, int>() {
private Dictionary<string, int> defaultDropLimits = new Dictionary<string, int>() {
// Blueprint items
// Blueprint items
{ "blueprint_fragment", 1 },
{ "blueprint_fragment", 60 },
{ "blueprint_library", 1 },
{ "blueprint_library", 1 },
{ "blueprint_page", 1 },
{ "blueprint_page", 1 },
{ "blueprint_book", 1 },
{ "blueprint_book", 1 },
// Ammunition
// Ammunition
{ "ammo.handmade.shell", 32 },
{ "ammo.handmade.shell", 32 },
{ "ammo.pistol", 32 },
{ "ammo.pistol", 32 },
{ "ammo.pistol.fire", 8 },
{ "ammo.pistol.fire", 8 },
{ "ammo.pistol.hv", 8 },
{ "ammo.pistol.hv", 8 },
{ "ammo.rifle", 32 },
{ "ammo.rifle", 32 },
{ "ammo.rifle.explosive", 8 },
{ "ammo.rifle.explosive", 8 },
{ "ammo.rifle.hv", 8 },
{ "ammo.rifle.hv", 8 },
{ "ammo.rifle.incendiary", 8 },
{ "ammo.rifle.incendiary", 8 },
{ "ammo.rocket.basic", 3 },
{ "ammo.rocket.basic", 3 },
{ "ammo.rocket.fire", 1 },
{ "ammo.rocket.fire", 1 },
{ "ammo.rocket.hv", 1 },
{ "ammo.rocket.hv", 1 },
{ "ammo.shotgun", 32 },
{ "ammo.shotgun", 32 },
{ "ammo.shotgun.slug", 16 },
{ "ammo.shotgun.slug", 16 },
{ "arrow.hv", 8 },
{ "arrow.hv", 8 },
{ "arrow.wooden", 16 },
{ "arrow.wooden", 16 },
{ "gunpowder", 200 },
{ "gunpowder", 200 },
// Explosives
// Explosives
{ "explosive.timed", 2 },
{ "explosive.timed", 2 },
{ "explosives", 50 },
{ "explosives", 50 },
// Animal parts
// Animal parts
{ "bone.fragments", 200 },
{ "bone.fragments", 200 },
{ "cloth", 200 },
{ "cloth", 200 },
{ "fat.animal", 200 },
{ "fat.animal", 200 },
{ "lowgradefuel", 200 },
{ "lowgradefuel", 200 },
// Medical
// Medical
{ "antiradpills", 5 },
{ "antiradpills", 5 },
{ "bandage", 5 },
{ "bandage", 5 },
{ "blood", 200 },
{ "blood", 200 },
{ "largemedkit", 2 },
{ "largemedkit", 2 },
// Resources
// Resources
{ "hq.metal.ore", 500 },
{ "hq.metal.ore", 500 },
{ "metal.fragments", 1000 },
{ "metal.fragments", 1000 },
{ "metal.ore", 1000 },
{ "metal.ore", 1000 },
{ "stones", 1000 },
{ "stones", 1000 },
{ "sulfur", 1000 },
{ "sulfur", 1000 },
{ "sulfur.ore", 1000 },
{ "sulfur.ore", 1000 },
{ "wood", 1000 },
{ "wood", 1000 },
// Food
// Food
{ "apple", 10 },
{ "apple", 10 },
{ "black.raspberries", 10 },
{ "black.raspberries", 10 },
{ "blueberries", 10 },
{ "blueberries", 10 },
{ "can.beans", 5 },
{ "can.beans", 5 },
{ "can.tuna", 5 },
{ "can.tuna", 5 },
{ "chicken.cooked", 5 },
{ "chicken.cooked", 5 },
{ "chocholate", 5 },
{ "chocholate", 5 },
{ "granolabar", 5 },
{ "granolabar", 5 },
{ "smallwaterbottle", 5},
{ "smallwaterbottle", 5},
{ "wolfmeat.cooked", 5 }
{ "wolfmeat.cooked", 5 }
};
};
private Dictionary<string, int> defaultRarityOverrides = new Dictionary<string, int>() {
// Put DEBUG = true to log the complete list to Log.Warning file
// ammo.rocket.smoke
// autoturret
// lmg.m249
// mining.pumpjack
// mining.quarry
// pookie.bear
// xmas.present.large
// xmas.present.medium
// xmas.present.small
// rock
// santahat
// stash.small
// stocking.large
// stocking.small
// targeting.computer
{ "blueprint_fragment", 0 },
{ "blueprint_page", 1 },
{ "blueprint_book", 2 },
{ "blueprint_library", 3 },
// Resources
{ "metal.refined", 2},
{ "hq.metal.ore", 2 },
{ "metal.fragments", 1 },
{ "metal.ore", 1 },
{ "stones", 0 },
{ "sulfur", 1 },
{ "sulfur.ore", 1 },
{ "wood", 0 }
};
private double blueprintProbability;
private double blueprintProbability;
private int minItemsPerBarrel;
private int minItemsPerBarrel;
private int maxItemsPerBarrel;
private int maxItemsPerBarrel;
private int minItemsPerCrate;
private int minItemsPerCrate;
private int maxItemsPerCrate;
private int maxItemsPerCrate;
private double baseItemRarity;
private double baseItemRarity;
private double baseBlueprintRarity;
private double baseBlueprintRarity;
private int refreshMinutes;
private int refreshMinutes;
private List<string> itemBlacklist;
private List<string> itemBlacklist;
private List<string> blueprintBlacklist;
private List<string> blueprintBlacklist;
private bool enforceBlacklist;
private bool enforceBlacklist;
private bool dropWeaponsWithAmmo;
private bool dropWeaponsWithAmmo;
private Dictionary<string, int> dropLimits;
private Dictionary<string, int> dropLimits;
private Dictionary<string, int> rarityOverrides;
#endregion
#endregion
// Stuff that will simply never drop
// Stuff that will simply never drop
private List<string> neverDropped = new List<string>() {
private List<string> neverDropped = new List<string>() {
// Food
// Food
"apple.spoiled", "bearmeat", "chicken.burned", "chicken.spoiled", "wolfmeat.burned", "wolfmeat.spoiled",
"apple.spoiled", "bearmeat", "chicken.burned", "chicken.spoiled", "wolfmeat.burned", "wolfmeat.spoiled",
// Resources
// Resources
"blood", "battery.small", "paper", "skull.human", "skull.wolf"
"blood", "battery.small", "paper", "skull.human", "skull.wolf"
};
};
// Stuff that will never drop as a blueprint (default/not craftable)
// Stuff that will never drop as a blueprint (default/not craftable)
private List<string> blueprintsNeverDropped = new List<string>() {
private List<string> blueprintsNeverDropped = new List<string>() {
// Building
// Building
"lock.key", "cupboard.tool", "building.planner", "door.key", "sign.wooden.small",
"lock.key", "cupboard.tool", "building.planner", "door.key", "sign.wooden.small",
// Items
// Items
"campfire", "box.wooden", "sleepingbag", "furnace", "research.table",
"campfire", "box.wooden", "sleepingbag", "furnace", "research.table",
// Resources
// Resources
"paper", "lowgradefuel", "gunpowder",
"paper", "lowgradefuel", "gunpowder",
// Attire
// Attire
"burlap.shirt", "burlap.shoes", "burlap.trousers",
"burlap.shirt", "burlap.shoes", "burlap.trousers",
"attire.hide.boots", "attire.hide.pants", "attire.hide.poncho", "attire.hide.vest",
"attire.hide.boots", "attire.hide.pants", "attire.hide.poncho", "attire.hide.vest",
"urban.pants", "urban.boots",
"urban.pants", "urban.boots",
// Tools
// Tools
"hammer", "torch", "stonehatchet", "stone.pickaxe", "box.repair.bench",
"hammer", "torch", "stonehatchet", "stone.pickaxe", "box.repair.bench",
// Weapons
// Weapons
"spear.wooden", "knife.bone", "bow.hunting", "pistol.eoka",
"spear.wooden", "knife.bone", "bow.hunting", "pistol.eoka",
// Ammunition
// Ammunition
"arrow.wooden", "ammo.handmade.shell",
"arrow.wooden", "ammo.handmade.shell",
// Traps
// Traps
// Misc
// Misc
"supply.signal"
"supply.signal"
};
};
// Ammunition used by the different kinds of weapons
// Ammunition used by the different kinds of weapons
private Dictionary<string, string> weaponAmmunition = new Dictionary<string, string>() {
private Dictionary<string, string> weaponAmmunition = new Dictionary<string, string>() {
{ "bow.hunting", "arrow.wooden" },
{ "bow.hunting", "arrow.wooden" },
{ "pistol.eoka", "ammo.handmade.shell" },
{ "pistol.eoka", "ammo.handmade.shell" },
{ "pistol.revolver", "ammo.pistol" },
{ "pistol.revolver", "ammo.pistol" },
{ "shotgun.waterpipe", "ammo.shotgun" },
{ "shotgun.waterpipe", "ammo.shotgun" },
{ "shotgun.pump", "ammo.shotgun" },
{ "shotgun.pump", "ammo.shotgun" },
{ "smg.thompson", "ammo.pistol" },
{ "smg.thompson", "ammo.pistol" },
{ "rifle.bolt", "ammo.rifle" },
{ "rifle.bolt", "ammo.rifle" },
{ "rifle.ak", "ammo.rifle" }
{ "rifle.ak", "ammo.rifle" }
};
};
// Translations
// Translations
private List<string> texts = new List<string>() {
private List<string> texts = new List<string>() {
"A barrel drops up to %N% items, a chest up to %M% items.",
"A barrel drops up to %N% items, a chest up to %M% items.",
"Base item rarity is %N% and base blueprint rarity is %M%.",
"Base item rarity is %N% and base blueprint rarity is %M%.",
"There is a <color=#aef45b>%N%%</color> chance that any drop is a blueprint.",
"There is a <color=#aef45b>%N%%</color> chance that any drop is a blueprint.",
"There is a <color=#f4e75b>%P%%</color> chance to get one of %N% %RARITY% items.",
"There is a <color=#f4e75b>%P%%</color> chance to get one of %N% %RARITY% items.",
"There is a <color=#5bbcf4>%P%%</color> chance to get one of %N% %RARITY% blueprints.",
"There is a <color=#5bbcf4>%P%%</color> chance to get one of %N% %RARITY% blueprints.",
"Usage: /droplimit \"ITEMNAME\" [LIMIT]",
"Usage: /droplimit \"ITEMNAME\" [LIMIT]",
"You are not authorized to modify drop limits",
"You are not authorized to modify drop limits",
"No such item:",
"No such item:",
"Drop limit of '%NAME%' is %LIMIT%",
"Drop limit of '%NAME%' is %LIMIT%",
"Drop limit of '%NAME%' has been changed from %LIMIT% to %NEWLIMIT%",
"Drop limit of '%NAME%' has been changed from %LIMIT% to %NEWLIMIT%",
"Usage: /blacklist [additem|deleteitem|addbp|deletebp] \"ITEMNAME\"",
"Usage: /blacklist [additem|deleteitem|addbp|deletebp] \"ITEMNAME\"",
"There are no blacklisted items",
"There are no blacklisted items",
"Blacklisted items:",
"Blacklisted items:",
"There are no blacklisted blueprints",
"There are no blacklisted blueprints",
"Blacklisted blueprints:",
"Blacklisted blueprints:",
"You are not authorized to modify the blacklist",
"You are not authorized to modify the blacklist",
"Not a valid item:",
"Not a valid item:",
"The item '%NAME%' is now blacklisted",
"The item '%NAME%' is now blacklisted",
"The item '%NAME%' is already blacklisted",
"The item '%NAME%' is already blacklisted",
"Not a valid blueprint:",
"Not a valid blueprint:",
"The blueprint '%NAME%' is now blacklisted",
"The blueprint '%NAME%' is now blacklisted",
"The blueprint '{0}' is already blacklisted",
"The blueprint '{0}' is already blacklisted",
"The item '%NAME%' is now no longer blacklisted",
"The item '%NAME%' is now no longer blacklisted",
"The item '%NAME%' is not blacklisted",
"The item '%NAME%' is not blacklisted",
"The blueprint '%NAME%' is now no longer blacklisted",
"The blueprint '%NAME%' is now no longer blacklisted",
"The blueprint '%NAME' is not blacklisted",
"The blueprint '%NAME' is not blacklisted",
"<color=\"#ffd479\">/loot</color> - Displays the details on loot tables",
"<color=\"#ffd479\">/loot</color> - Displays the details on loot tables",
"<color=\"#ffd479\">/blacklist</color> - Displays which items are blacklisted",
"<color=\"#ffd479\">/blacklist</color> - Displays which items are blacklisted",
"common",
"common",
"uncommon",
"uncommon",
"rare",
"rare",
"very rare"
"very rare"
};
};
private Dictionary<string, string> messages = new Dictionary<string, string>();
private Dictionary<string, string> messages = new Dictionary<string, string>();
// Regular expressions defining what to override
// Regular expressions defining what to override
private Regex barrelEx = new Regex(@"loot-barrel|loot_trash");
private Regex barrelEx = new Regex(@"loot-barrel|loot_trash|trash-pile|loot_barrel");
private Regex crateEx = new Regex(@"crate");
private Regex crateEx = new Regex(@"crate");
// Items and blueprints data
// Items and blueprints data
private List<string>[] items = new List<string>[4];
private List<string>[] items = new List<string>[4];
private int totalItems;
private int totalItems;
private List<string>[] blueprints = new List<string>[4];
private List<string>[] blueprints = new List<string>[4];
private int totalBlueprints;
private int totalBlueprints;
private int[] itemWeights = new int[4];
private int[] itemWeights = new int[4];
private int[] blueprintWeights = new int[4];
private int[] blueprintWeights = new int[4];
private int totalItemWeight;
private int totalItemWeight;
private int totalBlueprintWeight;
private int totalBlueprintWeight;
// What the game says
// What the game says
private List<ItemDefinition> originalItems;
private List<ItemDefinition> originalItems;
private List<ItemBlueprint> originalBlueprints;
private List<ItemBlueprint> originalBlueprints;
// Underlying random number generator
// Underlying random number generator
private Random rng = new Random();
private Random rng = new Random();
// Whether the plugin has been correctly initialized
// Whether the plugin has been correctly initialized
private bool initialized = false;
private bool initialized = false;
// Number of ticks until an internals update is scheduled
// Number of ticks until an internals update is scheduled
private int updateScheduled = -1;
private int updateScheduled = -1;
// List of containers to refresh periodically
// List of containers to refresh periodically
private List<ContainerToRefresh> refreshList = new List<ContainerToRefresh>();
private List<ContainerToRefresh> refreshList = new List<ContainerToRefresh>();
// Last time containers have been refreshed
// Last time containers have been refreshed
private DateTime lastRefresh = DateTime.MinValue;
private DateTime lastRefresh = DateTime.MinValue;
// Loads the default configuration parameters into the config object
// Loads the default configuration parameters into the config object
protected override void LoadDefaultConfig() {
protected override void LoadDefaultConfig() {
Config["blueprintProbability"] = defaultBlueprintProbability;
Config["blueprintProbability"] = defaultBlueprintProbability;
Config["minItemsPerBarrel"] = defaultMinItemsPerBarrel;
Config["minItemsPerBarrel"] = defaultMinItemsPerBarrel;
Config["maxItemsPerBarrel"] = defaultMaxItemsPerBarrel;
Config["maxItemsPerBarrel"] = defaultMaxItemsPerBarrel;
Config["minItemsPerCrate"] = defaultMinItemsPerCrate;
Config["minItemsPerCrate"] = defaultMinItemsPerCrate;
Config["maxItemsPerCrate"] = defaultMaxItemsPerCrate;
Config["maxItemsPerCrate"] = defaultMaxItemsPerCrate;
Config["baseItemRarity"] = defaultBaseItemRarity;
Config["baseItemRarity"] = defaultBaseItemRarity;
Config["baseBlueprintRarity"] = defaultBaseBlueprintRarity;
Config["baseBlueprintRarity"] = defaultBaseBlueprintRarity;
Config["refreshMinutes"] = defaultRefreshMinutes;
Config["refreshMinutes"] = defaultRefreshMinutes;
Config["itemBlacklist"] = new List<string>();
Config["itemBlacklist"] = new List<string>();
Config["blueprintBlacklist"] = new List<string>();
Config["blueprintBlacklist"] = new List<string>();
Config["enforceBlacklist"] = defaultEnforceBlacklist;
Config["enforceBlacklist"] = defaultEnforceBlacklist;
Config["dropWeaponsWithAmmo"] = defaultDropWeaponsWithAmmo;
Config["dropWeaponsWithAmmo"] = defaultDropWeaponsWithAmmo;
Config["dropLimits"] = defaultDropLimits;
Config["dropLimits"] = defaultDropLimits;
Config["rarityOverrides"] = defaultRarityOverrides;
var messages = new Dictionary<string, object>();
var messages = new Dictionary<string, object>();
foreach (var text in texts) {
foreach (var text in texts) {
if (messages.ContainsKey(text))
if (messages.ContainsKey(text))
Puts("{0}: {1}", Title, "Duplicate translation string: " + text);
Puts("{0}: {1}", Title, "Duplicate translation string: " + text);
else
else
messages.Add(text, text);
messages.Add(text, text);
}
}
Config["messages"] = messages;
Config["messages"] = messages;
}
}
// Gets a configuration value of a specific type
// Gets a configuration value of a specific type
T GetConfig<T>(string key, T defaultValue) {
T GetConfig<T>(string key, T defaultValue) {
try {
try {
var val = Config[key];
var val = Config[key];
if (val == null)
if (val == null)
return defaultValue;
return defaultValue;
if (val is List<object>) {
if (val is List<object>) {
var t = typeof(T).GetGenericArguments()[0];
var t = typeof(T).GetGenericArguments()[0];
if (t == typeof(String)) {
if (t == typeof(String)) {
var cval = new List<string>();
var cval = new List<string>();
foreach (var v in val as List<object>)
foreach (var v in val as List<object>)
cval.Add((string)v);
cval.Add((string)v);
val = cval;
val = cval;
} else if (t == typeof(int)) {
} else if (t == typeof(int)) {
var cval = new List<int>();
var cval = new List<int>();
foreach (var v in val as List<object>)
foreach (var v in val as List<object>)
cval.Add(Convert.ToInt32(v));
cval.Add(Convert.ToInt32(v));
val = cval;
val = cval;
}
}
} else if (val is Dictionary<string, object>) {
} else if (val is Dictionary<string, object>) {
var t = typeof(T).GetGenericArguments()[1];
var t = typeof(T).GetGenericArguments()[1];
if (t == typeof(int)) {
if (t == typeof(int)) {
var cval = new Dictionary<string,int>();
var cval = new Dictionary<string,int>();
foreach (var v in val as Dictionary<string, object>)
foreach (var v in val as Dictionary<string, object>)
cval.Add(Convert.ToString(v.Key), Convert.ToInt32(v.Value));
cval.Add(Convert.ToString(v.Key), Convert.ToInt32(v.Value));
val = cval;
val = cval;
}
}
}
}
return (T)Convert.ChangeType(val, typeof(T));
return (T)Convert.ChangeType(val, typeof(T));
} catch (Exception ex) {
} catch (Exception ex) {
Warn("Invalid config value: " + key+" ("+ex.Message+")");
Warn("Invalid config value: " + key+" ("+ex.Message+")");
return defaultValue;
return defaultValue;
}
}
}
}
// Updates the internal probability matrix and optionally logs the result
// Updates the internal probability matrix and optionally logs the result
private void UpdateInternals(bool doLog) {
private void UpdateInternals(bool doLog) {
Log("Updating internals ...");
Log("Updating internals ...");
originalItems = new List<ItemDefinition>(ItemManager.itemList);
originalItems = new List<ItemDefinition>(ItemManager.itemList);
originalBlueprints = new List<ItemBlueprint>(ItemManager.bpList);
originalBlueprints = new List<ItemBlueprint>(ItemManager.bpList);
if (originalItems.Count < 20 || originalBlueprints.Count < 10) {
if (originalItems.Count < 20 || originalBlueprints.Count < 10) {
Error("Resources did not contain a sane amount of items and/or blueprints: " + originalItems.Count +
Error("Resources did not contain a sane amount of items and/or blueprints: " + originalItems.Count +
" / " + originalBlueprints.Count);
" / " + originalBlueprints.Count);
return;
return;
}
}
if (doLog)
if (doLog)
Log("There are " + originalItems.Count + " items and " + originalBlueprints.Count + " blueprints in the game.");
Log("There are " + originalItems.Count + " items and " + originalBlueprints.Count + " blueprints in the game.");
for (var i = 0; i < 4; ++i) {
for (var i = 0; i < 4; ++i) {
items[i] = new List<string>();
items[i] = new List<string>();
blueprints[i] = new List<string>();
blueprints[i] = new List<string>();
}
}
totalItems = 0;
totalItems = 0;
totalBlueprints = 0;
totalBlueprints = 0;
var allItems = ItemManager.GetItemDefinitions();
var allItems = ItemManager.GetItemDefinitions();
if (allItems == null || allItems.Count < 20) {
if (allItems == null || allItems.Count < 20) {
Error("ItemManager did not return a sane amount of items. Is the game broken?");
Error("ItemManager did not return a sane amount of items. Is the game broken?");
return;
return;
}
}
var notExistingItems = 0;
var notExistingItems = 0;
var notExistingBlueprints = 0;
var notExistingBlueprints = 0;
var itemsWithNoRarity = 0;
var itemsWithNoRarity = 0;
foreach (var item in allItems) {
foreach (var item in allItems) {
#if DEBUG
Log("ItemShortName:" + item.shortname);
#endif
if (neverDropped.Contains(item.shortname))
if (neverDropped.Contains(item.shortname))
continue;
continue;
int index = RarityIndex(item.rarity);
int index = RarityIndex(item, rarityOverrides);
if (index >= 0) {
if (index >= 0) {
if (ItemExists(item.shortname)) {
if (ItemExists(item.shortname)) {
if (!itemBlacklist.Contains(item.shortname)) {
if (!itemBlacklist.Contains(item.shortname)) {
items[index].Add(item.shortname);
items[index].Add(item.shortname);
++totalItems;
++totalItems;
}
}
} else ++notExistingItems;
} else ++notExistingItems;
if (BlueprintExists(item.shortname)) {
if (BlueprintExists(item.shortname)) {
if (blueprintsNeverDropped.Contains(item.shortname))
if (blueprintsNeverDropped.Contains(item.shortname))
continue;
continue;
if (!blueprintBlacklist.Contains(item.shortname)) {
if (!blueprintBlacklist.Contains(item.shortname)) {
blueprints[index].Add(item.shortname);
blueprints[index].Add(item.shortname);
++totalBlueprints;
++totalBlueprints;
}
}
} else ++notExistingBlueprints;
} else ++notExistingBlueprints;
} else ++itemsWithNoRarity;
} else {
#if DEBUG
// Will occasionally get these for new items. Should default some in json or hardcode (i.e. blueprint fragments, etc)
Warn("Item has no rarity: " + item.shortname);
#endif
++itemsWithNoRarity;
}
}
}
if (totalItems < 20 || totalBlueprints < 10) {
if (totalItems < 20 || totalBlueprints < 10) {
Error("Failed to categorize items: "+notExistingItems+" items and "+notExistingBlueprints+" blueprints did not exist and "+itemsWithNoRarity+" items had no rarity");
Error("Failed to categorize items: "+notExistingItems+" items and "+notExistingBlueprints+" blueprints did not exist and "+itemsWithNoRarity+" items had no rarity");
if (itemsWithNoRarity > 10)
if (itemsWithNoRarity > 10)
Error("THIS IS MOST LIKELY CAUSED BY A MISCONFIGURED (OR BROKEN) PLUGIN THAT MODIFIES ITEMS!");
Error("THIS IS MOST LIKELY CAUSED BY A MISCONFIGURED (OR BROKEN) PLUGIN THAT MODIFIES ITEMS!");
else
else
Error("PLEASE REPORT THIS ON THE DEDICATED DISCUSSION THREAD! http://oxidemod.org/threads/betterloot.7063");
Error("PLEASE REPORT THIS ON THE DEDICATED DISCUSSION THREAD! http://oxidemod.org/threads/betterloot.7063");
return;
return;
}
}
if (doLog)
if (doLog)
Log("We are going to use " + totalItems + " items and " + totalBlueprints + " blueprints of them.");
Log("We are going to use " + totalItems + " items and " + totalBlueprints + " blueprints of them.");
totalItemWeight = 0;
totalItemWeight = 0;
totalBlueprintWeight = 0;
totalBlueprintWeight = 0;
for (var i = 0; i < 4; ++i) {
for (var i = 0; i < 4; ++i) {
totalItemWeight += (itemWeights[i] = ItemWeight(baseItemRarity, i) * items[i].Count);
totalItemWeight += (itemWeights[i] = ItemWeight(baseItemRarity, i) * items[i].Count);
totalBlueprintWeight += (blueprintWeights[i] = ItemWeight(baseBlueprintRarity, i) * blueprints[i].Count);
totalBlueprintWeight += (blueprintWeights[i] = ItemWeight(baseBlueprintRarity, i) * blueprints[i].Count);
}
}
if (doLog) {
if (doLog) {
Log(string.Format("Base item rarity is {0} and base blueprint rarity is {1}.", baseItemRarity, baseBlueprintRarity));
Log(string.Format("Base item rarity is {0} and base blueprint rarity is {1}.", baseItemRarity, baseBlueprintRarity));
Log(string.Format("With a {0:0.0}% chance that any drop is a blueprint we get:", 100 * blueprintProbability));
Log(string.Format("With a {0:0.0}% chance that any drop is a blueprint we get:", 100 * blueprintProbability));
double total = 0;
double total = 0;
for (var i = 0; i < 4; ++i) {
for (var i = 0; i < 4; ++i) {
double prob = (1 - blueprintProbability) * 100d * itemWeights[i] / totalItemWeight;
double prob = (1 - blueprintProbability) * 100d * itemWeights[i] / totalItemWeight;
Log(string.Format("There is a {0:0.000}% chance to get one of {1} " + RarityName(i) + " items (w={2}, {3}/{4}).", prob, items[i].Count, ItemWeight(baseItemRarity, i), itemWeights[i], totalItemWeight));
Log(string.Format("There is a {0:0.000}% chance to get one of {1} " + RarityName(i) + " items (w={2}, {3}/{4}).", prob, items[i].Count, ItemWeight(baseItemRarity, i), itemWeights[i], totalItemWeight));
total += prob;
total += prob;
}
}
for (var i = 0; i < 4; ++i) {
for (var i = 0; i < 4; ++i) {
double prob = blueprintProbability * 100d * blueprintWeights[i] / totalBlueprintWeight;
double prob = blueprintProbability * 100d * blueprintWeights[i] / totalBlueprintWeight;
Log(string.Format("There is a {0:0.000}% chance to get one of {1} " + RarityName(i) + " blueprints (w={2}, {3}/{4}).", prob, blueprints[i].Count, ItemWeight(baseBlueprintRarity, i), blueprintWeights[i], totalBlueprintWeight));
Log(string.Format("There is a {0:0.000}% chance to get one of {1} " + RarityName(i) + " blueprints (w={2}, {3}/{4}).", prob, blueprints[i].Count, ItemWeight(baseBlueprintRarity, i), blueprintWeights[i], totalBlueprintWeight));
total += prob;
total += prob;
}
}
// Log("Total chance: " + total + "% == 100%");
// Log("Total chance: " + total + "% == 100%");
}
}
// Update containers accordingly
// Update containers accordingly
var containers = UnityEngine.Object.FindObjectsOfType<LootContainer>();
var containers = UnityEngine.Object.FindObjectsOfType<LootContainer>();
foreach (var container in containers) {
foreach (var container in containers) {
try {
try {
PopulateContainer(container);
PopulateContainer(container);
} catch (Exception ex) {
} catch (Exception ex) {
Warn("Failed to populate container " + ContainerName(container) + ": " + ex.Message + "\n" + ex.StackTrace);
Warn("Failed to populate container " + ContainerName(container) + ": " + ex.Message + "\n" + ex.StackTrace);
}
}
}
}
initialized = true;
initialized = true;
Log("Internals have been updated");
Log("Internals have been updated");
}
}
// Initializes our custom loot tables
// Initializes our custom loot tables
[HookMethod("OnServerInitialized")]
[HookMethod("OnServerInitialized")]
void OnServerInitialized() {
void OnServerInitialized() {
if (initialized)
if (initialized)
return;
return;
try {
try {
LoadConfig();
LoadConfig();
blueprintProbability = GetConfig<double>("blueprintProbability", defaultBlueprintProbability);
blueprintProbability = GetConfig<double>("blueprintProbability", defaultBlueprintProbability);
minItemsPerBarrel = GetConfig<int>("minItemsPerBarrel", defaultMinItemsPerBarrel);
minItemsPerBarrel = GetConfig<int>("minItemsPerBarrel", defaultMinItemsPerBarrel);
maxItemsPerBarrel = GetConfig<int>("maxItemsPerBarrel", defaultMaxItemsPerBarrel);
maxItemsPerBarrel = GetConfig<int>("maxItemsPerBarrel", defaultMaxItemsPerBarrel);
minItemsPerCrate = GetConfig<int>("minItemsPerCrate", defaultMinItemsPerCrate);
minItemsPerCrate = GetConfig<int>("minItemsPerCrate", defaultMinItemsPerCrate);
maxItemsPerCrate = GetConfig<int>("maxItemsPerCrate", defaultMaxItemsPerCrate);
maxItemsPerCrate = GetConfig<int>("maxItemsPerCrate", defaultMaxItemsPerCrate);
baseItemRarity = GetConfig<double>("baseItemRarity", defaultBaseItemRarity);
baseItemRarity = GetConfig<double>("baseItemRarity", defaultBaseItemRarity);
baseBlueprintRarity = GetConfig<double>("baseBlueprintRarity", defaultBaseBlueprintRarity);
baseBlueprintRarity = GetConfig<double>("baseBlueprintRarity", defaultBaseBlueprintRarity);
refreshMinutes = GetConfig<int>("refreshMinutes", defaultRefreshMinutes);
refreshMinutes = GetConfig<int>("refreshMinutes", defaultRefreshMinutes);
itemBlacklist = GetConfig<List<string>>("itemBlacklist", new List<string>()); /* ref */ Config["itemBlacklist"] = itemBlacklist;
itemBlacklist = GetConfig<List<string>>("itemBlacklist", new List<string>()); /* ref */ Config["itemBlacklist"] = itemBlacklist;
blueprintBlacklist = GetConfig<List<string>>("blueprintBlacklist", new List<string>()); /* ref */ Config["blueprintBlacklist"] = blueprintBlacklist;
blueprintBlacklist = GetConfig<List<string>>("blueprintBlacklist", new List<string>()); /* ref */ Config["blueprintBlacklist"] = blueprintBlacklist;
enforceBlacklist = GetConfig<bool>("enforceBlacklist", defaultEnforceBlacklist);
enforceBlacklist = GetConfig<bool>("enforceBlacklist", defaultEnforceBlacklist);
dropWeaponsWithAmmo = GetConfig<bool>("dropWeaponsWithAmmo", defaultDropWeaponsWithAmmo);
dropWeaponsWithAmmo = GetConfig<bool>("dropWeaponsWithAmmo", defaultDropWeaponsWithAmmo);
dropLimits = GetConfig<Dictionary<string, int>>("dropLimits", defaultDropLimits); /* ref */ Config["dropLimits"] = dropLimits;
dropLimits = GetConfig<Dictionary<string, int>>("dropLimits", defaultDropLimits); /* ref */ Config["dropLimits"] = dropLimits;
rarityOverrides = GetConfig<Dictionary<string, int>>("rarityOverrides", defaultRarityOverrides); /* ref */ Config["rarityOverrides"] = rarityOverrides;
updateScheduled = 3;
updateScheduled = 3;
Log("Updating in T-" + updateScheduled + " ...");
Log("Updating in T-" + updateScheduled + " ...");
// ^ Wait a couple of ticks to give plugins that modify items a chance to do their thing prior to calculating loot tables.
// ^ Wait a couple of ticks to give plugins that modify items a chance to do their thing prior to calculating loot tables.
} catch (Exception ex) {
} catch (Exception ex) {
Error("OnServerInitialized failed: " + ex.Message);
Error("OnServerInitialized failed: " + ex.Message);
}
}
}
}
private int getRandomAmountForItem(Item item) {
int amount = 1;
int maxStackSize = item.info.stackable;
if (maxStackSize > 1 && dropLimits.TryGetValue(item.info.shortname, out amount))
amount = rng.Next(1, Math.Min(amount, maxStackSize) + 1);
return amount;
}
private void updateItemAmount(Item item) {
item.amount = getRandomAmountForItem(item);
}
// Asks the mighty RNG for an item
// Asks the mighty RNG for an item
private Item MightyRNG() {
private Item MightyRNG() {
bool blueprint = rng.NextDouble() < blueprintProbability;
bool blueprint = rng.NextDouble() < blueprintProbability;
List<string> selectFrom;
List<string> selectFrom;
int limit = 0;
int limit = 0;
string itemName;
string itemName;
Item item;
Item item;
int maxRetry = 20;
int maxRetry = 20;
do {
do {
selectFrom = null;
selectFrom = null;
item = null;
item = null;
if (blueprint) {
if (blueprint) {
var r = rng.Next(totalBlueprintWeight);
var r = rng.Next(totalBlueprintWeight);
for (var i=0; i<4; ++i) {
for (var i=0; i<4; ++i) {
limit += blueprintWeights[i];
limit += blueprintWeights[i];
if (r < limit) {
if (r < limit) {
selectFrom = blueprints[i];
selectFrom = blueprints[i];
break;
break;
}
}
}
}
} else {
} else {
var r = rng.Next(totalItemWeight);
var r = rng.Next(totalItemWeight);
for (var i=0; i<4; ++i) {
for (var i=0; i<4; ++i) {
limit += itemWeights[i];
limit += itemWeights[i];
if (r < limit) {
if (r < limit) {
selectFrom = items[i];
selectFrom = items[i];
break;
break;
}
}
}
}
}
}
if (selectFrom == null) {
if (selectFrom == null) {
if (--maxRetry <= 0) {
if (--maxRetry <= 0) {
Error("Endless loop detected: ABORTING");
Error("Endless loop detected: ABORTING");
break;
break;
}
}
Warn("Item list to select from is empty (trying another one)");
Warn("Item list to select from is empty (trying another one)");
continue;
continue;
}
}
itemName = selectFrom[rng.Next(0, selectFrom.Count)];
itemName = selectFrom[rng.Next(0, selectFrom.Count)];
item = ItemManager.CreateByName(itemName, 1);
item = ItemManager.CreateByName(itemName, 1);
if (item == null) {
if (item == null) {
Warn("Failed to create item: " + itemName + " (trying another one)");
Warn("Failed to create item: " + itemName + " (trying another one)");
continue;
continue;
}
}
if (item.info == null) {
if (item.info == null) {
Warn("Item has no definition: " + itemName+" (trying another one)");
Warn("Item has no definition: " + itemName+" (trying another one)");
continue;
continue;
}
}
break;
break;
} while (true);
} while (true);
if (item == null)
if (item == null)
return null;
return null;
if (blueprint) {
if (blueprint) {
item.IsBlueprint();
item = ItemManager.Create(item.info, 1, true);
} else if (item.info.stackable > 1 && dropLimits.TryGetValue(item.info.shortname, out limit)) {
} else {
item.amount = rng.Next(1, Math.Min(limit, item.info.stackable) + 1);
updateItemAmount(item);
}
}
return item;
return item;
}
}
// Clears a loot container's contents
// Clears a loot container's contents
private void ClearContainer(LootContainer container) {
private bool ClearContainer(LootContainer container) {
bool fragmentsFound = false;
while (container.inventory.itemList.Count > 0) {
while (container.inventory.itemList.Count > 0) {
var item = container.inventory.itemList[0];
var item = container.inventory.itemList[0];
if (item.info.shortname.Equals("blueprint_fragment")) {
fragmentsFound = true;
}
item.RemoveFromContainer();
item.RemoveFromContainer();
item.Remove(0f);
item.Remove(0f);
}
}
return fragmentsFound;
}
}
// Suppresses automatic refreshes of a container
// Suppresses automatic refreshes of a container
private void SuppressRefresh(LootContainer container) {
private void SuppressRefresh(LootContainer container) {
container.minSecondsBetweenRefresh = -1;
container.minSecondsBetweenRefresh = -1;
container.maxSecondsBetweenRefresh = 0;
container.maxSecondsBetweenRefresh = 0;
container.CancelInvoke("SpawnLoot");
container.CancelInvoke("SpawnLoot");
}
}
private static bool isExcludedContainer(LootContainer container) {
return container is SupplyDrop || container.lootDefinition.name.Equals("HeliLoot");
}
// Populates a container with loot
// Populates a container with loot
private void PopulateContainer(LootContainer container) {
private void PopulateContainer(LootContainer container) {
if (container.inventory == null) {
if (container.inventory == null) {
Warn("Container " + ContainerName(container) + " has no inventory (skipping)");
Warn("Container " + ContainerName(container) + " has no inventory (skipping)");
return;
ret
}
int min = 1;
int max = 0;
bool refresh = false;
if (container is SupplyDrop) {
SuppressRefresh(container);
var inv = container.inventory.itemList.ToArray();
foreach (var item in inv) {
if (itemBlacklist.Contains(item.info.shortname)) {
item.RemoveFromContainer();
item.Remove(0f);
++max;
}
}
if (max == 0)
return;
} else if (barrelEx.IsMatch(container.gameObject.name)) {
SuppressRefresh(container);
ClearContainer(container);
min = minItemsPerBarrel;
max = maxItemsPerBarrel;
} else if (crateEx.IsMatch(container.gameObject.name)) {
SuppressRefresh(container);
ClearContainer(container);
min = minItemsPerCrate;
max = maxItemsPerCrate;
refresh = true; // In case someone puts trash in it
} else {
#if DEBUG
Log("Container " + ContainerName(container) + " does not match any override");
#endif
return;
}
var n = min + rng.Next(0, max - min + 1);
var sb = new StringBuilder();
var items = new List<Item>();
bool hasAmmo = false;
for (int i = 0; i < n; ++i) {
var item = MightyRNG();
if (item == null) {
Error("Failed to obtain item: Is the plugin initialized yet?");
return;
}
items.Add(item);
if (sb.Length > 0)
sb.Append(", ");
if (item.amount > 1)
sb.Append(item.amount).Append("x ");
sb.Append(item.info.shortname);
if (item.IsBlueprint())
sb.Append(" (BP)");
else if (dropWeaponsWithAmmo && !hasAmmo && items.Count < container.inventorySlots) { // Drop some ammunition with first weapon
string ammo;
int limit;
if (weaponAmmunition.TryGetValue(item.info.shortname, out ammo) && dropLimits.TryGetValue(ammo, out limit)) {
try {
item = ItemManager.CreateByName(ammo, rng.Next(2, limit + 1));