Error compiling after today's update

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

Failed compiling 'PlayerAdministration.cs':
1. 'BasePlayer' does not contain a definition for 'lastAdminCheatTime' and no accessible extension method 'lastAdminCheatTime' accepting a first argument of type 'BasePlayer' could be found (are you missing a using directive or an assembly reference?) [CS1061]
(PlayerAdministration 25 line 1292)

Merged post

Same here

Same issue for me as well:

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

As a quick fix you can go to line 1292 and comment out these lines. The plugin will compile and we can wait for a proper fix.

            // Pre-calc last admin cheat
            //if (aPlayer.lastAdminCheatTime > 0f) {
            //    TimeSpan lastCheatSinceStart = new TimeSpan(0, 0, (int)(Time.realtimeSinceStartup - aPlayer.lastAdminCheatTime));
            //    lastCheatStr = $"{DateTime.UtcNow.Subtract(lastCheatSinceStart):yyyy-MM-dd HH:mm:ss} UTC";
            //}

A Fix with AI assistance full code corrected below

/* --- Contributor information ---
 * Please follow the following set of guidelines when working on this plugin,
 * this to help others understand this file more easily.
 *
 * NOTE: On Authors, new entries go BELOW the existing entries. As with any other software header comment.
 *
 * -- Authors --
 * Thimo (ThibmoRozier) <[email protected]> 2018-03-27 +
 * rfc1920 <[email protected]>
 * Mheetu <[email protected]>
 * Pho3niX90 <[email protected]> 2020-06 +
 * Gabriel Vergara Ezcurdia -AI asisted code fixes. for rust Update 2026.08.06.00
 *
 * -- Naming --
 * Avoid using non-alphabetic characters, eg: _
 * Avoid using numbers in method and class names (Upgrade methods are allowed to have these, for readability)
 * Private constants -------------------- SHOULD start with a uppercase "C" (PascalCase)
 * Private readonly fields -------------- SHOULD start with a uppercase "C" (PascalCase)
 * Private fields ----------------------- SHOULD start with a uppercase "F" (PascalCase)
 * Arguments/Parameters ----------------- SHOULD start with a lowercase "a" (camelCase)
 * Classes ------------------------------ SHOULD start with a uppercase character (PascalCase)
 * Methods ------------------------------ SHOULD start with a uppercase character (PascalCase)
 * Public properties (constants/fields) - SHOULD start with a uppercase character (PascalCase)
 * Variables ---------------------------- SHOULD start with a lowercase character (camelCase)
 *
 * -- Style --
 * Max-line-width ------- 160
 * Single-line comments - // Single-line comment
 * Multi-line comments -- Just like this comment block!
 */

using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Oxide.Game.Rust.Libraries;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using UnityEngine;
using RustLib = Oxide.Game.Rust.Libraries.Rust;

namespace Oxide.Plugins
{
    [Info("PlayerAdministration", "ThibmoRozier", "1.6.9")]
    [Description("Allows server admins to moderate users using a GUI from within the game.")]
    public class PlayerAdministration : CovalencePlugin
    {
        #region Plugin References
#pragma warning disable IDE0044, CS0649
        [PluginReference]
        private Plugin Economics;
        [PluginReference]
        private Plugin ServerRewards;
        [PluginReference]
        private Plugin Freeze;
        [PluginReference]
        private Plugin PermissionsManager;
        [PluginReference]
        private Plugin DiscordMessages;
        [PluginReference]
        private Plugin BetterChatMute;
        [PluginReference]
        private Plugin Backpacks;
        [PluginReference]
        private Plugin InventoryViewer;
        [PluginReference]
        private Plugin ServerArmour;
        [PluginReference]
        private Plugin Godmode;

#pragma warning restore IDE0044, CS0649
        #endregion Plugin References

        #region Library Imports
        private readonly RustLib rust = Interface.Oxide.GetLibrary<RustLib>();
        private readonly Player Player = Interface.Oxide.GetLibrary<Player>();
        #endregion Library Imports

        #region GUI
        #region Types
        /// <summary>
        /// UI Color object
        /// </summary>
        private class CuiColor
        {
            public byte R { get; set; }
            public byte G { get; set; }
            public byte B { get; set; }
            public float A { get; set; }

            public CuiColor(byte aRed = 255, byte aGreen = 255, byte aBlue = 255, float aAlpha = 1f) {
                R = aRed;
                G = aGreen;
                B = aBlue;
                A = aAlpha;
            }

            public override string ToString() => $"{(double)R / 255} {(double)G / 255} {(double)B / 255} {A}";

            public static readonly CuiColor Background = new CuiColor(240, 240, 240, 0.3f);
            public static readonly CuiColor BackgroundMedium = new CuiColor(76, 74, 72, 0.83f);
            public static readonly CuiColor BackgroundDark = new CuiColor(42, 42, 42, 0.93f);
            public static readonly CuiColor Button = new CuiColor(42, 42, 42, 1f);
            public static readonly CuiColor ButtonInactive = new CuiColor(168, 168, 168, 1f);
            public static readonly CuiColor ButtonDecline = new CuiColor(192, 0, 0, 1f);
            public static readonly CuiColor ButtonDanger = new CuiColor(193, 46, 42, 1f);
            public static readonly CuiColor ButtonWarning = new CuiColor(213, 133, 18, 1f);
            public static readonly CuiColor ButtonSuccess = new CuiColor(57, 132, 57, 1f);
            public static readonly CuiColor Text = new CuiColor(0, 0, 0, 1f);
            public static readonly CuiColor TextAlt = new CuiColor(255, 255, 255, 1f);
            public static readonly CuiColor TextTitle = new CuiColor(206, 66, 43, 1f);
            public static readonly CuiColor None = new CuiColor(0, 0, 0, 0f);
        }

        /// <summary>
        /// Element position object
        /// </summary>
        private class CuiPoint
        {
            public float X { get; set; }
            public float Y { get; set; }

            public CuiPoint(float aX = 0f, float aY = 0f) {
                X = aX;
                Y = aY;
            }

            public override string ToString() => $"{X} {Y}";

            public static readonly CuiPoint Zero = new CuiPoint();
        }

        /// <summary>
        /// UI pages to make the switching more humanly readable
        /// </summary>
        private enum UiPage
        {
            Main = 0,
            PlayersOnline,
            PlayersOffline,
            PlayersBanned,
            PlayerPage,
            PlayerPageBanned
        }
        #endregion Types

        #region UI object definitions
        /// <summary>
        /// Input field object
        /// </summary>
        private class CuiInputField
        {
            public CuiInputFieldComponent InputField { get; } = new CuiInputFieldComponent();
            public CuiRectTransformComponent RectTransform { get; } = new CuiRectTransformComponent();
            public float FadeOut { get; set; }
        }
        #endregion UI object definitions

        #region Component container
        /// <summary>
        /// Custom version of the CuiElementContainer to add InputFields
        /// </summary>
        private class CustomCuiElementContainer : CuiElementContainer
        {
            private readonly Action<string> LogError;

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="aLogErrorFunc">Error logging procedure</param>
            /// <returns></returns>
            public CustomCuiElementContainer(Action<string> aLogErrorFunc) : base() {
                LogError = aLogErrorFunc;
            }

            public string Add(CuiInputField aInputField, string aParent = Cui.ParentHud, string aName = "") {
                if (string.IsNullOrEmpty(aName))
                    aName = CuiHelper.GetGuid();

                if (aInputField == null) {
                    LogError($"CustomCuiElementContainer::Add > Parameter 'aInputField' is null");
                    return string.Empty;
                }

                Add(new CuiElement {
                    Name = aName,
                    Parent = aParent,
                    FadeOut = aInputField.FadeOut,
                    Components = {
                        aInputField.InputField,
                        aInputField.RectTransform
                    }
                });
                return aName;
            }
        }
        #endregion Component container

        /// <summary>
        /// Rust UI object
        /// </summary>
        private class Cui
        {
            public const string ParentHud = "Hud";
            public const string ParentOverlay = "Overlay";

            private readonly Action<string> LogDebug;
            private readonly Action<string> LogError;
            private readonly CustomCuiElementContainer FContainer;
            private readonly BasePlayer FPlayer;
            public readonly ulong PlayerId;
            public readonly string PlayerIdString;
            public readonly float StartTime = Time.realtimeSinceStartup;

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="aPlayer">The player this object is meant for</param>
            /// <param name="aLogDebugFunc">Debug logging procedure</param>
            /// <param name="aLogInfoFunc">Info logging procedure</param>
            /// <param name="aLogErrorFunc">Error logging procedure</param>
            /// <returns></returns>
            public Cui(BasePlayer aPlayer, Action<string> aLogDebugFunc, Action<string> aLogErrorFunc) {
                LogDebug = aLogDebugFunc;
                LogError = aLogErrorFunc;

                if (aPlayer == null) {
                    LogError("Cui::Cui > Parameter 'aPlayer' is null");
                    return;
                }

                FContainer = new CustomCuiElementContainer(aLogErrorFunc);
                FPlayer = aPlayer;
                PlayerId = aPlayer.userID;
                PlayerIdString = aPlayer.UserIDString;
                LogDebug("Cui instance created");
            }

            /// <summary>
            /// Add a new panel
            /// </summary>
            /// <param name="aParent">The parent object name</param>
            /// <param name="aLeftBottomAnchor">Left(x)-Bottom(y) relative position</param>
            /// <param name="aRightTopAnchor">Right(x)-Top(y) relative position</param>
            /// <param name="aIndCursorEnabled">The panel requires the cursor</param>
            /// <param name="aColor">Image color</param>
            /// <param name="aName">The object's name</param>
            /// <param name="aPng">Image PNG file path</param>
            /// <returns>New object name</returns>
            public string AddPanel(
                string aParent, CuiPoint aLeftBottomAnchor, CuiPoint aRightTopAnchor, bool aIndCursorEnabled, CuiColor aColor = null, string aName = "",
                string aPng = ""
            ) => AddPanel(aParent, aLeftBottomAnchor, aRightTopAnchor, CuiPoint.Zero, CuiPoint.Zero, aIndCursorEnabled, aColor, aName, aPng);

            /// <summary>
            /// Add a new panel
            /// </summary>
            /// <param name="aParent">The parent object name</param>
            /// <param name="aLeftBottomAnchor">Left(x)-Bottom(y) relative position</param>
            /// <param name="aRightTopAnchor">Right(x)-Top(y) relative position</param>
            /// <param name="aLeftBottomOffset">Left(x)-Bottom(y) relative offset</param>
            /// <param name="aRightTopOffset">Right(x)-Top(y) relative offset</param>
            /// <param name="aIndCursorEnabled">The panel requires the cursor</param>
            /// <param name="aColor">Image color</param>
            /// <param name="aName">The object's name</param>
            /// <param name="aPng">Image PNG file path</param>
            /// <returns>New object name</returns>
            public string AddPanel(
                string aParent, CuiPoint aLeftBottomAnchor, CuiPoint aRightTopAnchor, CuiPoint aLeftBottomOffset, CuiPoint aRightTopOffset,
                bool aIndCursorEnabled, CuiColor aColor = null, string aName = "", string aPng = ""
            ) {
                if (aLeftBottomAnchor == null || aRightTopAnchor == null || aLeftBottomOffset == null || aRightTopOffset == null) {
                    LogError($"Cui::AddPanel > One of the required parameters is null");
                    return string.Empty;
                }

                CuiPanel panel = new CuiPanel {
                    RectTransform =
                    {
                        AnchorMin = aLeftBottomAnchor.ToString(),
                        AnchorMax = aRightTopAnchor.ToString(),
                        OffsetMin = aLeftBottomOffset.ToString(),
                        OffsetMax = aRightTopOffset.ToString()
                    },
                    CursorEnabled = aIndCursorEnabled
                };

                if (!string.IsNullOrEmpty(aPng))
                    panel.Image = new CuiImageComponent { Png = aPng };

                if (aColor != null) {
                    if (panel.Image == null) {
                        panel.Image = new CuiImageComponent { Color = aColor.ToString() };
                    } else {
                        panel.Image.Color = aColor.ToString();
                    }
                }

                LogDebug("Added panel to container");
                return FContainer.Add(panel, aParent, string.IsNullOrEmpty(aName) ? null : aName);
            }

            /// <summary>
            /// Add a new label
            /// </summary>
            /// <param name="aParent">The parent object name</param>
            /// <param name="aLeftBottomAnchor">Left(x)-Bottom(y) relative position</param>
            /// <param name="aRightTopAnchor">Right(x)-Top(y) relative position</param>
            /// <param name="aColor">Text color</param>
            /// <param name="aText">Text to show</param>
            /// <param name="aName">The object's name</param>
            /// <param name="aFontSize">Font size</param>
            /// <param name="aAlign">Text alignment</param>
            /// <returns>New object name</returns>
            public string AddLabel(
                string aParent, CuiPoint aLeftBottomAnchor, CuiPoint aRightTopAnchor, CuiColor aColor, string aText, string aName = "", int aFontSize = 14,
                TextAnchor aAlign = TextAnchor.UpperLeft
            ) => AddLabel(aParent, aLeftBottomAnchor, aRightTopAnchor, CuiPoint.Zero, CuiPoint.Zero, aColor, aText, aName, aFontSize, aAlign);

            /// <summary>
            /// Add a new label
            /// </summary>
            /// <param name="aParent">The parent object name</param>
            /// <param name="aLeftBottomAnchor">Left(x)-Bottom(y) relative position</param>
            /// <param name="aRightTopAnchor">Right(x)-Top(y) relative position</param>
            /// <param name="aLeftBottomOffset">Left(x)-Bottom(y) relative offset</param>
            /// <param name="aRightTopOffset">Right(x)-Top(y) relative offset</param>
            /// <param name="aColor">Text color</param>
            /// <param name="aText">Text to show</param>
            /// <param name="aName">The object's name</param>
            /// <param name="aFontSize">Font size</param>
            /// <param name="aAlign">Text alignment</param>
            /// <returns>New object name</returns>
            public string AddLabel(
                string aParent, CuiPoint aLeftBottomAnchor, CuiPoint aRightTopAnchor, CuiPoint aLeftBottomOffset, CuiPoint aRightTopOffset, CuiColor aColor,
                string aText, string aName = "", int aFontSize = 14, TextAnchor aAlign = TextAnchor.UpperLeft
            ) {
                if (aLeftBottomAnchor == null || aRightTopAnchor == null || aLeftBottomOffset == null || aRightTopOffset == null || aColor == null) {
                    LogError($"Cui::AddLabel > One of the required parameters is null");
                    return string.Empty;
                }

                LogDebug("Added label to container");
                return FContainer.Add(
                    new CuiLabel {
                        Text =
                        {
                            Text = aText ?? string.Empty,
                            FontSize = aFontSize,
                            Align = aAlign,
                            Color = aColor.ToString()
                        },
                        RectTransform =
                        {
                            AnchorMin = aLeftBottomAnchor.ToString(),
                            AnchorMax = aRightTopAnchor.ToString(),
                            OffsetMin = aLeftBottomOffset.ToString(),
                            OffsetMax = aRightTopOffset.ToString()
                        }
                    },
                    aParent,
                    string.IsNullOrEmpty(aName) ? null : aName
                );
            }

            /// <summary>
            /// Add a new button
            /// </summary>
            /// <param name="aParent">The parent object name</param>
            /// <param name="aLeftBottomAnchor">Left(x)-Bottom(y) relative position</param>
            /// <param name="aRightTopAnchor">Right(x)-Top(y) relative position</param>
            /// <param name="aButtonColor">Button background color</param>
            /// <param name="aTextColor">Text color</param>
            /// <param name="aText">Text to show</param>
            /// <param name="aCommand">OnClick event callback command</param>
            /// <param name="aClose">Panel to close</param>
            /// <param name="aName">The object's name</param>
            /// <param name="aFontSize">Font size</param>
            /// <param name="aAlign">Text alignment</param>
            /// <returns>New object name</returns>
            public string AddButton(
                string aParent, CuiPoint aLeftBottomAnchor, CuiPoint aRightTopAnchor, CuiColor aButtonColor, CuiColor aTextColor, string aText,
                string aCommand = "", string aClose = "", string aName = "", int aFontSize = 14, TextAnchor aAlign = TextAnchor.MiddleCenter
            ) => AddButton(
                    aParent, aLeftBottomAnchor, aRightTopAnchor, CuiPoint.Zero, CuiPoint.Zero, aButtonColor, aTextColor, aText, aCommand, aClose, aName,
                    aFontSize, aAlign
                );

            /// <summary>
            /// Add a new button
            /// </summary>
            /// <param name="aParent">The parent object name</param>
            /// <param name="aLeftBottomAnchor">Left(x)-Bottom(y) relative position</param>
            /// <param name="aRightTopAnchor">Right(x)-Top(y) relative position</param>
            /// <param name="aLeftBottomOffset">Left(x)-Bottom(y) relative offset</param>
            /// <param name="aRightTopOffset">Right(x)-Top(y) relative offset</param>
            /// <param name="aButtonColor">Button background color</param>
            /// <param name="aTextColor">Text color</param>
            /// <param name="aText">Text to show</param>
            /// <param name="aCommand">OnClick event callback command</param>
            /// <param name="aClose">Panel to close</param>
            /// <param name="aName">The object's name</param>
            /// <param name="aFontSize">Font size</param>
            /// <param name="aAlign">Text alignment</param>
            /// <returns>New object name</returns>
            public string AddButton(
                string aParent, CuiPoint aLeftBottomAnchor, CuiPoint aRightTopAnchor, CuiPoint aLeftBottomOffset, CuiPoint aRightTopOffset,
                CuiColor aButtonColor, CuiColor aTextColor, string aText, string aCommand = "", string aClose = "", string aName = "", int aFontSize = 14,
                TextAnchor aAlign = TextAnchor.MiddleCenter
            ) {
                if (
                    aLeftBottomAnchor == null || aRightTopAnchor == null || aLeftBottomOffset == null || aRightTopOffset == null || aButtonColor == null ||
                    aTextColor == null
                ) {
                    LogError($"Cui::AddButton > One of the required parameters is null");
                    return string.Empty;
                }

                LogDebug("Added button to container");
                return FContainer.Add(
                    new CuiButton {
                        Button =
                        {
                            Command = aCommand ?? string.Empty,
                            Close = aClose ?? string.Empty,
                            Color = aButtonColor.ToString()
                        },
                        RectTransform =
                        {
                            AnchorMin = aLeftBottomAnchor.ToString(),
                            AnchorMax = aRightTopAnchor.ToString(),
                            OffsetMin = aLeftBottomOffset.ToString(),
                            OffsetMax = aRightTopOffset.ToString()
                        },
                        Text =
                        {
                            Text = aText ?? string.Empty,
                            FontSize = aFontSize,
                            Align = aAlign,
                            Color = aTextColor.ToString()
                        }
                    },
                    aParent,
                    string.IsNullOrEmpty(aName) ? null : aName
                );
            }

            /// <summary>
            /// Add a new input field
            /// </summary>
            /// <param name="aParent">The parent object name</param>
            /// <param name="aLeftBottomAnchor">Left(x)-Bottom(y) relative position</param>
            /// <param name="aRightTopAnchor">Right(x)-Top(y) relative position</param>
            /// <param name="aColor">Text color</param>
            /// <param name="aText">Text to show</param>
            /// <param name="aCharsLimit">Max character count</param>
            /// <param name="aCommand">OnChanged event callback command</param>
            /// <param name="aIndPassword">Indicates that this input should show password chars</param>
            /// <param name="aName">The object's name</param>
            /// <param name="aFontSize">Font size</param>
            /// <param name="aAlign">Text alignment</param>
            /// <returns>New object name</returns>
            public string AddInputField(
                string aParent, CuiPoint aLeftBottomAnchor, CuiPoint aRightTopAnchor, CuiColor aColor, string aText = "", int aCharsLimit = 100,
                string aCommand = "", bool aIndPassword = false, string aName = "", int aFontSize = 14, TextAnchor aAlign = TextAnchor.MiddleLeft
            ) => AddInputField(
                    aParent, aLeftBottomAnchor, aRightTopAnchor, CuiPoint.Zero, CuiPoint.Zero, aColor, aText, aCharsLimit, aCommand, aIndPassword, aName,
                    aFontSize, aAlign
                );

            /// <summary>
            /// Add a new input field
            /// </summary>
            /// <param name="aParent">The parent object name</param>
            /// <param name="aLeftBottomAnchor">Left(x)-Bottom(y) relative position</param>
            /// <param name="aRightTopAnchor">Right(x)-Top(y) relative position</param>
            /// <param name="aLeftBottomOffset">Left(x)-Bottom(y) relative offset</param>
            /// <param name="aRightTopOffset">Right(x)-Top(y) relative offset</param>
            /// <param name="fadeOut">Fade-out time</param>
            /// <param name="aColor">Text color</param>
            /// <param name="aText">Text to show</param>
            /// <param name="aCharsLimit">Max character count</param>
            /// <param name="aCommand">OnChanged event callback command</param>
            /// <param name="aIndPassword">Indicates that this input should show password chars</param>
            /// <param name="aName">The object's name</param>
            /// <param name="aFontSize">Font size</param>
            /// <returns>New object name</returns>
            public string AddInputField(
                string aParent, CuiPoint aLeftBottomAnchor, CuiPoint aRightTopAnchor, CuiPoint aLeftBottomOffset, CuiPoint aRightTopOffset, CuiColor aColor,
                string aText = "", int aCharsLimit = 100, string aCommand = "", bool aIndPassword = false, string aName = "", int aFontSize = 14,
                TextAnchor aAlign = TextAnchor.MiddleLeft
            ) {
                if (aLeftBottomAnchor == null || aRightTopAnchor == null || aLeftBottomOffset == null || aRightTopOffset == null || aColor == null) {
                    LogError($"Cui::AddInputField > One of the required parameters is null");
                    return string.Empty;
                }

                LogDebug("Added input field to container");
                return FContainer.Add(
                    new CuiInputField {
                        InputField =
                        {
                            Text = aText ?? string.Empty,
                            FontSize = aFontSize,
                            Align = aAlign,
                            Color = aColor.ToString(),
                            CharsLimit = aCharsLimit,
                            Command = aCommand ?? string.Empty,
                            IsPassword = aIndPassword
                        },
                        RectTransform =
                        {
                            AnchorMin = aLeftBottomAnchor.ToString(),
                            AnchorMax = aRightTopAnchor.ToString(),
                            OffsetMin = aLeftBottomOffset.ToString(),
                            OffsetMax = aRightTopOffset.ToString()
                        }
                    },
                    aParent,
                    string.IsNullOrEmpty(aName) ? null : aName
                );
            }

            /// <summary>
            /// Add a new element
            /// </summary>
            /// <param name="aParent">The parent object name</param>
            /// <param name="aElement">The object itself</param>
            /// <param name="aName">The object's name</param>
            /// <returns>New object name</returns>
            public string AddElement(string aParent, CuiPanel aElement, string aName = "") =>
                FContainer.Add(aElement, aParent, string.IsNullOrEmpty(aName) ? null : aName);

            /// <summary>
            /// Add a new element
            /// </summary>
            /// <param name="aParent">The parent object name</param>
            /// <param name="aElement">The object itself</param>
            /// <param name="aName">The object's name</param>
            /// <returns>New object name</returns>
            public string AddElement(string aParent, CuiLabel aElement, string aName = "") =>
                FContainer.Add(aElement, aParent, string.IsNullOrEmpty(aName) ? null : aName);

            /// <summary>
            /// Add a new element
            /// </summary>
            /// <param name="aParent">The parent object name</param>
            /// <param name="aElement">The object itself</param>
            /// <param name="aName">The object's name</param>
            /// <returns>New object name</returns>
            public string AddElement(string aParent, CuiButton aElement, string aName = "") =>
                FContainer.Add(aElement, aParent, string.IsNullOrEmpty(aName) ? null : aName);

            /// <summary>
            /// Add a new element
            /// </summary>
            /// <param name="aParent">The parent object name</param>
            /// <param name="aElement">The object itself</param>
            /// <param name="aName">The object's name</param>
            /// <returns>New object name</returns>
            public string AddElement(string aParent, CuiInputField aElement, string aName = "") =>
                FContainer.Add(aElement, aParent, string.IsNullOrEmpty(aName) ? null : aName);

            /// <summary>
            /// Draw the UI to the player's client
            /// </summary>
            /// <returns></returns>
            public bool Draw() => CuiHelper.AddUi(FPlayer, CuiHelper.ToJson(FContainer));

            public string JSON {
                get { return CuiHelper.ToJson(FContainer, true); }
            }
        }
        #endregion GUI

        #region Utility methods
        /// <summary>
        /// Add a button to the tab menu
        /// </summary>
        /// <param name="aUIObj">Cui object</param>
        /// <param name="aParent">Name of the parent object</param>
        /// <param name="aCaption">Text to show</param>
        /// <param name="aCommand">Button to execute</param>
        /// <param name="aPos">Bounds of the button</param>
        /// <param name="aIndActive">To indicate whether or not the button is active</param>
        private void AddTabMenuBtn(ref Cui aUIObj, string aParent, string aCaption, string aCommand, int aPos, bool aIndActive) {
            Vector2 dimensions = new Vector2(0.096f, 0.75f);
            Vector2 offset = new Vector2(0.005f, 0.1f);
            CuiColor btnColor = (aIndActive ? CuiColor.ButtonInactive : CuiColor.Button);
            CuiPoint lbAnchor = new CuiPoint(((dimensions.x + offset.x) * aPos) + offset.x, offset.y);
            CuiPoint rtAnchor = new CuiPoint(lbAnchor.X + dimensions.x, offset.y + dimensions.y);
            aUIObj.AddButton(aParent, lbAnchor, rtAnchor, btnColor, CuiColor.TextAlt, aCaption, (aIndActive ? string.Empty : aCommand));
        }

        /// <summary>
        /// Add a set of user buttons to the parent object
        /// </summary>
        /// <param name="aUIObj">Cui object</param>
        /// <param name="aParent">Name of the parent object</param>
        /// <param name="aUserList">List of entities</param>
        /// <param name="aCommandFmt">Base format of the command to execute (Will be completed with the user ID</param>
        /// <param name="aPage">User list page</param>
        private void AddPlayerButtons(ref Cui aUIObj, string aParent, ref IEnumerable<KeyValuePair<ulong, string>> aUserList, string aCommandFmt, int aPage) {
            IEnumerable<KeyValuePair<ulong, string>> userRange = aUserList.Skip(aPage * CMaxPlayerButtons).Take(CMaxPlayerButtons);
            Vector2 dimensions = new Vector2(0.194f, 0.06f);
            Vector2 offset = new Vector2(0.005f, 0.01f);
            int col = -1;
            int row = 0;
            float margin = 0.09f;
            List<string> addedNames = new List<string>();

            foreach (KeyValuePair<ulong, string> user in userRange) {
                if (++col >= CMaxPlayerCols) {
                    row++;
                    col = 0;
                }

                float calcTop = (1f - margin) - (((dimensions.y + offset.y) * row) + offset.y);
                float calcLeft = ((dimensions.x + offset.x) * col) + offset.x;
                CuiPoint lbAnchor = new CuiPoint(calcLeft, calcTop - dimensions.y);
                CuiPoint rtAnchor = new CuiPoint(calcLeft + dimensions.x, calcTop);
                int suffix = 0;

                string btnTextTemp = EscapeString(user.Value ?? string.Empty);
                string btnCommand = string.Format(aCommandFmt, user.Key);

                if (string.IsNullOrEmpty(btnTextTemp) || CUnknownNameList.Contains(btnTextTemp.ToLower()))
                    btnTextTemp = user.Key.ToString();

                string btnText = btnTextTemp;

                while (addedNames.FindIndex(item => btnText.Equals(item, StringComparison.OrdinalIgnoreCase)) >= 0) {
                    btnText = $"{btnTextTemp} {++suffix}";
                }

                aUIObj.AddButton(aParent, lbAnchor, rtAnchor, CuiColor.Button, CuiColor.TextAlt, btnText, btnCommand, string.Empty, string.Empty, 16);
                addedNames.Add(btnText);
            }

            LogDebug("Added the player buttons to the container");
        }

        /// <summary>
        /// Log an error message to the logfile
        /// </summary>
        /// <param name="aMessage"></param>
        private void LogError(string aMessage) => LogToFile(string.Empty, $"[{DateTime.Now:hh:mm:ss}] ERROR > {aMessage}", this);

        /// <summary>
        /// Log an informational message to the logfile
        /// </summary>
        /// <param name="aMessage"></param>
        private void LogInfo(string aMessage) => LogToFile(string.Empty, $"[{DateTime.Now:hh:mm:ss}] INFO > {aMessage}", this);

        /// <summary>
        /// Log a debugging message to the logfile
        /// </summary>
        /// <param name="aMessage"></param>
        private void LogDebug(string aMessage) {
#pragma warning disable CS0162
            if (CDebugEnabled)
                LogToFile(string.Empty, $"[{DateTime.Now:hh:mm:ss}] DEBUG > {aMessage}", this);
#pragma warning restore CS0162
        }

        /// <summary>
        /// Verify if a user has the specified permission
        /// </summary>
        /// <param name="aPlayer">The player</param>
        /// <param name="aPermission">Pass <see cref="string.Empty"/> to only verify <see cref="CPermUiShow"/></param>
        /// <param name="aIndReport">Indicates that issues should be reported</param>
        /// <returns></returns>
        private bool VerifyPermission(ref BasePlayer aPlayer, string aPermission, bool aIndReport = false) {
            bool result = permission.UserHasPermission(aPlayer.UserIDString, CPermUiShow);
            aPermission = aPermission ?? string.Empty; // We need to get rid of possible null values

            if (FConfigData.UsePermSystem && result && aPermission.Length > 0)
                result = permission.UserHasPermission(aPlayer.UserIDString, aPermission);

            if (aIndReport && !result) {
                rust.SendChatMessage(aPlayer, string.Empty, lang.GetMessage("Permission Error Text", this, aPlayer.UserIDString));
                LogError(string.Format(lang.GetMessage("Permission Error Log Text", this, aPlayer.UserIDString), aPlayer.displayName, aPermission));
            }

            return result;
        }

        /// <summary>
        /// Verify if a user has the specified permission
        /// Note that base-type comparison is WAY faster then String comparison
        /// </summary>
        /// <param name="aPlayerId">The player's ID</param>
        /// <param name="aPermission">Pass <see cref="string.Empty"/> to only verify <see cref="CPermUiShow"/></param>
        /// <returns></returns>
        private bool VerifyPermission(ulong aPlayerId, string aPermission) {
            BasePlayer player = BasePlayer.FindByID(aPlayerId);
            return VerifyPermission(ref player, aPermission);
        }

        /// <summary>
        /// Retrieve server users
        /// </summary>
        /// <param name="aIndFiltered">Indicates if the output should be filtered</param>
        /// <param name="aUserId">User ID for retrieving filter text</param>
        /// <param name="aIndOffline">Retrieve the list of sleepers (offline players)</param>
        /// <returns></returns>
        private IEnumerable<KeyValuePair<ulong, string>> GetServerUserList(bool aIndFiltered, ulong aUserId, bool aIndOffline = false) {
            IEnumerable<KeyValuePair<ulong, string>> result = (aIndOffline ? FOfflineUserList : FOnlineUserList).AsEnumerable();

            if (aIndFiltered && FUserBtnPageSearchInputText.ContainsKey(aUserId))
                result = result.Where(x =>
                        x.Value.IndexOf(FUserBtnPageSearchInputText[aUserId], StringComparison.OrdinalIgnoreCase) >= 0 ||
                        x.Key.ToString().IndexOf(FUserBtnPageSearchInputText[aUserId], StringComparison.OrdinalIgnoreCase) >= 0
                    );

            LogDebug("Retrieved the server user list");
            return result.OrderBy(x => x.Value).ThenBy(x => x.Key);
        }

        /// <summary>
        /// Retrieve server users
        /// </summary>
        /// <param name="aIndFiltered">Indicates if the output should be filtered</param>
        /// <param name="aUserId">User ID for retrieving filter text</param>
        /// <returns></returns>
        private IEnumerable<KeyValuePair<ulong, string>> GetBannedUserList(bool aIndFiltered, ulong aUserId) {
            IEnumerable<KeyValuePair<ulong, string>> result = ServerUsers.GetAll(ServerUsers.UserGroup.Banned).Select(
                x => new KeyValuePair<ulong, string>(x.steamid, x.username)
            );

            if (aIndFiltered && FUserBtnPageSearchInputText.ContainsKey(aUserId))
                result = result.Where(x =>
                        x.Value.IndexOf(FUserBtnPageSearchInputText[aUserId], StringComparison.OrdinalIgnoreCase) >= 0 ||
                        x.Key.ToString().IndexOf(FUserBtnPageSearchInputText[aUserId], StringComparison.OrdinalIgnoreCase) >= 0
                    ).ToList();

            LogDebug("Retrieved the banned user list");
            return result.OrderBy(x => x.Value).ThenBy(x => x.Key);
        }

        /// <summary>
        /// Retrieve the target player ID from the arguments and report success
        /// </summary>
        /// <param name="aArg">Argument object</param>
        /// <param name="aTarget">Player ID</param>
        /// <returns></returns>
        private bool GetTargetFromArg(string[] aArgs, out ulong aTarget) {
            aTarget = 0;
            return aArgs.Length > 0 && ulong.TryParse(aArgs[0], out aTarget);
        }

        /// <summary>
        /// Retrieve the target player ID and amount from the arguments and report success
        /// </summary>
        /// <param name="aArg">Argument object</param>
        /// <param name="aTarget">Player ID</param>
        /// <param name="aAmount">Amount</param>
        /// <returns></returns>
        private bool GetTargetAmountFromArg(string[] aArgs, out ulong aTarget, out float aAmount) {
            aTarget = 0;
            aAmount = 0;
            return aArgs.Length >= 2 && ulong.TryParse(aArgs[0], out aTarget) && float.TryParse(aArgs[1], out aAmount);
        }

        /// <summary>
        /// Check if the player has the ChatMute flag set
        /// </summary>
        /// <param name="aPlayer">The player</param>
        /// <returns></returns>
        private bool GetIsMuted(ref BasePlayer aPlayer) {
            bool isServerMuted = aPlayer.HasPlayerFlag(BasePlayer.PlayerFlags.ChatMute);

            if (BetterChatMute != null) {
                return isServerMuted || (bool)BetterChatMute.Call("API_IsMuted", aPlayer.IPlayer);
            } else {
                return isServerMuted;
            }
        }

        /// <summary>
        /// Check if the player has the freeze.frozen permission
        /// </summary>
        /// <param name="aPlayerId">The player's ID</param>
        /// <returns></returns>
        private bool GetIsFrozen(ulong aPlayerId) => permission.UserHasPermission(aPlayerId.ToString(), CPermFreezeFrozen);

        /// <summary>
        /// Send either a kick or a ban message to Discord via the DiscordMessages plugin
        /// </summary>
        /// <param name="aAdminName">The name of the admin</param>
        /// <param name="aAdminId">The ID of the admin</param>
        /// <param name="aTargetName">The name of the target player</param>
        /// <param name="aTargetId">The ID of the target player</param>
        /// <param name="aReason">The reason message</param>
        /// <param name="aIndIsBan">If this is true a ban message is sent, else a kick message is sent</param>
        private void SendDiscordKickBanMessage(string aAdminName, ulong aAdminId, string aTargetName, ulong aTargetId, string aReason, bool aIndIsBan) {
            if (DiscordMessages != null) {
                if (CUnknownNameList.Contains(aTargetName.ToLower()))
                    aTargetName = aTargetId.ToString();

                object fields = new[]
                {
                    new {
                        name = "Player",
                        value = $"[{aTargetName}](https://steamcommunity.com/profiles/{aTargetId})",
                        inline = true
                    },
                    new {
                        name = aIndIsBan ? "Banned by" : "Kicked by",
                        value = $"[{aAdminName}](https://steamcommunity.com/profiles/{aAdminId})",
                        inline = true
                    },
                    new {
                        name = "Reason",
                        value = aReason,
                        inline = false
                    }
                };
                DiscordMessages.Call(
                    "API_SendFancyMessage",
                    aIndIsBan ? FConfigData.BanMsgWebhookUrl : FConfigData.KickMsgWebhookUrl,
                    aIndIsBan ? "Player Ban" : "Player Kick",
                    3329330,
                    JsonConvert.SerializeObject(fields)
                );
            }
        }

        /// <summary>
        /// Transform a string array into a printable string.
        /// </summary>
        /// <param name="aObj"></param>
        /// <returns></returns>
        private string StringArrToString(ref string[] aObj) => $"[ {string.Join(", ", aObj)} ]";

        /// <summary>
        /// Transform a dictionary of strings into a printable string.
        /// </summary>
        /// <param name="aObj"></param>
        /// <returns></returns>
        private string StringDictToString(ref Dictionary<string, string> aObj) {
            StringBuilder result = new StringBuilder("{\n");

            foreach (KeyValuePair<string, string> item in aObj)
                result.Append($"'{item.Key}': '{item.Value}'\n");

            result.Append('}');
            return result.ToString();
        }

        /// <summary>
        /// Escape strings to make them usable in the UI.
        /// </summary>
        /// <param name="aStr"></param>
        /// <returns></returns>
        private string EscapeString(string aStr) => aStr.Replace("\0", string.Empty)
            .Replace("\a", string.Empty)
            .Replace("\b", string.Empty)
            .Replace("\f", string.Empty)
            .Replace("\r", string.Empty)
            .Replace('\n', ' ')
            .Replace('\t', ' ')
            .Replace("\v", string.Empty)
            .Replace('"', '\u02EE')
            .Replace('/', '\u2215')
            .Replace('\\', '\u2216');

        /// <summary>
        /// Gets the reason from the input box on the players screen.
        /// </summary>
        /// <param name="playerId">The player ID that entered the reason.</param>
        /// <param name="targetId">The target ID the reason is for.</param>
        /// <returns></returns>
        private string GetReason(ulong playerId, string targetId = "", bool indIsKick = false) {
            string reasonMsg;

            if (FUserPageReasonInputText.ContainsKey(playerId)) {
                reasonMsg = FUserPageReasonInputText[playerId].Trim();

                if (string.IsNullOrEmpty(reasonMsg))
                    reasonMsg = lang.GetMessage(indIsKick ? "Kick Reason Message Text" : "Ban Reason Message Text", this, targetId);
            } else {
                reasonMsg = lang.GetMessage(indIsKick ? "Kick Reason Message Text" : "Ban Reason Message Text", this, targetId);
            }

            return reasonMsg;
        }

        private void BroadcastKickBan(string aAdminId, ulong aTargetId, string aReason, bool aIndIsKick) {
            string broadcastMessage = aIndIsKick
                ? lang.GetMessage("Kick Broadcast Message Format", this, aAdminId)
                : lang.GetMessage("Ban Broadcast Message Format", this, aAdminId);
            string targetName = ServerUsers.Get(aTargetId)?.username;

            if (string.IsNullOrEmpty(targetName) || CUnknownNameList.Contains(targetName.ToLower()))
                targetName = aTargetId.ToString();

            rust.BroadcastChat(string.Empty, String.Format(broadcastMessage, targetName, aReason));
        }
        #endregion Utility methods

        #region Upgrade methods
        /// <summary>
        /// Upgrade the config to 1.3.10 if needed
        /// </summary>
        /// <returns></returns>
        private bool UpgradeTo1310() {
            bool result = false;
            Config.Load();

            if (Config["Use Permission System"] == null) {
                FConfigData.UsePermSystem = true;
                result = true;
            }

            // Remove legacy config items
            if (
                Config["Enable kick action"] != null || Config["Enable ban action"] != null || Config["Enable unban action"] != null ||
                Config["Enable kill action"] != null || Config["Enable inventory clear action"] != null || Config["Enable blueprint reset action"] != null ||
                Config["Enable metabolism reset action"] != null || Config["Enable hurt action"] != null || Config["Enable heal action"] != null ||
                Config["Enable mute action"] != null || Config["Enable perms action"] != null || Config["Enable freeze action"] != null
            )
                result = true;

            Config.Clear();

            if (result)
                Config.WriteObject(FConfigData);

            return result;
        }

        /// <summary>
        /// Upgrade the config to 1.3.13 if needed
        /// </summary>
        /// <returns></returns>
        private bool UpgradeTo1313() {
            bool result = false;
            Config.Load();

            if (Config["Discord Webhook url for ban messages"] == null) {
                FConfigData.BanMsgWebhookUrl = string.Empty;
                result = true;
            }

            if (Config["Discord Webhook url for kick messages"] == null) {
                FConfigData.KickMsgWebhookUrl = string.Empty;
                result = true;
            }

            Config.Clear();

            if (result)
                Config.WriteObject(FConfigData);

            return result;
        }

        /// <summary>
        /// Upgrade the config to 1.5.6 if needed
        /// </summary>
        /// <returns></returns>
        private bool UpgradeTo156() {
            bool result = false;
            Dictionary<string, string> oldPerms = new Dictionary<string, string>() {
                { "playeradministration.show", CPermUiShow },
                { "playeradministration.kick", CPermKick },
                { "playeradministration.ban", CPermBan },
                { "playeradministration.kill", CPermKill },
                { "playeradministration.perms", CPermPerms },
                { "playeradministration.voicemute", CPermMute },
                { "playeradministration.chatmute", CPermMute },
                { "playeradministration.freeze", CPermFreeze },
                { "playeradministration.clearinventory", CPermClearInventory },
                { "playeradministration.resetblueprint", CPermResetBP },
                { "playeradministration.resetmetabolism", CPermResetMetabolism },
                { "playeradministration.recovermetabolism", CPermRecoverMetabolism },
                { "playeradministration.hurt", CPermHurt },
                { "playeradministration.heal", CPermHeal },
                { "playeradministration.teleport", CPermTeleport },
                { "playeradministration.spectate", CPermSpectate }
            };
            LogDebug($"Old Perms: {StringDictToString(ref oldPerms)}");

            foreach (KeyValuePair<string, string> item in oldPerms) {
                string[] groups = permission.GetPermissionGroups(item.Key);
                LogDebug($"Groups: {StringArrToString(ref groups)}");
                string[] users = permission.GetPermissionUsers(item.Key);
                LogDebug($"Users: {StringArrToString(ref users)}");

                if (groups.Length + users.Length <= 0) {
                    LogDebug("Counts are zero");
                    continue;
                }

                result = true;

                foreach (string group in groups) {
                    permission.RevokeGroupPermission(group, item.Key);
                    permission.GrantGroupPermission(group, item.Value, this);
                    LogInfo($"Fixed group permission: {group} (OLD) {item.Key} -> (NEW) {item.Value}");
                }

                foreach (string user in users) {
                    string uid = user.Substring(0, user.IndexOf('('));
                    permission.RevokeUserPermission(uid, item.Key);
                    permission.GrantUserPermission(uid, item.Value, this);
                    LogInfo($"Fixed user permission: {user} (OLD) {item.Key} -> (NEW) {item.Value}");
                }
            }

            permission.SaveData();
            return result;
        }

        /// <summary>
        /// Upgrade the config to 1.5.19 if needed
        /// </summary>
        /// <returns></returns>
        private bool UpgradeTo1519() {
            bool result = false;
            Dictionary<string, string> oldPerms = new Dictionary<string, string>() {
                { "playeradministration.access.voicemute", CPermMute },
                { "playeradministration.access.chatmute", CPermMute }
            };
            LogDebug($"Old Perms: {StringDictToString(ref oldPerms)}");

            foreach (KeyValuePair<string, string> item in oldPerms) {
                string[] groups = permission.GetPermissionGroups(item.Key);
                LogDebug($"Groups: {StringArrToString(ref groups)}");
                string[] users = permission.GetPermissionUsers(item.Key);
                LogDebug($"Users: {StringArrToString(ref users)}");

                if (groups.Length + users.Length <= 0) {
                    LogDebug("Counts are zero");
                    continue;
                }

                result = true;

                foreach (string group in groups) {
                    permission.RevokeGroupPermission(group, item.Key);
                    permission.GrantGroupPermission(group, item.Value, this);
                    LogInfo($"Fixed group permission: {group} (OLD) {item.Key} -> (NEW) {item.Value}");
                }

                foreach (string user in users) {
                    string uid = user.Substring(0, user.IndexOf('('));
                    permission.RevokeUserPermission(uid, item.Key);
                    permission.GrantUserPermission(uid, item.Value, this);
                    LogInfo($"Fixed user permission: {user} (OLD) {item.Key} -> (NEW) {item.Value}");
                }
            }

            permission.SaveData();
            return result;
        }

        /// <summary>
        /// Upgrade the config to 1.6.4 if needed
        /// </summary>
        /// <returns></returns>
        private bool UpgradeTo164() {
            bool result = false;
            Config.Load();

            if (Config["Broadcast Kicks"] == null) {
                FConfigData.BroadcastKicks = true;
                result = true;
            }

            if (Config["Broadcast Bans"] == null) {
                FConfigData.BroadcastBans = true;
                result = true;
            }

            Config.Clear();

            if (result)
                Config.WriteObject(FConfigData);

            return result;
        }
        #endregion

        #region GUI build methods
        /// <summary>
        /// Build the tab nav-bar
        /// </summary>
        /// <param name="aUIObj">Cui object</param>
        /// <param name="aPageType">The active page type</param>
        private void BuildTabMenu(ref Cui aUIObj, UiPage aPageType) {
            // Add the panels and title label
            string headerPanel = aUIObj.AddElement(CMainPanelName, CTabHeaderPanel);
            string tabBtnPanel = aUIObj.AddElement(CMainPanelName, CTabTabBtnPanel);
            aUIObj.AddElement(headerPanel, CTabMenuHeaderLbl);
            aUIObj.AddElement(headerPanel, CTabMenuCloseBtn);
            // Add the tab menu buttons
            AddTabMenuBtn(
                ref aUIObj, tabBtnPanel, lang.GetMessage("Main Tab Text", this, aUIObj.PlayerIdString), $"{CSwitchUiCmd} {CCmdArgMain}", 0,
                aPageType == UiPage.Main
            );
            AddTabMenuBtn(
                ref aUIObj, tabBtnPanel, lang.GetMessage("Online Player Tab Text", this, aUIObj.PlayerIdString), $"{CSwitchUiCmd} {CCmdArgPlayersOnline} 0", 1,
                aPageType == UiPage.PlayersOnline
            );
            AddTabMenuBtn(
                ref aUIObj, tabBtnPanel, lang.GetMessage("Offline Player Tab Text", this, aUIObj.PlayerIdString), $"{CSwitchUiCmd} {CCmdArgPlayersOffline} 0",
                2, aPageType == UiPage.PlayersOffline
            );
            AddTabMenuBtn(
                ref aUIObj, tabBtnPanel, lang.GetMessage("Banned Player Tab Text", this, aUIObj.PlayerIdString), $"{CSwitchUiCmd} {CCmdArgPlayersBanned} 0", 3,
                aPageType == UiPage.PlayersBanned
            );
            LogDebug("Built the tab menu");
        }

        /// <summary>
        /// Build the main-menu
        /// </summary>
        /// <param name="aUIObj">Cui object</param>
        private void BuildMainPage(ref Cui aUIObj) {
            // Add the panels and title
            string panel = aUIObj.AddElement(CMainPanelName, CMainPagePanel);
            aUIObj.AddElement(panel, CMainPageTitleLbl);
            // Add the ban by ID group
            aUIObj.AddLabel(
                panel, CMainPageLblBanByIdTitleLbAnchor, CMainPageLblBanByIdTitleRtAnchor, CuiColor.TextTitle,
                lang.GetMessage("Ban By ID Title Text", this, aUIObj.PlayerIdString), string.Empty, 16, TextAnchor.MiddleLeft
            );
            aUIObj.AddLabel(
                panel, CMainPageLblBanByIdLbAnchor, CMainPageLblBanByIdRtAnchor, CuiColor.TextAlt,
                lang.GetMessage("Ban By ID Label Text", this, aUIObj.PlayerIdString), string.Empty, 14, TextAnchor.MiddleLeft
            );
            string panelBanByIdGroup = aUIObj.AddElement(panel, CBanByIdGroupPanel);

            if (VerifyPermission(aUIObj.PlayerId, CPermBan)) {
                aUIObj.AddElement(panelBanByIdGroup, CBanByIdEdt);
                aUIObj.AddElement(panel, CBanByIdActiveBtn);
            } else {
                aUIObj.AddElement(panel, CBanByIdInactiveBtn);
            }

            LogDebug("Built the main page");
            LogDebug($"Elapsed time (BuildMainPage): {Time.realtimeSinceStartup - aUIObj.StartTime:F8}");
        }

        /// <summary>
        /// Build the current user buttons
        /// </summary>
        /// <param name="aUIObj">Cui object</param>
        /// <param name="aParent">The active page type</param>
        /// <param name="aPageType">The active page type</param>
        /// <param name="aPage">User list page</param>
        /// <param name="aBtnCommandFmt">Command format for the buttons</param>
        /// <param name="aUserCount">Total user count</param>
        /// <param name="aIndFiltered">Indicates if the output should be filtered</param>
        private void BuildUserButtons(
            ref Cui aUIObj, string aParent, UiPage aPageType, ref int aPage, out string aBtnCommandFmt, out int aUserCount, bool aIndFiltered
        ) {
            string commandFmt = $"{CSwitchUiCmd} {CCmdArgPlayerPage} {{0}}";
            IEnumerable<KeyValuePair<ulong, string>> userList = GetServerUserList(aIndFiltered, aUIObj.PlayerId, aPageType == UiPage.PlayersOffline);

            aBtnCommandFmt = aPageType == UiPage.PlayersOnline
                ? $"{CSwitchUiCmd} {(aIndFiltered ? CCmdArgPlayersOnlineSearch : CCmdArgPlayersOnline)} {{0}}"
                : $"{CSwitchUiCmd} {(aIndFiltered ? CCmdArgPlayersOfflineSearch : CCmdArgPlayersOffline)} {{0}}";
            aUserCount = userList.Count();

            if ((aPage != 0) && (aUserCount <= CMaxPlayerButtons))
                aPage = 0; // Reset page to 0 if user count is lower or equal to max button count

            AddPlayerButtons(ref aUIObj, aParent, ref userList, commandFmt, aPage);
            LogDebug("Built the current page of user buttons");
        }

        /// <summary>
        /// Build a page of user buttons
        /// </summary>
        /// <param name="aUIObj">Cui object</param>
        /// <param name="aPageType">The active page type</param>
        /// <param name="aPage">User list page</param>
        /// <param name="aIndFiltered">Indicates if the output should be filtered</param>
        private void BuildUserBtnPage(ref Cui aUIObj, UiPage aPageType, int aPage, bool aIndFiltered) {
            string npBtnCommandFmt;
            int userCount;

            string panel = aUIObj.AddElement(CMainPanelName, CMainPagePanel);
            aUIObj.AddLabel(
                panel, CUserBtnPageLblTitleLbAnchor, CUserBtnPageLblTitleRtAnchor, CuiColor.TextAlt,
                lang.GetMessage("User Button Page Title Text", this, aUIObj.PlayerIdString), string.Empty, 18, TextAnchor.MiddleLeft
            );
            // Add search elements
            aUIObj.AddLabel(
                panel, CUserBtnPageLblSearchLbAnchor, CUserBtnPageLblSearchRtAnchor, CuiColor.TextAlt,
                lang.GetMessage("Search Label Text", this, aUIObj.PlayerIdString), string.Empty, 16, TextAnchor.MiddleLeft
            );
            string panelSearchGroup = aUIObj.AddElement(panel, CUserBtnPageSearchInputPanel);
            aUIObj.AddInputField(
                panelSearchGroup, CUserBtnPageEdtSearchInputLbAnchor, CUserBtnPageEdtSearchInputRtAnchor, CuiColor.TextAlt,
                (FUserBtnPageSearchInputText.ContainsKey(aUIObj.PlayerId) ? FUserBtnPageSearchInputText[aUIObj.PlayerId] : string.Empty), 100,
                CUserBtnPageSearchInputTextCmd, false, string.Empty, 16
            );

            switch (aPageType) {
                case UiPage.PlayersOnline: {
                        aUIObj.AddButton(
                            panel, CUserBtnPageBtnSearchLbAnchor, CUserBtnPageBtnSearchRtAnchor, CuiColor.Button, CuiColor.TextAlt,
                            lang.GetMessage("Go Button Text", this, aUIObj.PlayerIdString), $"{CSwitchUiCmd} {CCmdArgPlayersOnlineSearch} 0", string.Empty,
                            string.Empty, 16
                        );
                        BuildUserButtons(ref aUIObj, panel, aPageType, ref aPage, out npBtnCommandFmt, out userCount, aIndFiltered);
                        break;
                    }
                case UiPage.PlayersOffline: {
                        aUIObj.AddButton(
                            panel, CUserBtnPageBtnSearchLbAnchor, CUserBtnPageBtnSearchRtAnchor, CuiColor.Button, CuiColor.TextAlt,
                            lang.GetMessage("Go Button Text", this, aUIObj.PlayerIdString), $"{CSwitchUiCmd} {CCmdArgPlayersOfflineSearch} 0", string.Empty,
                            string.Empty, 16
                        );
                        BuildUserButtons(ref aUIObj, panel, aPageType, ref aPage, out npBtnCommandFmt, out userCount, aIndFiltered);
                        break;
                    }
                default: {
                        aUIObj.AddButton(
                            panel, CUserBtnPageBtnSearchLbAnchor, CUserBtnPageBtnSearchRtAnchor, CuiColor.Button, CuiColor.TextAlt,
                            lang.GetMessage("Go Button Text", this, aUIObj.PlayerIdString), $"{CSwitchUiCmd} {CCmdArgPlayersBannedSearch} 0", string.Empty,
                            string.Empty, 16
                        );

                        string commandFmt = $"{CSwitchUiCmd} {CCmdArgPlayerPageBanned} {{0}}";
                        IEnumerable<KeyValuePair<ulong, string>> userList = GetBannedUserList(aIndFiltered, aUIObj.PlayerId);
                        npBtnCommandFmt = $"{CSwitchUiCmd} {(aIndFiltered ? CCmdArgPlayersBannedSearch : CCmdArgPlayersBanned)} {{0}}";
                        userCount = userList.Count();

                        if ((aPage != 0) && (userCount <= CMaxPlayerButtons))
                            aPage = 0; // Reset page to 0 if user count is lower or equal to max button count

                        AddPlayerButtons(ref aUIObj, panel, ref userList, commandFmt, aPage);
                        LogDebug("Built the current page of banned user buttons");
                        break;
                    }
            }

            // Decide whether or not to activate the "previous" button
            if (aPage == 0) {
                aUIObj.AddElement(panel, CUserBtnPagePreviousInactiveBtn);
            } else {
                aUIObj.AddButton(
                    panel, CUserBtnPageBtnPreviousLbAnchor, CUserBtnPageBtnPreviousRtAnchor, CuiColor.Button, CuiColor.TextAlt, "<<",
                    string.Format(npBtnCommandFmt, aPage - 1), string.Empty, string.Empty, 18
                );
            }

            // Decide whether or not to activate the "next" button
            if (userCount > CMaxPlayerButtons * (aPage + 1)) {
                aUIObj.AddButton(
                    panel, CUserBtnPageBtnNextLbAnchor, CUserBtnPageBtnNextRtAnchor, CuiColor.Button, CuiColor.TextAlt, ">>",
                    string.Format(npBtnCommandFmt, aPage + 1), string.Empty, string.Empty, 18
                );
            } else {
                aUIObj.AddElement(panel, CUserBtnPageNextInactiveBtn);
            }

            LogDebug("Built the user button page");
            LogDebug($"Elapsed time (BuildUserBtnPage): {Time.realtimeSinceStartup - aUIObj.StartTime:F8}");
        }

        /// <summary>
        /// Add the user information labels to the parent element
        /// </summary>
        /// <param name="aUIObj">Cui object</param>
        /// <param name="aParent">Parent panel name</param>
        /// <param name="aPlayerId">Player ID (SteamId64)</param>
        /// <param name="aPlayer">Player who's information we need to display</param>
        private void AddUserPageInfoLabels(ref Cui aUIObj, string aParent, ulong aPlayerId, ref BasePlayer aPlayer) {
            string lastCheatStr = lang.GetMessage("Never Label Text", this, aUIObj.PlayerIdString);
            string authLevel = ServerUsers.Get(aPlayerId)?.group.ToString() ?? "None";        
            string CpAddress = lang.GetMessage("OffLine Label Text", this, aUIObj.PlayerIdString);
            string CpPing = lang.GetMessag

Its cut off...

v36fGdXQn1yzbtD.jpg snaplatack

Its cut off...

i did a quick fix:   "

Just sharing this in case it helps anyone.

I've updated PlayerAdministration to fix the following compile error after the latest Rust update:

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

You can grab the updated PlayerAdministration.cs here:

Hopefully this saves a few people some time until an official update is available.

If you run into any issues or find anything I've missed, let me know and I'll take a look."

Not doing that. I sent a patch

Heres the parts to change

LINE 1271 - 1295: Copy paste and should work

/// <summary>
/// Add the user information labels to the parent element
/// </summary>
/// <param name="aUIObj">Cui object</param>
/// <param name="aParent">Parent panel name</param>
/// <param name="aPlayerId">Player ID (SteamId64)</param>
/// <param name="aPlayer">Player who's information we need to display</param>
private void AddUserPageInfoLabels(ref Cui aUIObj, string aParent, ulong aPlayerId, ref BasePlayer aPlayer) {
    string lastCheatStr = lang.GetMessage("Never Label Text", this, aUIObj.PlayerIdString);
    string authLevel = ServerUsers.Get(aPlayerId)?.group.ToString() ?? "None";        
    string CpAddress = lang.GetMessage("OffLine Label Text", this, aUIObj.PlayerIdString);
    string CpPing = lang.GetMessage("OffLine Label Text", this, aUIObj.PlayerIdString);
    float cheatTime = AntiHack.PlayerStates[aPlayer.ActivePlayerInd].LastAdminCheatTime;

    // Recover & pre-process user connection data(player should be connected so calls are safe)
    if (aPlayer.IsConnected)
    {
        CpAddress = aPlayer.net.connection.ipaddress.Split(':')[0];
        CpPing = Network.Net.sv.GetAveragePing(aPlayer.net.connection).ToString();
    }

    // Pre-calc last admin cheat
    if (cheatTime > 0f) {
        TimeSpan lastCheatSinceStart = new TimeSpan(0, 0, (int)(Time.realtimeSinceStartup - cheatTime));
        lastCheatStr = $"{DateTime.UtcNow.Subtract(lastCheatSinceStart):yyyy-MM-dd HH:mm:ss} UTC";
    }
v36fGdXQn1yzbtD.jpg snaplatack

Not doing that. I sent a patch

Heres the parts to change

LINE 1271 - 1295: Copy paste and should work

/// <summary>
/// Add the user information labels to the parent element
/// </summary>
/// <param name="aUIObj">Cui object</param>
/// <param name="aParent">Parent panel name</param>
/// <param name="aPlayerId">Player ID (SteamId64)</param>
/// <param name="aPlayer">Player who's information we need to display</param>
private void AddUserPageInfoLabels(ref Cui aUIObj, string aParent, ulong aPlayerId, ref BasePlayer aPlayer) {
    string lastCheatStr = lang.GetMessage("Never Label Text", this, aUIObj.PlayerIdString);
    string authLevel = ServerUsers.Get(aPlayerId)?.group.ToString() ?? "None";        
    string CpAddress = lang.GetMessage("OffLine Label Text", this, aUIObj.PlayerIdString);
    string CpPing = lang.GetMessage("OffLine Label Text", this, aUIObj.PlayerIdString);
    float cheatTime = AntiHack.PlayerStates[aPlayer.ActivePlayerInd].LastAdminCheatTime;

    // Recover & pre-process user connection data(player should be connected so calls are safe)
    if (aPlayer.IsConnected)
    {
        CpAddress = aPlayer.net.connection.ipaddress.Split(':')[0];
        CpPing = Network.Net.sv.GetAveragePing(aPlayer.net.connection).ToString();
    }

    // Pre-calc last admin cheat
    if (cheatTime > 0f) {
        TimeSpan lastCheatSinceStart = new TimeSpan(0, 0, (int)(Time.realtimeSinceStartup - cheatTime));
        lastCheatStr = $"{DateTime.UtcNow.Subtract(lastCheatSinceStart):yyyy-MM-dd HH:mm:ss} UTC";
    }

 

This here is the only fix matters. Thanks Bud!