C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
// Reference: Oxide.Ext.Rust
// Reference: Newtonsoft.Json
// Reference: UnityEngine
using Oxide.Core.Plugins;
using Rust;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace Oxide.Plugins {
[Info("BetterLoot", "playrust.io / dcode", "1.8.4", ResourceId = 828)]
public class BetterLoot : RustPlugin {
// To configure, use config/BetterLoot.json instead
#region Configuration
private double defaultBlueprintProbability = 0.11;
private int defaultMinItemsPerBarrel = 1;
private int defaultMaxItemsPerBarrel = 3;
private int defaultMinItemsPerCrate = 3;
private int defaultMaxItemsPerCrate = 6;
private double defaultBaseItemRarity = 2;
private double defaultBaseBlueprintRarity = 2;
private int defaultRefreshMinutes = 15;
private bool defaultEnforceBlacklist = false;
private bool defaultDropWeaponsWithAmmo = true;
private Dictionary<string, int> defaultDropLimits = new Dictionary<string, int>() {
// C4
{
"explosives", 50
}
, {
"explosive.timed", 2
}
, // Ammunition
{
"arrow_wooden", 16
}
, {
"ammo_handmade_shell", 32
}
, {
"ammo_shotgun", 32
}
, {
"ammo_pistol", 32
}
, {
"ammo_rifle", 32
}
, {
"gunpowder", 200
}
, // From animals
{
"fat_animal", 200
}
, {
"cloth", 200
}
, {
"lowgradefuel", 200
}
, {
"bone_fragments", 200
}
, // Medical
{
"antiradpills", 5
}
, {
"bandage", 5
}
, {
"largemedkit", 2
}
, {
"blood", 200
}
, // Resources
{
"wood", 1000
}
, {
"stones", 1000
}
, {
"metal_ore", 1000
}
, {
"sulfur_ore", 1000
}
, {
"metal_fragments", 1000
}
, {
"sulfur", 1000
}
, // Food
{
"apple", 10
}
, {
"blueberries", 10
}
, {
"black raspberries", 10
}
, {
"wolfmeat_cooked", 5
}
, {
"chicken_cooked", 5
}
, {
"can_beans", 5
}
, {
"can_tuna", 5
}
, {
"granolabar", 5
}
, {
"chocholate", 5
}
, {
"smallwaterbottle", 5
}
}
;
private double blueprintProbability;
private int minItemsPerBarrel;
private int maxItemsPerBarrel;
private int minItemsPerCrate;
private int maxItemsPerCrate;
private double baseItemRarity;
private double baseBlueprintRarity;
private int refreshMinutes;
private List<string> itemBlacklist;
private List<string> blueprintBlacklist;
private bool enforceBlacklist;
private bool dropWeaponsWithAmmo;
private Dictionary<string, int> dropLimits;
#endregion
// Stuff that will never drop as a blueprint (default craftable)
private List<string> defaultBlueprints = new List<string>() {
// Building
"lock.key", "cupboard.tool", "building_planner", "door_key", "sign.wooden.small", // Items
"campfire", "box_wooden", "sleepingbag", "furnace", // Resources
"paper", "lowgradefuel", "gunpowder", // Attire
"burlap_shirt", "burlap_shoes", "burlap_trousers", "attire.hide.boots", "attire.hide.pants", "attire.hide.poncho", "attire.hide.vest", "urban_pants", "urban_boots", // Tools
"hammer", "torch", "stonehatchet", "stone_pickaxe", "box_repair_bench", // Weapons
"spear_wooden", "knife_bone", "bow_hunting", "pistol_eoka", // Ammunition
"arrow_wooden", "ammo_handmade_shell" // Traps
}
;
// Stuff that will simply never drop
private List<string> doesNotDrop = new List<string>() {
// Food
"apple_spoiled", "bearmeat", "chicken_burned", "chicken_spoiled", "wolfmeat_burned", "wolfmeat_spoiled", // Resources
"blood", "battery_small", "paper", "skull_human", "skull_wolf"
}
;
// Ammunition used by the differnt kinds of weapons
private Dictionary<string, string> weaponAmmunition = new Dictionary<string, string>() {
{
"bow_hunting", "arrow_wooden"
}
, {
"pistol_eoka", "ammo_handmade_shell"
}
, {
"pistol_revolver", "ammo_pistol"
}
, {
"shotgun_waterpipe", "ammo_shotgun"
}
, {
"shotgun_pump", "ammo_shotgun"
}
, {
"smg_thompson", "ammo_pistol"
}
, {
"rifle_bolt", "ammo_rifle"
}
, {
"rifle_ak", "ammo_rifle"
}
}
;
// Translations
private List<string> texts = new List<string>() {
"A barrel drops up to %N% items, a chest up to %M% items.", "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=#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.", "Usage: /droplimit \"ITEMNAME\" [LIMIT]", "You are not authorized to modify drop limits", "No such item:", "Drop limit of '%NAME%' is %LIMIT%", "Drop limit of '%NAME%' has been changed from %LIMIT% to %NEWLIMIT%", "Usage: /blacklist [additem|deleteitem|addbp|deletebp] \"ITEMNAME\"", "There are no blacklisted items", "Blacklisted items:", "There are no blacklisted blueprints", "Blacklisted blueprints:", "You are not authorized to modify the blacklist", "Not a valid item:", "The item '%NAME%' is now blacklisted", "The item '%NAME%' is already blacklisted", "Not a valid blueprint:", "The blueprint '%NAME%' is now blacklisted", "The blueprint '{0}' is already blacklisted", "The item '%NAME%' is now no longer blacklisted", "The item '%NAME%' is not blacklisted", "The blueprint '%NAME%' is now no longer blacklisted", "The blueprint '%NAME' is not blacklisted", "<color=\"#ffd479\">/loot</color> - Displays the details on loot tables", "<color=\"#ffd479\">/blacklist</color> - Displays which items are blacklisted", "common", "uncommon", "rare", "very rare"
}
;
private Dictionary<string, string> messages = new Dictionary<string, string>();
// Regular expressions defining what to override
private Regex barrelEx = new Regex("loot_barrel|loot_trash");
private Regex crateEx = new Regex("crate");
private Regex overrideLootOf = new Regex("(loot_barrel|loot_trash|crate)");
// Items and blueprints data
private List<string>[] items = new List<string>[4];
private int totalItems;
private List<string>[] blueprints = new List<string>[4];
private int totalBlueprints;
private int[] itemWeights = new int[4];
private int[] blueprintWeights = new int[4];
private int totalItemWeight;
private int totalBlueprintWeight;
// What the game says
private List<ItemDefinition> originalItems;
private List<ItemBlueprint> originalBlueprints;
// Underlying random number generator
private Random rng = new Random();
// Whether the plugin has been correctly initialized
private bool initialized = false;
// Number of ticks until an internals update is scheduled
private int updateScheduled = -1;
// List of containers to refresh periodically
private List<ContainerToRefresh> refreshList = new List<ContainerToRefresh>();
// Last time containers have been refreshed
private DateTime lastRefresh = DateTime.MinValue;
// Loads the default configuration parameters into the config object
protected override void LoadDefaultConfig() {
Config["blueprintProbability"] = defaultBlueprintProbability;
Config["minItemsPerBarrel"] = defaultMinItemsPerBarrel;
Config["maxItemsPerBarrel"] = defaultMaxItemsPerBarrel;
Config["minItemsPerCrate"] = defaultMinItemsPerCrate;
Config["maxItemsPerCrate"] = defaultMaxItemsPerCrate;
Config["baseItemRarity"] = defaultBaseItemRarity;
Config["baseBlueprintRarity"] = defaultBaseBlueprintRarity;
Config["refreshMinutes"] = defaultRefreshMinutes;
Config["itemBlacklist"] = new List<string>();
Config["blueprintBlacklist"] = new List<string>();
Config["enforceBlacklist"] = defaultEnforceBlacklist;
Config["dropWeaponsWithAmmo"] = defaultDropWeaponsWithAmmo;
Config["dropLimits"] = defaultDropLimits;
var messages = new Dictionary<string, object>();
foreach (var text in texts) {
if (messages.ContainsKey(text)) Puts("{0}: {1}", Title, "Duplicate translation string: " + text);
else messages.Add(text, text);
}
Config["messages"] = messages;
}
// Gets a configuration value of a specific type
T GetConfig<T>(string key, T defaultValue) {
try {
var val = Config[key];
if (val == null) return defaultValue;
if (val is List<object>) {
var t = typeof(T).GetGenericArguments()[0];
if (t == typeof(String)) {
var cval = new List<string>();
foreach (var v in val as List<object>) cval.Add((string)v);
val = cval;
}
else if (t == typeof(int)) {
var cval = new List<int>();
foreach (var v in val as List<object>) cval.Add(Convert.ToInt32(v));
val = cval;
}
}
else if (val is Dictionary<string, object>) {
var t = typeof(T).GetGenericArguments()[1];
if (t == typeof(int)) {
var cval = new Dictionary<string, int>();
foreach (var v in val as Dictionary<string, object>) cval.Add(Convert.ToString(v.Key), Convert.ToInt32(v.Value));
val = cval;
}
}
return (T)Convert.ChangeType(val, typeof(T));
}
catch (Exception ex) {
Warn("Invalid config value: " + key+" ("+ex.Message+")");
return defaultValue;
}
}
// Updates the internal probability matrix and optionally logs the result
private void UpdateInternals(bool doLog) {
Log("Updating internals...");
originalItems = UnityEngine.Resources.LoadAll<ItemDefinition>("items/").ToList<ItemDefinition>();
originalBlueprints = UnityEngine.Resources.LoadAll<ItemBlueprint>("items/").ToList<ItemBlueprint>();
if (originalItems.Count < 20 || originalBlueprints.Count < 10) {
Error("Resources did not contain a sane amount of items and/or blueprints.Is the game broken?");
return;
}
if (doLog) Log("There are " + originalItems.Count + " items and " + originalBlueprints.Count + " blueprints in the game.");
for (var i = 0; i < 4; ++i) {
items[i] = new List<string>();
blueprints[i] = new List<string>();
}
totalItems = 0;
totalBlueprints = 0;
var allItems = ItemManager.GetItemDefinitions();
if (allItems == null || allItems.Count < 20) {
Error("ItemManager did not return a sane amount of items.Is the game broken?");
return;
}
var notExistingItems = 0;
var notExistingBlueprints = 0;
var itemsWithNoRarity = 0;
foreach (var item in allItems) {
if (doesNotDrop.Contains(item.shortname)) continue;
int index = RarityIndex(item.rarity);
if (index >= 0) {
if (ItemExists(item.shortname)) {
if (!itemBlacklist.Contains(item.shortname)) {
items[index].Add(item.shortname);
++totalItems;
}
}
else ++notExistingItems;
if (BlueprintExists(item.shortname)) {
if (!blueprintBlacklist.Contains(item.shortname)) {
blueprints[index].Add(item.shortname);
++totalBlueprints;
}
}
else ++notExistingBlueprints;
}
else ++itemsWithNoRarity;
}
if (totalItems < 20 || totalBlueprints < 10) {
Error("Failed to categorize items: "+notExistingItems+" items and "+notExistingBlueprints+" blueprints did not exist and "+itemsWithNoRarity+" items had no rarity");
if (itemsWithNoRarity > 10) Error("THIS IS MOST LIKELY CAUSED BY A MISCONFIGURED (OR BROKEN) PLUGIN THAT MODIFIES ITEMS!");
else Error("PLEASE REPORT THIS ON THE DEDICATED DISCUSSION THREAD! http://oxidemod.org/threads/betterloot.7063");
return;
}
if (doLog) Log("We are going to use " + totalItems + " items and " + totalBlueprints + " blueprints of them.");
totalItemWeight = 0;
totalBlueprintWeight = 0;
for (var i = 0; i < 4; ++i) {
totalItemWeight += (itemWeights[i] = ItemWeight(baseItemRarity, i) * items[i].Count);
totalBlueprintWeight += (blueprintWeights[i] = ItemWeight(baseBlueprintRarity, i) * blueprints[i].Count);
}
if (doLog) {
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));
double total = 0;
for (var i = 0; i < 4; ++i) {
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));
total += prob;
}
for (var i = 0; i < 4; ++i) {
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));
total += prob;
}
// Log("Total chance: " + total + "% == 100%");
}
// Update containers accordingly
var containers = UnityEngine.Object.FindObjectsOfType<LootContainer>();
foreach (var container in containers) {
try {
PopulateContainer(container);
}
catch (Exception ex) {
Warn("Failed to populate container " + ContainerName(container) + ": " + ex.Message + "\n" + ex.StackTrace);
}
}
initialized = true;
Log("Internals have been updated");
}
// Initializes our custom loot tables
[HookMethod("OnServerInitialized")]
void OnServerInitialized() {
if (initialized) return;
try {
LoadConfig();
blueprintProbability = GetConfig<double>("blueprintProbability", defaultBlueprintProbability);
minItemsPerBarrel = GetConfig<int>("minItemsPerBarrel", defaultMinItemsPerBarrel);
maxItemsPerBarrel = GetConfig<int>("maxItemsPerBarrel", defaultMaxItemsPerBarrel);
minItemsPerCrate = GetConfig<int>("minItemsPerCrate", defaultMinItemsPerCrate);
maxItemsPerCrate = GetConfig<int>("maxItemsPerCrate", defaultMaxItemsPerCrate);
baseItemRarity = GetConfig<double>("baseItemRarity", defaultBaseItemRarity);
baseBlueprintRarity = GetConfig<double>("baseBlueprintRarity", defaultBaseBlueprintRarity);
refreshMinutes = GetConfig<int>("refreshMinutes", defaultRefreshMinutes);
itemBlacklist = GetConfig<List<string>>("itemBlacklist", new List<string>());
/* ref */
Config["itemBlacklist"] = itemBlacklist;
blueprintBlacklist = GetConfig<List<string>>("blueprintBlacklist", new List<string>());
/* ref */
Config["blueprintBlacklist"] = blueprintBlacklist;
enforceBlacklist = GetConfig<bool>("enforceBlacklist", defaultEnforceBlacklist);
dropWeaponsWithAmmo = GetConfig<bool>("dropWeaponsWithAmmo", defaultDropWeaponsWithAmmo);
dropLimits = GetConfig<Dictionary<string, int>>("dropLimits", defaultDropLimits);
/* ref */
Config["dropLimits"] = dropLimits;
updateScheduled = 3;
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.} catch (Exception ex) {
Error("OnServerInitialized failed: " + ex.Message);
}
}
// Asks the mighty RNG for an item
private Item MightyRNG() {
bool blueprint = rng.NextDouble() < blueprintProbability;
List<string> selectFrom;
int limit = 0;
string itemName;
Item item;
int maxRetry = 20;
do {
selectFrom = null;
item = null;
if (blueprint) {
var r = rng.Next(totalBlueprintWeight);
for (var i=0; i<4; ++i) {
limit += blueprintWeights[i];
if (r < limit) {
selectFrom = blueprints[i];
break;
}
}
}
else {
var r = rng.Next(totalItemWeight);
for (var i=0; i<4; ++i) {
limit += itemWeights[i];
if (r < limit) {
selectFrom = items[i];
break;
}
}
}
if (selectFrom == null) {
if (--maxRetry <= 0) {
Error("Endless loop detected: ABORTING");
break;
}
Warn("Item list to select from is empty (trying another one)");
continue;
}
itemName = selectFrom[rng.Next(0, selectFrom.Count)];
item = ItemManager.CreateByName(itemName, 1);
if (item == null) {
Warn("Failed to create item: " + itemName + " (trying another one)");
continue;
}
if (item.info == null) {
Warn("Item has no definition: " + itemName+" (trying another one)");
continue;
}
break;
}
while (true);
if (item == null) return null;
if (blueprint) {
item.isBlueprint = true;
}
else if (item.info.stackable > 1 && dropLimits.TryGetValue(item.info.shortname, out limit)) {
item.amount = rng.Next(1, Math.Min(limit, item.info.stackable) + 1);
}
return item;
}
// Clears a loot container's contents
private void ClearContainer(LootContainer container) {
while (container.inventory.itemList.Count > 0) {
var item = container.inventory.itemList[0];
item.RemoveFromContainer();
item.Remove(0f);
}
}
// Suppresses automatic refreshes of a container
private void SuppressRefresh(LootContainer container) {
container.minSecondsBetweenRefresh = -1;
container.maxSecondsBetweenRefresh = 0;
container.CancelInvoke("SpawnLoot");
}
// Populates a container with loot
private void PopulateContainer(LootContainer container) {
if (container.inventory == null) {
Warn("Container " + ContainerName(container) + " has no inventory (skipping)");
return;
}
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 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));
items.Add(item);
sb.Append(" + ");
if (item.amount > 1) sb.Append(item.amount).Append("x ");
sb.Append(item.info.shortname);
hasAmmo = true;
}
catch (Exception) {
Warn("Failed to obtain ammo item: "+ammo);
}
}
}
}
// Log("Populating " + ContainerName(container) + " with " + sb.ToString());
foreach (var item in items) item.MoveToContainer(container.inventory, -1, false);
container.inventory.MarkDirty();
if (refresh) refreshList.Add(new ContainerToRefresh() {
container = container, time = DateTime.UtcNow.AddMinutes(refreshMinutes)
}
);
}
[HookMethod("OnEntitySpawned")]
private void OnEntitySpawned(BaseNetworkable entity) {
if (!initialized) return;
try {
var container = entity as LootContainer;
if (container == null || container.inventory == null || container.inventory.itemList == null) return;
PopulateContainer(container);
}
catch (Exception ex) {
Error("OnEntitySpawned failed: " + ex.Message);
}
}
[HookMethod("SendHelpText")]
private void SendHelpText(BasePlayer player) {
var sb = new StringBuilder().Append("<size=18>BetterLoot</size> by <color=#ce422b>http://playrust.io</color>\n").Append(" ").Append(_("<color=\"#ffd479\">/loot</color> - Displays the details on loot tables")).Append("\n").Append(" ").Append(_("<color=\"#ffd479\">/blacklist</color> - Displays which items are blacklisted"));
player.ChatMessage(sb.ToString());
}
[ChatCommand("loot")]
private void cmdChatLoot(BasePlayer player, string command, string[] args) {
if (!initialized) return;
var sb = new StringBuilder();
sb.Append("<size=22>BetterLoot</size> "+Version+" by <color=#ce422b>http://playrust.io</color>\n");
sb.Append(_("A barrel drops up to %N% items, a chest up to %M% items.", new Dictionary<string, string>() {
{
"N", maxItemsPerBarrel.ToString()
}
, {
"M", maxItemsPerCrate.ToString()
}
}
)).Append("\n");
sb.Append(_("Base item rarity is %N% and base blueprint rarity is %M%.", new Dictionary<string, string>() {
{
"N", string.Format("{0:0.00}", baseItemRarity)
}
, {
"M", string.Format("{0:0.00}", baseBlueprintRarity)
}
}
)).Append("\n");
sb.Append(_("There is a <color=#aef45b>%P%%</color> chance that any drop is a blueprint.", new Dictionary<string, string>() {
{
"P", string.Format("{0:0.0}", 100 * blueprintProbability)
}
}
)).Append("\n");
for (var i = 0; i < 4; ++i) {
double prob = (1 - blueprintProbability) * 100d * itemWeights[i] / totalItemWeight;
sb.Append(_("There is a <color=#f4e75b>%P%%</color> chance to get one of %N% %RARITY% items.", new Dictionary<string, string>() {
{
"P", string.Format("{0:0.000}", prob)
}
, {
"N", items[i].Count.ToString()
}
, {
"RARITY", _(RarityName(i))
}
}
)).Append("\n");
}
for (var i = 0; i < 4; ++i) {
double prob = blueprintProbability * 100d * blueprintWeights[i] / totalBlueprintWeight;
sb.Append(_("There is a <color=#5bbcf4>%P%%</color> chance to get one of %N% %RARITY% blueprints.", new Dictionary<string, string>() {
{
"P", string.Format("{0:0.000}", prob)
}
, {
"N", blueprints[i].Count.ToString()
}
, {
"RARITY", _(RarityName(i))
}
}
)).Append("\n");
}
SendReply(player, sb.ToString().TrimEnd());
}
[ChatCommand("droplimit")]
private void cmdChatDroplimit(BasePlayer player, string command, string[] args) {
var usage = _("Usage: /droplimit \"ITEMNAME\" [LIMIT]");
if (!initialized) return;
if (!ServerUsers.Is(player.userID, ServerUsers.UserGroup.Owner)) {
SendReply(player, _("You are not authorized to modify drop limits"));
return;
}
if (args.Length < 1) {
SendReply(player, usage);
return;
}
string name = args[0];
int currentLimit;
if (!dropLimits.TryGetValue(name, out currentLimit)) {
SendReply(player, _("No such item:")+" "+name);
return;
}
if (args.Length == 1) {
SendReply(player, _("Drop limit of '%NAME%' is %LIMIT%", new Dictionary<string, string>() {
{
"ITEM", name
}
, {
"LIMIT", currentLimit.ToString()
}
}
));
return;
}
int limit = Convert.ToInt32(args[1]);
dropLimits[name] = limit;
SaveConfig();
SendReply(player, "Drop limit of '%NAME%' has been changed from %LIMIT% to %NEWLIMIT%", new Dictionary<string, string>() {
{
"NAME", name
}
, {
"LIMIT", currentLimit.ToString()
}
, {
"NEWLIMIT", limit.ToString()
}
}
);
}
[ChatCommand("blacklist")]
private void cmdChatBlacklist(BasePlayer player, string command, string[] args) {
var usage = _("Usage: /blacklist [additem|deleteitem|addbp|deletebp] \"ITEMNAME\"");
if (!initialized) return;
if (args.Length == 0) {
if (itemBlacklist.Count == 0) {
SendReply(player, _("There are no blacklisted items"));
}
else {
var sb = new StringBuilder();
foreach (var item in itemBlacklist) {
if (sb.Length > 0) sb.Append(", ");
sb.Append(item);
}
SendReply(player, _("Blacklisted items:")+" "+sb.ToString());
}
if (blueprintBlacklist.Count == 0) {
SendReply(player, _("There are no blacklisted blueprints"));
}
else {
var sb = new StringBuilder();
foreach (var item in blueprintBlacklist) {
if (sb.Length > 0) sb.Append(", ");
sb.Append(item);
}
SendReply(player, _("Blacklisted blueprints:")+" "+sb.ToString());
}
return;
}
if (!ServerUsers.Is(player.userID, ServerUsers.UserGroup.Owner)) {
SendReply(player, _("You are not authorized to modify the blacklist"));
return;
}
if (args.Length != 2) {
SendReply(player, usage);
return;
}
if (args[0] == "additem") {
if (!ItemExists(args[1])) {
SendReply(player, _("Not a valid item:")+" "+args[1]);
return;
}
if (!itemBlacklist.Contains(args[1])) {
itemBlacklist.Add(args[1]);
UpdateInternals(false);
SendReply(player, _("The item '%NAME%' is now blacklisted", new Dictionary<string, string>() {
{
"NAME", args[1]
}
}
));
SaveConfig();
return;
}
else {
SendReply(player, _("The item '%NAME%' is already blacklisted", new Dictionary<string, string>() {
{
"NAME", args[1]
}
}
));
return;
}
}
else if (args[0] == "addbp") {
if (!BlueprintExists(args[1])) {
SendReply(player, _("Not a valid blueprint:") + " " + args[1]);
return;
}
if (!blueprintBlacklist.Contains(args[1])) {
blueprintBlacklist.Add(args[1]);
UpdateInternals(false);
SendReply(player, _("The blueprint '%NAME%' is now blacklisted", new Dictionary<string, string>() {
{
"NAME", args[1]
}
}
));
SaveConfig();
return;
}
else {
SendReply(player, _("The blueprint '%NAME%' is already blacklisted", new Dictionary<string, string>() {
{
"NAME", args[1]
}
}
));
return;
}
}
else if (args[0] == "deleteitem") {
if (!ItemExists(args[1])) {
SendReply(player, _("Not a valid item:")+" "+args[1]);
return;
}
if (itemBlacklist.Contains(args[1])) {
itemBlacklist.Remove(args[1]);
UpdateInternals(false);
SendReply(player, _("The item '%NAME%' is now no longer blacklisted", new Dictionary<string, string>() {
{
"NAME", args[1]
}
}
));
SaveConfig();
return;
}
else {
SendReply(player, _("The item '%NAME%' is not blacklisted", new Dictionary<string, string>() {
{
"NAME", args[1]
}
}
));
return;
}
}
else if (args[0] == "deletebp") {
if (!BlueprintExists(args[1])) {
SendReply(player, _("Not a valid blueprint:")+" "+args[1]);
return;
}
if (blueprintBlacklist.Contains(args[1])) {
blueprintBlacklist.Remove(args[1]);
UpdateInternals(false);
SendReply(player, _("The blueprint '%NAME%' is now no longer blacklisted", new Dictionary<string, string>() {
{
"NAME", args[1]
}
}
));
SaveConfig();
return;
}
else {
SendReply(player, _("The blueprint '%NAME' is not blacklisted", new Dictionary<string, string>() {
{
"NAME", args[1]
}
}
));
return;
}
}
else {
SendReply(player, usage);
return;
}
}
/* [HookMethod("OnItemCraft")]
private void OnItemCraft(ItemCraftTask item) {
} */
[HookMethod("OnItemAddedToContainer")]
private void OnItemAddedToContainer(ItemContainer container, Item item) {
if (!initialized || !enforceBlacklist) return;
try {
var owner = item.GetOwnerPlayer();
if (owner != null && (ServerUsers.Is(owner.userID, ServerUsers.UserGroup.Owner) || ServerUsers.Is(owner.userID, ServerUsers.UserGroup.Moderator))) return;
if (!item.isBlueprint && itemBlacklist.Contains(item.info.shortname)) {
Log(string.Format("Destroying item instance of '{0}'", item.info.shortname));
item.RemoveFromContainer();
item.Remove(0f);
}
else if (item.isBlueprint && blueprintBlacklist.Contains(item.info.shortname)) {
Log(string.Format("Destroying blueprint instance of '{0}'", item.info.shortname));
item.RemoveFromContainer();
item.Remove(0f);
}
}
catch (Exception ex) {
Error("OnItemAddedToContainer failed: " + ex.Message);
}
}
[HookMethod("OnTick")]
private void OnTick() {
try {
if (updateScheduled == 0) {
updateScheduled = -1;
UpdateInternals(true);
}
else if (updateScheduled > 0) {
--updateScheduled;
}
}
catch (Exception ex) {
Error("OnTick scheduled update failed: " + ex.Message);
}
try {
var now = DateTime.UtcNow;
if (lastRefresh < now.AddMinutes(-1)) {
lastRefresh = now;
int n = 0;
int m = 0;
var all = refreshList.ToArray();
refreshList.Clear();
foreach (var ctr in all) {
if (ctr.time < now) {
if (ctr.container.isDestroyed) {
// Discard destroyed containers
++m;
continue;
}
if (ctr.container.IsOpen()) {
refreshList.Add(ctr);
// Re-add for later if occupied
continue;
}
try {
PopulateContainer(ctr.container);
// Will re-add
++n;
}
catch (Exception ex) {
Error("Failed to refresh container: " + ContainerName(ctr.container) + ": " + ex.Message + "\n" + ex.StackTrace);
}
}
else refreshList.Add(ctr);
// Re-add for later
}
if (n > 0 || m > 0) Log("Refreshed " + n + " containers (" + m + " destroyed)");
}
}
catch (Exception ex) {
Error("OnTick scheduled refresh failed: " + ex.Message);
}
}
[HookMethod("BuildServerTags")]
private void BuildServerTags(IList<string> taglist) {
try {
taglist.Add("betterloot");
}
catch (Exception ex) {
Error("BuildServerTags failed: " + ex.Message);
}
}
#region Utility Methods
private void Log(string message) {
Puts("{0}: {1}", Title, message);
}
private void Warn(string message) {
PrintWarning("{0}: {1}", Title, message);
}
private void Error(string message) {
PrintError("{0}: {1}", Title, message);
}
private static string ContainerName(LootContainer container) {
var name = container.gameObject.name;
name = name.Substring(name.LastIndexOf("/") + 1);
name += "#" + container.gameObject.GetInstanceID();
return name;
}
private static int RarityIndex(Rarity rarity) {
switch (rarity) {
case Rarity.Common : return 0;
case Rarity.Uncommon : return 1;
case Rarity.Rare : return 2;
case Rarity.VeryRare : return 3;
}
return -1;
}
private static string RarityName(int index) {
switch (index) {
case 0 : return "common";
case 1 : return "uncommon";
case 2 : return "rare";
case 3 : return "very rare";
}
return null;
}
private bool BlueprintExists(string name) {
if (defaultBlueprints.Contains(name)) return false;
foreach (var def in originalBlueprints) {
if (def.targetItem.shortname != name) continue;
var testItem = ItemManager.CreateByName(name, 1);
if (testItem != null) {
testItem.Remove(0f);
return true;
}
}
return false;
}
private bool ItemExists(string name) {
foreach (var def in originalItems) {
if (def.shortname != name) continue;
var testItem = ItemManager.CreateByName(name, 1);
if (testItem != null) {
testItem.Remove(0f);
return true;
}
}
return false;
}
private bool IsWeapon(string name) {
return weaponAmmunition.ContainsKey(name);
}
private int ItemWeight(double baseRarity, int index) {
return (int)(Math.Pow(baseRarity, 3 - index) * 1000);
// Round to 3 decimals
}
// Translates a string
private string _(string text, Dictionary<string, string> replacements = null) {
if (messages.ContainsKey(text) && messages[text] != null) text = messages[text];
if (replacements != null) foreach (var replacement in replacements) text = text.Replace("%" + replacement.Key + "%", replacement.Value);
return text;
}
#endregion
private class ContainerToRefresh {
public LootContainer container;
public DateTime time;
}
}
}