Untitled diff

Created Diff never expires
69 removals
Lines
Total
Removed
Words
Total
Removed
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
549 lines
76 additions
Lines
Total
Added
Words
Total
Added
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
556 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));