Does not compile after forced update (07 Sept 2023)

Here's the error that appears in console:

Error while compiling VendingManager: 'PlayerInventory' does not contain a definition for 'FindItemIDs' and no accessible extension method 'FindItemIDs' accepting a first argument of type 'PlayerInventory' could be found (are you missing a using directive or an assembly reference?) | Line: 400, Pos: 54 

I have the same error.

Me too...

Same here :(Β  Hoping to find some info if anyone learns anything.

The compilation error is caused by `FindItemIDs` being outdated on line 398 and 402. To resolve this compilation error you can change `FinditemIDs` => `FindItemsByItemID`.

Even if this fixes your issue, be sure to check back here as the plugin maintainer may wish to update their own fix.

T0CypyCvXpxgvBT.jpg Steel

The compilation error is caused by `FindItemIDs` being outdated on line 398 and 402. To resolve this compilation error you can change `FinditemIDs` => `FindItemsByItemID`.

Even if this fixes your issue, be sure to check back here as the plugin maintainer may wish to update their own fix.

Thanks for this info! I just want to be clear what you mean with =>?Β  You want us to change the 1st one to the 2nd one?

drummerjacob

Thanks for this info! I just want to be clear what you mean with =>?Β  You want us to change the 1st one to the 2nd one?

Correct πŸ‘

qGqlxOt6BkmMY2K.jpg Steel

The compilation error is caused by `FindItemIDs` being outdated on line 398 and 402. To resolve this compilation error you can change `FinditemIDs` => `FindItemsByItemID`. Hope this is found useful to some of you until and official plugin update is posted😬

Very helpful - thanks! I've updated the file here for those who need it:

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Oxide.Core;
using Oxide.Core.Plugins;
using Rust;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Vending Manager", "Whispers88", "0.2.12a")]
    [Description("Improved vending machine control")]
    class VendingManager : RustPlugin
    {
        #region Variables

        [PluginReference]
        Plugin CustomVendingSetup, Economics, ServerRewards;

        // usage permission
        private const string PermCanUse = "vendingmanager.canuse";
        private const string PermCanEject = "vendingmanager.caneject";

        // valid commands
        private enum Command
        {
            add, clear, eject, info, list, load, reset, save, set, unset
        };

        // configuration options
        private enum Option
        {
            destroyOnUnload,    // destroy locks on unload
            ejectLocks,         // eject locks on unload/reload
            health,             // health
            lockable,           // allow attaching locks
            lockFailureMessage, // display message on lock attach failure
            saveLocks,          // save locks on unload
            setHealth,          // enable setting health
            noBroadcast,        // blocks broadcasting
            restricted,         // restrict panel access to owners
            useEconomics,       // use Economics
            useServerRewards,   // use ServerRewards
            transactionTimeout, // timeout to end transactions
            logTransSuccess,    // enable logging transaction success
            logTransFailure,    // enable logging transaction failures
            transMessages,      // enable transaction success messages
            currencyItem        // currency item shortname
        }

        // default configuration values
        object[] defaults = new object[] { false, false, defaultHealth, true, false, true, true, false, false, false, false, 300f, false, false, true, "blood" };

        // container for config/data
        VendingData data = new VendingData();
        Dictionary<ulong, VendingMachineInfo> vms = new Dictionary<ulong, VendingMachineInfo>();
        Dictionary<ulong, LockInfo> locks = new Dictionary<ulong, LockInfo>();
        const float defaultHealth = 500f;
        ProtectionProperties defaultProtection;
        ProtectionProperties customProtection;

        const string CodeLockPrefab = "assets/prefabs/locks/keypad/lock.code.prefab";
        const string KeyLockPrefab = "assets/prefabs/locks/keylock/lock.key.prefab";

        Dictionary<ulong, Timer> transactionTimers = new Dictionary<ulong, Timer>();
        Dictionary<ulong, Timer> timeoutTimers = new Dictionary<ulong, Timer>();

        bool isShuttingDown = false;
        bool useEconomics = false;
        bool useServerRewards = false;
        string currencyPlugin
        {
            get
            {
                if (useEconomics) return Economics.Name;
                if (useServerRewards) return ServerRewards.Name;
                return null;
            }
        }

        int currencyIndex = 24;
        ItemDefinition currencyItem;

        #endregion

        #region Lang

        // load default messages to Lang
        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                {"Prefix", "<color=orange>[ VendingManager ]</color> "},
                {"ClearSuccess", "Successfully cleared Vending Machine sell orders"},
                {"SaveSuccess", "Saved Vending Machine sell orders to \"{0}\""},
                {"LoadSuccess", "Loaded Vending Machine sell orders from \"{0}\""},
                {"ResetSuccess", "Successfully cleared and reset VendingManager configuration to defaults"},
                {"ConfirmReset", "To reset the VendingManager configuration and remove all saved templates, type: /vm reset confirm"},
                {"VMNotFound", "No Vending Machine found"},
                {"EmptyTemplate", "Template \"{0}\" is empty"},
                {"EmptyVM", "Vending Machine has no sell orders defined"},
                {"TemplateNotFound", "No sell order template found with name \"{0}\""},
                {"TemplateExists", "Template with name \"{0}\" already exists, add \"overwrite\" parameter to command to overwrite template"},
                {"InvalidCommand", "Invalid command: {0}"},
                {"InvalidParameter", "Invalid parameter"},
                {"NotAuthorized", "You cannot add a lock to that Vending Machine"},
                {"CommandList", "<color=cyan>Valid Commands:</color>" + Environment.NewLine + "{0}"},
                {"TemplateList", "<color=cyan>Templates:</color>" + Environment.NewLine + "{0}"},
                {"Ejected", "Ejected {0} locks from Vending Machines"},
                {"NoBroadcast", "Broadcasting is not allowed" },
                {"Restricted", "You do not have access to administrate that VendingMachine" },
                {"Information", "Vending Machine ID: <color=cyan>{0}</color>" + Environment.NewLine + "Has configuration? <color=cyan>{1}</color>" + Environment.NewLine + "Flags: <color=cyan>{2}</color>" },

                {"EconomicsNotEnoughMoney", "Transaction Cancelled (Economics): Not enough money" },
                {"EconomicsNotEnoughMoneyOwner", "Transaction Cancelled (Economics): Buyer doesn't have enough money" },
                {"EconomicsTransferFailed", "Transaction Cancelled (Economics): Money transfer failed" },
                {"EconomicsPurchaseSuccess", "Successfully purchased {0} {1} for {2:C}; Remaining balance: {3:C}" },
                {"EconomicsSellSuccess", "Successfully sold {0} {1} for {2:C}; New balance: {3:C}" },

                {"ServerRewardsNotEnoughMoney", "Transaction Cancelled (ServerRewards): Not enough RP" },
                {"ServerRewardsNotEnoughMoneyOwner", "Transaction Cancelled (ServerRewards): Buyer doesn't have enough RP" },
                {"ServerRewardsTransferFailed", "Transaction Cancelled (ServerRewards): RP transfer failed" },
                {"ServerRewardsPurchaseSuccess", "Successfully purchased {0} {1} for {2}RP; Remaining balance: {3}RP" },
                {"ServerRewardsSellSuccess", "Successfully sold {0} {1} for {2}RP; New balance: {3}RP" },

                {"SetSuccess", "Successfully set flag <color=cyan>{0}</color>" },
                {"UnsetSuccess", "Successfully removed flag <color=cyan>{0}</color>" },
                {"WarnEconAndSREnabled", "Economics and ServerRewards are both enabled as currency; ServerRewards has been forcibly disabled." },

                {"NotAllowed", "You are not allowed to use this command!"},
                {"CmdBase", "vm"}
            }, this);
        }

        // get message from Lang
        string GetMessage(string key, string userId = null) => lang.GetMessage(key, this, userId);

        #endregion

        #region Loading/Unloading

        // on load
        //void Loaded()
        void Init()
        {
            cmd.AddChatCommand(GetMessage("CmdBase"), this, "CommandDelegator");
            permission.RegisterPermission(PermCanUse, this);
            permission.RegisterPermission(PermCanEject, this);
            LoadData();
        }

        // on unload, reset all vending machines
        void Unload()
        {
            if (ConfigValue<bool>(Option.saveLocks) || isShuttingDown)
                SaveVMsAndLocks();
            SetAll(false);
            if (ConfigValue<bool>(Option.destroyOnUnload) || isShuttingDown)
                DestroyLocks();

            foreach (Timer t in transactionTimers.Values)
                t?.Destroy();
            foreach (Timer t in timeoutTimers.Values)
                t?.Destroy();
        }

        // server initialized
        void OnServerInitialized()
        {
            SetAll(ConfigValue<bool>(Option.lockable), ConfigValue<float>(Option.health));
            if (ConfigValue<bool>(Option.saveLocks))
                LoadLocks();
            currencyItem = ItemManager.FindItemDefinition(ConfigValue<string>(Option.currencyItem)) ?? ItemManager.FindItemDefinition("blood"); // assume blood if item missing
            useEconomics = ConfigValue<bool>(Option.useEconomics);
            useServerRewards = ConfigValue<bool>(Option.useServerRewards);
            Check(true);
        }

        void OnPluginLoaded(Plugin plugin)
        {
            if (plugin.Name == "Economics" && ConfigValue<bool>(Option.useEconomics))
                Check();
            else if (plugin.Name == "ServerRewards" && ConfigValue<bool>(Option.useServerRewards))
                Check();
        }

        void OnPluginUnloaded(Plugin plugin)
        {
            if (plugin.Name == "Economics" && ConfigValue<bool>(Option.useEconomics))
                Check();
            else if (plugin.Name == "ServerRewards" && ConfigValue<bool>(Option.useServerRewards))
                Check();
        }

        void Check(bool initial = false)
        {
            bool prev = useEconomics;
            useEconomics = ConfigValue<bool>(Option.useEconomics) && Economics != null;
            if (initial || prev != useEconomics)
                Puts("Economics " + (useEconomics ? "detected - money purchases enabled" : "not detected - money purchases disabled"));
            prev = useServerRewards;
            useServerRewards = ConfigValue<bool>(Option.useServerRewards) && ServerRewards != null;
            if (initial || prev != useServerRewards)
                Puts("ServerRewards " + (useServerRewards ? "detected - RP purchases enabled" : "not detected - RP purchases disabled"));
            if (useEconomics && useServerRewards)
            {
                useServerRewards = false;
                PrintWarning(GetMessage("WarnEconAndSREnabled"));
            }
        }

        // save/destroy locks on server shutdown to avoid NULL in saveList
        void OnServerShutdown()
        {
            isShuttingDown = true;
        }

        // save locks when server saves
        void OnServerSave()
        {
            // delayed save to fight the lag monster
            timer.In(5f, () => SaveVMsAndLocks());
        }

        #endregion

        #region Configuration

        // load default config
        bool LoadDefaultConfig()
        {
            data = new VendingData();
            CheckConfig();
            data.templates = new Dictionary<string, SellOrderTemplate>();
            return true;
        }

        void LoadData()
        {
            bool dirty = false;
            try
            {
                data = Config.ReadObject<VendingData>();
            }
            catch (Exception) { }
            dirty = CheckConfig();
            if (data.templates == null)
                dirty |= LoadDefaultConfig();
            if (dirty)
                SaveData();
            vms = Interface.GetMod()?.DataFileSystem?.ReadObject<Dictionary<ulong, VendingMachineInfo>>("VendingManagerVMs");
            locks = Interface.GetMod()?.DataFileSystem?.ReadObject<Dictionary<ulong, LockInfo>>("VendingManagerLocks");
        }

        // write data container to config
        void SaveData()
        {
            Config.WriteObject(data);
        }

        void SaveVendingMachineData()
        {
            foreach (ulong k in vms.Keys.ToList())
            {
                BaseNetworkable net = BaseNetworkable.serverEntities.Find(new NetworkableId(k));
                VendingMachine vm = net as VendingMachine;
                if (net == null || vm == null)
                    vms.Remove(k);
            }
            Interface.GetMod().DataFileSystem.WriteObject("VendingManagerVMs", vms);
        }

        // save locks data to file
        void SaveLocksData()
        {
            Interface.GetMod().DataFileSystem.WriteObject("VendingManagerLocks", locks);
        }

        // get value from config (handles type conversion)
        T GetConfig<T>(string group, string name, T value)
        {
            if (Config[group, name] == null)
            {
                Config[group, name] = value;
                SaveConfig();
            }
            return (T)Convert.ChangeType(Config[group, name], typeof(T));
        }

        // validate configuration
        bool CheckConfig()
        {
            bool dirty = false;
            foreach (Option option in Enum.GetValues(typeof(Option)))
                if (!data.config.ContainsKey(option))
                {
                    data.config[option] = defaults[(int)option];
                    dirty = true;
                }
            return dirty;
        }

        #endregion

        #region Hooks

        // set newly spawned vending machines to the value of lockable
        void OnEntitySpawned(BaseNetworkable entity)
        {
            if (entity.GetType() == typeof(VendingMachine))
                Set(entity as VendingMachine, ConfigValue<bool>(Option.lockable), ConfigValue<float>(Option.health));
        }

        // block unauthorized lock deployment onto vending machines
        // only allow attachment from the rear, except if player is
        // the owner of the vending machine
        void OnItemDeployed(Deployer deployer, BaseEntity entity)
        {
            if (!ConfigValue<bool>(Option.lockable)) return;
            if (deployer == null || entity == null) return;
            BasePlayer player = deployer.GetOwnerPlayer();
            VendingMachine vm = entity as VendingMachine;
            if (vm == null || player == null) return;
            if (deployer.GetDeployable().slot == BaseEntity.Slot.Lock && !(vm.CanPlayerAdmin(player) || player.userID == vm.OwnerID))
            {
                BaseLock lockEntity = vm.GetSlot(BaseEntity.Slot.Lock) as BaseLock;
                if (lockEntity == null) return;
                deployer.GetItem().amount++;
                lockEntity.Kill();
                if (ConfigValue<bool>(Option.lockFailureMessage))
                    SendMessage(player, "NotAuthorized");
            }
        }

        // handle blocking broadcasting
        void OnToggleVendingBroadcast(VendingMachine vm, BasePlayer player)
        {
            if (ConfigValue<bool>(Option.noBroadcast))
            {
                vm.SetFlag(BaseEntity.Flags.Reserved4, false, false);
                SendMessage(player, "NoBroadcast");
            }
        }

        object OnVendingTransaction(VendingMachine vm, BasePlayer player, int sellOrderId, int numTransactions)
        {
            var cvsResult = CustomVendingSetup?.Call("API_IsCustomized", vm);
            if (cvsResult is bool && (bool)cvsResult)
                return null;

            VendingMachineInfo i;
            vms.TryGetValue(vm.net.ID.Value, out i);
            bool bottomless = i == null ? false : i.HasFlag(VendingMachineInfo.VMFlags.Bottomless);

            bool log = ConfigValue<bool>(Option.logTransSuccess) || ConfigValue<bool>(Option.logTransFailure) || (i != null && i.HasFlag(VendingMachineInfo.VMFlags.LogTransactions));
            bool force = i != null && i.HasFlag(VendingMachineInfo.VMFlags.LogTransactions);
            ProtoBuf.VendingMachine.SellOrder sellOrder = vm.sellOrders.sellOrders[sellOrderId];

            bool isCurrencySellOrder = (useEconomics || useServerRewards) && sellOrder.currencyID == currencyItem.itemid;
            bool isCurrencyBuyOrder = (useEconomics || useServerRewards) && sellOrder.itemToSellID == currencyItem.itemid;

            LogEntry logEntry = new LogEntry();
            if (log)
            {
                logEntry.id = vm.net.ID;
                logEntry.playerID = player.userID;
                logEntry.playerName = player.displayName;
            }

            List<Item> items = vm.inventory.FindItemsByItemID(sellOrder.itemToSellID);
            if (sellOrder.itemToSellIsBP)
            {
                items = (
                    from x in vm.inventory.FindItemsByItemID(vm.blueprintBaseDef.itemid)
                    where x.blueprintTarget == sellOrder.itemToSellID
                    select x).ToList();
            }
            if (items == null || items.Count == 0)
            {
                return false;
            }
            int numberOfTransactions = Mathf.Clamp(numTransactions, 1, (!items[0].hasCondition ? 1000000 : 1));
            int sellCount = sellOrder.itemToSellAmount * numberOfTransactions;
            int buyCount = sellOrder.currencyAmountPerItem * numberOfTransactions;

            if (sellCount > items.Sum(x => x.amount))
                return false;

            int cost = 0;
            if (!isCurrencySellOrder)
            {
                int num2 = sellOrder.currencyAmountPerItem * numberOfTransactions;

                if (log) logEntry.cost = num2 + " " + ItemManager.FindItemDefinition(sellOrder.currencyID).displayName.translated + (sellOrder.currencyIsBP ? " (BP)" : "");

                List<Item> items1 = player.inventory.FindItemsByItemID(sellOrder.currencyID);
                if (sellOrder.currencyIsBP)
                {
                    items1 = (
                        from x in player.inventory.FindItemsByItemID(vm.blueprintBaseDef.itemid)
                        where x.blueprintTarget == sellOrder.currencyID
                        select x).ToList();
                }
                if (items1.Count == 0)
                {
                    if (log)
                    {
                        logEntry.success = false;
                        logEntry.reason = LogEntry.FailureReason.NoItems;
                        LogTransaction(logEntry, force);
                    }
                    return false;
                }

                int num1 = items1.Sum(x => x.amount);
                if (num1 < num2)
                {
                    if (log)
                    {
                        logEntry.success = false;
                        logEntry.reason = LogEntry.FailureReason.NoItems;
                        LogTransaction(logEntry, force);
                    }
                    return false;
                }

                vm.transactionActive = true;
                int num3 = 0;
                Item item;
                foreach (Item item2 in items1)
                {
                    int num4 = Mathf.Min(num2 - num3, item2.amount);
                    item = (item2.amount > num4 ? item2.SplitItem(num4) : item2);
                    if (bottomless)
                        item.Remove();
                    else
                        if (!item.MoveToContainer(vm.inventory, -1, true))
                    {
                        item.Drop(vm.inventory.dropPosition, Vector3.zero, new Quaternion());
                    }
                    num3 = num3 + num4;
                    if (num3 < num2)
                        continue;
                    break;
                }
            }
            else
            {
                cost = sellOrder.currencyAmountPerItem * numberOfTransactions;
                if (log)
                {
                    logEntry.isBuyOrder = true;
                    logEntry.cost = string.Format("{0:C}", cost);
                }
                double money = GetBalance(player.userID);
                if (money < 1.0)
                {
                    if (log)
                    {
                        logEntry.success = false;
                        logEntry.reason = LogEntry.FailureReason.NoMoney;
                        LogTransaction(logEntry, force);
                    }
                    return false;
                }

                if (Mathf.FloorToInt((float)money) < cost)
                {
                    if (log)
                    {
                        logEntry.success = false;
                        logEntry.reason = LogEntry.FailureReason.NoMoney;
                        LogTransaction(logEntry, force);
                    }
                    SendMessage(player, currencyPlugin + "NotEnoughMoney");
                    return false;
                }

                vm.transactionActive = true;
                bool success = false;
                if (bottomless)
                    success = Withdraw(player.userID, cost);
                else
                    success = Transfer(player.userID, vm.OwnerID, cost);

                if (!success)
                {
                    if (log)
                    {
                        logEntry.success = false;
                        logEntry.reason = LogEntry.FailureReason.Unknown;
                        LogTransaction(logEntry, force);
                    }
                    SendMessage(player, currencyPlugin + "TransferFailed");
                    vm.transactionActive = false;
                    return false;
                }
            }
            int amount = 0;
            if (isCurrencyBuyOrder)
            {
                amount = sellOrder.itemToSellAmount * numberOfTransactions;
                if (log)
                {
                    logEntry.isBuyOrder = false;
                    logEntry.cost = string.Format("{0:C}", amount);
                    logEntry.bought = sellOrder.currencyAmountPerItem + " " + ItemManager.FindItemDefinition(sellOrder.currencyID).displayName.translated;
                }

                double money = GetBalance(vm.OwnerID);
                if (!bottomless)
                {
                    if (money < 1.0)
                    {
                        if (log)
                        {
                            logEntry.success = false;
                            logEntry.reason = LogEntry.FailureReason.NoMoney;
                            LogTransaction(logEntry, force);
                        }
                        return false;
                    }

                    if (Mathf.FloorToInt((float)money) < amount)
                    {
                        if (log)
                        {
                            logEntry.success = false;
                            logEntry.reason = LogEntry.FailureReason.NoMoney;
                            LogTransaction(logEntry, force);
                        }
                        SendMessage(player, currencyPlugin + "NotEnoughMoneyOwner");
                        return false;
                    }
                }

                vm.transactionActive = true;
                bool success = false;
                if (bottomless)
                    success = Deposit(player.userID, amount);
                else
                    success = Transfer(vm.OwnerID, player.userID, amount);

                if (!success)
                {
                    if (log)
                    {
                        logEntry.success = false;
                        logEntry.reason = LogEntry.FailureReason.Unknown;
                        LogTransaction(logEntry, force);
                    }
                    SendMessage(player, currencyPlugin + "TransferFailed");
                    vm.transactionActive = false;
                    return false;
                }
            }
            else
            {
                if (log) logEntry.bought = sellOrder.itemToSellAmount + " " + ItemManager.FindItemDefinition(sellOrder.itemToSellID).displayName.translated + (sellOrder.itemToSellIsBP ? " (BP)" : "");
                if (!bottomless)
                {
                    int num5 = 0;
                    Item item1 = null;
                    foreach (Item item3 in items)
                    {
                        item1 = (item3.amount > sellCount ? item3.SplitItem(sellCount) : item3);
                        num5 = num5 + item1.amount;
                        player.GiveItem(item1, BaseEntity.GiveItemReason.PickedUp);
                        if (num5 < sellCount)
                            continue;
                        break;
                    }
                }
                else
                {
                    Item item = null;
                    if (sellOrder.itemToSellIsBP)
                    {
                        item = ItemManager.CreateByItemID(vm.blueprintBaseDef.itemid, sellCount);
                        item.blueprintTarget = sellOrder.itemToSellID;
                    }
                    else
                        item = ItemManager.CreateByItemID(sellOrder.itemToSellID, sellCount, vm.inventory.FindItemsByItemID(sellOrder.itemToSellID).Select(e => e.skin).FirstOrDefault());
                    player.GiveItem(item, BaseEntity.GiveItemReason.PickedUp);
                }
            }

            vm.UpdateEmptyFlag();
            vm.transactionActive = false;

            if (ConfigValue<bool>(Option.transMessages) && isCurrencySellOrder && cost > 0)
            {
                double remaining = GetBalance(player.userID);
                SendMessage(player, currencyPlugin + "PurchaseSuccess", new object[] { sellCount, ItemManager.FindItemDefinition(sellOrder.itemToSellID).displayName.translated, cost, remaining });
            }
            else if (ConfigValue<bool>(Option.transMessages) && isCurrencyBuyOrder && amount > 0)
            {
                double balance = GetBalance(player.userID);
                SendMessage(player, currencyPlugin + "SellSuccess", new object[] { buyCount, ItemManager.FindItemDefinition(sellOrder.currencyID).displayName.translated, amount, balance });
            }
            if (log)
            {
                logEntry.success = true;
                LogTransaction(logEntry, force);
            }

            return true;
        }

        // override administration if restricted access on
        object CanAdministerVending(VendingMachine vm, BasePlayer player)
        {
            bool restricted = ConfigValue<bool>(Option.restricted);
            if (!restricted)
            {
                VendingMachineInfo i;
                if (vms.TryGetValue(vm.net.ID.Value, out i))
                    restricted = i.HasFlag(VendingMachineInfo.VMFlags.Restricted);
            }
            if (restricted && vm.OwnerID != player.userID && !player.IsAdmin)
            {
                SendMessage(player, "Restricted");
                return false;
            }
            return null;
        }

        // block damage for Immortal vending machines
        void OnEntityTakeDamage(BaseCombatEntity entity, HitInfo hitinfo)
        {
            if (entity == null || hitinfo == null) return;
            if (entity is VendingMachine)
            {
                VendingMachineInfo i;
                if (vms.TryGetValue(entity.net.ID.Value, out i))
                {
                    if (i.HasFlag(VendingMachineInfo.VMFlags.Immortal))
                        hitinfo.damageTypes = new DamageTypeList();
                }
            }
        }

        // hack to show vending buttons - when VM shop opened, add currency items
        // to hidden inventory slot to represent current economics balance
        void OnVendingShopOpened(VendingMachine vm, BasePlayer player)
        {
            if (!useEconomics && !useServerRewards) return;
            if (vm.sellOrders.sellOrders.Count == 0) return;

            bool hasCurrencySellOrder = false;
            bool hasCurrencyBuyOrder = true;
            // create and add items to player inventory to prevent "Can't Afford" button
            foreach (ProtoBuf.VendingMachine.SellOrder so in vm.sellOrders.sellOrders)
            {
                if (so.currencyID == currencyItem.itemid)
                    hasCurrencySellOrder = true;
                else if (so.itemToSellID == currencyItem.itemid)
                    hasCurrencyBuyOrder = true;
            }
            if (!hasCurrencySellOrder && !hasCurrencyBuyOrder) return;

            int playerMoney = 0;
            if (hasCurrencyBuyOrder)
            {
                vm.inventory.capacity = currencyIndex + 1;
                VendingMachineInfo i;
                vms.TryGetValue(vm.net.ID.Value, out i);
                bool bottomless = i == null ? false : i.HasFlag(VendingMachineInfo.VMFlags.Bottomless);
                if (bottomless)
                {
                    Item money = ItemManager.CreateByItemID(currencyItem.itemid, 10000, 0);
                    money.MoveToContainer(vm.inventory, currencyIndex, true);
                }
                else
                {
                    playerMoney = Mathf.FloorToInt((float)GetBalance(vm.OwnerID));
                    if (playerMoney > 0)
                    {
                        Item money = ItemManager.CreateByItemID(currencyItem.itemid, playerMoney);
                        money.MoveToContainer(vm.inventory, currencyIndex, true);
                    }
                }
                /*
                int lastMoney = playerMoney;
                transactionTimers[player.userID] = timer.Every(0.5f, () => {
                    int m = Mathf.FloorToInt((float)GetBalance(vm.OwnerID));
                    if (lastMoney != m)
                    {
                        lastMoney = m;
                        Item item = vm.inventory.GetSlot(currencyIndex);
                        if (item != null)
                        {
                            if (lastMoney == 0)
                                item.Remove();
                            else
                                item.amount = lastMoney;
                            vm.RefreshSellOrderStockLevel();
                        }
                    }
                });
                */
            }
            playerMoney = 0;
            if (hasCurrencySellOrder)
            {
                player.inventory.containerMain.capacity = currencyIndex + 1;
                playerMoney = Mathf.FloorToInt((float)GetBalance(player.userID));
                if (playerMoney > 0)
                {
                    Item money = ItemManager.CreateByItemID(currencyItem.itemid, playerMoney, 0);
                    money.MoveToContainer(player.inventory.containerMain, currencyIndex, true);
                }

                int lastMoney = playerMoney;
                transactionTimers[player.userID] = timer.Every(0.5f, () => {
                    int m = Mathf.FloorToInt((float)GetBalance(player.userID));
                    if (lastMoney != m)
                    {
                        if (lastMoney == 0 && m > 0)
                        {
                            Item money = ItemManager.CreateByItemID(currencyItem.itemid, m, 0);
                            money.MoveToContainer(player.inventory.containerMain, currencyIndex, true);
                        }
                        lastMoney = m;
                        Item item = player.inventory.containerMain.GetSlot(currencyIndex);
                        if (item != null)
                        {
                            if (lastMoney == 0)
                                item.Remove();
                            else
                                item.amount = lastMoney;
                            player.inventory.SendSnapshot();
                        }
                    }
                });
            }
            if (ConfigValue<float>(Option.transactionTimeout) > 0f)
                timeoutTimers[player.userID] = timer.Once(ConfigValue<float>(Option.transactionTimeout), () => player.EndLooting());
        }

        // when VM shop closed, remove all currency items from player's inventory
        void OnLootEntityEnd(BasePlayer player, BaseEntity entity)
        {
            if ((!useEconomics && !useServerRewards) || entity == null || !(entity is VendingMachine)) return;
            if (transactionTimers.ContainsKey(player.userID))
                transactionTimers[player.userID]?.Destroy();
            if (timeoutTimers.ContainsKey(player.userID))
                timeoutTimers[player.userID]?.Destroy();

            int i = player.inventory.containerMain.capacity;
            while (i >= currencyIndex)
                player.inventory.containerMain.GetSlot(i--)?.Remove();
            Item b = player.inventory.containerMain.FindItemByItemID(currencyItem.itemid);
            if (b != null) b.Remove();
            player.inventory.containerMain.capacity = currencyIndex;

            VendingMachine vm = entity as VendingMachine;
            int j = vm.inventory.capacity;
            while (j >= currencyIndex)
                vm.inventory.GetSlot(j--)?.Remove();
            Item c = vm.inventory.FindItemByItemID(currencyItem.itemid);
            if (c != null) c.Remove();
            vm.inventory.capacity = currencyIndex;
        }

        // on rotate, send network update for lock position
        void OnRotateVendingMachine(VendingMachine vm, BasePlayer player)
        {
            BaseLock l = vm.GetSlot(BaseEntity.Slot.Lock) as BaseLock;
            if (l != null)
                NextTick(() => l.SendNetworkUpdate());
        }

        #endregion

        #region Command Handling

        // command delegator
        void CommandDelegator(BasePlayer player, string command, string[] args)
        {
            if (!hasPermission(player, PermCanUse))
            {
                SendReply(player, GetMessage("NotAllowed", player.UserIDString));
                return;
            }
            string message = "InvalidCommand";
            // assume args[0] is the command (beyond /vm)
            if (args != null && args.Length > 0)
                command = args[0];
            // shift arguments
            if (args != null)
            {
                if (args.Length > 1)
                    args = args.Skip(1).ToArray();
                else
                    args = new string[] { };
            }
            object[] opts = new object[] { command };
            if (Enum.IsDefined(typeof(Command), command))
            {
                switch ((Command)Enum.Parse(typeof(Command), command))
                {
                    case Command.add:
                        HandleLoad(player, args, false, out message, out opts);
                        break;
                    case Command.clear:
                        HandleClear(player, out message);
                        break;
                    case Command.eject:
                        if (hasPermission(player, PermCanEject))
                            EjectAll(out message, out opts);
                        break;
                    case Command.info:
                        HandleInfo(player, out message, out opts);
                        break;
                    case Command.list:
                        HandleList(out message, out opts);
                        break;
                    case Command.load:
                        HandleLoad(player, args, true, out message, out opts);
                        break;
                    case Command.reset:
                        HandleReset(args, out message);
                        break;
                    case Command.save:
                        HandleSave(player, args, out message, out opts);
                        break;
                    case Command.set:
                        HandleSet(player, args, out message, out opts);
                        break;
                    case Command.unset:
                        HandleSet(player, args, out message, out opts, true);
                        break;
                    default:
                        break;
                }
            }
            else
                ShowCommands(out message, out opts);
            if (message != null && message != "")
                SendMessage(player, message, opts);
        }

        // handle reset command
        void HandleReset(string[] args, out string message)
        {
            bool confirm = (args.Length > 0 && args[0] != null && args[0].ToLower() == "confirm");
            if (confirm)
            {
                Config.Clear();
                data = new VendingData();
                SaveData();
                message = "ResetSuccess";
            }
            else
                message = "ConfirmReset";
        }

        // handle clear command
        void HandleClear(BasePlayer player, out string message)
        {
            message = "VMNotFound";
            object entity;
            if (GetRaycastTarget(player, out entity))
                if (entity != null && entity is VendingMachine)
                {
                    (entity as VendingMachine).sellOrders.sellOrders.Clear();
                    message = "ClearSuccess";
                }
        }

        void HandleInfo(BasePlayer player, out string message, out object[] opts)
        {
            message = "VMNotFound";
            opts = new object[] { };
            object entity;
            if (GetRaycastTarget(player, out entity))
                if (entity != null && entity is VendingMachine)
                {
                    ulong id = (entity as VendingMachine).net.ID.Value;
                    bool isConfigured = vms.ContainsKey(id);
                    string flags = "None";
                    if (isConfigured)
                        flags = vms[id].flags.ToString();
                    message = "Information";
                    opts = new object[] { id, isConfigured, flags };
                }
        }

        // handle load command
        void HandleLoad(BasePlayer player, string[] args, bool replaceAll, out string message, out object[] opts)
        {
            message = "";
            opts = new object[] { };
            if (args == null || args.Length == 0 || args[0] == null || args[0] == "")
            {
                message = "InvalidParameter";
                return;
            }
            object entity;
            if (!GetRaycastTarget(player, out entity))
            {
                message = "VMNotFound";
                return;
            }

            opts = new object[] { args[0] };
            if (entity != null && entity is VendingMachine)
                LoadSellOrders(entity as VendingMachine, args[0], replaceAll, out message);
        }

        // handle loading the sell orders into a vending machine
        void LoadSellOrders(VendingMachine vm, string templateName, bool replace, out string message)
        {
            message = "LoadSuccess";
            if (!data.templates.ContainsKey(templateName))
            {
                message = "TemplateNotFound";
                return;
            }
            if (data.templates[templateName].Empty())
            {
                message = "EmptyTemplate";
                return;
            }
            if (replace)
                vm.sellOrders.sellOrders.Clear();
            foreach (SellOrderEntry e in data.templates[templateName].entries)
            {
                ProtoBuf.VendingMachine.SellOrder o = new ProtoBuf.VendingMachine.SellOrder();
                o.itemToSellID = ItemManager.FindItemDefinition(e.itemToSellName).itemid;
                o.itemToSellAmount = e.itemToSellAmount;
                o.itemToSellIsBP = e.itemToSellIsBP;
                o.currencyID = ItemManager.FindItemDefinition(e.currencyName).itemid;
                o.currencyAmountPerItem = e.currencyAmountPerItem;
                o.currencyIsBP = e.currencyIsBP;
                vm.sellOrders.sellOrders.Add(o);
            }
            vm.RefreshSellOrderStockLevel();
            return;
        }

        // handle save command
        void HandleSave(BasePlayer player, string[] args, out string message, out object[] opts)
        {
            message = "";
            opts = new object[] { };
            if (args == null || args.Length == 0 || args[0] == null || args[0] == "")
            {
                message = "InvalidParameter";
                return;
            }
            bool overwrite = (args.Length > 1 && args[1] != null && args[1].ToLower() == "overwrite");
            object entity;
            if (!GetRaycastTarget(player, out entity))
            {
                message = "VMNotFound";
                return;
            }
            opts = new object[] { args[0] };
            if (entity != null && entity is VendingMachine)
                SaveSellOrders(entity as VendingMachine, args[0], out message, overwrite);
        }

        // handle saving the sell orders from a vending machine
        void SaveSellOrders(VendingMachine vm, string templateName, out string message, bool overwrite = false)
        {
            message = "SaveSuccess";
            if (templateName == null || templateName == "")
            {
                message = "InvalidParameter";
                return;
            }
            if (data.templates.ContainsKey(templateName) && !overwrite)
            {
                message = "TemplateExists";
                return;
            }

            ProtoBuf.VendingMachine.SellOrderContainer sellOrderContainer = vm.sellOrders;
            if (sellOrderContainer == null || sellOrderContainer.sellOrders == null || sellOrderContainer.sellOrders.Count == 0)
            {
                message = "EmptyVM";
                return;
            }
            SellOrderTemplate template = new SellOrderTemplate();
            template.PopulateTemplate(sellOrderContainer.sellOrders);
            if (!template.Empty())
            {
                data.templates[templateName] = template;
                SaveData();
            }
            return;
        }

        // handle set/unset flag
        void HandleSet(BasePlayer player, string[] args, out string message, out object[] opts, bool unset = false)
        {
            message = unset ? "UnsetSuccess" : "SetSuccess";
            opts = new object[] { };
            if (args == null || args.Length == 0 || args[0] == null || args[0] == "" || !Enum.IsDefined(typeof(VendingMachineInfo.VMFlags), args[0]))
            {
                message = "InvalidParameter";
                return;
            }
            object entity;
            if (!GetRaycastTarget(player, out entity))
            {
                message = "VMNotFound";
                return;
            }
            opts = new object[] { args[0] };
            if (entity != null && entity is VendingMachine)
            {
                VendingMachineInfo.VMFlags flags = (VendingMachineInfo.VMFlags)Enum.Parse(typeof(VendingMachineInfo.VMFlags), args[0]);
                VendingMachineInfo i;
                if (!vms.TryGetValue((entity as VendingMachine).net.ID.Value, out i))
                {
                    i = new VendingMachineInfo();
                    i.id = (entity as VendingMachine).net.ID.Value;
                    vms[i.id] = i;
                }
                if (unset)
                    i.flags &= ~flags;
                else
                    i.flags |= flags;
                if (i.flags == VendingMachineInfo.VMFlags.None)
                    vms.Remove(i.id);
                SaveVendingMachineData();
            }
        }

        #endregion

        #region Messaging

        // send reply to a player
        void SendMessage(BasePlayer player, string message, object[] options = null)
        {
            string msg = GetMessage(message, player.UserIDString);
            if (options != null && options.Length > 0)
                msg = String.Format(msg, options);
            SendReply(player, GetMessage("Prefix", player.UserIDString) + msg);
        }

        // handle list command
        void HandleList(out string message, out object[] opts)
        {
            message = "TemplateList";
            opts = new object[] { data.GetTemplateList() };
        }

        // show list of valid commands
        void ShowCommands(out string message, out object[] opts)
        {
            message = "CommandList";
            opts = new object[] { string.Join(", ", Enum.GetValues(typeof(Command)).Cast<Command>().Select(x => x.ToString()).ToArray()) };
        }

        void LogTransaction(LogEntry logEntry, bool force = false)
        {
            if ((ConfigValue<bool>(Option.logTransSuccess) && logEntry.success) || (ConfigValue<bool>(Option.logTransFailure) && !logEntry.success) || force)
            {
                string logString = logEntry.ToString();
                LogToFile("Transactions", logString, this, true);
            }
        }

        #endregion

        #region Helper Procedures

        // set all vending machines
        void SetAll(bool lockable, float health = defaultHealth)
        {
            foreach (VendingMachine vm in GameObject.FindObjectsOfType(typeof(VendingMachine)))
                Set(vm, lockable, health);
        }

        // setup a specific vending machine
        void Set(VendingMachine vm, bool lockable, float health = defaultHealth, bool restoreProtection = false)
        {
            if (ConfigValue<bool>(Option.noBroadcast))
                vm.SetFlag(BaseEntity.Flags.Reserved4, false, false);
            if (defaultProtection == null)
            {
                defaultProtection = vm.baseProtection;
                if (data.resistances == null)
                {
                    data.SetResistances(defaultProtection.amounts);
                    SaveData();
                }
            }
            else
            {
                if (customProtection == null)
                    customProtection = UnityEngine.Object.Instantiate(vm.baseProtection) as ProtectionProperties;
                if (data.resistances != null && !restoreProtection)
                {
                    customProtection.amounts = data.GetResistances();
                    vm.baseProtection = customProtection;
                    //vm.baseProtection.amounts = data.GetResistances();
                }
                if (restoreProtection)
                    vm.baseProtection = defaultProtection;
            }
            if (!lockable && ConfigValue<bool>(Option.ejectLocks)) Eject(vm);
            vm.isLockable = lockable;
            if (ConfigValue<bool>(Option.setHealth))
            {
                float h = health * vm.healthFraction;
                vm.InitializeHealth(h, health);
                vm.SendNetworkUpdate();
            }
        }

        // eject lock from vending machine
        bool Eject(VendingMachine m)
        {
            BaseEntity lockEntity = m.GetSlot(BaseEntity.Slot.Lock);
            if (lockEntity != null && lockEntity is BaseLock)
            {
                Item lockItem = ItemManager.Create((lockEntity as BaseLock).itemType, 1, lockEntity.skinID);
                lockEntity.Kill();
                lockItem.Drop(m.GetDropPosition(), m.GetDropVelocity(), m.transform.rotation);
                m.isLockable = ConfigValue<bool>(Option.lockable);
                return true;
            }
            return false;
        }

        // eject locks from all vending machines
        void EjectAll(out string message, out object[] opts)
        {
            int counter = 0;
            foreach (VendingMachine m in GameObject.FindObjectsOfType(typeof(VendingMachine)))
                if (Eject(m)) counter++;

            message = "Ejected";
            opts = new object[] { counter };
        }

        // raycast to find entity being looked at
        bool GetRaycastTarget(BasePlayer player, out object closestEntity)
        {
            closestEntity = null;
            RaycastHit hit;
            if (!Physics.Raycast(player.eyes.HeadRay(), out hit, 5f))
                return false;
            closestEntity = hit.GetEntity();
            return true;
        }

        // check if player is an admin
        private static bool isAdmin(BasePlayer player)
        {
            if (player?.net?.connection == null) return true;
            return player.net.connection.authLevel > 0;
        }

        // check if player has permission or is an admin
        private bool hasPermission(BasePlayer player, string permname)
        {
            //return isAdmin(player) || permission.UserHasPermission(player.UserIDString, permname);
            return permission.UserHasPermission(player.UserIDString, permname);
        }

        // get config value and convert type
        T ConfigValue<T>(Option option)
        {
            return (T)Convert.ChangeType(data.config[option], typeof(T));
        }

        // save all locks
        void SaveVMsAndLocks()
        {
            locks.Clear();
            foreach (VendingMachine vm in GameObject.FindObjectsOfType(typeof(VendingMachine)))
            {
                BaseLock l = (BaseLock)vm.GetSlot(BaseEntity.Slot.Lock);
                if (l == null) continue;

                LockInfo li = new LockInfo(vm.net.ID.Value, l);
                locks[vm.net.ID.Value] = li;
            }
            SaveVendingMachineData();
            SaveLocksData();
        }

        // load all locks
        void LoadLocks()
        {
            foreach (LockInfo li in locks.Values)
            {
                VendingMachine vm = (VendingMachine)BaseNetworkable.serverEntities.Find(new NetworkableId(li.vmId));
                if (vm == null) continue;
                if (vm.GetSlot(BaseEntity.Slot.Lock) != null) continue;

                BaseLock l;
                if (li.isCodeLock)
                    l = (CodeLock)GameManager.server.CreateEntity(CodeLockPrefab);
                else
                    l = (KeyLock)GameManager.server.CreateEntity(KeyLockPrefab);

                if (l == null) continue;

                l.gameObject.Identity();
                li.ToLock(ref l);
                l.SetParent(vm, vm.GetSlotAnchorName(BaseEntity.Slot.Lock));
                l.OnDeployed(vm, null, null);
                l.Spawn();
                vm.SetSlot(BaseEntity.Slot.Lock, l);
            }
        }

        // Destroy all attached locks on shutdown
        void DestroyLocks()
        {
            foreach (VendingMachine vm in GameObject.FindObjectsOfType(typeof(VendingMachine)))
            {
                BaseEntity l;
                if ((l = vm.GetSlot(BaseEntity.Slot.Lock)) != null)
                    l.Kill();
            }
        }

        double GetBalance(ulong playerId)
        {
            if (useEconomics)
            {
                return (double)Economics.CallHook("Balance", playerId.ToString());
            }
            else if (useServerRewards)
            {
                return (int)(ServerRewards.CallHook("CheckPoints", playerId) ?? 0.0);
            }
            return 0.0;
        }

        bool Withdraw(ulong playerId, double amount)
        {
            if (useEconomics)
            {
                return (bool)Economics.CallHook("Withdraw", playerId.ToString(), amount);
            }
            else if (useServerRewards)
            {
                return (bool)ServerRewards.CallHook("TakePoints", playerId, (int)amount);
            }
            return false;
        }

        bool Deposit(ulong playerId, double amount)
        {
            if (useEconomics)
            {
                Economics.CallHook("Deposit", playerId.ToString(), amount);
                return true;
            }
            else if (useServerRewards)
            {
                return (bool)ServerRewards.Call("AddPoints", new object[] { playerId, (int)amount });
            }
            return false;
        }

        bool Transfer(ulong fromId, ulong toId, double amount)
        {
            if (useEconomics)
            {
                return (bool)Economics.CallHook("Transfer", fromId.ToString(), toId.ToString(), amount);
            }
            else if (useServerRewards)
            {
                if (Withdraw(fromId, amount))
                {
                    bool result = Deposit(toId, amount);
                    if (!result)
                        Deposit(fromId, amount); // if transfer failed, refund
                    return result;
                }
            }
            return false;
        }

        #endregion

        #region Subclasses

        // config/data container
        class VendingData
        {
            public Dictionary<Option, object> config = new Dictionary<Option, object>();
            public Dictionary<DamageType, float> resistances;
            public Dictionary<string, SellOrderTemplate> templates;

            public string GetTemplateList()
            {
                string list = string.Join(", ", templates.Keys.ToArray());
                if (list == null || list == "")
                    list = "(empty)";
                return list;
            }

            public void SetResistances(float[] amounts)
            {
                resistances = new Dictionary<DamageType, float>();
                for (int i = 0; i < amounts.Length; i++)
                    resistances[(DamageType)i] = amounts[i];
            }

            public float[] GetResistances()
            {
                float[] values = new float[30];
                if (resistances != null)
                    foreach (KeyValuePair<DamageType, float> entry in resistances)
                        values[(int)entry.Key] = entry.Value;
                return values;
            }
        }

        // helper class for building sell order entries
        class SellOrderTemplate
        {
            public List<SellOrderEntry> entries = new List<SellOrderEntry>();

            public void PopulateTemplate(List<ProtoBuf.VendingMachine.SellOrder> sellOrders)
            {
                if (sellOrders == null) return;
                foreach (ProtoBuf.VendingMachine.SellOrder o in sellOrders)
                    AddSellOrder(o);
            }

            public void AddSellOrder(ProtoBuf.VendingMachine.SellOrder o)
            {
                if (o == null) return;
                SellOrderEntry e = new SellOrderEntry();
                e.itemToSellName = ItemManager.FindItemDefinition(o.itemToSellID).shortname;
                e.itemToSellAmount = o.itemToSellAmount;
                e.currencyName = ItemManager.FindItemDefinition(o.currencyID).shortname;
                e.currencyAmountPerItem = o.currencyAmountPerItem;
                e.itemToSellIsBP = o.itemToSellIsBP;
                e.currencyIsBP = o.currencyIsBP;
                entries.Add(e);
            }

            public bool Empty()
            {
                return (entries == null || entries.Count == 0);
            }
        }

        // simple sell order entry container
        struct SellOrderEntry
        {
            public string itemToSellName;
            public int itemToSellAmount;
            public string currencyName;
            public int currencyAmountPerItem;
            public bool itemToSellIsBP;
            public bool currencyIsBP;
        }

        struct LogEntry
        {
            public enum FailureReason { NoMoney, NoItems, Unknown }
            public NetworkableId id;
            public ulong playerID;
            public string playerName;
            public string bought;
            public string cost;
            public bool success;
            public bool isBuyOrder;
            public FailureReason reason;

            public override string ToString()
            {
                if (isBuyOrder)
                    return "VM " + id + ": " + playerName + " [" + playerID + "] " + (success ? "bought " : "failed to buy ") + bought + " for " + cost + (success ? "" : " - Reason: " + GetReason());
                else
                    return "VM " + id + ": " + playerName + " [" + playerID + "] " + (success ? "sold " : "failed to sell ") + bought + " for " + cost + (success ? "" : " - Reason: " + GetReason());
            }
            string GetReason()
            {
                if (reason == FailureReason.NoItems)
                    return "Not enough currency items";
                if (reason == FailureReason.NoMoney)
                    return "Not enough money";
                return "Unknown reason";
            }
        }

        class VendingMachineInfo
        {
            [Flags]
            public enum VMFlags
            {
                None = 0,
                Bottomless = 1,
                Immortal = 1 << 1,
                Restricted = 1 << 2,
                LogTransactions = 1 << 3
            }
            public ulong id;
            [JsonConverter(typeof(StringEnumConverter))]
            public VMFlags flags;
            public bool HasFlag(VMFlags flag) => (flags & flag) == flag;
        }

        // Lock details container
        class LockInfo
        {
            static readonly byte[] entropy = new byte[] { 11, 7, 5, 3 };
            public ulong vmId;
            public bool isCodeLock = false;
            public string codeEncrypted;
            [JsonIgnore]
            public string code
            {
                get
                {
                    return Shift(codeEncrypted, -4);
                }
                set
                {
                    codeEncrypted = Shift(value, 4);
                }
            }
            public string guestCodeEncrypted;
            [JsonIgnore]
            public string guestCode
            {
                get
                {
                    return Shift(guestCodeEncrypted, -4);
                }
                set
                {
                    guestCodeEncrypted = Shift(value, 4);
                }
            }
            public List<ulong> whitelist;
            public List<ulong> guests;
            public int keyCode;
            public bool firstKey;
            public bool isLocked;

            public LockInfo() { }
            public LockInfo(ulong vmId, BaseLock l)
            {
                this.vmId = vmId;
                FromLock(l);
            }

            public void FromLock(BaseLock l)
            {
                if (l.GetType() == typeof(CodeLock))
                {
                    isCodeLock = true;
                    code = (l as CodeLock).code;
                    guestCode = (l as CodeLock).guestCode;
                    whitelist = (l as CodeLock).whitelistPlayers;
                    guests = (l as CodeLock).guestPlayers;
                }
                else if (l.GetType() == typeof(KeyLock))
                {
                    keyCode = (l as KeyLock).keyCode;
                    firstKey = (l as KeyLock).firstKeyCreated;
                }
                isLocked = l.IsLocked();
            }

            public void ToLock(ref BaseLock l)
            {
                if (l.GetType() == typeof(CodeLock))
                {
                    (l as CodeLock).code = code;
                    (l as CodeLock).guestCode = guestCode;
                    (l as CodeLock).whitelistPlayers = whitelist;
                    (l as CodeLock).guestPlayers = guests;
                }
                else if (l.GetType() == typeof(KeyLock))
                {
                    (l as KeyLock).keyCode = keyCode;
                    (l as KeyLock).firstKeyCreated = firstKey;
                }
                l.SetFlag(BaseEntity.Flags.Locked, isLocked);
            }

            // simple obfuscation for codes
            static string Shift(string source, int shift)
            {
                int maxChar = Convert.ToInt32(char.MaxValue);
                int minChar = Convert.ToInt32(char.MinValue);

                char[] buffer = source.ToCharArray();

                for (int i = 0; i < buffer.Length; i++)
                {
                    int shifted = Convert.ToInt32(buffer[i]) + (shift * entropy[i]);

                    if (shifted > maxChar)
                    {
                        shifted -= maxChar;
                    }
                    else if (shifted < minChar)
                    {
                        shifted += maxChar;
                    }

                    buffer[i] = Convert.ToChar(shifted);
                }

                return new string(buffer);
            }
        }

        #endregion
    }
}

Thanks, it worked... πŸ€—