using System; using System.Text; using System.Reflection; using System.Collections.Generic; using System.Linq; using Oxide.Core; using Oxide.Core.Plugins; using Rust; using Facepunch; using UnityEngine; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace Oxide.Plugins { [Info("Bank", "rustservers.io", "0.1.2", ResourceId = 2116)] [Description("Safe player storage")] class Bank : RustPlugin { #region Variables string defaultBoxPrefab; int defaultSlots; private Dictionary boxPrefabs; private Dictionary boxSlots; private bool keyring; private float cooldownMinutes; private bool npconly; private List npcids; private float radiationMax; public static DataFileSystem datafile; public static DataFileSystem configFile; FieldInfo keyCodeField = typeof(KeyLock).GetField("keyCode", (BindingFlags.Instance | BindingFlags.NonPublic)); private Dictionary itemLimits = new Dictionary(); [PluginReference] Plugin MasterKey; #endregion #region Bank/Item Profile public class ItemProfile { public string id; public int amount; public int slot; public Item.Flag flags; public float condition; public ulong skin; public List contents; public int primaryMagazine; public int ammoType; public int dataInt; [JsonConstructor] public ItemProfile(string id, int amount, int slot, Item.Flag flags, float condition = 0.0f, ulong skin = 0, List contents = null, int primaryMagazine = 0, int ammoType = 0, int dataInt = 0) { this.id = id; this.amount = amount; this.slot = slot; this.flags = flags; this.condition = condition; this.skin = skin; this.contents = contents; this.primaryMagazine = primaryMagazine; this.ammoType = ammoType; this.dataInt = dataInt; } public static ItemProfile Create(Item item) { List contents = new List(); int primaryMagazine = 0; int ammoType = 0; if (item.contents != null) { if (item.contents.itemList.Count > 0) { foreach (Item content in item.contents.itemList) { contents.Add(Create(content)); } } } var weapon = item.GetHeldEntity() as BaseProjectile; if (weapon != null) { BaseProjectile projectile = weapon as BaseProjectile; primaryMagazine = projectile.primaryMagazine.contents; ammoType = projectile.primaryMagazine.ammoType.itemid; } int dataInt = 0; if(item.instanceData != null && item.info.shortname == "door.key") { dataInt = item.instanceData.dataInt; } ItemProfile newItem = new ItemProfile(item.info.itemid.ToString(), item.amount, item.position, item.flags, item.condition, item.skin, contents, primaryMagazine, ammoType, dataInt); return newItem; } } public class BankProfile { protected ulong playerID; public List items = new List(); [JsonIgnore] public bool open = false; [JsonIgnore] public bool dirty = false; [JsonIgnore] public BasePlayer Player { get { return BasePlayer.Find(playerID.ToString()); } protected set { playerID = value.userID; } } [JsonIgnore] public ulong PlayerID { get { return playerID; } private set { } } [JsonIgnore] public int Count { get { return items.Count; } private set { } } public BankProfile() { } public BankProfile(BasePlayer player, List items = null) { playerID = player.userID; if(items is List) { this.items = items; } } [JsonConstructor] public BankProfile(ulong playerID, List items) { this.playerID = playerID; this.items = items; } public bool Add(Item item) { ItemProfile profile = ItemProfile.Create(item); this.items.Add(profile); this.dirty = true; return true; } public bool Add(Item[] items) { foreach(Item item in items) { if(!this.Add(item)) { return false; } } return true; } public bool Add(List items) { return this.Add(items.ToArray()); } public bool Remove(Item item) { ItemProfile removing = null; foreach(ItemProfile profile in this.items) { if(profile.id == item.info.itemid.ToString() && profile.amount == item.amount) { removing = profile; break; } } if(removing is ItemProfile) { this.dirty = true; this.items.Remove(removing); return true; } return false; } public bool Remove(Item[] items) { foreach(Item item in items) { if(!Remove(item)) { return false; } } return true; } public bool Remove(List items) { return Remove(items.ToArray()); } [JsonIgnore] private ItemContainer container; public ItemContainer GetContainer(BasePlayer player, int slots = 30) { if(this.container is ItemContainer) { this.container.playerOwner = player; this.container.itemList.Clear(); this.PopulateContainer(player, this.container); return this.container; } ItemContainer container = new ItemContainer(); container.ServerInitialize(null, slots); if ((int) container.uid == 0) container.GiveUID(); container.playerOwner = player; PopulateContainer(player, container); return this.container = container; } private void PopulateContainer(BasePlayer player, ItemContainer container, List items = null) { if(items == null) { items = this.items; } foreach(ItemProfile profile in items) { Item item = ItemManager.CreateByItemID(Convert.ToInt32(profile.id), profile.amount); if(item is Item) { item.flags = profile.flags; item.skin = profile.skin; if(item.hasCondition) { item.condition = profile.condition; } var held = item.GetHeldEntity(); if(held is BaseEntity) { held.skinID = profile.skin; } var weapon = held as BaseProjectile; if(weapon != null) { BaseProjectile projectile = weapon as BaseProjectile; projectile.primaryMagazine.contents = profile.primaryMagazine; if(profile.ammoType != 0) { projectile.primaryMagazine.ammoType = ItemManager.FindItemDefinition(profile.ammoType); } } if(profile.contents != null) { if(profile.contents.Count > 0) { PopulateContainer(player, item.contents, profile.contents); } } if(item.info.shortname == "door.key" && profile.dataInt != 0) { ProtoBuf.Item.InstanceData instanceData = Facepunch.Pool.Get(); item.instanceData = instanceData; item.instanceData.ShouldPool = false; item.instanceData.dataInt = profile.dataInt; } item.MoveToContainer(container, profile.slot); } } } } #endregion #region Configuration class ItemLimit { public bool enabled; public int minimum; public int maximum; [JsonConstructor] public ItemLimit(bool enabled, int minimum, int maximum) { this.enabled = enabled; this.minimum = minimum; this.maximum = maximum; } } #endregion #region State class OnlinePlayer { public BasePlayer Player; public BasePlayer Target; public StorageContainer View; public List Matches; public OnlinePlayer(BasePlayer player) { } } public Dictionary containers = new Dictionary(); public Dictionary banks = new Dictionary(); [OnlinePlayers] Hash onlinePlayers = new Hash(); private Dictionary bankCooldowns = new Dictionary(); #endregion #region Initialization & Data void Init() { //Unsubscribe(nameof(CanNetworkTo)); Unsubscribe(nameof(OnEntityTakeDamage)); ////Unsubscribe(nameof(OnItemAddedToContainer)); } void Loaded() { configFile = new DataFileSystem(Interface.Oxide.ConfigDirectory); itemLimits = configFile.ReadObject>("Bank_ItemLimits"); permission.RegisterPermission("bank.use", this); CheckConfig(); LoadMessages(); datafile = new DataFileSystem(Interface.Oxide.DataDirectory + "\\" + this.GetConfig("subDirectory", "banks")); boxPrefabs = GetConfig("Settings", "boxes", GetDefaultBoxes()); boxSlots = GetConfig("Settings","slots", GetDefaultSlots()); defaultBoxPrefab = GetConfig("Settings", "defaultBox", "assets/prefabs/deployable/woodenbox/woodbox_deployed.prefab"); defaultSlots = GetConfig("Settings", "defaultSlots", 4); cooldownMinutes = GetConfig("Settings", "cooldownMinutes", 5f); radiationMax = GetConfig("Settings", "radiationMax", 1f); npconly = GetConfig("Settings", "NPCBankersOnly", false); npcids = GetConfig("Settings", "NPCIDs", new List()); keyring = GetConfig("Settings", "Keyring", true); foreach(KeyValuePair kvp in boxPrefabs) { permission.RegisterPermission(kvp.Key, this); } foreach(KeyValuePair kvp in boxSlots) { if(!boxPrefabs.ContainsKey(kvp.Key)) { permission.RegisterPermission(kvp.Key, this); } } foreach(BasePlayer player in BasePlayer.activePlayerList) { LoadProfile(player.userID); } } void LoadMessages() { lang.RegisterMessages(new Dictionary { {"Denied: Permission", "You lack permission to do that"}, {"Denied: Privilege", "You lack permission to do that"}, {"Denied: Swimming", "You cannot do that while swimming"}, {"Denied: Falling", "You cannot do that while falling"}, {"Denied: Wounded", "You cannot do that while wounded"}, {"Denied: Generic", "You cannot do that right now"}, {"Denied: Irradiated", "You cannot do that while irradiated"}, {"Cooldown: Seconds", "You are doing that too often, try again in a {0} seconds(s)."}, {"Cooldown: Minutes", "You are doing that too often, try again in a {0} minute(s)."}, {"Limit: Return", "Some items were returned to your inventory"} }, this); } private Dictionary GetDefaultBoxes() { return new Dictionary() { {"bank.default", "assets/prefabs/deployable/woodenbox/woodbox_deployed.prefab"}, {"bank.big", "assets/prefabs/deployable/large wood storage/box.wooden.large.prefab"} }; } private Dictionary GetDefaultSlots() { return new Dictionary() { {"bank.default", 4}, {"bank.big", 30} }; } private Dictionary GetDefaultItemLimits() { return new Dictionary() { {"explosive.timed", new ItemLimit(false, 0, 1)} }; } protected override void LoadDefaultConfig() { Config["Settings", "boxes"] = GetDefaultBoxes(); Config["Settings", "slots"] = GetDefaultSlots(); Config["Settings", "defaultBox"] = "assets/prefabs/deployable/woodenbox/woodbox_deployed.prefab"; Config["Settings", "defaultSlots"] = 4; Config["Settings", "keyring"] = true; Config["Settings", "cooldownMinutes"] = 5; Config["Settings", "radiationMax"] = 1; Config["Settings", "NPCBankersOnly"] = false; Config["Settings", "NPCIDs"] = new List(); if(configFile == null) configFile = new DataFileSystem(Interface.Oxide.ConfigDirectory); configFile.WriteObject>("Bank_ItemLimits", GetDefaultItemLimits()); Config["VERSION"] = Version.ToString(); } void Unloaded() { foreach (var player in BasePlayer.activePlayerList) { OnlinePlayer onlinePlayer; if (onlinePlayers.TryGetValue(player, out onlinePlayer) && onlinePlayer.View != null) { SaveProfileByUser(player.userID); CloseBank(player, onlinePlayer.View); } } } void OnServerSave() { SaveData(); } void CheckConfig() { if (Config["VERSION"] == null) { // FOR COMPATIBILITY WITH INITIAL VERSIONS WITHOUT VERSIONED CONFIG ReloadConfig(); } else if (GetConfig("VERSION", "") != Version.ToString()) { // ADDS NEW, IF ANY, CONFIGURATION OPTIONS ReloadConfig(); } } protected void ReloadConfig() { Config["VERSION"] = Version.ToString(); // NEW CONFIGURATION OPTIONS HERE Config["Settings", "radiationMax"] = GetConfig("Settings", "radiationMax", 1f); Config["Settings", "NPCBankersOnly"] = GetConfig("Settings", "NPCBankersOnly", false); Config["Settings", "NPCIDs"] = GetConfig("Settings", "NPCIDs", new List()); if(itemLimits.Count == 0) { itemLimits = GetDefaultItemLimits(); } configFile.WriteObject>("Bank_ItemLimits", itemLimits); // END NEW CONFIGURATION OPTIONS PrintWarning("Upgrading configuration file"); SaveConfig(); } void SaveProfileByUser(ulong userID) { if(banks.ContainsKey(userID)) { SaveProfile(userID, banks[userID]); } } void SaveData() { int t = 0; foreach (KeyValuePair kvp in banks) { if (kvp.Value.dirty) { SaveProfile(kvp.Key, kvp.Value); t++; } } PrintToConsole("Saved " + t.ToString() + " banks"); } protected bool LoadProfile(ulong playerID, bool reload = false) { if (playerID == 0) { return false; } string path = "bank_" + playerID.ToString(); BankProfile profile = datafile.ReadObject(path); if (!(profile is BankProfile)) { return false; } if (profile.Count == 0) { return false; } if (banks.ContainsKey(playerID)) { banks[playerID] = profile; } else { banks.Add(playerID, profile); } return true; } void SaveProfile(ulong playerID, BankProfile profile = null) { if(profile == null) { if(!banks.ContainsKey(playerID)) { return; } profile = banks[playerID]; } string path = "bank_"+playerID.ToString(); int pc = profile.Count; datafile.WriteObject(path, profile); profile.dirty = false; } private bool IsBank(BaseNetworkable entity) { foreach(KeyValuePair kvp in onlinePlayers) { if(kvp.Value.View != null && kvp.Value.View.net.ID == entity.net.ID) { return true; } } return false; } #endregion #region Oxide Hooks void OnUseNPC(BasePlayer npc, BasePlayer player) { if (!npcids.Contains(npc.UserIDString)) return; ShowBank(player, player); } //object CanNetworkTo(BaseNetworkable entity, BasePlayer target) //{ // if (entity == null || target == null || entity == target || entity.net == null) return null; // if (target.IsAdmin) return null; // OnlinePlayer onlinePlayer; // bool IsMyBank = false; // if(onlinePlayers.TryGetValue(target, out onlinePlayer)) { // if(onlinePlayer.View != null && onlinePlayer.View.net != null && onlinePlayer.View.net.ID == entity.net.ID) { // IsMyBank = true; // } // } // if (IsBank(entity) && !IsMyBank) return false; // return null; //} private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo hitInfo) { if (hitInfo == null) return null; if(IsBank(entity)) return false; return null; } object CanUseLockedEntity(BasePlayer player, BaseLock lockItem) { if(!keyring) { return null; } if(MasterKey != null) { var result = MasterKey.Call("CanUseLockedEntity", player, lockItem); if(result is bool) { return null; } } BankProfile bank; if (lockItem is KeyLock && banks.TryGetValue(player.userID, out bank)) { KeyLock keyLock = (KeyLock)lockItem; List codes = bank.items.Select(profile => profile.dataInt).Where(dataInt => dataInt != 0).ToList(); //foreach(ItemProfile profile in bank.items) { // if(profile.dataInt != 0) { // codes.Add(profile.dataInt); // } //} if (!keyLock.IsLocked()) { return null; } if(keyLock.HasLockPermission(player)) { return null; } int keyCode = (int)keyCodeField.GetValue(keyLock); foreach(int code in codes) { if(code == keyCode) { return true; } } return false; } return null; } void OnPlayerInit(BasePlayer player) { onlinePlayers[player].View = null; onlinePlayers[player].Target = null; onlinePlayers[player].Matches = null; if(!LoadProfile(player.userID)) { if(!banks.ContainsKey(player.userID)) { banks.Add(player.userID, new BankProfile(player)); } } } void OnPlayerDisconnected(BasePlayer player) { if (onlinePlayers[player].View != null) { ShowBank(player, onlinePlayers[player].View); SaveProfile(player.userID); } } void OnPlayerLootEnd(PlayerLoot inventory) { BasePlayer player; if ((player = inventory.GetComponent()) == null) return; if (onlinePlayers.ContainsKey(player) && onlinePlayers[player].View != null) { if(onlinePlayers[player].View == inventory.entitySource) { CloseBank(player, (StorageContainer)inventory.entitySource); } } } //void OnItemAddedToContainer(ItemContainer container, Item item) //{ // BasePlayer player = container.playerOwner; // OnlinePlayer onlinePlayer; // if(player != null && onlinePlayers.TryGetValue(player, out onlinePlayer)) { // if(onlinePlayer.View != null && onlinePlayer.View.inventory == container) { // int validAmount = GetValidAmount(item, container); // if(validAmount <= 0) { // item.MoveToContainer(container.playerOwner.inventory.containerMain); // return; // } // if(validAmount < item.amount) { // Item splitItem = item.SplitItem(item.amount - validAmount); // splitItem.MoveToContainer(container.playerOwner.inventory.containerMain); // } // } // } //} #endregion #region Commands [ChatCommand("viewbank")] void ViewBank(BasePlayer player, string command, string[] args) { if (!IsAllowed(player)) return; if (args.Length < 1) { return; } var name = args[0]; var ply = onlinePlayers[player]; if (name == "list") { if (ply.Matches == null) { return; } if (args.Length == 1) { ShowMatchingPlayers(player); return; } int index; if (!int.TryParse(args[1], out index)) { return; } if (index > ply.Matches.Count) {} else ShowBank(player, ply.Matches[index - 1]); return; } var matches = FindPlayersByName(name); if (matches.Count < 1) { return; } if (matches.Count > 1) { ply.Matches = matches; ShowMatchingPlayers(player); return; } ShowBank(player, matches[0]); } [ConsoleCommand("bank")] void ccBank(ConsoleSystem.Arg arg) { cmdBank(arg.Connection.player as BasePlayer, arg.cmd.Name, arg.Args); } [ChatCommand("bank")] void cmdBank(BasePlayer player, string command, string[] args) { if(npconly) return; ShowBank(player, player); } #endregion #region Core methods ItemLimit GetItemLimit(Item item) { object limit; if(itemLimits.TryGetValue(item.info.shortname, out limit) && limit is JObject) { return ((JObject)limit).ToObject(); } return null; } //int GetValidAmount(Item item, ItemContainer container) { // ItemLimit limit; // int totalAmount = container.GetAmount(item.info.itemid, false) - item.amount; // if(itemLimits.TryGetValue(item.info.shortname, out limit)) { // if(!limit.enabled) { // return 0; // } // if(totalAmount < limit.minimum) { // return 0; // } // if(totalAmount == 0 && item.amount > limit.maximum) { // return limit.maximum; // } // if(totalAmount > limit.maximum) { // return totalAmount - limit.maximum; // } // if(totalAmount == limit.maximum) { // return 0; // } // } // return item.amount; //} bool CanPlayerBank(BasePlayer player) { if (!permission.UserHasPermission(player.UserIDString, "bank.use")) { SendReply(player, GetMsg("Denied: Permission", player)); return false; } if (!player.CanBuild()) { SendReply(player, GetMsg("Denied: Privilege", player)); return false; } if(radiationMax > 0 && player.radiationLevel > radiationMax) { SendReply(player, GetMsg("Denied: Irradiated", player)); return false; } if (player.IsSwimming()) { SendReply(player, GetMsg("Denied: Swimming", player)); return false; } if (!player.IsOnGround() || player.IsFlying) { SendReply(player, GetMsg("Denied: Falling", player)); return false; } if (player.IsWounded()) { SendReply(player, GetMsg("Denied: Wounded", player)); return false; } var canTrade = Interface.Call("CanBank", player); if (canTrade != null) { if (canTrade is string) { SendReply(player, Convert.ToString(canTrade)); } else { SendReply(player, GetMsg("Denied: Generic", player)); } return false; } return true; } void ShowBank(BasePlayer player, BaseEntity target) { if(!CanPlayerBank(player)) return; string playerID = player.userID.ToString(); if(cooldownMinutes > 0 && player.net.connection.authLevel < 1) { if(bankCooldowns.ContainsKey(playerID)) { DateTime startTime = bankCooldowns[playerID]; DateTime endTime = DateTime.Now; TimeSpan span = endTime.Subtract(startTime); if(span.TotalMinutes > 0 && span.TotalMinutes < Convert.ToDouble(cooldownMinutes)) { double timeleft = System.Math.Round(Convert.ToDouble(cooldownMinutes) - span.TotalMinutes, 2); if(timeleft < 1) { double timelefts = System.Math.Round((Convert.ToDouble(cooldownMinutes) * 60) - span.TotalSeconds); SendReply(player, string.Format(GetMsg("Cooldown: Seconds", player), timelefts.ToString())); } else { SendReply(player, string.Format(GetMsg("Cooldown: Minutes", player), System.Math.Round(timeleft).ToString())); } return; } else { bankCooldowns.Remove(playerID); } } } if(!LoadProfile(player.userID) && !banks.ContainsKey(player.userID)) { banks.Add(player.userID, new BankProfile(player)); } if(!bankCooldowns.ContainsKey(player.userID.ToString()) && player.net.connection.authLevel < 1) { bankCooldowns.Add(playerID, DateTime.Now); } var ply = onlinePlayers[player]; if (ply.View == null) { OpenBank(player, target); return; } CloseBank(player, ply.View); timer.In(1f, () => OpenBank(player, target)); } void HideBank(BasePlayer player) { player.EndLooting(); var ply = onlinePlayers[player]; if (ply.View == null) { return; } CloseBank(player, ply.View); } string GetBox(BasePlayer player) { foreach(KeyValuePair kvp in boxPrefabs) { if(permission.UserHasPermission(player.UserIDString, kvp.Key)) { return kvp.Value.ToString(); } } return defaultBoxPrefab; } int GetSlots(BasePlayer player) { foreach(KeyValuePair kvp in boxSlots) { if(permission.UserHasPermission(player.UserIDString, kvp.Key)) { return Convert.ToInt32(kvp.Value); } } return defaultSlots; } void OpenBank(BasePlayer player, BaseEntity targArg) { //Subscribe(nameof(CanNetworkTo)); Subscribe(nameof(OnEntityTakeDamage)); //Subscribe(nameof(OnItemAddedToContainer)); var pos = new Vector3(player.transform.position.x, player.transform.position.y-1, player.transform.position.z); string box = GetBox(player); int slots = GetSlots(player); var view = GameManager.server.CreateEntity(box,pos) as StorageContainer; if (!view) return; view.limitNetworking = true; view.transform.position = pos; player.EndLooting(); if(targArg is BasePlayer) { BasePlayer target = targArg as BasePlayer; BankProfile profile = banks[target.userID]; ItemContainer bank = profile.GetContainer(target, slots); if(!containers.ContainsKey(bank)) { containers.Add(bank, player.userID); } view.enableSaving = false; view.Spawn(); view.inventory = bank; profile.open = true; onlinePlayers[player].View = view; onlinePlayers[player].Target = target; timer.Once(0.1f, delegate() { view.PlayerOpenLoot(player); }); } } void CloseBank(BasePlayer player, StorageContainer view) { if (!onlinePlayers.ContainsKey(player)) return; if (onlinePlayers[player].View == null) return; if(!banks.ContainsKey(player.userID)) { return; } BankProfile profile = banks[player.userID]; InvalidateBank(player, profile, view); profile.items.Clear(); foreach(Item item in view.inventory.itemList) { profile.Add(item); } SaveProfile(player.userID, profile); foreach(Item item in view.inventory.itemList.ToArray()) { if(item.position != -1) { item.RemoveFromContainer(); item.Remove(0f); } } profile.open = false; if(containers.ContainsKey(view.inventory)) { containers.Remove(view.inventory); } player.inventory.loot.containers = new List(); view.inventory = new ItemContainer(); if (player.inventory.loot.IsLooting()) { player.SendConsoleCommand("inventory.endloot", null); } onlinePlayers[player].View = null; onlinePlayers[player].Target = null; view.Kill(BaseNetworkable.DestroyMode.None); //view.KillMessage(); if (onlinePlayers.Values.Count(p => p.View != null) <= 0) { //Unsubscribe(nameof(CanNetworkTo)); Unsubscribe(nameof(OnEntityTakeDamage)); } } void InvalidateBank(BasePlayer player, BankProfile profile, StorageContainer view) { bool returned = false; foreach(var item in view.inventory.itemList.ToArray()) { ItemLimit limit = GetItemLimit(item); if(limit != null) { if(item.amount < limit.minimum) { returned = true; if(!item.MoveToContainer(player.inventory.containerMain)) { item.Drop(player.eyes.HeadForward() * 2, default(Vector3)); } continue; } if(item.amount > limit.maximum) { returned = true; Item invalidItem = item.SplitItem(item.amount - limit.maximum); if(!invalidItem.MoveToContainer(player.inventory.containerMain)) { invalidItem.Drop(player.eyes.HeadForward() * 2, default(Vector3)); } continue; } } } if(returned) { SendReply(player, GetMsg("Limit: Return", player)); } } #endregion #region HelpText private void SendHelpText(BasePlayer player) { var sb = new StringBuilder() .Append("Bank by http://rustservers.io\n") .Append(" ").Append("/bank - Open your bank box").Append("\n"); if(player.IsAdmin) { sb.Append(" ").Append("/viewbank \"Player Name\" - View any players bank").Append("\n"); } player.ChatMessage(sb.ToString()); } #endregion #region Helper methods string GetMsg(string key, BasePlayer player = null) { return lang.GetMessage(key, this, player == null ? null : player.UserIDString); } List FindPlayersByName(string name) { List matches = new List(); foreach (var ply in BasePlayer.activePlayerList) { if (ply.displayName.ToLower().Contains(name.ToLower())) matches.Add(ply); } foreach (var ply in BasePlayer.sleepingPlayerList) { if (ply.displayName.ToLower().Contains(name.ToLower())) matches.Add(ply); } return matches; } void ShowMatchingPlayers(BasePlayer player) { int i = 0; foreach (var ply in onlinePlayers[player].Matches) { i++; player.ChatMessage($"{i} - {ply.displayName} ({ply.userID})"); } } bool IsAllowed(BasePlayer player) { if(player.IsAdmin) return true; SendReply(player, GetMsg("Denied: Permission", player)); return false; } private T GetConfig(string name, string name2, T defaultValue) { if (Config[name, name2] == null) { return defaultValue; } return (T)Convert.ChangeType(Config[name, name2], typeof(T)); } private T GetConfig(string name, T defaultValue) { if (Config[name] == null) { return defaultValue; } return (T)Convert.ChangeType(Config[name], typeof(T)); } #endregion } }