RustServer/plugins/CraftingController.cs

693 lines
28 KiB
C#

using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using System.Collections.Generic;
using System.Linq;
namespace Oxide.Plugins
{
[Info("Crafting Controller", "Whispers88", "3.2.2")]
[Description("Allows you to modify the time spend crafting and which items can be crafted")]
//Credits to previous authors Nivex & Mughisi
public class CraftingController : RustPlugin
{
#region Config
private Configuration config;
private static Dictionary<string, float> defaultsetup = new Dictionary<string, float>();
public class CraftingData
{
public bool canCraft;
public bool canResearch;
public bool useCrafteRateMultiplier;
public float craftTime;
public int workbenchLevel;
public ulong defaultskinid;
public CraftingData()
{
canCraft = true;
canResearch = true;
useCrafteRateMultiplier = true;
craftTime = 0;
workbenchLevel = -1;
defaultskinid = 0;
}
}
public class Configuration
{
[JsonProperty("Default crafting rate percentage")]
public float CraftingRate = 50;
[JsonProperty("Save commands to config (save config changes via command to the configuration)")]
public bool SaveCommands = true;
[JsonProperty("Simple Mode (disables: instant bulk craft, skin options and full inventory checks for better performance)")]
public bool SimpleMode = false;
[JsonProperty("Allow crafting when inventory is full")]
public bool FullInventory = false;
[JsonProperty("Complete crafting on server shut down")]
public bool CompleteCrafting = false;
[JsonProperty("Craft items with random skins if not already skinned")]
public bool RandomSkins = false;
[JsonProperty("Show Crafting Notes")]
public bool ShowCraftNotes = false;
[JsonProperty("Crafting rate bonus mulitplier (apply oxide perms for additional mulitpliers")]
public Dictionary<string, float> BonusMultiplier = new Dictionary<string, float>() { { "vip1", 90 }, { "vip2", 80 } };
[JsonProperty("Advanced Crafting Options")]
public Dictionary<string, CraftingData> CraftingOptions = new Dictionary<string, CraftingData>();
public string ToJson() => JsonConvert.SerializeObject(this);
public Dictionary<string, object> ToDictionary() => JsonConvert.DeserializeObject<Dictionary<string, object>>(ToJson());
}
protected override void LoadDefaultConfig() => config = new Configuration();
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<Configuration>();
if (config == null)
{
throw new JsonException();
}
if (!config.ToDictionary().Keys.SequenceEqual(Config.ToDictionary(x => x.Key, x => x.Value).Keys))
{
PrintToConsole("Configuration appears to be outdated; updating and saving");
SaveConfig();
}
}
catch
{
PrintToConsole($"Configuration file {Name}.json is invalid; using defaults");
LoadDefaultConfig();
}
}
protected override void SaveConfig()
{
PrintToConsole($"Configuration changes saved to {Name}.json");
Config.WriteObject(config, true);
}
#endregion Config
#region Init
private const string perminstantbulkcraft = "craftingcontroller.instantbulkcraft";
private const string permblockitems = "craftingcontroller.blockitems";
private const string permitemrate = "craftingcontroller.itemrate";
private const string permcraftingrate = "craftingcontroller.craftingrate";
private const string permsetbenchlvl = "craftingcontroller.setbenchlvl";
private const string permsetskins = "craftingcontroller.setskins";
private List<string> permissions = new List<string> { perminstantbulkcraft, permblockitems, permitemrate, permcraftingrate, permsetbenchlvl, permsetskins };
private List<string> permissionsBonusMultiplier = new List<string>();
private List<string> commands = new List<string> { nameof(CommandCraftingRate), nameof(CommandCraftTime), nameof(CommandBlockItem), nameof(CommandUnblockItem), nameof(CommandSetDefaultSkin), nameof(CommandWorkbenchLVL) };
private void OnServerInitialized()
{
ItemManager.bpList.ForEach(bp => defaultsetup[bp.name] = bp.time);
config.BonusMultiplier.Keys.ToList().ForEach(perm => permissionsBonusMultiplier.Add("craftingcontroller." + perm));
//register permissions
permissions.ForEach(perm => permission.RegisterPermission(perm, this));
permissionsBonusMultiplier.ForEach(perm => permission.RegisterPermission(perm, this));
//register commands
commands.ForEach(command => AddLocalizedCommand(command));
if (config.SimpleMode)
{
Unsubscribe("OnItemCraft");
Unsubscribe("OnItemCraftFinished");
Unsubscribe("OnItemCraftCancelled");
}
var bplist = ItemManager.bpList.Where(item => config.CraftingOptions.ContainsKey(item.targetItem.shortname) == false).ToList();
bplist.ForEach(x => config.CraftingOptions.Add(x.targetItem.shortname, new CraftingData() { craftTime = x.time, workbenchLevel = x.workbenchLevelRequired }));
SaveConfig();
UpdateCraftingRate();
}
private void Unload()
{
ItemManager.bpList.ForEach(bp => bp.time = defaultsetup[bp.name]);
}
#endregion Init
#region Localization
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["NoInvSpace"] = "You don't have enough room to craft this item!",
["NoPerms"] = "You don't have permission to use this command.",
["CannotFindItem"] = "Cannot find item {0}.",
["ItemBlocked"] = "{0} has been blocked from crafting.",
["ItemUnblocked"] = "{0} has been unblocked from crafting.",
["NeedsAdvancedOptions"] = "You need to enable advanced crafting options in your config to use this.",
["WrongNumberInput"] = "Your input needs to be a number.",
["ItemCraftTimeSet"] = "{0} craft time set to {1} seconds",
["WorkbenchLevelSet"] = "{0} workbench level set to {1}",
["CurrentCraftinRate"] = "The current crafting rate is {0 }%",
["CraftingRateUpdated"] = "The crafting rate was updated to {0} %",
["CraftTime2Args"] = "This command needs two arguments in the format /crafttime item.shortname timetocraft",
["BlockItem1Args"] = "This command needs one argument in the format /blockitem item.shortname",
["UnblockItem1Args"] = "This command needs one argument in the format /unblockitem item.shortname",
["WorckBenchLvl2Args"] = "This command needs two arguments in the format /benchlvl item.shortname workbenchlvl",
["BenchLevelInput"] = "The work bench level must be between 0 and 3",
["SetSkin2Args"] = "This command needs one argument in the format /setcraftskin item.shortname skinworkshopid",
["SkinSet"] = "The default skin for {0} was set to {1}",
["CraftTimeCheck"] = "The craft time of this item is {0}",
//Commands
["CommandCraftingRate"] = "craftrate",
["CommandCraftTime"] = "crafttime",
["CommandBlockItem"] = "blockitem",
["CommandUnblockItem"] = "unblockitem",
["CommandWorkbenchLVL"] = "benchlvl",
["CommandSetDefaultSkin"] = "setcraftskin"
}, this);
}
#endregion Localization
#region Commands
private void CommandCraftingRate(IPlayer iplayer, string command, string[] args)
{
if (!HasPerm(iplayer.Id, permitemrate))
{
Message(iplayer, "NoPerms");
return;
}
int craftingrate;
if (args.Length == 0)
{
Message(iplayer, "CurrentCraftinRate", config.CraftingRate);
return;
}
if (!int.TryParse(args[0], out craftingrate))
{
Message(iplayer, "WrongNumberInput");
return;
}
if (craftingrate < 0) craftingrate = 0;
config.CraftingRate = craftingrate;
UpdateCraftingRate();
Message(iplayer, "CraftingRateUpdated", config.CraftingRate);
if (config.SaveCommands) SaveConfig();
}
private void CommandCraftTime(IPlayer iplayer, string command, string[] args)
{
if (!HasPerm(iplayer.Id, permitemrate))
{
Message(iplayer, "NoPerms");
return;
}
if (args.Count() == 1)
{
var itemcheck = FindItem(args[0]);
if (itemcheck)
{
Message(iplayer, "CraftTimeCheck", itemcheck.Blueprint.time);
return;
}
}
if (args.Count() < 2)
{
Message(iplayer, "CraftTime2Args");
return;
}
var setitem = FindItem(args[0]);
if (!setitem)
{
Message(iplayer, "CannotFindItem", args[0]);
return;
}
if (args[1].ToLower() == "default")
{
config.CraftingOptions[setitem.shortname].useCrafteRateMultiplier = true;
config.CraftingOptions[setitem.shortname].craftTime = (defaultsetup[setitem.Blueprint.name] * (config.CraftingRate / 100));
Message(iplayer, "ItemCraftTimeSet", setitem.shortname, (setitem.Blueprint.time).ToString());
if (config.SaveCommands) SaveConfig();
return;
}
int crafttime;
if (!int.TryParse(args[1], out crafttime))
{
Message(iplayer, "WrongNumberInput");
return;
}
config.CraftingOptions[setitem.shortname].craftTime = crafttime;
config.CraftingOptions[setitem.shortname].useCrafteRateMultiplier = false;
var bp = ItemManager.bpList.Where(item => item.targetItem.shortname == setitem.shortname).FirstOrDefault();
bp.time = crafttime;
Message(iplayer, "ItemCraftTimeSet", setitem.shortname, crafttime.ToString());
if (config.SaveCommands) SaveConfig();
}
private void CommandBlockItem(IPlayer iplayer, string command, string[] args)
{
if (!HasPerm(iplayer.Id, permblockitems))
{
Message(iplayer, "NoPerms");
return;
}
if (args.Count() < 1)
{
Message(iplayer, "BlockItem1Args");
return;
}
var blockitem = FindItem(args[0]);
if (!blockitem)
{
Message(iplayer, "CannotFindItem", args[0]);
return;
}
config.CraftingOptions[blockitem.shortname].canCraft = false;
config.CraftingOptions[blockitem.shortname].canResearch = false;
var bp = ItemManager.bpList.Where(item => item.targetItem.shortname == blockitem.shortname).FirstOrDefault();
bp.userCraftable = false;
bp.isResearchable = false;
Message(iplayer, "ItemBlocked", blockitem.shortname);
if (config.SaveCommands) SaveConfig();
}
private void CommandUnblockItem(IPlayer iplayer, string command, string[] args)
{
if (!HasPerm(iplayer.Id, permblockitems))
{
Message(iplayer, "NoPerms");
return;
}
if (args.Count() < 1)
{
Message(iplayer, "UnbockItem1Args");
return;
}
var blockitem = FindItem(args[0]);
if (!blockitem)
{
Message(iplayer, "CannotFindItem", args[0]);
return;
}
config.CraftingOptions[blockitem.shortname].canCraft = true;
config.CraftingOptions[blockitem.shortname].canResearch = true;
var bp = ItemManager.bpList.Where(item => item.targetItem.shortname == blockitem.shortname).FirstOrDefault();
bp.userCraftable = true;
bp.isResearchable = true;
Message(iplayer, "ItemUnblocked", blockitem.shortname);
if (config.SaveCommands) SaveConfig();
}
private void CommandWorkbenchLVL(IPlayer iplayer, string command, string[] args)
{
if (!HasPerm(iplayer.Id, permsetbenchlvl))
{
Message(iplayer, "NoPerms");
return;
}
if (args.Count() < 2)
{
Message(iplayer, "WorckBenchLvl2Args");
return;
}
var item = FindItem(args[0]);
if (!item)
{
Message(iplayer, "CannotFindItem", args[0]);
return;
}
int benchlvl;
if (!int.TryParse(args[1], out benchlvl))
{
Message(iplayer, "WrongNumberInput");
return;
}
if (benchlvl < 0 || benchlvl > 3)
{
Message(iplayer, "BenchLevelInput");
return;
}
config.CraftingOptions[item.shortname].workbenchLevel = benchlvl;
var bp = ItemManager.bpList.Where(x => x.targetItem.shortname == item.shortname).FirstOrDefault();
bp.workbenchLevelRequired = benchlvl;
Message(iplayer, "WorkbenchLevelSet", item.shortname, benchlvl.ToString());
if (config.SaveCommands) SaveConfig();
}
private void CommandSetDefaultSkin(IPlayer iplayer, string command, string[] args)
{
if (!HasPerm(iplayer.Id, permsetskins))
{
Message(iplayer, "NoPerms");
return;
}
if (args.Count() < 2)
{
Message(iplayer, "SetSkin2Args");
return;
}
var setitem = FindItem(args[0]);
if (!setitem)
{
Message(iplayer, "CannotFindItem", args[0]);
return;
}
ulong skinid;
if (!ulong.TryParse(args[1], out skinid))
{
Message(iplayer, "WrongNumberInput", args);
return;
}
config.CraftingOptions[setitem.shortname].defaultskinid = skinid;
Message(iplayer, "SkinSet", setitem.shortname, skinid.ToString());
if (config.SaveCommands) SaveConfig();
}
#endregion Commands
#region Methods
private void UpdateCraftingRate()
{
foreach (var bp in ItemManager.bpList)
{
CraftingData data;
if (!config.CraftingOptions.TryGetValue(bp.targetItem.shortname, out data)) continue;
if (!data.canCraft)
{
bp.userCraftable = false;
continue;
}
if (!data.canResearch)
{
bp.isResearchable = false;
continue;
}
if (!data.useCrafteRateMultiplier)
{
bp.time = data.craftTime;
continue;
}
if (config.CraftingRate == 0f)
{
bp.time = 0f;
continue;
}
bp.time *= (float)(config.CraftingRate / 100);
if (data.workbenchLevel > -1 && data.workbenchLevel < 4)
bp.workbenchLevelRequired = data.workbenchLevel;
}
}
private void InstantBulkCraft(BasePlayer player, ItemCraftTask task, ItemDefinition item, List<int> stacks, int craftSkin, ulong skin)
{
if (skin == 0uL)
{
skin = ItemDefinition.FindSkin(item.itemid, craftSkin);
}
foreach (var stack in stacks)
{
var itemtogive = ItemManager.Create(item, stack, craftSkin != 0 && skin == 0uL ? (ulong)craftSkin : skin);
var held = itemtogive.GetHeldEntity();
if (held != null)
{
held.skinID = skin == 0uL ? (ulong)craftSkin : skin;
held.SendNetworkUpdate();
}
player.GiveItem(itemtogive);
if (config.ShowCraftNotes) player.Command(string.Concat(new object[] { "note.inv ", item.itemid, " ", stack }), new object[0]);
Interface.CallHook("OnItemCraftFinished", task, itemtogive);
}
}
private static void CompleteCrafting(BasePlayer player)
{
if (player.inventory.crafting.queue.Count == 0) return;
player.inventory.crafting.FinishCrafting(player.inventory.crafting.queue.First.Value);
player.inventory.crafting.queue.RemoveFirst();
}
private static void CancelAllCrafting(BasePlayer player)
{
var crafter = player.inventory.crafting;
crafter.queue.ToList().ForEach(x => crafter.CancelTask(x.taskUID, true));
}
#endregion Methods
#region Hooks
private Dictionary<ItemCraftTask, ulong> skinupdate = new Dictionary<ItemCraftTask, ulong>();
private object OnItemCraft(ItemCraftTask task, BasePlayer crafter)
{
var player = task.owner;
var target = task.blueprint.targetItem;
if (task.instanceData?.dataInt != null) return null;
var stacks = GetStacks(target, task.amount * task.blueprint.amountToCreate);
ulong defaultskin = 0uL;
int freeslots = FreeSlots(player);
if (!config.FullInventory && stacks.Count >= freeslots)
{
int space = FreeSpace(player, target);
if (space < 1)
{
ReturnCraft(task, player);
return false;
}
int taskamt = task.amount * task.blueprint.amountToCreate;
for (int i = 0; i < 20 && taskamt > space; i++)
{
var oldtaskamt = taskamt;
taskamt = space;
foreach (var item in task.takenItems)
{
var itemtogive = item;
double fraction = (double)taskamt / (double)oldtaskamt;
int amttogive = (int)(item.amount * (1 - fraction));
if (amttogive <= 1)
{
ReturnCraft(task, player);
return false;
}
itemtogive = ItemManager.Create(item.info, amttogive, 0uL);
item.amount -= amttogive;
player.GiveItem(itemtogive);
}
space -= (freeslots - FreeSlots(player)) * target.stackable;
if (space < 1 || taskamt < 1)
{
ReturnCraft(task, player);
return false;
}
if (taskamt <= space) break;
}
task.amount = (int)(taskamt / task.blueprint.amountToCreate);
}
if (task.skinID == 0)
{
CraftingData data;
if (config.CraftingOptions.TryGetValue(target.shortname, out data))
{
defaultskin = data.defaultskinid;
}
if (config.RandomSkins && defaultskin == 0)
{
var skins = GetSkins(ItemManager.FindItemDefinition(target.itemid));
defaultskin = skins.GetRandom();
}
if (defaultskin > 999999)
skinupdate[task] = defaultskin;
else
task.skinID = (int)defaultskin;
}
List<string> bonusperms = permissionsBonusMultiplier.Where(perm => HasPerm(player.UserIDString, perm)).ToList();
if (bonusperms.Count > 0)
{
task.blueprint.time *= (float)(config.BonusMultiplier[bonusperms.First().Split('.')[1].ToString()] / 100);
}
if (task.blueprint.time == 0f || HasPerm(player.UserIDString, perminstantbulkcraft))
{
skinupdate.Remove(task);
stacks = GetStacks(target, task.amount * task.blueprint.amountToCreate);
InstantBulkCraft(player, task, target, stacks, task.skinID, defaultskin);
task.cancelled = true;
return false;
}
return null;
}
private void OnItemCraftFinished(ItemCraftTask task, Item item)
{
ulong skinid;
if (!skinupdate.TryGetValue(task, out skinid)) return;
item.skin = skinid;
var held = item.GetHeldEntity();
if (held != null)
{
held.skinID = skinid;
held.SendNetworkUpdate();
}
if (task.amount == 0)
skinupdate.Remove(task);
}
void OnItemCraftCancelled(ItemCraftTask task)
{
ulong skinid;
if (!skinupdate.TryGetValue(task, out skinid)) return;
skinupdate.Remove(task);
}
private void OnServerQuit()
{
foreach (var player in BasePlayer.activePlayerList.Where(player => player.inventory.crafting.queue.Count > 0))
{
if (config.CompleteCrafting)
CompleteCrafting(player);
CancelAllCrafting(player);
}
}
#endregion Hooks
#region Helpers
private void ReturnCraft(ItemCraftTask task, BasePlayer crafter)
{
task.cancelled = true;
Message(crafter.IPlayer, "NoInvSpace");
foreach (var item in task.takenItems.Where(x => x.amount > 0)) task.owner.GiveItem(item);
}
private ItemDefinition FindItem(string itemNameOrId)
{
ItemDefinition itemDef = ItemManager.FindItemDefinition(itemNameOrId.ToLower());
if (itemDef == null)
{
int itemId;
if (int.TryParse(itemNameOrId, out itemId))
{
itemDef = ItemManager.FindItemDefinition(itemId);
}
}
return itemDef;
}
private int FreeSpace(BasePlayer player, ItemDefinition item)
{
var slots = player.inventory.containerMain.capacity + player.inventory.containerBelt.capacity;
List<Item> containeritems = new List<Item>();
Dictionary<ItemDefinition, int> queueamts = new Dictionary<ItemDefinition, int>();
containeritems.AddRange(player.inventory.containerMain.itemList);
containeritems.AddRange(player.inventory.containerBelt.itemList);
int value = 0;
//Sum all items in crafting queue not including the item to be crafted
foreach (var queueitem in player.inventory.crafting.queue)
{
if (queueitem.blueprint.targetItem == item) continue;
if (queueamts.TryGetValue(queueitem.blueprint.targetItem, out value))
{
queueamts[queueitem.blueprint.targetItem] += queueitem.amount * queueitem.blueprint.amountToCreate;
continue;
}
queueamts[queueitem.blueprint.targetItem] = (queueitem.amount * queueitem.blueprint.amountToCreate);
}
//Take into account room of other stacks
int queuestacks = 0;
queueamts.ToList().ForEach(i => queuestacks += GetStacks(i.Key, i.Value - Stackroom(containeritems, i.Key.shortname)).Count);
//calculate total room in inventory for the item required
int invstackroom = (slots - containeritems.Count - queuestacks) * item.stackable;
invstackroom += containeritems.Where(x => x.info == item && x.amount < x.MaxStackable()).Sum(x => x.MaxStackable() - x.amount);
invstackroom -= player.inventory.crafting.queue.ToList().Where(x => x.blueprint.targetItem.shortname == item.shortname).Sum(x => x.amount * x.blueprint.amountToCreate);
return invstackroom;
}
private int FreeSlots(BasePlayer player)
{
var slots = player.inventory.containerMain.capacity + player.inventory.containerBelt.capacity;
var taken = player.inventory.containerMain.itemList.Count + player.inventory.containerBelt.itemList.Count;
return slots - taken;
}
private int Stackroom(List<Item> items, string item)
{
return items.Where(x => x.info.shortname == item && x.amount < x.MaxStackable()).Sum(x => x.MaxStackable() - x.amount);
}
private List<int> GetStacks(ItemDefinition item, int amount)
{
var list = new List<int>();
var maxStack = item.stackable;
while (amount > maxStack)
{
amount -= maxStack;
list.Add(maxStack);
}
list.Add(amount);
return list;
}
private readonly Dictionary<string, List<ulong>> skinsCache = new Dictionary<string, List<ulong>>();
private List<ulong> GetSkins(ItemDefinition def)
{
List<ulong> skins;
if (skinsCache.TryGetValue(def.shortname, out skins)) return skins;
skins = new List<ulong>();
skins.AddRange(ItemSkinDirectory.ForItem(def).Select(skin => (ulong)skin.id));
skins.AddRange(Rust.Workshop.Approved.All.Values.Where(skin => skin.Skinnable.ItemName == def.shortname).Select(skin => skin.WorkshopdId));
skinsCache.Add(def.shortname, skins);
return skins;
}
private string GetLang(string langKey, string playerId = null, params object[] args)
{
return string.Format(lang.GetMessage(langKey, this, playerId), args);
}
private void Message(IPlayer player, string langKey, params object[] args)
{
if (player.IsConnected) player.Message(GetLang(langKey, player.Id, args));
}
private bool HasPerm(string id, string perm) => permission.UserHasPermission(id, perm);
private void AddLocalizedCommand(string command)
{
foreach (string language in lang.GetLanguages(this))
{
Dictionary<string, string> messages = lang.GetMessages(language, this);
foreach (KeyValuePair<string, string> message in messages)
{
if (!message.Key.Equals(command)) continue;
if (string.IsNullOrEmpty(message.Value)) continue;
AddCovalenceCommand(message.Value, command);
}
}
}
#endregion Helpers
}
}