C#:
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Globalization;
using System.IO;
using System.Diagnostics;
using System.Runtime.Serialization.Formatters.Binary;
using System.Reflection;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Core.Libraries.Covalence;
using Oxide.Game.Rust.Cui;
using Oxide.Plugins.LoottableExtensions;
using UnityEngine;
using UnityEngine.Networking;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using StaticLootableModels = Oxide.Plugins.LoottableExtensions.StaticLootableModels;
/**
* VERSION HISTORY
*
* V 1.0.1
* - replaced FindObjectsOfType with BaseNetworkable.serverEntities.Find
*
* V 1.0.2
* - added gui item selection
* - auto refresh loot after closing the editor
* - added gather manager ui
* - fixed editor sometimes not opening bug
* - loot distribution rework
* - stacksize bugfixes
*
* V 1.0.3
* - fixed category item distribution
* - item.stackable now adjusts with stacksize
* - fixed item amount in crate config ignored
*
* V 1.0.4
* - updated input fields to work with latest oxide version
* - changed input field clamping to match min/max values
* - fixed npc corpse custom loot profiles
* - added gather rates for animals
*
* V 1.0.5
* - fixed plugin not loading on server startup
* - fixed players without admin permission unable to open editor
*
* V 1.0.6
* - fixed invalid cast exception when adjusting item count of npcs
* - added page navigation to crate loot editor
* - added hooks for container population
* - fixed invisible cursor in some input fields
* - improved image loading
*
* V 1.0.7
* - added flags to bypass editor restrictions
* - added addition loot option
* - added default loot config for npcs and some crates
* - added the option to import loot tables
* - fixed a bug where the despawn time of a locked crate would be reset upon reload
*
* V 1.0.7 HOTFIX / V 1.0.8
* - fixed plugin not working after Rust update
*
* V 1.0.9
* - fixed several stacksize related bugs
* - added spray can decal item to ignore list
* - excluded player quarries form quarry config
* - fixed max item amount of crates ignored in some cases
* - loot distribution improvements (again)
* - fixed editor not opening without image library
* - removed stacking of items w/ durability field as it caused confusion
* - replaced GatherControl and StacksizeControl lootable with GenericLootable
* - wear container now only accepts 1 item (no stacked clothes)
* - heli fuel can now be adjusted in the editor
* - smelting speed can now be controlled in the editor
* - updated images for some crates
*
* V 1.0.10
* - added bbq and campfire to smelting config
* - new icons for mini and srappy fuel config
* - added the option to multiply an entire loot table
* - slightly changed category section to look better
* - fixed invalid cast exception when editing category item amounts
* - fixed typo in 'UnlockGahterMultiplier' that caused an error when changing gather multipliers
* - fixed recycler not stopping when items are removed
* - fixed a bug where too much items are in a crate (again)
* - allowed multiple items with same itemid but different skin id
* - added the option to add extra items to an item
* - added air drop config options
* - added scientistnpc_roam to npc config
* - fixed gather items not droping when inventory is full
* - added support for static lootables (wip)
* - disable quick smelt if speed multiplier is 1
* - added category stack size multipliers
*
* V 1.0.11
* - fixed plugin not initializing
*
* V 1.0.12
* - fixed server freezing (caused by infinite loop)
* - fixed unable to edit gather items
* - items can now have drop chances as low as 0.01% (was 1% before)
* - added the option to multiply items of collectables
* - added the option to load the default config for collectables
* - improved loot distribution
* - added indicator for extra items
*
* V 1.0.13
* - fixed unable to edit excavator/quarry items
* - allowed multiple items in excavator config
* - support for static lootables (beta)
* - changed ui parent from hud.menu to overlay
* - fixed recycler bugs
* - added mixing table to smelting speed config
* - added drop chances for gather items
*
* V 1.0.14
* - fixed some typos
* - fixed missing delete button when editing items
* - fixed supply drop bug with loot defender (and fancy drop)
* - added charcoal config for furnaces
* - more config options for static lootables (wip)
*
* V 1.0.15
* - removed deprecated hook OnCollectiblePickup and replaced it
* - cleaned up api code
* - fixed weird console/debug message behavior
* - fixed null reference exception when populating npc corpses
* - added invisible crates
* - slightly changed loot refresh function and included loot wagons
* - added train wagon loot config
*
* V 1.0.16
* - fixed null reference exception in InitializeEditorConfig
* - fixed null reference exception in OnCollectiblePickup
* - added locomotive to list of ignored items
* - added support for custom items
* - fixed issue with xlevels OnCollectible pickup
* - fixed issue when splitting clones
* - fixed issues with simple splitter
* - added hook OnCargoPlaneSignaled to custom supply drops
* - fixed null reference exception when calling a supply drop
*
* V 1.0.17
* - patched for rust update
*
* V 1.0.18
* - fixed problems caused by update
* - re-enabled supply drop
* - re-enabled mixing table
* - fixed npc loot issues
* - fixed error when creating items
* - fixed null reference exception in OnEntitySpawned
*
* V 1.0.19
* - fix IndexOutOfRangeException when selecting last category in stack size editor
* - added TracedAction to find origin of NullReferenceException in timer callback in OnEntitySpawned(LootableCorpse corpse)
* - fixed furnaces not working after server reboot
* - fixed stacking of world items with different skins
*
* V 1.0.20
* - fix bug with category stack size multiplier
* - fix error when npc corpse of BetterNpc NPC spawned
* - added flag to disable stacking hooks
*
**/
namespace Oxide.Plugins {
[Info("Loottable", "nuclear", "1.0.20")]
[Description("A configurable loot table with GUI")]
public class Loottable : CovalencePlugin {
#region Fields
[PluginReference]
private Plugin ImageLibrary, StaticLootables, LootDefender, FancyDrop, SimpleSplitter, BetterNpc;
private static bool UseStaticLootables => (bool)_instance.StaticLootables;
private const int configVersion = 6;
private const string COMMAND = "loottable";
private const string PERM_EDIT = "loottable.edit";
private const string PERM_DEBUG = "loottable.debug";
private static readonly NumberFormatInfo DEFAULT_NUMBER_FORMAT = new CultureInfo("en", false).NumberFormat;
private static NumberFormatInfo NUMBER_FORMAT = DEFAULT_NUMBER_FORMAT;
private static new string Name => ((CovalencePlugin)_instance).Name;
private static EditorConfig Flags;
private static Loottable _instance;
#endregion
#region Editor Config
private static void InitializeEditorConfig() {
Flags = Interface.Oxide.DataFileSystem.ReadObject<EditorConfig>(DataFilePath("editorconfig"));
if (Flags == null) {
CErr("Editor config is corrupted, creating a new one");
Flags = new EditorConfig();
}
Flags.Validate();
SaveEditorConfig();
}
private static void SaveEditorConfig() {
Interface.Oxide.DataFileSystem.WriteObject<EditorConfig>(DataFilePath("editorconfig"), Flags);
}
private class EditorConfig {
[JsonProperty("version")]
private int version = configVersion;
public int ItemLimit {
get;
private set;
}
= 1000000;
public MinMaxFloat GatherMultiplier {
get;
private set;
}
= new MinMaxFloat(1f, 1000f);
public MinMaxFloat FurnaceMultiplier {
get;
private set;
}
= new MinMaxFloat(0.1f, 100f);
public MinMaxFloat ItemMultiplier {
get;
private set;
}
= new MinMaxFloat(0.1f, 10f);
[JsonIgnore]
public bool Debug => flags["Debug"];
[JsonIgnore]
public bool UnlockGatherMultiplier => flags["UnlockGatherMultiplier"];
[JsonIgnore]
public bool DisableItemLimit => flags["DisableItemLimit"];
[JsonIgnore]
public bool UnlockFurnaceMultiplier => flags["UnlockFurnaceMultiplier"];
[JsonIgnore]
public bool UnlockItemMultiplier => flags["UnlockItemMultiplier"];
[JsonIgnore]
public bool DisableStackingHooks => flags["DisableStackingHooks"];
[JsonIgnore]
public bool Update {
get;
}
= false;
public Dictionary<string, bool> flags {
get;
private set;
}
= new Dictionary<string, bool> {
{
"Debug", false
}
, {
"UnlockGatherMultiplier", false
}
, {
"DisableItemLimit", false
}
, {
"UnlockFurnaceMultiplier", false
}
, {
"UnlockItemMultiplier", false
}
, {
"DisableStackingHooks", false
}
}
;
public EditorConfig() {
}
[JsonConstructor]
public EditorConfig(int version, int ItemLimit, int ItemLimitUnlocked, MinMaxFloat GatherMultiplier, MinMaxFloat FurnaceMultiplier, Dictionary<string, bool> flags) {
if (version != configVersion) Update = true;
this.version = configVersion;
this.ItemLimit = ItemLimit;
this.GatherMultiplier = GatherMultiplier;
this.FurnaceMultiplier = FurnaceMultiplier;
foreach(var flag in flags) this.flags[flag.Key] = flag.Value;
Save();
if (Update) CPrint("Update detected, performing reload of remote content");
}
public void Validate() {
EditorConfig validator = new EditorConfig();
if (ItemLimit == 0) ItemLimit = validator.ItemLimit;
if (GatherMultiplier == MinMaxFloat.Null) GatherMultiplier = validator.GatherMultiplier;
if (FurnaceMultiplier == MinMaxFloat.Null) FurnaceMultiplier = validator.FurnaceMultiplier;
foreach (var flag in flags.ToList()) {
if (!validator.flags.ContainsKey(flag.Key)) flags.Remove(flag.Key);
}
foreach (var vflag in validator.flags) {
if (!flags.ContainsKey(vflag.Key)) flags.Add(vflag.Key, false);
}
}
public bool TrySetFlag(string flag, string value, out bool newValue) {
bool val;
newValue = false;
value = value.Replace("0", Boolean.FalseString).Replace("1", Boolean.TrueString);
if (!flags.ContainsKey(flag)) return false;
if (!Boolean.TryParse(value, out val)) return false;
flags[flag] = val;
newValue = val;
Save();
return true;
}
public void Save() => SaveEditorConfig();
}
private static string DataFilePath(string file) => String.Format("{0}/{1}", Name, file);
private static string DataFilePath(string folder, string file) => String.Format("{0}/{1}/{2}", Name, folder, file);
private static long CurrentUnixTime() => ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds();
#endregion
#region Image Library
private void LoadImages() {
Dictionary<string, string> images = LootManager.GetImageDictionary();
images = images.MergeWith(ConfigManager.GetCustomImages());
Puts($"Import {images.Count} images");
timer.In(1f, () => {
if (!ImageLibrary) PrintError("ERROR: ImageLibrary not found, you can download it here: https://umod.org/plugins/image-library");
else ImageLibrary?.Call("ImportImageList", Title, images, 0UL, Flags?.Update ?? false);
}
);
}
private CuiImageComponent CreateItemImage(int itemid, ulong skin = 0) {
if (skin == 0) return new CuiImageComponent {
ItemId = itemid
}
;
else return new CuiImageComponent {
ItemId = itemid, SkinId = skin
}
;
}
private CuiRawImageComponent CreateImage(string key) {
return new CuiRawImageComponent {
Png = ImageLibrary?.Call<string>("GetImage", key)
}
;
}
#endregion
#region Custom Hooks
private static bool CanPopulateContainerHook(LootContainer container) {
object result = Interface.CallHook("OnContainerPopulate", container);
return result == null;
}
private static bool CanPopulateCorpseHook(LootableCorpse corpse) {
object result = Interface.CallHook("OnCorpsePopulate", corpse);
return result == null;
}
#endregion
#region Hooks
#region Loot
object OnLootSpawn(LootContainer container) {
LootableConfig lootConfig = LootManager.GetContainerConfig(container);
if (lootConfig == null) return null;
if (!CanPopulateContainerHook(container)) return null;
if (lootConfig.Enabled) {
if (lootConfig.lootType == LootableConfig.LootType.Custom) {
lootConfig.ApplyToCrate(container);
return false;
}
else {
timer.In(0.1f, () => {
lootConfig.ApplyToCrate(container);
}
);
return null;
}
}
return null;
}
#endregion
#region Quarries
void OnQuarryToggled(MiningQuarry quarry, BasePlayer player) {
if (!quarry.isStatic) return;
var config = LootManager.GetQuarryConfig(quarry);
if (quarry.IsEngineOn()) config.QuarryInit(ref quarry);
else config.ResetQuarry(ref quarry);
}
void OnQuarryGather(MiningQuarry quarry, Item item) {
if (!quarry.isStatic) return;
var config = LootManager.GetQuarryConfig(quarry);
config.QuarryUpdate(ref quarry, ref item);
}
void OnExcavatorMiningToggled(ExcavatorArm arm) {
var config = LootManager.GetExcavatorConfig();
config.Apply(ref arm);
}
object OnExcavatorGather(ExcavatorArm arm, Item item) {
var config = LootManager.GetExcavatorConfig();
config.GatherUpdate(ref arm, item);
return null;
}
#endregion
#region NPCs
void OnEntitySpawned(LootableCorpse corpse) {
var config = LootManager.GetNpcConfig(corpse);
if (config == null) return;
if (!CanPopulateCorpseHook(corpse)) return;
if (config.Enabled) {
timer.In(0.1f, () => {
if (!corpse.IsDestroyed) {
config.ApplyToCorpse(corpse?.containers[0], true);
}
}
);
}
}
void OnEntitySpawned(BasePlayer npc) {
if (npc.IsNpc) LootManager.CacheNpc(npc);
}
#endregion
#region Gathering
object OnCollectiblePickup(CollectibleEntity entity, BasePlayer player) {
if (player == null || entity == null) return null;
CollectibleConfig config = LootManager.GetCollectibleConfig(entity);
if (config == null) return null;
if (!config.enabled) return null;
config.DoPickup(entity, player);
return false;
}
object OnDispenserGather(ResourceDispenser dispenser, BasePlayer player, Item item) {
BaseEntity ent = dispenser.GetComponent<BaseEntity>();
GatherConfig config = GatherManager.gatherConfig;
if (config == null) return null;
if (!config.enabled) return null;
item.amount = config.GetNewAmount(ent.ShortPrefabName, item.info.itemid, item.amount);
return null;
}
void OnDispenserBonus(ResourceDispenser dispenser, BasePlayer player, Item item) {
BaseEntity ent = dispenser.GetComponent<BaseEntity>();
GatherConfig config = GatherManager.gatherConfig;
if (config == null) return;
if (!config.enabled) return;
item.amount = config.GetNewAmount(ent.ShortPrefabName, item.info.itemid, item.amount);
}
#endregion
#region Stacking
bool? CanStackItem(Item item, Item target, bool combine = false) {
if (Flags.DisableStackingHooks) return null;
if ((item.GetOwnerPlayer().IsUnityNull() || target.GetOwnerPlayer().IsUnityNull()) && !combine) return null;
if (item.info.itemid != target.info.itemid) return false;
if (item.skin != target.skin) return false;
if (item.name != target.name) return false;
if (item.condition != target.condition) return false;
if (item.info.amountType == ItemDefinition.AmountType.Genetics || target.info.amountType == ItemDefinition.AmountType.Genetics) {
if ((item.instanceData?.dataInt ?? -1) != (target.instanceData?.dataInt ?? -1)) {
return false;
}
}
if (target.GetHeldEntity() is BaseProjectile && !combine) {
BaseProjectile.Magazine mag = (target.GetHeldEntity() as BaseProjectile).primaryMagazine;
if (mag.contents > 0) {
item.parent.GetOwnerPlayer().GiveItem(ItemManager.Create(mag.ammoType, mag.contents));
mag.contents = 0;
}
}
if (target.GetHeldEntity() is Chainsaw && !combine) {
Chainsaw chainsaw = target.GetHeldEntity() as Chainsaw;
if (chainsaw.ammo > 0) {
item.GetOwnerPlayer().GiveItem(ItemManager.Create(chainsaw.fuelType, chainsaw.ammo));
chainsaw.ammo = 0;
}
}
if (target.GetHeldEntity() is FlameThrower && !combine) {
FlameThrower flameThrower = target.GetHeldEntity() as FlameThrower;
if (flameThrower.ammo > 0) {
item.GetOwnerPlayer().GiveItem(ItemManager.Create(flameThrower.fuelType, flameThrower.ammo));
flameThrower.ammo = 0;
}
}
if ((target.info.shortname == "hat.miner" || target.info.shortname == "hat.candle") && (target.contents?.itemList.Count ?? 0) > 0 && !combine) {
Item content = target.contents.itemList.First();
Item drop = ItemManager.Create(content.info, content.amount, content.skin);
target.GetOwnerPlayer().GiveItem(drop);
}
if (target.GetHeldEntity() is BaseLiquidVessel) {
if (!target.contents.IsEmpty() || !item.contents.IsEmpty()) return false;
}
return true;
}
Item OnItemSplit(Item item, int amount) {
if (Flags.DisableStackingHooks) return null;
item.amount -= amount;
Item split = ItemManager.Create(item.info, amount, item.skin);
split.name = item.name;
split.condition = item.condition;
if (item.IsBlueprint()) split.blueprintTarget = item.blueprintTarget;
item.MarkDirty();
if (item.info.amountType == ItemDefinition.AmountType.Genetics && item.instanceData != null && item.instanceData.dataInt != 0) {
split.instanceData = new ProtoBuf.Item.InstanceData() {
dataInt = item.instanceData.dataInt, ShouldPool = false
}
;
}
if (split.GetHeldEntity() is BaseProjectile) {
BaseProjectile weapon = split.GetHeldEntity() as BaseProjectile;
weapon.primaryMagazine.contents = 0;
weapon.SendNetworkUpdateImmediate();
}
if (split.GetHeldEntity() is Chainsaw) {
Chainsaw chainsaw = split.GetHeldEntity() as Chainsaw;
if (chainsaw.ammo > 0) {
chainsaw.ammo = 0;
chainsaw.SendNetworkUpdateImmediate();
}
}
if (split.GetHeldEntity() is FlameThrower) {
FlameThrower flameThrower = split.GetHeldEntity() as FlameThrower;
if (flameThrower.ammo > 0) {
flameThrower.ammo = 0;
flameThrower.SendNetworkUpdateImmediate();
}
}
if (split.GetHeldEntity() is BaseLiquidVessel) {
split.contents.ForceClear();
//item.contents.Clear();
}
split.MarkDirty();
return split;
}
private void OnItemAddedToContainer(ItemContainer container, Item item) {
if (Flags.DisableStackingHooks) return;
BasePlayer player = container.GetOwnerPlayer();
if (player?.IsNpc ?? true) return;
if (player.inventory.containerWear.uid == container.uid && item.amount > 1) {
int amount = item.amount - 1;
item.amount = 1;
Item split = ItemManager.Create(item.info, amount, item.skin);
split.name = item.name;
split.condition = item.condition;
item.MarkDirty();
split.MarkDirty();
split.MoveToContainer(player.inventory.containerMain);
}
}
bool? CanCombineDroppedItem(WorldItem witem1, WorldItem witem2) => CanStackItem(witem1.item, witem2.item, true);
#endregion
#region Heli Fuel
private void OnEntitySpawned(MiniCopter heli) {
if (!ConfigManager.IsReady) return;
NextTick(() => {
if (heli.creatorEntity == null) return;
EntityFuelSystem fs = heli?.GetFuelSystem();
if (fs == null) return;
int fuel = heli is ScrapTransportHelicopter ? ConfigManager.AirwolfConfig.scrapHeliFuel : ConfigManager.AirwolfConfig.minicopterFuel;
if (fuel < 0) return;
Item fuelItem = fs.GetFuelItem();
if (fuelItem != null && fuelItem.amount != fuel) {
fuelItem.amount = fuel;
fuelItem.MarkDirty();
}
}
);
}
#endregion
#region Smelting & Mixing
private object OnOvenToggle(StorageContainer furnace, BasePlayer player) {
if (!ConfigManager.IsReady || SimpleSplitter != null) return null;
BaseOven oven = furnace.GetComponent<BaseOven>();
if (oven == null) return null;
ConfigManager.FurnaceConfig.ToggleOven(oven);
return null;
}
private void OnMixingTableToggle(MixingTable table, BasePlayer player) {
if (!ConfigManager.IsReady) return;
ConfigManager.FurnaceConfig.ToggleMixingTable(table);
}
#endregion
#region Recycler
object OnRecyclerToggle(Recycler recycler, BasePlayer player) {
if ((ConfigManager.FurnaceConfig?.recyclingSpeedMultiplier ?? 1) == 1) return null;
ConfigManager.FurnaceConfig.ToggleRecycler(recycler);
return null;
}
#endregion
#region Air Drop
void OnExplosiveDropped(BasePlayer player, BaseEntity entity, ThrownWeapon item) => OnExplosiveThrown(player, entity, item);
void OnExplosiveThrown(BasePlayer player, BaseEntity entity, ThrownWeapon item) {
if (!ConfigManager.IsReady) return;
if (entity == null) return;
if (!(entity is SupplySignal)) return;
ConfigManager.AirdropConfig.OnAirdropThrown(player, entity);
}
#endregion
#region General
void OnServerInitialized() {
LootManager.onReady = LoadImages;
LootManager.Initialize();
StackManager.Initialize();
GatherManager.Initialize();
ConfigManager.Initialize();
TrainLootConfig.Initialize();
LootManager.PopulateNpcCache();
LootManager.RefreshCrateLoot(null);
}
void Init() {
_instance = this;
InitializeEditorConfig();
permission.RegisterPermission(PERM_EDIT, this);
permission.RegisterPermission(PERM_DEBUG, this);
UiHelper.EditorCache.Clear();
CustomItemStorage.LoadCustomItems();
if (Flags.DisableStackingHooks) {
Unsubscribe(nameof(CanStackItem));
Unsubscribe(nameof(CanCombineDroppedItem));
Unsubscribe(nameof(OnItemSplit));
Unsubscribe(nameof(OnItemAddedToContainer));
}
}
void Unload() {
SaveEditorConfig();
UiHelper.DestroyUI();
ConfigManager.FurnaceConfig.ResetBurnable();
StackManager.RevertStacksize(true);
LootManager.RefreshCrateLoot(null, true);
CustomItemStorage.SaveCustomItems();
}
object CanSpectateTarget(BasePlayer player, string filter) {
if (permission.UserHasPermission(player.UserIDString, PERM_EDIT) && UiHelper.uiUser == player.userID) {
return false;
}
return null;
}
void OnPluginUnloaded(Plugin name) {
CustomItemStorage.RemoveCustomItems(name);
}
#endregion
#endregion
#region API
private void AddCustomItem(Plugin plugin, int itemId, ulong skinId) => AddCustomItem(plugin, itemId, skinId, null);
private void AddCustomItem(Plugin plugin, int itemId, ulong skinId, string customName) {
CustomItemStorage.AddCustomItem(new CustomItemStorage.CustomItem {
plugin = plugin.Name, itemId = itemId, skinId = skinId, customName = customName
}
);
}
#endregion
#region Custom Items
private class CustomItemStorage {
[JsonProperty]
public List<CustomItem> customItems = new List<CustomItem>();
[JsonProperty]
private DateTime lastSave;
[JsonIgnore]
public const string itemCategory = "custom";
[JsonIgnore]
private static CustomItemStorage _storage;
[JsonIgnore]
public static List<CustomItem> CustomItems {
get;
private set;
}
= new List<CustomItem>();
public static void LoadCustomItems() {
_storage = Interface.Oxide.DataFileSystem.ReadObject<CustomItemStorage>(DataFilePath("custom_items"));
if (_storage == null || _storage.customItems == null || DateTime.UtcNow.Subtract(_storage.lastSave).TotalSeconds > 20) return;
CustomItems = _storage.customItems;
}
public static void AddCustomItem(CustomItem item) {
CustomItems.Add(item);
DPrint($"{item.plugin} added custom item {item.customName}");
}
public static void RemoveCustomItems(Plugin plugin) {
CustomItems.RemoveAll(x => x.plugin == plugin.Name);
}
public static void SaveCustomItems() {
_storage = new CustomItemStorage {
customItems = CustomItems, lastSave = DateTime.UtcNow
}
;
Interface.Oxide.DataFileSystem.WriteObject(DataFilePath("custom_items"), _storage);
}
public static void SortItemList() {
CustomItems = CustomItems.OrderBy(x => x.plugin).ToList();
}
public class CustomItem {
public string plugin;
public int itemId;
public ulong skinId;
public string customName;
}
}
#endregion
#region StackManager
private static class StackManager {
// Vanilla stack size file https://pastebin.com/raw/vtjnscHq
public static bool stacksizeIsReverted {
get;
private set;
}
= true;
public static StacksizeConfig stacksizeConfig {
get;
private set;
}
public static Dictionary<string, List<int>> itemCategoryList {
get;
private set;
}
public static List<ItemDefinition> itemList {
get;
private set;
}
public static readonly List<int> ignoreItems = new List<int> {
-1779180711, -277057363, // Water + Salt water
-1759188988, 1426574435, -1884328185, //HAB, MC, ScrapHeli repair
-810326667, -1364246987, 1768112091, 1394042569, 1878053256, -1449152644, //Wcart, Snowmobile, TSnowmobile, RHIB, Boat, MLRS
1770744540, 878301596, -44066790, -44066823, -44066600, //Generic VChassis, Generic VModule, 4x chassis, 3x chassis, 2x chassis
1015352446, -187031121, 550753330, // Duo/Solo Sub, Snowball Ammo
996757362, -1366326648, -2027988285 // train wagon, spray can decal, locomotive
}
;
public static bool StacksizeIsVanilla(int itemid) => stacksizeConfig.GetVanillaStacksize(itemid) == stacksizeConfig.GetCustomStacksize(itemid);
public static int GetCurrentStacksize(int itemid) => GetCurrentStacksize(ItemManager.FindItemDefinition(itemid));
public static int GetCurrentStacksize(ItemDefinition itemDefinition) {
int itemid = itemDefinition.itemid;
if (ignoreItems.Contains(itemid)) return itemDefinition.stackable;
int vs = stacksizeConfig.GetVanillaStacksize(itemid), cs = stacksizeConfig.GetCustomStacksize(itemid);
if (stacksizeConfig.enabled) {
float cat_mpl = stacksizeConfig.categoryMultipliers[MapItemCategory(itemDefinition.category)];
if (vs == cs) return Mathf.RoundToInt(vs * stacksizeConfig.globalMultiplier * cat_mpl);
return cs;
}
else {
return vs;
}
}
public static void Initialize() {
itemCategoryList = new Dictionary<string, List<int>>();
LoadData();
RefreshItemLists();
ApplyStacksize();
}
public static void Save() {
SaveData();
}
private static void RefreshItemLists() {
itemCategoryList = new Dictionary<string, List<int>>();
itemList = new List<ItemDefinition>();
if (!itemCategoryList.ContainsKey(CustomItemStorage.itemCategory)) itemCategoryList.Add(CustomItemStorage.itemCategory, new List<int>());
foreach (var item in ItemManager.itemList) {
if (ignoreItems.Contains(item.itemid)) continue;
var cat = item.category.ToString();
if (!itemCategoryList.ContainsKey(cat)) {
itemCategoryList[cat] = new List<int>();
}
itemList.Add(item);
itemCategoryList[cat].Add(item.itemid);
}
}
public static void RevertStacksize(bool force = false) {
if (stacksizeIsReverted && !force) return;
foreach (ItemDefinition item in ItemManager.itemList) {
if (ignoreItems.Contains(item.itemid)) continue;
item.stackable = stacksizeConfig.GetVanillaStacksize(item.itemid);
}
stacksizeIsReverted = true;
}
public static void ApplyStacksize() {
if (!stacksizeConfig.enabled) {
RevertStacksize();
return;
}
stacksizeIsReverted = false;
foreach (ItemDefinition item in ItemManager.itemList) {
if (ignoreItems.Contains(item.itemid)) continue;
item.stackable = GetCurrentStacksize(item);
}
}
private static int MapItemCategory(ItemCategory itemCat) {
int cat = 0;
List<int> cats = new List<int>();
foreach (ItemDefinition itemdef in ItemManager.itemList) {
if (!cats.Contains((int)itemdef.category)) {
cats.Add((int)itemdef.category);
if (itemdef.category == itemCat) return cat;
cat++;
}
}
return -1;
}
private static void LoadData() {
StacksizeConfig config = Interface.Oxide.DataFileSystem.ReadObject<StacksizeConfig>($"{Name}/stacksize");
stacksizeConfig = config;
}
private static void SaveData() {
Interface.Oxide.DataFileSystem.WriteObject($"{Name}/stacksize", stacksizeConfig);
}
}
private class StacksizeConfig {
[JsonProperty("ssc")]
private Dictionary<int, int> itemStacksize;
[JsonProperty("ssv")]
private Dictionary<int, int> vanillaStacksize;
[JsonProperty("vanilla")]
public bool enabled {
get;
private set;
}
[JsonProperty("catMpl")]
public Dictionary<int, float> categoryMultipliers;
[JsonProperty("multiplier")]
public float globalMultiplier;
[JsonConstructor]
public StacksizeConfig(Dictionary<int, int> itemStacksize, Dictionary<int, int> vanillaStacksize, bool enabled, Dictionary<int, float> categoryMultipliers, float globalMultiplier) {
this.itemStacksize = itemStacksize;
this.vanillaStacksize = vanillaStacksize;
this.enabled = enabled;
this.categoryMultipliers = categoryMultipliers;
this.globalMultiplier = globalMultiplier;
if (this.categoryMultipliers.IsNullOrEmpty()) InitCategoryMultipliers();
}
public StacksizeConfig() {
itemStacksize = new Dictionary<int, int>();
vanillaStacksize = new Dictionary<int, int>();
enabled = false;
InitCategoryMultipliers();
globalMultiplier = 1f;
SaveVanillaStacksize();
}
public int GetVanillaStacksize(int itemid) {
if (!vanillaStacksize.ContainsKey(itemid)) vanillaStacksize[itemid] = ItemManager.FindItemDefinition(itemid).stackable;
return vanillaStacksize[itemid];
}
public int GetCustomStacksize(int itemid) {
if (!itemStacksize.ContainsKey(itemid)) itemStacksize[itemid] = ItemManager.FindItemDefinition(itemid).stackable;
return itemStacksize[itemid];
}
public void SetStacksize(int itemid, int amount) {
amount = Mathf.Clamp(amount, 1, Int32.MaxValue);
itemStacksize[itemid] = amount;
}
public void ResetCategory(int catid) {
foreach(int itemid in StackManager.itemCategoryList.Values.ToList()[catid]) {
itemStacksize[itemid] = vanillaStacksize[itemid];
}
}
public void TryEnable(bool enabled) => this.enabled = enabled;
private void SaveVanillaStacksize() {
foreach(ItemDefinition item in ItemManager.itemList) {
if (StackManager.ignoreItems.Contains(item.itemid)) continue;
vanillaStacksize[item.itemid] = item.stackable;
itemStacksize[item.itemid] = item.stackable;
}
}
private void InitCategoryMultipliers() {
categoryMultipliers = new Dictionary<int, float>();
int cat = 0;
List<int> cats = new List<int>();
foreach(ItemDefinition itemdef in ItemManager.itemList) {
if (!cats.Contains((int)itemdef.category)) {
cats.Add((int)itemdef.category);
categoryMultipliers[cat] = 1f;
cat++;
}
}
}
}
#endregion
#region GatherManager
private static class GatherManager {
public static GatherConfig gatherConfig {
get;
private set;
}
public static void Initialize() {
LoadData();
}
public static void Save() => SaveData();
private static void LoadData() {
GatherConfig config = Interface.Oxide.DataFileSystem.ReadObject<GatherConfig>($"{Name}/gatherconfig");
gatherConfig = config;
}
private static void SaveData() {
Interface.Oxide.DataFileSystem.WriteObject($"{Name}/gatherconfig", gatherConfig);
}
public static Dictionary<string, Dictionary<int, float>> GetGatherItems() {
var temp = new Dictionary<string, Dictionary<int, float>>();
temp["default"] = new Dictionary<int, float> {
{
-151838493, 1f
}
, {
-2099697608, 1f
}
, {
-4031221, 1f
}
, {
-1982036270, 1f
}
, {
-1157596551, 1f
}
}
;
temp["bear.corpse"] = new Dictionary<int, float> {
{
-1018587433, 1f
}
, {
1719978075, 1f
}
, {
1381010055, 1f
}
, {
-858312878, 1f
}
, {
-1520560807, 1f
}
}
;
temp["polarbear.corpse"] = new Dictionary<int, float> {
{
-1018587433, 1f
}
, {
1719978075, 1f
}
, {
1381010055, 1f
}
, {
-858312878, 1f
}
, {
-1520560807, 1f
}
}
;
temp["boar.corpse"] = new Dictionary<int, float> {
{
-1018587433, 1f
}
, {
1719978075, 1f
}
, {
1381010055, 1f
}
, {
-858312878, 1f
}
, {
621915341, 1f
}
}
;
temp["wolf.corpse"] = new Dictionary<int, float> {
{
-1018587433, 1f
}
, {
1719978075, 1f
}
, {
1381010055, 1f
}
, {
-858312878, 1f
}
, {
-395377963, 1f
}
, {
2048317869, 1f
}
}
;
temp["stag.corpse"] = new Dictionary<int, float> {
{
-1018587433, 1f
}
, {
1719978075, 1f
}
, {
1381010055, 1f
}
, {
-858312878, 1f
}
, {
1422530437, 1f
}
}
;
temp["chicken.corpse"] = new Dictionary<int, float> {
{
-858312878, 1f
}
, {
1719978075, 1f
}
, {
-1440987069, 1f
}
}
;
return temp;
}
}
private class GatherConfig {
[JsonProperty("g")]
public float globalMultiplier;
[JsonProperty("mpl")]
public Dictionary<string, Dictionary<int, float>> multipliers;
[JsonProperty("enabled")]
public bool enabled {
get;
private set;
}
[JsonConstructor]
public GatherConfig(Dictionary<string, Dictionary<int, float>> multipliers, bool enabled, float globalMultiplier) {
this.multipliers = multipliers;
this.enabled = enabled;
this.globalMultiplier = globalMultiplier;
}
public GatherConfig() {
this.multipliers = new Dictionary<string, Dictionary<int, float>>();
this.enabled = false;
this.globalMultiplier = 1f;
SetDefaults();
}
private void SetDefaults() => multipliers = GatherManager.GetGatherItems();
public void TryEnable(bool enabled) => this.enabled = enabled;
public void Reset() => SetDefaults();
public void SetMultiplier(string prefabName, int itemid, float multi) {
multipliers[prefabName][itemid] = multi;
}
public int GetNewAmount(string prefabName, int itemid, int amount) {
if (!enabled) return amount;
if (multipliers.ContainsKey(prefabName)) {
if (multipliers[prefabName][itemid] == 1f) return Mathf.RoundToInt(amount * globalMultiplier);
return Mathf.RoundToInt(amount * multipliers[prefabName][itemid]);
}
else if (multipliers["default"].ContainsKey(itemid)) {
if (multipliers["default"][itemid] == 1f) return Mathf.RoundToInt(amount * globalMultiplier);
return Mathf.RoundToInt(amount * multipliers["default"][itemid]);
}
return Mathf.RoundToInt(amount * globalMultiplier);
}
}
#endregion
#region ConfigManager
private static class ConfigManager {
public static readonly List<GenericLootable> genericLootableList = new List<GenericLootable> {
new GenericLootable(LootManager.Lootables.StackSizeControl, "stacksize", "Stack Size Control", "https://cdn.icon-icons.com/icons2/2518/PNG/512/stack_icon_151083.png"), new GenericLootable(LootManager.Lootables.GatherConfig, "gathering", "Gather Control", "https://static.wikia.nocookie.net/play-rust/images/8/86/Pick_Axe_icon.png"), new GenericLootable(LootManager.Lootables.FurnaceConfig, "smelting", "Furnace Config", "https://static.wikia.nocookie.net/play-rust/images/e/ee/Large_Furnace_icon.png"), new GenericLootable(LootManager.Lootables.AirwolfConfig, "airwolf", "Air Wolf Config", "https://static.wikia.nocookie.net/play-rust/images/9/95/494EDB03-BAEA-42BB-8FAE-748F0D2B50A9.png"), new GenericLootable(LootManager.Lootables.RecyclerConfig, "recycling", "Recycler Config", "https://static.wikia.nocookie.net/play-rust/images/e/ef/Recycler_icon.png"), new GenericLootable(LootManager.Lootables.AirDropConfig, "airdrop", "Air Drop Config", "https://static.wikia.nocookie.net/play-rust/images/2/24/Supply_Signal_icon.png")
}
;
public static readonly LootManager.Lootables[] labelIgnore = new LootManager.Lootables[] {
LootManager.Lootables.StackSizeControl, LootManager.Lootables.AirwolfConfig, LootManager.Lootables.FurnaceConfig, LootManager.Lootables.RecyclerConfig, LootManager.Lootables.AirDropConfig
}
;
public static bool IsReady {
get;
private set;
}
= false;
private static string FurnaceConfigFile => DataFilePath("config_smelting");
private static string AirwolfConfigFile => DataFilePath("config_airwolf");
private static string AirdropConfigFile => DataFilePath("config_airdrop");
public static FurnaceConfiguration FurnaceConfig {
get;
private set;
}
public static AirwolfConfiguration AirwolfConfig {
get;
private set;
}
public static AirDropConfiguration AirdropConfig {
get;
private set;
}
public static void Initialize() {
LoadData();
FurnaceConfig.UpdateBurnable();
FurnaceConfig.InitializeFurnaces();
IsReady = true;
// ... [Code preview truncated due to length. Full source in attached file] ...