Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of PersonalProgression v1.0.0
plugins/PersonalProgression.dll
Decompiled 2 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = "")] [assembly: AssemblyCompany("PersonalProgression")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("PersonalProgression")] [assembly: AssemblyTitle("PersonalProgression")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace PersonalProgression { public static class API { public static event Action<long, string> OnKeyGranted; public static bool HasKey(Player player, string key) { if ((Object)(object)player == (Object)null || string.IsNullOrEmpty(key)) { return false; } if ((Object)(object)player == (Object)(object)Player.m_localPlayer) { return GameAccess.HasUniqueKey(player, key); } return Net.IsServer && Net.ServerHasKey(player.GetPlayerID(), key); } public static bool HasKey(long playerId, string key) { return Net.IsServer && Net.ServerHasKey(playerId, key); } internal static void RaiseKeyGranted(long playerId, string key) { try { API.OnKeyGranted?.Invoke(playerId, key); } catch (Exception arg) { Plugin.Log.LogError((object)$"OnKeyGranted handler threw: {arg}"); } } } internal static class GameAccess { private static readonly MethodInfo AddUniqueKeyM = AccessTools.Method(typeof(Player), "AddUniqueKey", new Type[1] { typeof(string) }, (Type[])null); private static readonly MethodInfo RemoveUniqueKeyM = AccessTools.Method(typeof(Player), "RemoveUniqueKey", new Type[1] { typeof(string) }, (Type[])null); private static readonly FieldInfo UniquesF = AccessTools.Field(typeof(Player), "m_uniques"); private static readonly FieldInfo GuardianPowerF = AccessTools.Field(typeof(Player), "m_guardianPower"); private static readonly MethodInfo SetGuardianPowerM = AccessTools.Method(typeof(Player), "SetGuardianPower", new Type[1] { typeof(string) }, (Type[])null); private static readonly FieldInfo ItemStandPowerF = AccessTools.Field(typeof(ItemStand), "m_guardianPower"); private static readonly FieldInfo NviewF = AccessTools.Field(typeof(Character), "m_nview"); private static readonly FieldInfo DefeatKeyF = AccessTools.Field(typeof(Character), "m_defeatSetGlobalKey"); private static readonly MethodInfo GetServerPeerIdM = AccessTools.Method(typeof(ZRoutedRpc), "GetServerPeerID", (Type[])null, (Type[])null); private static readonly FieldInfo RpcIdF = AccessTools.Field(typeof(ZRoutedRpc), "m_id"); private static readonly MethodInfo GetServerPeerM = AccessTools.Method(typeof(ZNet), "GetServerPeer", (Type[])null, (Type[])null); private static readonly Type LocalizationT = AccessTools.TypeByName("Localization"); private static readonly PropertyInfo LocInstanceP = ((LocalizationT != null) ? AccessTools.Property(LocalizationT, "instance") : null); private static readonly FieldInfo LocInstanceF = ((LocalizationT != null) ? AccessTools.Field(LocalizationT, "m_instance") : null); private static readonly MethodInfo LocalizeM = ((LocalizationT != null) ? AccessTools.Method(LocalizationT, "Localize", new Type[1] { typeof(string) }, (Type[])null) : null); private static readonly FieldInfo WorldF = AccessTools.Field(typeof(ZNet), "m_world"); private static readonly FieldInfo AdminListF = AccessTools.Field(typeof(ZNet), "m_adminList"); internal static long ServerPeerId() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null) { return 0L; } if (GetServerPeerIdM != null) { return (long)GetServerPeerIdM.Invoke(instance, null); } if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && RpcIdF != null) { return (long)RpcIdF.GetValue(instance); } object? obj = GetServerPeerM?.Invoke(ZNet.instance, null); return ((ZNetPeer)(((obj is ZNetPeer) ? obj : null)?)).m_uid ?? 0; } internal static string Localize(string text) { try { object obj = LocInstanceP?.GetValue(null, null) ?? LocInstanceF?.GetValue(null); if (obj != null && LocalizeM != null) { return (string)LocalizeM.Invoke(obj, new object[1] { text }); } } catch { } return text; } internal static void SelfCheck() { Check(UniquesF, "Player.m_uniques"); Check(GuardianPowerF, "Player.m_guardianPower"); Check(SetGuardianPowerM, "Player.SetGuardianPower"); Check(ItemStandPowerF, "ItemStand.m_guardianPower"); Check(NviewF, "Character.m_nview"); Check(DefeatKeyF, "Character.m_defeatSetGlobalKey"); static void Check(object member, string name) { if (member == null) { Plugin.Log.LogWarning((object)("Could not find " + name + "; a game update may have renamed it.")); } } } internal static HashSet<string> Uniques(Player p) { return UniquesF?.GetValue(p) as HashSet<string>; } internal static bool HasUniqueKey(Player p, string key) { HashSet<string> hashSet = Uniques(p); if (hashSet == null) { return false; } foreach (string item in hashSet) { if (string.Equals(item, key, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } internal static void AddUniqueKey(Player p, string key) { if (!HasUniqueKey(p, key)) { if (AddUniqueKeyM != null) { AddUniqueKeyM.Invoke(p, new object[1] { key }); } else { Uniques(p)?.Add(key); } } } internal static void RemoveUniqueKey(Player p, string key) { if (RemoveUniqueKeyM != null) { RemoveUniqueKeyM.Invoke(p, new object[1] { key }); } Uniques(p)?.RemoveWhere((string k) => string.Equals(k, key, StringComparison.OrdinalIgnoreCase)); } internal static string GetGuardianPower(Player p) { return (GuardianPowerF?.GetValue(p) as string) ?? ""; } internal static void ClearGuardianPower(Player p) { SetGuardianPowerM?.Invoke(p, new object[1] { "" }); } internal static string ItemStandPowerName(ItemStand stand) { object? obj = ItemStandPowerF?.GetValue(stand); StatusEffect val = (StatusEffect)((obj is StatusEffect) ? obj : null); return ((Object)(object)val != (Object)null) ? ((Object)val).name : null; } internal static ZNetView Nview(Character c) { object? obj = NviewF?.GetValue(c); return (ZNetView)((obj is ZNetView) ? obj : null); } internal static string DefeatKey(Character c) { return DefeatKeyF?.GetValue(c) as string; } internal static string WorldName() { object? obj = WorldF?.GetValue(null); return ((World)(((obj is World) ? obj : null)?)).m_name ?? "default"; } internal static bool IsAdmin(string hostName) { if (string.IsNullOrEmpty(hostName) || (Object)(object)ZNet.instance == (Object)null) { return false; } object obj = AdminListF?.GetValue(ZNet.instance); if (obj == null) { return false; } MethodInfo methodInfo = AccessTools.Method(obj.GetType(), "Contains", new Type[1] { typeof(string) }, (Type[])null); return methodInfo != null && (bool)methodInfo.Invoke(obj, new object[1] { hostName }); } } internal static class ItemGate { internal enum Punishment { Burn, Block, BlockAndBurn } private sealed class TierInfo { public string Key; public string Via; } internal static readonly string[] TierKeys = new string[7] { "defeated_eikthyr", "defeated_gdking", "defeated_bonemass", "defeated_dragon", "defeated_goblinking", "defeated_queen", "defeated_fader" }; private static Dictionary<string, int> _materialTier; private static Dictionary<string, string> _overrides; private static readonly Dictionary<string, TierInfo> Cache = new Dictionary<string, TierInfo>(StringComparer.OrdinalIgnoreCase); private static float _nextBurn; private static float _nextMessage; private static bool _dumped; internal static void ResetCache() { _materialTier = null; _overrides = null; Cache.Clear(); } private static void BuildTables() { _materialTier = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); for (int i = 0; i < Plugin.TierMaterials.Length; i++) { foreach (string item in Split(Plugin.TierMaterials[i].Value)) { _materialTier[item] = i; } } _overrides = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); foreach (string item2 in Split(Plugin.ItemOverrides.Value)) { string[] array = item2.Split(new char[1] { ':' }); if (array.Length == 2) { _overrides[array[0].Trim()] = array[1].Trim(); } } } private static IEnumerable<string> Split(string s) { return from x in s.Split(new char[1] { ',' }) select x.Trim() into x where x.Length > 0 select x; } private static string PrefabName(ItemData item) { return ((Object)(object)item?.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : (item?.m_shared?.m_name ?? ""); } internal static string RequiredKey(ItemData item, out string via) { via = null; if (item == null) { return null; } if (_materialTier == null) { BuildTables(); } string text = PrefabName(item); if (Cache.TryGetValue(text, out var value)) { via = value.Via; return value.Key; } string text2 = null; if (_overrides.TryGetValue(text, out var value2)) { text2 = ((value2.Length == 0) ? null : value2); via = "override"; } else { ObjectDB instance = ObjectDB.instance; int num = TierOf(text, (instance != null) ? instance.GetRecipe(item) : null, 0, out via); if (num >= 0) { text2 = TierKeys[num]; } } Cache[text] = new TierInfo { Key = text2, Via = via }; return text2; } private static int TierOf(string prefab, Recipe recipe, int depth, out string via) { via = null; if (_materialTier.TryGetValue(prefab, out var value) && depth > 0) { via = prefab; return value; } if ((Object)(object)recipe == (Object)null || recipe.m_resources == null || depth > 3) { return -1; } int num = -1; Requirement[] resources = recipe.m_resources; foreach (Requirement val in resources) { if (!((Object)(object)val?.m_resItem == (Object)null)) { string name = ((Object)((Component)val.m_resItem).gameObject).name; string via2; if (_materialTier.TryGetValue(name, out var value2)) { via2 = name; } else { ObjectDB instance = ObjectDB.instance; value2 = TierOf(name, (instance != null) ? instance.GetRecipe(val.m_resItem.m_itemData) : null, depth + 1, out via2); } if (value2 > num) { num = value2; via = via2; } } } return num; } private static bool IsGatedType(ItemData item) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected I4, but got Unknown ItemType itemType = item.m_shared.m_itemType; ItemType val = itemType; switch (val - 3) { case 0: case 1: case 2: case 3: case 4: case 8: case 11: case 12: case 14: case 15: case 16: case 19: return true; default: return false; } } internal static string MissingKey(Player p, ItemData item) { if (!Plugin.ItemGatingEnabled.Value || item == null || !IsGatedType(item)) { return null; } if (BossPowers.Bypass()) { return null; } string via; string text = RequiredKey(item, out via); return (text == null || GameAccess.HasUniqueKey(p, text)) ? null : text; } internal static void ShowMessage(Player p, string missingKey, ItemData item) { ConfigEntry<string> value; string s = (Plugin.BossMessages.TryGetValue(missingKey, out value) ? value.Value : ""); string[] array = SplitLines(s); if (array.Length == 0) { array = SplitLines(Plugin.GateMessages.Value); } string text = ((array.Length != 0) ? array[Random.Range(0, array.Length)] : "You are not yet ready to wield this gear."); string text2 = item?.m_shared?.m_name ?? "this gear"; text2 = GameAccess.Localize(text2); text = text.Replace("{item}", text2).Replace("{boss}", Plugin.PrettyKey(missingKey)); ((Character)p).Message((MessageType)2, text, 0, (Sprite)null, false); ((Character)p).Message((MessageType)1, "Requires: defeated " + Plugin.PrettyKey(missingKey), 0, (Sprite)null, false); _nextMessage = Time.time + Plugin.MessageInterval.Value; } private static string[] SplitLines(string s) { return (from x in (s ?? "").Split(new char[1] { '|' }) select x.Trim() into x where x.Length > 0 select x).ToArray(); } internal static void Tick() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsDead() || !Plugin.ItemGatingEnabled.Value) { return; } if (!_dumped && (Object)(object)ObjectDB.instance != (Object)null && ObjectDB.instance.m_items.Count > 0) { _dumped = true; DumpTiers(); } Punishment value = Plugin.GatePunishment.Value; string text = null; ItemData item = null; foreach (ItemData equippedItem in ((Humanoid)localPlayer).GetInventory().GetEquippedItems()) { string text2 = MissingKey(localPlayer, equippedItem); if (text2 != null) { if (value != Punishment.Burn) { ((Humanoid)localPlayer).UnequipItem(equippedItem, true); } if (text == null || Array.IndexOf(TierKeys, text2) > Array.IndexOf(TierKeys, text)) { text = text2; item = equippedItem; } } } if (text != null && value != Punishment.Block) { if (Time.time >= _nextBurn) { BurnOnce(localPlayer); } if (Time.time >= _nextMessage) { ShowMessage(localPlayer, text, item); } } } internal static void BurnOnce(Player p) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) _nextBurn = Time.time + Plugin.BurnInterval.Value; HitData val = new HitData(); val.m_damage.m_fire = Plugin.BurnDamage.Value; val.m_point = ((Component)p).transform.position + Vector3.up; val.m_dir = Vector3.up; ((Character)p).Damage(val); } private static void DumpTiers() { try { List<string> list = new List<string> { "# item prefab | required key | decided by", "# Generated each launch. Fix wrong ones with [ItemGating] ItemOverrides." }; foreach (GameObject item in ObjectDB.instance.m_items) { ItemDrop val = (((Object)(object)item != (Object)null) ? item.GetComponent<ItemDrop>() : null); if ((Object)(object)val == (Object)null) { continue; } ItemData itemData = val.m_itemData; if (itemData?.m_shared != null && IsGatedType(itemData)) { if ((Object)(object)itemData.m_dropPrefab == (Object)null) { itemData.m_dropPrefab = item; } string via; string text = RequiredKey(itemData, out via); list.Add(((Object)item).name + " | " + (text ?? "-") + " | " + (via ?? "-")); } } string text2 = Path.Combine(Paths.ConfigPath, "PersonalProgression", "items.generated.txt"); Directory.CreateDirectory(Path.GetDirectoryName(text2)); File.WriteAllLines(text2, list.ToArray()); Plugin.Log.LogInfo((object)$"Wrote item tiers for {list.Count - 2} items to {text2}"); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not write items.generated.txt: " + ex.Message)); } } } internal sealed class CharacterRef { public long Id; public string Name; public CharacterRef(long id, string name) { Id = id; Name = name; } } internal static class KeyLedger { private class Entry { public string Name = ""; public HashSet<string> Keys = new HashSet<string>(StringComparer.OrdinalIgnoreCase); } private static readonly Dictionary<long, Entry> Entries = new Dictionary<long, Entry>(); private static string _loadedWorld; private static DateTime _createdUtc; internal static DateTime CreatedUtc { get { EnsureLoaded(); return _createdUtc; } } private static string FilePath(string world) { return Path.Combine(Paths.ConfigPath, "PersonalProgression", Sanitize(world) + ".keys.txt"); } private static string Sanitize(string s) { return string.Concat(s.Select((char c) => Path.GetInvalidFileNameChars().Contains(c) ? '_' : c)); } private static void EnsureLoaded() { string text = GameAccess.WorldName(); if (_loadedWorld == text) { return; } Entries.Clear(); _loadedWorld = text; _createdUtc = DateTime.UtcNow; string path = FilePath(text); if (!File.Exists(path)) { Save(); return; } string[] array = File.ReadAllLines(path); foreach (string text2 in array) { if (text2.StartsWith("#created=") && long.TryParse(text2.Substring(9), out var result)) { _createdUtc = new DateTime(result, DateTimeKind.Utc); continue; } string[] array2 = text2.Split(new char[1] { '|' }); if (array2.Length < 3 || !long.TryParse(array2[0], out var result2)) { continue; } Entry entry = new Entry { Name = array2[1] }; foreach (string item in from k in array2[2].Split(new char[1] { ',' }) where k.Length > 0 select k) { entry.Keys.Add(item); } Entries[result2] = entry; } Plugin.Log.LogInfo((object)$"Loaded personal keys for {Entries.Count} characters ({text})"); } private static void Save() { string path = FilePath(_loadedWorld); Directory.CreateDirectory(Path.GetDirectoryName(path)); List<string> list = new List<string> { "#created=" + _createdUtc.Ticks }; list.AddRange(Entries.Select((KeyValuePair<long, Entry> kv) => string.Format("{0}|{1}|{2}", kv.Key, kv.Value.Name.Replace("|", ""), string.Join(",", kv.Value.Keys)))); File.WriteAllLines(path, list.ToArray()); } internal static bool Touch(long playerId, string name) { EnsureLoaded(); Entry value; bool flag = !Entries.TryGetValue(playerId, out value); if (flag) { value = new Entry(); Entries[playerId] = value; } if (value.Name != name) { value.Name = name; Save(); } else if (flag) { Save(); } return flag; } internal static List<string> GetKeys(long playerId) { EnsureLoaded(); Entry value; return Entries.TryGetValue(playerId, out value) ? value.Keys.ToList() : new List<string>(); } internal static bool HasKey(long playerId, string key) { EnsureLoaded(); Entry value; return Entries.TryGetValue(playerId, out value) && value.Keys.Contains(key); } internal static bool Add(long playerId, string key) { EnsureLoaded(); if (!Entries.TryGetValue(playerId, out var value)) { value = new Entry(); Entries[playerId] = value; } if (!value.Keys.Add(key)) { return false; } Save(); return true; } internal static bool Remove(long playerId, string key) { EnsureLoaded(); if (!Entries.TryGetValue(playerId, out var value) || !value.Keys.Remove(key)) { return false; } Save(); return true; } internal static void Reset(long playerId) { EnsureLoaded(); if (Entries.TryGetValue(playerId, out var value)) { value.Keys.Clear(); Save(); } } internal static List<CharacterRef> FindByName(string name) { EnsureLoaded(); return (from kv in Entries where string.Equals(kv.Value.Name, name, StringComparison.OrdinalIgnoreCase) select new CharacterRef(kv.Key, kv.Value.Name)).ToList(); } } internal static class Net { private const string RpcHello = "PP_Hello"; private const string RpcSync = "PP_Sync"; private const string RpcGrant = "PP_Grant"; private const string RpcDefeated = "PP_Defeated"; private const string RpcAdmin = "PP_Admin"; private const string RpcReply = "PP_Reply"; private static ZRoutedRpc _registeredOn; private static Player _helloSentFor; private static readonly Dictionary<long, CharacterRef> Online = new Dictionary<long, CharacterRef>(); private static string _lastDefeatKey; private static float _lastDefeatTime; private static Vector3 _lastDefeatPos; internal static bool IsServer => (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer(); internal static void ClientTick() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null) { _registeredOn = null; _helloSentFor = null; Online.Clear(); return; } if (_registeredOn != instance) { instance.Register<long, string>("PP_Hello", (Action<long, long, string>)OnHello); instance.Register<ZPackage>("PP_Sync", (Action<long, ZPackage>)OnSync); instance.Register<string>("PP_Grant", (Action<long, string>)OnGrant); instance.Register<string, Vector3>("PP_Defeated", (Action<long, string, Vector3>)OnDefeated); instance.Register<string, string, string>("PP_Admin", (Action<long, string, string, string>)OnAdmin); instance.Register<string>("PP_Reply", (Action<long, string>)OnReply); _registeredOn = instance; GameAccess.SelfCheck(); } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && (Object)(object)_helloSentFor != (Object)(object)localPlayer) { _helloSentFor = localPlayer; instance.InvokeRoutedRPC(GameAccess.ServerPeerId(), "PP_Hello", new object[2] { localPlayer.GetPlayerID(), localPlayer.GetPlayerName() }); } } internal static void ReportDefeat(string key, Vector3 pos) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(GameAccess.ServerPeerId(), "PP_Defeated", new object[2] { key, pos }); } } internal static void SendAdmin(string sub, string name, string key) { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null) { Console instance2 = Console.instance; if (instance2 != null) { instance2.Print("Not connected."); } } else { instance.InvokeRoutedRPC(GameAccess.ServerPeerId(), "PP_Admin", new object[3] { sub, name ?? "", key ?? "" }); } } private static void OnSync(long sender, ZPackage pkg) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } int num = pkg.ReadInt(); HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); for (int i = 0; i < num; i++) { hashSet.Add(pkg.ReadString()); } foreach (string item in hashSet) { GameAccess.AddUniqueKey(localPlayer, item); } if (Plugin.ServerAuthoritative.Value) { foreach (string trackedKey in Plugin.GetTrackedKeys()) { if (!hashSet.Contains(trackedKey) && GameAccess.HasUniqueKey(localPlayer, trackedKey)) { GameAccess.RemoveUniqueKey(localPlayer, trackedKey); } } } BossPowers.StripIfUnearned(localPlayer); Plugin.Log.LogInfo((object)$"Synced {hashSet.Count} personal keys"); } private static void OnGrant(long sender, string key) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { GameAccess.AddUniqueKey(localPlayer, key); ((Character)localPlayer).Message((MessageType)2, "Progress earned: " + Plugin.PrettyKey(key), 0, (Sprite)null, false); API.RaiseKeyGranted(localPlayer.GetPlayerID(), key); } } private static void OnReply(long sender, string text) { Console instance = Console.instance; if (instance != null) { instance.Print(text); } } private static void OnHello(long sender, long playerId, string name) { if (!IsServer) { return; } Online[sender] = new CharacterRef(playerId, name); bool flag = KeyLedger.Touch(playerId, name); bool flag2 = DateTime.UtcNow - KeyLedger.CreatedUtc < TimeSpan.FromDays(Plugin.GrandfatherDays.Value); if (flag && Plugin.GrandfatherExistingPlayers.Value && flag2 && (Object)(object)ZoneSystem.instance != (Object)null) { foreach (string trackedKey in Plugin.GetTrackedKeys()) { if (ZoneSystem.instance.GetGlobalKey(trackedKey)) { KeyLedger.Add(playerId, trackedKey); } } Plugin.Log.LogInfo((object)("Grandfathered " + name + " with the world's current keys")); } SendSync(sender, playerId); } private static void SendSync(long uid, long playerId) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown List<string> keys = KeyLedger.GetKeys(playerId); ZPackage val = new ZPackage(); val.Write(keys.Count); foreach (string item in keys) { val.Write(item); } ZRoutedRpc.instance.InvokeRoutedRPC(uid, "PP_Sync", new object[1] { val }); } private static void OnDefeated(long sender, string key, Vector3 pos) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) if (!IsServer || string.IsNullOrEmpty(key) || !Plugin.GetTrackedKeys().Contains(key) || (key == _lastDefeatKey && Time.time - _lastDefeatTime < 5f && Vector3.Distance(pos, _lastDefeatPos) < 30f)) { return; } _lastDefeatKey = key; _lastDefeatTime = Time.time; _lastDefeatPos = pos; float value = Plugin.KillCreditRadius.Value; List<string> list = new List<string>(); foreach (KeyValuePair<long, CharacterRef> item in Online.ToList()) { if (!TryGetPosition(item.Key, out var pos2)) { Online.Remove(item.Key); } else if (!(Vector3.Distance(pos2, pos) > value)) { long id = item.Value.Id; string name = item.Value.Name; if (KeyLedger.Add(id, key)) { ZRoutedRpc.instance.InvokeRoutedRPC(item.Key, "PP_Grant", new object[1] { key }); list.Add(name); } } } Plugin.Log.LogInfo((object)((list.Count > 0) ? (key + ": granted to " + string.Join(", ", list)) : (key + ": nobody new in range"))); } private static bool TryGetPosition(long uid, out Vector3 pos) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) ZNetPeer peer = ZNet.instance.GetPeer(uid); if (peer != null) { pos = peer.m_refPos; return true; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && Online.TryGetValue(uid, out var value) && value.Id == localPlayer.GetPlayerID()) { pos = ((Component)localPlayer).transform.position; return true; } pos = Vector3.zero; return false; } internal static bool SenderIsAdmin(long sender) { ZNetPeer peer = ZNet.instance.GetPeer(sender); if (peer == null) { return true; } return GameAccess.IsAdmin(peer.m_socket.GetHostName()); } private static void Reply(long uid, string text) { ZRoutedRpc.instance.InvokeRoutedRPC(uid, "PP_Reply", new object[1] { text }); } private static void OnAdmin(long sender, string sub, string name, string key) { if (!IsServer) { return; } if (!SenderIsAdmin(sender)) { Reply(sender, "pp: admins only."); return; } List<CharacterRef> list = KeyLedger.FindByName(name); if (list.Count == 0) { Reply(sender, "pp: no character named '" + name + "' on this world."); return; } if (list.Count > 1) { Reply(sender, $"pp: {list.Count} characters named '{name}': " + string.Join(", ", list.Select((CharacterRef m) => m.Id.ToString()))); return; } long id = list[0].Id; string name2 = list[0].Name; switch (sub) { case "keys": { List<string> keys = KeyLedger.GetKeys(id); Reply(sender, (keys.Count == 0) ? (name2 + ": no keys") : (name2 + ": " + string.Join(", ", keys))); return; } case "grant": if (string.IsNullOrEmpty(key)) { Reply(sender, "pp grant <name> <key>"); return; } KeyLedger.Add(id, key); Reply(sender, "Granted " + key + " to " + name2); break; case "revoke": if (string.IsNullOrEmpty(key)) { Reply(sender, "pp revoke <name> <key>"); return; } Reply(sender, KeyLedger.Remove(id, key) ? ("Revoked " + key + " from " + name2) : (name2 + " didn't have " + key)); break; case "reset": KeyLedger.Reset(id); Reply(sender, "Reset all keys for " + name2); break; default: Reply(sender, "pp: unknown command"); return; } foreach (KeyValuePair<long, CharacterRef> item in Online) { if (item.Value.Id == id) { SendSync(item.Key, id); } } } internal static bool ServerHasKey(long playerId, string key) { return KeyLedger.HasKey(playerId, key); } } [HarmonyPatch(typeof(Character), "OnDeath")] internal static class KillCreditPatch { private static void Prefix(Character __instance) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) string text = GameAccess.DefeatKey(__instance); if (!string.IsNullOrEmpty(text)) { ZNetView val = GameAccess.Nview(__instance); if (!((Object)(object)val == (Object)null) && val.IsValid() && val.IsOwner()) { Net.ReportDefeat(text, ((Component)__instance).transform.position); } } } } internal static class BossPowers { internal static string RequiredKey(string powerName) { if (string.IsNullOrEmpty(powerName)) { return null; } string value; return Plugin.GetPowerKeyMap().TryGetValue(powerName, out value) ? value : null; } internal static bool Bypass() { return Plugin.AdminBypass.Value && SynchronizationManager.Instance.PlayerIsAdmin; } internal static void StripIfUnearned(Player p) { if (Plugin.BossPowersEnabled.Value && Plugin.StripUnearnedPowerOnLogin.Value && !Bypass()) { string guardianPower = GameAccess.GetGuardianPower(p); string text = RequiredKey(guardianPower); if (text != null && !GameAccess.HasUniqueKey(p, text)) { GameAccess.ClearGuardianPower(p); ((Character)p).Message((MessageType)2, string.Format(Plugin.PowerDeniedMessage.Value, Plugin.PrettyKey(text)), 0, (Sprite)null, false); Plugin.Log.LogInfo((object)("Removed unearned power " + guardianPower)); } } } } [HarmonyPatch(typeof(ItemStand), "Interact")] internal static class BossStoneInteractPatch { private static bool Prefix(ItemStand __instance, Humanoid user, bool hold, ref bool __result) { if (hold || !Plugin.BossPowersEnabled.Value) { return true; } Player val = (Player)(object)((user is Player) ? user : null); if (val == null || (Object)(object)val != (Object)(object)Player.m_localPlayer) { return true; } string text = BossPowers.RequiredKey(GameAccess.ItemStandPowerName(__instance)); if (text == null || GameAccess.HasUniqueKey(val, text) || BossPowers.Bypass()) { return true; } ((Character)val).Message((MessageType)2, string.Format(Plugin.PowerDeniedMessage.Value, Plugin.PrettyKey(text)), 0, (Sprite)null, false); __result = false; return false; } } [HarmonyPatch(typeof(Humanoid), "EquipItem")] internal static class EquipGatePatch { private static bool Prefix(Humanoid __instance, ItemData item, ref bool __result) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val == null || (Object)(object)val != (Object)(object)Player.m_localPlayer) { return true; } string text = ItemGate.MissingKey(val, item); if (text == null) { return true; } ItemGate.ShowMessage(val, text, item); switch (Plugin.GatePunishment.Value) { case ItemGate.Punishment.Burn: ItemGate.BurnOnce(val); return true; case ItemGate.Punishment.BlockAndBurn: ItemGate.BurnOnce(val); __result = false; return false; default: __result = false; return false; } } } [BepInPlugin("OblitusEs.PersonalProgression", "PersonalProgression", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string Guid = "OblitusEs.PersonalProgression"; public const string Name = "PersonalProgression"; public const string Version = "1.0.0"; internal static Plugin Instance; internal static ManualLogSource Log; internal static ConfigEntry<float> KillCreditRadius; internal static ConfigEntry<string> TrackedKeys; internal static ConfigEntry<bool> ServerAuthoritative; internal static ConfigEntry<bool> GrandfatherExistingPlayers; internal static ConfigEntry<float> GrandfatherDays; internal static ConfigEntry<bool> AdminBypass; internal static ConfigEntry<bool> BossPowersEnabled; internal static ConfigEntry<string> PowerKeyMap; internal static ConfigEntry<bool> StripUnearnedPowerOnLogin; internal static ConfigEntry<string> PowerDeniedMessage; internal static ConfigEntry<bool> ItemGatingEnabled; internal static ConfigEntry<ItemGate.Punishment> GatePunishment; internal static ConfigEntry<float> BurnDamage; internal static ConfigEntry<float> BurnInterval; internal static ConfigEntry<float> MessageInterval; internal static ConfigEntry<string> GateMessages; internal static readonly Dictionary<string, ConfigEntry<string>> BossMessages = new Dictionary<string, ConfigEntry<string>>(StringComparer.OrdinalIgnoreCase); internal static ConfigEntry<string> ItemOverrides; internal static ConfigEntry<string>[] TierMaterials; private Harmony _harmony; private static readonly Dictionary<string, string> BossNames = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { ["defeated_eikthyr"] = "Eikthyr", ["defeated_gdking"] = "The Elder", ["defeated_bonemass"] = "Bonemass", ["defeated_dragon"] = "Moder", ["defeated_goblinking"] = "Yagluth", ["defeated_queen"] = "The Queen", ["defeated_fader"] = "Fader", ["KilledTroll"] = "a Troll", ["killed_surtling"] = "a Surtling", ["KilledBat"] = "a Bat" }; private void Awake() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Expected O, but got Unknown //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Expected O, but got Unknown //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Expected O, but got Unknown //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Expected O, but got Unknown //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Expected O, but got Unknown //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Expected O, but got Unknown //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Expected O, but got Unknown //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Expected O, but got Unknown //IL_0251: Unknown result type (might be due to invalid IL or missing references) //IL_025b: Expected O, but got Unknown //IL_0281: Unknown result type (might be due to invalid IL or missing references) //IL_028b: Expected O, but got Unknown //IL_02c3: Unknown result type (might be due to invalid IL or missing references) //IL_02cd: Expected O, but got Unknown //IL_0305: Unknown result type (might be due to invalid IL or missing references) //IL_030f: Expected O, but got Unknown //IL_0347: Unknown result type (might be due to invalid IL or missing references) //IL_0351: Expected O, but got Unknown //IL_037b: Unknown result type (might be due to invalid IL or missing references) //IL_0385: Expected O, but got Unknown //IL_046f: Unknown result type (might be due to invalid IL or missing references) //IL_0479: Expected O, but got Unknown //IL_04be: Unknown result type (might be due to invalid IL or missing references) //IL_04c8: Expected O, but got Unknown //IL_0573: Unknown result type (might be due to invalid IL or missing references) //IL_057d: Expected O, but got Unknown //IL_05f6: Unknown result type (might be due to invalid IL or missing references) //IL_0600: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; ConfigurationManagerAttributes val = new ConfigurationManagerAttributes { IsAdminOnly = true }; KillCreditRadius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "KillCreditRadius", 50f, new ConfigDescription("Players within this many metres of a boss or key creature when it dies earn its key.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 500f), new object[1] { val })); TrackedKeys = ((BaseUnityPlugin)this).Config.Bind<string>("General", "TrackedKeys", "defeated_eikthyr,defeated_gdking,defeated_bonemass,defeated_dragon,defeated_goblinking,defeated_queen,defeated_fader,KilledTroll,killed_surtling,KilledBat", new ConfigDescription("Comma-separated keys this mod gives out per player. Kills that set any other key are ignored. Add modded boss keys here.", (AcceptableValueBase)null, new object[1] { val })); ServerAuthoritative = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ServerAuthoritative", true, new ConfigDescription("The server's key list is the truth. Tracked keys a character has that the server doesn't are removed on join (stops edited character files).", (AcceptableValueBase)null, new object[1] { val })); GrandfatherExistingPlayers = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "GrandfatherExistingPlayers", true, new ConfigDescription("Characters that join within GrandfatherDays of the mod's first run on this world get every tracked key the world already has. Characters first seen later start from nothing.", (AcceptableValueBase)null, new object[1] { val })); GrandfatherDays = ((BaseUnityPlugin)this).Config.Bind<float>("General", "GrandfatherDays", 7f, new ConfigDescription("How long after install the grandfather window stays open. Set 0 to close it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 365f), new object[1] { val })); AdminBypass = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "AdminBypass", false, new ConfigDescription("Server admins ignore every gate (for testing).", (AcceptableValueBase)null, new object[1] { val })); BossPowersEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("BossPowers", "Enabled", true, new ConfigDescription("Players can only take a Forsaken power at the start temple if they killed that boss.", (AcceptableValueBase)null, new object[1] { val })); PowerKeyMap = ((BaseUnityPlugin)this).Config.Bind<string>("BossPowers", "PowerKeyMap", "GP_Eikthyr:defeated_eikthyr,GP_TheElder:defeated_gdking,GP_Bonemass:defeated_bonemass,GP_Moder:defeated_dragon,GP_Yagluth:defeated_goblinking,GP_Queen:defeated_queen,GP_Fader:defeated_fader", new ConfigDescription("power:key pairs. Powers not listed are never gated.", (AcceptableValueBase)null, new object[1] { val })); StripUnearnedPowerOnLogin = ((BaseUnityPlugin)this).Config.Bind<bool>("BossPowers", "StripUnearnedPowerOnLogin", true, new ConfigDescription("If a character holds a power it hasn't earned (e.g. after an admin reset) it is removed on join.", (AcceptableValueBase)null, new object[1] { val })); PowerDeniedMessage = ((BaseUnityPlugin)this).Config.Bind<string>("BossPowers", "DeniedMessage", "You have not proven yourself against {0}.", new ConfigDescription("Shown when a player tries to take a power they haven't earned. {0} = boss name.", (AcceptableValueBase)null, new object[1] { val })); ItemGatingEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("ItemGating", "Enabled", true, new ConfigDescription("Gear made from materials beyond the bosses a character has killed is punished.", (AcceptableValueBase)null, new object[1] { val })); GatePunishment = ((BaseUnityPlugin)this).Config.Bind<ItemGate.Punishment>("ItemGating", "Punishment", ItemGate.Punishment.Burn, new ConfigDescription("Burn = they can equip it but burn while it's on. Block = it won't equip. BlockAndBurn = won't equip and they get scorched for trying.", (AcceptableValueBase)null, new object[1] { val })); BurnDamage = ((BaseUnityPlugin)this).Config.Bind<float>("ItemGating", "BurnDamage", 5f, new ConfigDescription("Fire damage per tick. Fire resistance reduces it. Can kill.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 200f), new object[1] { val })); BurnInterval = ((BaseUnityPlugin)this).Config.Bind<float>("ItemGating", "BurnInterval", 2f, new ConfigDescription("Seconds between burn ticks while forbidden gear is on.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 30f), new object[1] { val })); MessageInterval = ((BaseUnityPlugin)this).Config.Bind<float>("ItemGating", "MessageInterval", 10f, new ConfigDescription("Seconds between repeat messages while burning.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 120f), new object[1] { val })); GateMessages = ((BaseUnityPlugin)this).Config.Bind<string>("ItemGating", "Messages", "Odin is disappointed in you.|You are not yet ready to wield this gear.", new ConfigDescription("Fallback messages separated by |, used when a boss has no lines of its own. {item} and {boss} are filled in.", (AcceptableValueBase)null, new object[1] { val })); Dictionary<string, string> dictionary = new Dictionary<string, string> { ["defeated_eikthyr"] = "The stag still roams. {item} is not yours to bear.|Eikthyr laughs at your borrowed bronze.|Odin is disappointed in you.", ["defeated_gdking"] = "The Elder's roots still grip the swamp. Iron burns in unworthy hands.|You smell of the Black Forest, not the swamp. Slay The Elder first.", ["defeated_bonemass"] = "Bonemass still festers. Silver rejects you.|The mountain's chill is earned, not stolen. Face Bonemass.", ["defeated_dragon"] = "Moder still rules the skies. Black metal will not obey you.|The plains bow only to those who felled Moder.", ["defeated_goblinking"] = "Yagluth's fire still burns. The mist's gifts scorch you.|You have not silenced Yagluth, and your {item} knows it.", ["defeated_queen"] = "The Queen still stirs beneath the mist. Flametal will not be tamed.|Flametal answers only those who slew The Queen.", ["defeated_fader"] = "Fader still waits in the ash. This power is beyond you.|Fader's shadow falls on the unproven." }; foreach (KeyValuePair<string, string> item in dictionary) { BossMessages[item.Key] = ((BaseUnityPlugin)this).Config.Bind<string>("ItemGating", "Messages_" + item.Key, item.Value, new ConfigDescription("Lines shown for gear that needs " + PrettyKey(item.Key) + " dead, separated by |. Leave empty to use the fallback Messages.", (AcceptableValueBase)null, new object[1] { val })); } ItemOverrides = ((BaseUnityPlugin)this).Config.Bind<string>("ItemGating", "ItemOverrides", "", new ConfigDescription("item:key pairs that win over the material rule, e.g. FetidMyra:defeated_gdking. Use item: (nothing after) to never gate an item. Check items.generated.txt for prefab names.", (AcceptableValueBase)null, new object[1] { val })); string[] array = new string[7] { "CopperOre,TinOre,Copper,Tin,Bronze,BronzeNails,TrollHide", "IronScrap,IronOre,Iron,IronNails,Chain,ElderBark,Guck,Root,Bloodbag,Ooze,WitheredBone", "SilverOre,Silver,WolfPelt,WolfFang,WolfClaw,FreezeGland,Obsidian,Crystal,FenrisHair,FenrisClaw,JuteRed", "BlackMetalScrap,BlackMetal,Flax,LinenThread,Needle,LoxPelt,DragonTear,Tar", "Carapace,Eitr,BlackCore,Sap,YggdrasilWood,ScaleHide,Mandible,BlackMarble,Wisp,GiantBloodSack,JuteBlue", "FlametalOreNew,FlametalNew,Blackwood,AskHide,AskBladder,MoltenCore,CelestialFeather,Grausten,CharcoalResin,BellFragment,MorgenSinew,MorgenHeart", "" }; TierMaterials = new ConfigEntry<string>[ItemGate.TierKeys.Length]; for (int i = 0; i < TierMaterials.Length; i++) { string text = ItemGate.TierKeys[i]; TierMaterials[i] = ((BaseUnityPlugin)this).Config.Bind<string>("ItemGating", "Materials_" + text, array[i], new ConfigDescription("Material prefabs unlocked by killing " + PrettyKey(text) + ".", (AcceptableValueBase)null, new object[1] { val })); TierMaterials[i].SettingChanged += delegate { ItemGate.ResetCache(); }; } ItemOverrides.SettingChanged += delegate { ItemGate.ResetCache(); }; _harmony = new Harmony("OblitusEs.PersonalProgression"); _harmony.PatchAll(); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new PpCommand()); Log.LogInfo((object)"PersonalProgression 1.0.0 loaded"); } private void Update() { Net.ClientTick(); ItemGate.Tick(); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } internal static HashSet<string> GetTrackedKeys() { return new HashSet<string>(from s in TrackedKeys.Value.Split(new char[1] { ',' }) select s.Trim() into s where s.Length > 0 select s, StringComparer.OrdinalIgnoreCase); } internal static Dictionary<string, string> GetPowerKeyMap() { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); string[] array = PowerKeyMap.Value.Split(new char[1] { ',' }); foreach (string text in array) { string[] array2 = text.Split(new char[1] { ':' }); if (array2.Length == 2 && array2[0].Trim().Length > 0) { dictionary[array2[0].Trim()] = array2[1].Trim(); } } return dictionary; } internal static string PrettyKey(string key) { string value; return BossNames.TryGetValue(key, out value) ? value : key; } } internal class PpCommand : ConsoleCommand { public override string Name => "pp"; public override string Help => "Personal progression: pp mykeys | pp keys <name> | pp grant <name> <key> | pp revoke <name> <key> | pp reset <name>"; public override void Run(string[] args) { if (args.Length == 0) { Console.instance.Print(((ConsoleCommand)this).Help); return; } string text = args[0].ToLowerInvariant(); if (text == "mykeys") { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { Console.instance.Print("No local player."); return; } HashSet<string> trackedKeys = Plugin.GetTrackedKeys(); List<string> list = (GameAccess.Uniques(localPlayer) ?? new HashSet<string>()).Where(trackedKeys.Contains).ToList(); Console.instance.Print((list.Count == 0) ? "You have no personal keys yet." : ("Your keys: " + string.Join(", ", list.Select((string k) => k + " (" + Plugin.PrettyKey(k) + ")")))); } else if (args.Length < 2) { Console.instance.Print(((ConsoleCommand)this).Help); } else { string name = args[1].Replace('_', ' '); string key = ((args.Length > 2) ? args[2] : ""); Net.SendAdmin(text, name, key); } } public override List<string> CommandOptionList() { return new List<string> { "mykeys", "keys", "grant", "revoke", "reset" }; } } }