Иконка ресурса

FREE Loottable : 1.0.33 1.0.33

Нет прав для скачивания
C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
 //#define HOOKTIME_DEBUG
// Reference: 0Harmony
using Facepunch;
using Harmony;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Oxide.Plugins.LoottableExtensions;
using ProtoBuf;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;
using static TrainCarUnloadable;
using StaticLootableModels = Oxide.Plugins.LoottableExtensions.StaticLootableModels;
#if CARBON
using Carbon.Base;
using Carbon.Modules;
#endif
/**
 * 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
 * 
 * V 1.0.21
 * - call hook OnSupplyDropDropped for custom air drops
 * - added the option to disable loot refresh (should fix problems with increased entity count)
 * - fixed fuel duplication glitch with miner hats
 * - fixed stacking bugs with dropped items
 * 
 * V 1.0.22
 * - fix problem with BradleyDrops
 * - add blueprint option for items
 * - items can now have condition
 * - fix blueprint stacking issues
 * - furnace and airdrop config can be disabled
 * - add electrical furnace to smelting config
 * 
 * V 1.0.23
 * - fix KeyNotFoundException in OnDispenserGather
 * - put gahtered items in hotbar
 * - fix issue where adding an item would add it as extra item
 * - added gather multipliers for growable plants
 * 
 * V 1.0.24
 * - fix global gather multiplier not working for animals
 * - fix furnace speed issues
 * - add support for HeliSignals
 * - add support for DefendableHomes
 * - add support for Carbon and it's ImageDatabase
 * - rework train loot configuration
 * - change plugin type to RustPlugin
 * 
 * V 1.0.25
 * - change command system to work with RustPlugin
 * - add warning if sandbox is enabled
 * 
 * V 1.0.26
 * - remove sandbox check
 * 
 * V 1.0.27
 * - extend custom item api
 * - add the ability to create custom items in the editor
 * - add page navigation for custom items
 * - fix smelting speed for electric furnace
 * - fix display bug with info message for train loot config
 * - add validation to furnace config (only enable when SimpleSplitter is not present)
 * - handle splitting of items with container (BagOfHolding compatibility)
 * - remove debug message for default loot profile (no more console spam)
 * - add the ability to create / load config backups
 * - fix gathered items always going into hotbar
 * 
 * V 1.0.28
 * - lower min item time for quarry config from 0.1 to 0.01
 * - change uint net ids to NetworkableId
 * 
 * V 1.0.29
 * - rework npc loot system
 * - add npc configs for lauch site, missile silo, power plant
 * - persist item.text when splitting items, don't stack items with different item.text
 * - fix furnace speed not working when using igniter or button to start furnace
 * - fix conflict with deployable nature
 * - add support for "everything" quarry
 * 
 * V 1.0.30
 * - fix possible NRE in OnPluginLoaded
 * - change MiniCopter to PlayerHelicopter
 * - add AttackHelicopter to fuel config
 * - fix loot additions not working properly
 * - rework loot refresh to be more performant
 * - misc improvements
 * 
 * V 1.0.31
 * - fix supply drop issues
 * - add page navigation for static lootables
 * - misc fixes/improvements
 * 
 * V 1.0.32
 * - modify OnCustomAirdrop hook to resolve issue with VehicleAirdrops
 * - performance optimization for stacking hooks
 * - performance optimization for npc loot hooks
 * - add hook time tracker for debugging
 * - npc loot system optimization - should now consume less memory
 * 
 * V 1.0.33
 * - add support for legacy furnace
 * - fix error when opening ui
 * 
**/
namespace Oxide.Plugins {
    [Info("Loottable", "The_Kiiiing", "1.0.33")]
    [Description("A configurable loot table with GUI")]
    public class Loottable : RustPlugin {
#region Hook time tracker
#if HOOKTIME_DEBUG
        private sealed class HookTimeTracker : IDisposable {
            public static readonly Dictionary<string, HookInfo> hookInfo = new();
            public static float loadTime;
            private readonly string hookName;
            private readonly Stopwatch sw;
            private List<double> breakpoints;
            public HookTimeTracker(string hookName) {
                this.hookName = hookName;
                breakpoints = Pool.GetList<double>();
                sw = Stopwatch.StartNew();
            }
            public void SetBreakpoint() {
                var time = sw.Elapsed.TotalMilliseconds;
                if (breakpoints.Count > 0) {
                    time -= breakpoints[^1];
                }
                breakpoints.Add(time);
            }
            public void Dispose() {
                sw.Stop();
                if (!hookInfo.TryGetValue(hookName, out var info)) {
                    info = new HookInfo(hookName, true);
                    hookInfo.Add(hookName, info);
                }
                info.AddCall(sw.Elapsed.TotalMilliseconds, breakpoints);
                Pool.FreeList(ref breakpoints);
            }
            public class HookInfo {
                public string hookName;
                public int calls;
                public double averageTime;
                public double minTime = double.MaxValue;
                public double maxTime;
                public double totalTime;
                public List<HookInfo> breakpoints;
                public HookInfo(string hookName) : this (hookName, false) {
                }
                public HookInfo(string hookName, bool hasBreakpoints) {
                    this.hookName = hookName;
                    if (hasBreakpoints) {
                        breakpoints = new List<HookInfo>();
                    }
                }
                public void AddCall(double time, IList<double> breakpointTime) {
                    calls++;
                    averageTime = (averageTime + time) / calls;
                    totalTime += time;
                    if (time > maxTime) {
                        maxTime = time;
                    }
                    else if (time < minTime) {
                        minTime = time;
                    }
                    if (breakpoints != null && breakpointTime != null) {
                        for(int i = 0; i < breakpointTime.Count; i++) {
                            if (i < breakpoints.Count) {
                                breakpoints[i].AddCall(breakpointTime[i], null);
                            }
                            else {
                                var info = new HookInfo($"Sec {i}");
                                info.AddCall(breakpointTime[i], null);
                                breakpoints.Add(info);
                            }
                        }
                    }
                }
            }
        }
        [ConsoleCommand("l.hooktime")]
        void CmdHookTime(ConsoleSystem.Arg arg) {
            var player = arg.Player();
            if (player != null && !permission.UserHasPermission(player.UserIDString, PERM_DEBUG)) {
                arg.ReplyWith("No permission");
                return;
            }
            var sb = new StringBuilder($"Hook time overview (Plugin uptime {Mathf.RoundToInt(Time.time - HookTimeTracker.loadTime)}s)");
            sb.AppendLine();
            sb.AppendLine(ToFixed("Hook", 35) + ToFixed("Calls") + ToFixed("Avg") + ToFixed("Min") + ToFixed("Max") + ToFixed("Total"));
            foreach (var info in HookTimeTracker.hookInfo.Values) {
                sb.AppendLine(ToFixed(info.hookName, 35) + ToFixed(info.calls.ToString()) + ToFixed(info.averageTime.ToString("N6")) + ToFixed(info.minTime.ToString("N6")) + ToFixed(info.maxTime.ToString("N6")) + ToFixed(info.totalTime.ToString("N2")));
                if (info.breakpoints != null) foreach(var bp in info.breakpoints) {
                    sb.AppendLine("    " + ToFixed(bp.hookName, 31) + ToFixed(bp.calls.ToString()) + ToFixed(bp.averageTime.ToString("N6")) + ToFixed(bp.minTime.ToString("N6")) + ToFixed(bp.maxTime.ToString("N6")) + ToFixed(bp.totalTime.ToString("N2")));
                }
            }
            arg.ReplyWith(sb.ToString());
            static string ToFixed(string str, int length = 14) {
                if (str.Length > length) {
                    str = "invalid";
                }
                var fill = length - str.Length;
                if (fill > 0) {
                    return str + new string(' ', fill);
                }
                return str;
            }
        }
#endif
#endregion
#region Fields
        [PluginReference]
        private Plugin ImageLibrary, StaticLootables, LootDefender, FancyDrop, SimpleSplitter, BetterNpc, DefendableHomes, DeployableNature;
        private static bool UseStaticLootables => (bool)_instance.StaticLootables;
        private const int configVersion = 8;
        private const string COMMAND = "loottable";
        private const string UI_COMMAND = "loottable.cmd";
        private const string BACKUP_COMMAND = "loottable.backup";
        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 => ((RustPlugin)_instance).Name;
        private static EditorConfig Flags;
        private static Loottable _instance;
#if CARBON
        ImageDatabaseModule imageDatabase;
#endif
#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 RefreshLootOnExit => flags["RefreshLootOnExit"];
            [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
                }
                , {
                    "RefreshLootOnExit", true
                }
            }
            ;
            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 DataFilePathFull() => Path.Combine(Interface.Oxide.DataDirectory, Name);
        private static string DataFilePathFull(string file) => Path.Combine(Interface.Oxide.DataDirectory, Name, file);
        private static string DataFilePathFull(string folder, string file) => Path.Combine(Interface.Oxide.DataDirectory, Name, folder, file);
        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");
#if CARBON
            imageDatabase.Queue(Flags?.Update ?? false, images);
#else
            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);
            }
            );
#endif
        }
        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) {
#if CARBON
            return new CuiRawImageComponent {
                Png = imageDatabase.GetImageString(key, silent : true)
            }
            ;
#else
            return new CuiRawImageComponent {
                Png = ImageLibrary?.Call<string>("GetImage", key)
            }
            ;
#endif
        }
#endregion
#region Backup
        private void CmdBackup(IPlayer player, string command, string[] args) {
            if (!permission.UserHasPermission(player.Id, PERM_DEBUG)) {
                player.Reply("You don't have permission to do that");
                return;
            }
            if (args.Length < 2) {
                player.Reply($"Invalid args! Usage:\n    {command} load <name> - Load backup with the given name from the backups folder" + $"\n    {command} create <name> - Create backup of everything with the given name");
                // TODO uncomment when enabling scoped backups
                //$"\n    {command} create <name> <crates|gathering|stacksize|config|all> - Create a backup of the specified configurations with the given name");
                return;
            }
            string name = args[1];
            if (args[0] == "load") {
                string result = BackupManager.LoadBackupFile(name);
                player.Reply(result);
            }
            else if (args[0] == "create") {
                string target = args.Length == 3 ? args[2] : "all";
                string result = BackupManager.CreateBackupFile(name, target);
                player.Reply(result);
            }
        }
        private static class BackupManager {
            public enum BackupScope {
                NONE = 0x00, CRATES = 0x01, GATHERING = 0x02, STACKSIZE = 0x04, CONFIG = 0x08, ALL = CRATES | GATHERING | STACKSIZE | CONFIG
            }
            private const string BACKUP_FOLDER = "backups";
            private const string BACKUP_EXTENSION = "lootprofile";
            private static bool ExcludeFile(string nameWithExtension) {
                // Protect from potentially malicious files
                if (!nameWithExtension.EndsWith(".json")) {
                    return true;
                }
                // Other unwanted files
                return nameWithExtension == "editorconfig.json" || nameWithExtension == "custom_items.json" || nameWithExtension == "custom_items_v2.json";
            }
            // TODO apply backup scope
            public static string CreateBackupFile(string name, string target) {
                BackupScope scope;
                if (!Enum.TryParse<BackupScope>(target, true, out scope) || scope == BackupScope.NONE) {
                    return $"Failed to create backup - Invalid selector '{target}'";
                }
                CheckBackupDirectory();
                string backupFile = DataFilePathFull(BACKUP_FOLDER, $"{name}.{BACKUP_EXTENSION}");
                if (File.Exists(backupFile)) {
                    return $"Failed to create backup - File {backupFile} already exists";
                }
                List<FileBackup> backups = new List<FileBackup>();
                try {
                    foreach (var file in Directory.GetFiles(DataFilePathFull())) {
                        string fileName = Path.GetFileName(file);
                        if (ExcludeFile(fileName)) continue;
                        string contents = File.ReadAllText(file);
                        var backup = new FileBackup(fileName);
                        backup.CompressData(contents);
                        backups.Add(backup);
                    }
                    var fileStream = File.Create(backupFile);
                    SerializeMany(backups, fileStream);
                    fileStream.Dispose();
                }
                catch (Exception ex) {
                    return $"An exception occured while creating a backup: {ex}";
                }
                return $"Backup {backupFile} successfully created";
            }
            public static string LoadBackupFile(string name) {
                CheckBackupDirectory();
                string backupFile = DataFilePathFull(BACKUP_FOLDER, $"{name}.{BACKUP_EXTENSION}");
                if (!File.Exists(backupFile)) {
                    return $"Failed to load backup - file {backupFile} does not exist";
                }
                try {
                    var fileStream = File.OpenRead(backupFile);
                    List<FileBackup> backups = DeserializeMany<FileBackup>(fileStream);
                    foreach (var backup in backups) {
                        if (ExcludeFile(backup.OriginalName)) continue;
                        string fileName = DataFilePathFull(backup.OriginalName);
                        if (File.Exists(fileName)) {
                            File.Delete(fileName);
                        }
                        string data = backup.DecompressData();
                        File.WriteAllText(fileName, data);
                    }
                }
                catch (Exception ex) {
                    return $"An exception occured while loading a backup: {ex}";
                }
                _instance.timer.In(2f, () => ConsoleSystem.Run(ConsoleSystem.Option.Server, "o.reload", Name));
                return "Backup sucessfully loaded, plugin will reload to apply the changes";
            }
            private static void CheckBackupDirectory() {
                string path = DataFilePathFull(BACKUP_FOLDER);
                if (!Directory.Exists(path)) {
                    Directory.CreateDirectory(path);
                }
            }
#region Protobuf
            [ProtoContract]
            private class FileBackup {
                [ProtoMember(1)]
                public string OriginalName {
                    get;
                    set;
                }
                [ProtoMember(2)]
                public byte[] CompressedData {
                    get;
                    set;
                }
                public FileBackup() {
                }
                public FileBackup(string fileName) {
                    OriginalName = fileName;
                }
#region Compress / Decompress
                public void CompressData(string json) {
                    var bytes = Encoding.UTF8.GetBytes(json);
                    var msi = new MemoryStream(bytes);
                    var mso = new MemoryStream();
                    var gs = new GZipStream(mso, CompressionMode.Compress);
                    msi.CopyTo(gs);
                    gs.Dispose();
                    CompressedData = mso.ToArray();
                    // WHY DOES THIS ANCIENT COMPILER NOT SUPPORT FUCKING USINGS?
                    msi.Dispose();
                    mso.Dispose();
                }
                public string DecompressData() {
                    var msi = new MemoryStream(CompressedData);
                    var mso = new MemoryStream();
                    var gs = new GZipStream(msi, CompressionMode.Decompress);
                    gs.CopyTo(mso);
                    gs.Dispose();
                    string result = Encoding.UTF8.GetString(mso.ToArray());
                    msi.Dispose();
                    mso.Dispose();
                    return result;
                }
#endregion
            }
            private static void SerializeMany<T>(IEnumerable<T> data, Stream stream) {
                foreach (var obj in data) {
                    Serializer.SerializeWithLengthPrefix<T>(stream, obj, PrefixStyle.Fixed32);
                }
            }
            private static List<T> DeserializeMany<T>(Stream stream) {
                List<T> list = new List<T>();
                T obj;
                do {
                    obj = Serializer.DeserializeWithLengthPrefix<T>(stream, PrefixStyle.Fixed32);
                    if (obj != null) {
                        list.Add(obj);
                    }
                }
                while (obj != null);
                return list;
            }
#endregion
        }
#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;
        }
        private static bool CanUseCustomAirdrop(SupplySignal signal, ThrownWeapon item) {
            object result = Interface.CallHook("OnCustomAirdrop", signal, item);
            return result == null;
        }
#endregion
#region Hooks
#region Loot
        object OnLootSpawn(LootContainer container) {
#if HOOKTIME_DEBUG
            using var htt = new HookTimeTracker("OnLootSpawn");
#endif
            var lootConfig = LootManager.GetContainerConfig(container);
            if (lootConfig == null || !CanPopulateContainerHook(container)) {
                return null;
            }
#if HOOKTIME_DEBUG
            htt.SetBreakpoint();
#endif
            if (lootConfig.Enabled) {
                if (lootConfig.lootType != LootableConfig.LootType.Custom) {
                    container.PopulateLoot();
                }
                lootConfig.ApplyToCrate(container);
                if (container.shouldRefreshContents) {
                    container.CancelInvoke(container.SpawnLoot);
                    container.Invoke(container.SpawnLoot, UnityEngine.Random.Range(container.minSecondsBetweenRefresh, container.maxSecondsBetweenRefresh));
                }
#if HOOKTIME_DEBUG
                htt.SetBreakpoint();
#endif
                return false;
            }
            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) {
#if HOOKTIME_DEBUG
            using var htt = new HookTimeTracker("OnEntitySpawned [LootableCorpse]");
#endif
            var config = LootManager.GetNpcConfig(corpse);
#if HOOKTIME_DEBUG
            htt.SetBreakpoint();
#endif
            if (config == null || !CanPopulateCorpseHook(corpse)) {
                return;
            }
            if (config.Enabled) {
                timer.In(0.1f, () => {
                    if (!corpse.IsDestroyed) {
                        config.ApplyToCorpse(corpse?.containers[0], true);
                    }
                }
                );
            }
        }
        void OnEntitySpawned(BasePlayer npc) {
#if HOOKTIME_DEBUG
            using var htt = new HookTimeTracker("OnEntitySpawned [BasePlayer]");
#endif
            if (npc.IsNpc) {
                LootManager.CacheNpc(npc);
            }
        }
#endregion
#region Gathering
        object OnCollectiblePickup(CollectibleEntity entity, BasePlayer player) {
            if (player == null || entity == null) return null;
            if (IsDeployableNature(entity)) {
                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);
        }
        void OnGrowableGathered(GrowableEntity plant, Item item, BasePlayer player) {
            GatherConfig config = GatherManager.gatherConfig;
            if (config == null) return;
            if (!config.enabled) return;
            item.amount = config.GetNewGrowableAmount(plant.ShortPrefabName, item.amount);
        }
        private bool IsDeployableNature(BaseEntity entity) {
            if (DeployableNature == null) {
                return false;
            }
            var b = DeployableNature.Call("STCanGainXP", null, entity) as bool?;
            if (b == false) {
                return true;
            }
            return false;
        }
#endregion
#region Stacking
        bool? CanStackItem(Item target, Item item, bool combine = false) {
#if HOOKTIME_DEBUG
            using var htt = new HookTimeTracker(combine ? "CanCombineDroppedItem" : "CanStackItem");
#endif
            if (Flags.DisableStackingHooks) return null;
            if ((target.GetOwnerPlayer().IsUnityNull() || item.GetOwnerPlayer().IsUnityNull()) && !combine) return null;
            if (target.info.itemid != item.info.itemid) return false;
            if (target.skin != item.skin) return false;
            if (target.name != item.name) return false;
            if (target.text != item.text) return false;
            if (target.condition != item.condition) return false;
            if (target.blueprintTarget != item.blueprintTarget) return false;
            if (target.info.amountType == ItemDefinition.AmountType.Genetics || item.info.amountType == ItemDefinition.AmountType.Genetics) {
                if ((target.instanceData?.dataInt ?? -1) != (item.instanceData?.dataInt ?? -1)) {
                    return false;
                }
            }
#if HOOKTIME_DEBUG
            htt.SetBreakpoint();
#endif
            var heldEntity = item.GetHeldEntity();
            if (heldEntity is BaseProjectile weapon && !combine) {
                var mag = weapon.primaryMagazine;
                if (mag.contents > 0) {
                    target.parent.GetOwnerPlayer().GiveItem(ItemManager.Create(mag.ammoType, mag.contents));
                    mag.contents = 0;
                }
            }
            if (heldEntity is Chainsaw chainsaw && !combine) {
                if (chainsaw.ammo > 0) {
                    target.GetOwnerPlayer().GiveItem(ItemManager.Create(chainsaw.fuelType, chainsaw.ammo));
                    chainsaw.ammo = 0;
                }
            }
            if (heldEntity is FlameThrower flameThrower && !combine) {
                if (flameThrower.ammo > 0) {
                    target.GetOwnerPlayer().GiveItem(ItemManager.Create(flameThrower.fuelType, flameThrower.ammo));
                    flameThrower.ammo = 0;
                }
            }
            if (heldEntity is BaseLiquidVessel && (!item.contents.IsEmpty() || !target.contents.IsEmpty())) {
                return false;
            }
#if HOOKTIME_DEBUG
            htt.SetBreakpoint();
#endif
            // target.info.stackable > target.amount prevents infinite fuel glitch
            if ((item.info.shortname == "hat.miner" || item.info.shortname == "hat.candle") && (item.contents?.itemList.Count ?? 0) > 0 && !combine && target.info.stackable > target.amount) {
                // Check if all items can be moved to new stack
                // Only take out fuel if old stack gets removed
                if (item.amount - (item.info.stackable - target.amount) <= 0) {
                    var content = item.contents.itemList.First();
                    var drop = ItemManager.Create(content.info, content.amount, content.skin);
                    item.GetOwnerPlayer().GiveItem(drop);
                }
            }
            return true;
        }
        Item OnItemSplit(Item item, int amount) {
#if HOOKTIME_DEBUG
            using var htt = new HookTimeTracker("OnItemSplit");
#endif
            if (Flags.DisableStackingHooks) return null;
            if (DefendableHomes != null) {
                if (item.skin == 2591851360 || item.skin == 2817854052 || item.skin == 2817854377 || item.skin == 2817854677 || item.skin == 2888602635 || item.skin == 2888602942 || item.skin == 2888603247) return null;
            }
#if HOOKTIME_DEBUG
            htt.SetBreakpoint();
#endif
            item.amount -= amount;
            Item split = ItemManager.Create(item.info, amount, item.skin);
            split.name = item.name;
            split.text = item.text;
            split.condition = item.condition;
#if HOOKTIME_DEBUG
            htt.SetBreakpoint();
#endif
            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 HOOKTIME_DEBUG
            htt.SetBreakpoint();
#endif
            var heldEntity = split.GetHeldEntity();
            if (heldEntity is BaseProjectile weapon) {
                weapon.primaryMagazine.contents = 0;
                weapon.SendNetworkUpdateImmediate();
            }
            if (heldEntity is Chainsaw chainsaw) {
                if (chainsaw.ammo > 0) {
                    chainsaw.ammo = 0;
                    chainsaw.SendNetworkUpdateImmediate();
                }
            }
            if (heldEntity is FlameThrower flameThrower) {
                if (flameThrower.ammo > 0) {
                    flameThrower.ammo = 0;
                    flameThrower.SendNetworkUpdateImmediate();
                }
            }
            if (heldEntity is BaseLiquidVessel) {
                split.contents.ForceClear();
                //item.contents.Clear();
            }
#if HOOKTIME_DEBUG
            htt.SetBreakpoint();
#endif
            if (item.contents != null) {
                if (split.contents == null) {
                    split.contents = new ItemContainer();
                    split.contents.ServerInitialize(split, item.contents.capacity);
                    split.contents.GiveUID();
                }
                else {
                    split.contents.capacity = item.contents.capacity;
                }
            }
            split.MarkDirty();
            return split;
        }
        private void OnItemAddedToContainer(ItemContainer container, Item item) {
#if HOOKTIME_DEBUG
            using var htt = new HookTimeTracker("OnItemAddedToContainer");
#endif
            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.text = item.text;
                split.condition = item.condition;
                item.MarkDirty();
                split.MarkDirty();
                split.MoveToContainer(player.inventory.containerMain);
            }
        }
        bool? CanCombineDroppedItem(DroppedItem item1, DroppedItem item2) {
            bool? res = CanStackItem(item1.item, item2.item, true);
            // Return null if hook returned null or true
            if (res ?? true) {
                return null;
            }
            return false;
        }
#endregion
#region Heli Fuel
        private void OnEntitySpawned(PlayerHelicopter heli) {
            if (!ConfigManager.IsReady) return;
            NextTick(() => {
                if (heli.creatorEntity == null) return;
                EntityFuelSystem fs = heli?.GetFuelSystem();
                if (fs == null) return;
                int fuel = -1;
                if (heli is ScrapTransportHelicopter) {
                    fuel = ConfigManager.AirwolfConfig.scrapHeliFuel;
                }
                else if (heli is Minicopter) {
                    fuel = ConfigManager.AirwolfConfig.minicopterFuel;
                }
                else if (heli is AttackHelicopter) {
                    fuel = ConfigManager.AirwolfConfig.attackHeliFuel;
                }
                if (fuel < 0) return;
                Item fuelItem = fs.GetFuelItem();
                if (fuelItem != null) {
                    if (fuel == 0) {
                        fuelItem.Remove();
                    }
                    else if (fuel != fuelItem.amount) {
                        fuelItem.amount = fuel;
                        fuelItem.MarkDirty();
                    }
                }
            }
            );
        }
#endregion
#region Smelting & Mixing
        private void OnOvenStarted(BaseOven oven) {
            if (!ConfigManager.IsReady || !ConfigManager.FurnaceConfig.enabled) {
                return;
            }
            ConfigManager.FurnaceConfig.UpdateOvenSpeed(oven);
        }
        private void OnMixingTableToggle(MixingTable table, BasePlayer player) {
            if (!ConfigManager.IsReady || !ConfigManager.FurnaceConfig.enabled) return;
            ConfigManager.FurnaceConfig.ToggleMixingTable(table);
        }
#endregion
#region Recycler
        object OnRecyclerToggle(Recycler recycler, BasePlayer player) {
            if (!(ConfigManager.FurnaceConfig?.recyclerEnabled ?? false)) 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 || !ConfigManager.AirdropConfig.enabled || entity is not SupplySignal signal) {
                return;
            }
            ConfigManager.AirdropConfig.OnSupplySignalThrown(signal, item);
        }
#endregion
#region General
        void OnServerInitialized() {
            LootManager.Initialize();
            LoadImages();
            StackManager.Initialize();
            GatherManager.Initialize();
            ConfigManager.Initialize();
            TrainLootManager.Initialize();
            LootManager.PopulateNpcCache();
            LootManager.RefreshCrateLoot(null);
        }
        void Init() {
            _instance = this;
#if HOOKTIME_DEBUG
            HookTimeTracker.loadTime = Time.time;
#endif
            InitializeEditorConfig();
#if CARBON
            DPrint("Using Carbon's built-in Image Database");
            imageDatabase = BaseModule.GetModule<ImageDatabaseModule>();
#endif
            AddCovalenceCommand(COMMAND, nameof(CMD_loottable));
            AddCovalenceCommand(UI_COMMAND, nameof(CMD_cmd));
            AddCovalenceCommand(BACKUP_COMMAND, nameof(CmdBackup));
            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();
            TrainLootManager.Shutdown();
            ConfigManager.FurnaceConfig.ResetBurnable();
            ConfigManager.FurnaceConfig.InitializeFurnaces();
        
// ... [Code preview truncated due to length. Full source in attached file] ...

Автор
15FPS
Скачиваний
0
Просмотры
1
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок
Сверху