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 BossGatedPortals v0.1.4
plugins/BossGatedPortals.dll
Decompiled 19 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using HarmonyLib; using Jotunn; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("BossGatedPortals")] [assembly: AssemblyProduct("BossGatedPortals")] [assembly: ComVisible(false)] [assembly: AssemblyFileVersion("0.1.4")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.4.0")] [module: UnverifiableCode] namespace BossGatedPortals; [HarmonyPatch(typeof(ZoneSystem), "RPC_SetGlobalKey")] internal static class Announcements { private static void Prefix(ZoneSystem __instance, string name, out bool __state) { try { __state = WasSet(__instance, name); } catch (Exception e) { Failsafe.Report("Tier unlock announcement", e); __state = true; } } [MethodImpl(MethodImplOptions.NoInlining)] private static bool WasSet(ZoneSystem zoneSystem, string name) { return zoneSystem.GetGlobalKey(name); } private static void Postfix(ZoneSystem __instance, string name, bool __state) { try { Announce(__instance, name, __state); } catch (Exception e) { Failsafe.Report("Tier unlock announcement", e); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void Announce(ZoneSystem zoneSystem, string name, bool wasSet) { if (wasSet || !Settings.Enabled.Value || !Settings.AnnounceTierUnlock.Value || !zoneSystem.GetGlobalKey(name)) { return; } string text = name.Trim().ToLowerInvariant(); foreach (Tier tier in Settings.Tiers) { if (!(tier.GlobalKey != text) && !string.IsNullOrEmpty(tier.UnlockMessage)) { Logger.LogInfo((object)$"AnnounceTierUnlock: '{text}' set; announcing T{tier.Index} {tier.Name}."); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "ShowMessage", new object[2] { 2, tier.UnlockMessage }); } } } } [BepInPlugin("com.jtmill01.bossgatedportals", "BossGatedPortals", "0.1.4")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] internal class BossGatedPortals : BaseUnityPlugin { public const string PluginGUID = "com.jtmill01.bossgatedportals"; public const string PluginName = "BossGatedPortals"; public const string PluginVersion = "0.1.4"; public const string XPortalGUID = "yay.spikehimself.xportal"; private readonly Harmony harmony = new Harmony("com.jtmill01.bossgatedportals.harmony"); private void Awake() { Settings.Bind(((BaseUnityPlugin)this).Config); Settings.LogSummary(); ((BaseUnityPlugin)this).Config.SettingChanged += delegate { Settings.Parse(); }; SynchronizationManager.OnConfigurationSynchronized += delegate { ReportSettings(); }; SynchronizationManager.OnConfigurationWindowClosed += ReportSettings; ItemManager.OnItemsRegistered += Settings.Validate; ItemManager.OnItemsRegistered += Hints.LogUnmappedItems; PatchEachHook(); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new StatusCommand()); bool flag = Chainloader.PluginInfos.ContainsKey("yay.spikehimself.xportal"); Logger.LogInfo((object)string.Format("{0} {1} loaded. XPortal detected: {2}", "BossGatedPortals", "0.1.4", flag)); } private void Start() { Compatibility.LogInstalledMods(); } private void PatchEachHook() { Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(typeof(BossGatedPortals).Assembly); foreach (Type type in typesFromAssembly) { try { harmony.CreateClassProcessor(type).Patch(); } catch (Exception arg) { Logger.LogError((object)("Couldn't hook the game for " + type.Name + " (game update?). " + $"That feature stays vanilla; the rest of the mod still works. {arg}")); } } } private static void ReportSettings() { Settings.Parse(); Settings.LogSummary(); Settings.Validate(); Hints.LogUnmappedItems(); } } internal static class Compatibility { private static readonly Dictionary<string, string> Conflicts = new Dictionary<string, string> { { "unrestrictedportals", "changes which items portals allow" }, { "teleporteverything", "turn its item transport OFF (keep its creature/cart transport if you want it)" }, { "advancedportals", "has its own item tiers for portals" }, { "progressionportals", "has its own progression item gate" }, { "worldadvancementprogression", "hooks the SAME item check with high priority and can switch a block back to allowed; never run it alongside this mod" }, { "serversideqol", "its PortalProgression feature gates portal items too; turn that feature off" } }; public static void LogInstalledMods() { List<PluginInfo> list = Chainloader.PluginInfos.Values.Where((PluginInfo p) => p.Metadata.GUID != "com.jtmill01.bossgatedportals").ToList(); if (Settings.WarnOnConflictingMods.Value) { foreach (PluginInfo item in list) { string id = Normalize(item.Metadata.GUID) + " " + Normalize(item.Metadata.Name); foreach (KeyValuePair<string, string> item2 in Conflicts.Where((KeyValuePair<string, string> c) => id.Contains(c.Key))) { Logger.LogWarning((object)("WarnOnConflictingMods: " + Describe(item) + " also changes item teleport rules (" + item2.Value + "). Two item-gate mods together give unpredictable results.")); } } } if (Settings.LogPortalModDetection.Value) { PluginInfo val = ((IEnumerable<PluginInfo>)list).FirstOrDefault((Func<PluginInfo, bool>)((PluginInfo p) => p.Metadata.GUID == "yay.spikehimself.xportal")); PluginInfo val2 = ((IEnumerable<PluginInfo>)list).FirstOrDefault((Func<PluginInfo, bool>)((PluginInfo p) => Matches(p, "xportalnetworks"))); PluginInfo val3 = ((IEnumerable<PluginInfo>)list).FirstOrDefault((Func<PluginInfo, bool>)((PluginInfo p) => p.Metadata.GUID == "com.sweetgiorni.anyportal" || Matches(p, "anyportal"))); Logger.LogInfo((object)("LogPortalModDetection: XPortal " + ((val != null) ? Describe(val) : "not installed") + ", XPortalNetworks " + ((val2 != null) ? Describe(val2) : "not installed") + ".")); if (val3 != null) { Logger.LogWarning((object)("LogPortalModDetection: " + Describe(val3) + " is installed. XPortal refuses to load alongside it; remove AnyPortal and use XPortal instead.")); } } } private static bool Matches(PluginInfo p, string fragment) { if (!Normalize(p.Metadata.GUID).Contains(fragment)) { return Normalize(p.Metadata.Name).Contains(fragment); } return true; } private static string Normalize(string s) { return new string((from c in (s ?? "").ToLowerInvariant() where c != ' ' && c != '-' && c != '_' select c).ToArray()); } private static string Describe(PluginInfo p) { return $"{p.Metadata.Name} {p.Metadata.Version} ({p.Metadata.GUID})"; } } internal static class Failsafe { private static readonly HashSet<string> reported = new HashSet<string>(); public static void Report(string where, Exception e) { if (reported.Add(where)) { Logger.LogError((object)(where + " failed, using vanilla behaviour there instead " + $"(game update?): {e}")); } } public static bool TeleportAllSet() { return ZoneSystem.instance.GetGlobalKey("teleportall"); } } internal static class Gate { public static bool CanTeleport(Player player, ItemData item, bool portalAllowsAll = false) { bool teleportable = item.m_shared.m_teleportable; bool flag = item.m_shared.m_toolTier >= 1000; bool flag2 = portalAllowsAll || Failsafe.TeleportAllSet(); if (!Settings.Enabled.Value) { if (!flag) { return flag2 || teleportable; } return false; } string text = PrefabName(item); if (text != null && Settings.NeverTeleportItems.Contains(text)) { return false; } if (flag) { if (Settings.FinalTierIncludesCheatTier.Value) { return FinalTierUnlocksEverything(player); } return false; } if (flag2 && Settings.RespectVanillaAllowAll.Value) { return true; } Tier tier = ((text != null) ? TierFor(text) : null); if (teleportable && (tier == null || !Settings.GateListedVanillaItems.Value)) { return true; } if (tier == null || !IsTierUnlocked(player, tier)) { return FinalTierUnlocksEverything(player); } return true; } public static bool IsTierUnlocked(Player player, Tier tier) { if (IsBypassing(player)) { return true; } List<Tier> tiers = Settings.Tiers; int num = (Settings.CumulativeTiers.Value ? (tiers.Count - 1) : tier.Index); for (int i = tier.Index; i <= num; i++) { if (IsTierEarned(player, tiers[i])) { return true; } } return false; } public static bool IsTierEarned(Player player, Tier tier) { if (Settings.EffectiveRequireWorldKey && (string.IsNullOrEmpty(tier.GlobalKey) || !ZoneSystem.instance.GetGlobalKey(tier.GlobalKey))) { return false; } if (Settings.RequirePlayerBossItem.Value && (string.IsNullOrEmpty(tier.BossItemToken) || !player.IsMaterialKnown(tier.BossItemToken))) { return false; } return true; } public static Tier TierFor(string prefab) { foreach (Tier tier in Settings.Tiers) { if (tier.Items.Contains(prefab)) { return tier; } } return null; } private static bool FinalTierUnlocksEverything(Player player) { List<Tier> tiers = Settings.Tiers; if (tiers.Count == 0) { return false; } Tier tier = tiers[tiers.Count - 1]; if (tier.AllowEverything) { return IsTierUnlocked(player, tier); } return false; } private static bool IsBypassing(Player player) { if (Settings.AdminBypass.Value && (Object)(object)player == (Object)(object)Player.m_localPlayer) { return SynchronizationManager.Instance.PlayerIsAdmin; } return false; } public static string PrefabName(ItemData item) { if (Object.op_Implicit((Object)(object)item.m_dropPrefab)) { return ((Object)item.m_dropPrefab).name; } GameObject val = default(GameObject); if (Object.op_Implicit((Object)(object)ObjectDB.instance) && ObjectDB.instance.TryGetItemPrefab(item.m_shared, ref val) && Object.op_Implicit((Object)(object)val)) { return ((Object)val).name; } return null; } } [HarmonyPatch(typeof(Inventory), "IsTeleportable")] internal static class InventoryIsTeleportablePatch { private static readonly FieldInfo VagonInstances = AccessTools.Field(typeof(Vagon), "m_instances"); private static void Postfix(Inventory __instance, bool allowAllItems, ref bool __result) { try { Gatekeep(__instance, allowAllItems, ref __result); } catch (Exception e) { Failsafe.Report("Portal item check", e); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void Gatekeep(Inventory inventory, bool allowAllItems, ref bool result) { Player localPlayer = Player.m_localPlayer; if (!Settings.Enabled.Value || (Object)(object)localPlayer == (Object)null) { return; } Inventory val = AttachedCartCargo(localPlayer); bool flag = ((Humanoid)localPlayer).GetInventory() == inventory; if (flag || inventory == val) { Hints.LastBlocked.Clear(); AddBlocked(localPlayer, inventory, allowAllItems); if (flag && val != null) { AddBlocked(localPlayer, val, allowAllItems); } result = Hints.LastBlocked.Count == 0; } } private static void AddBlocked(Player player, Inventory inventory, bool allowAllItems) { foreach (ItemData allItem in inventory.GetAllItems()) { if (!Gate.CanTeleport(player, allItem, allowAllItems)) { Hints.LastBlocked.Add(allItem); } } } private static Inventory AttachedCartCargo(Player player) { if (!Settings.GateAttachedCartCargo.Value) { return null; } if (!(VagonInstances?.GetValue(null) is List<Vagon> list)) { return null; } foreach (Vagon item in list) { if (Object.op_Implicit((Object)(object)item) && Object.op_Implicit((Object)(object)item.m_container) && item.IsAttached((Character)(object)player)) { return item.m_container.GetInventory(); } } return null; } } internal static class Hints { [HarmonyPatch(typeof(Player), "Message")] private static class PlayerMessagePatch { private static bool Prefix(Player __instance, ref MessageType type, ref string msg) { try { return SwapForHint(__instance, ref type, ref msg); } catch (Exception e) { Failsafe.Report("Unlock hint", e); return true; } } [MethodImpl(MethodImplOptions.NoInlining)] private static bool SwapForHint(Player player, ref MessageType type, ref string msg) { if (msg != "$msg_noteleport" || (Object)(object)player != (Object)(object)Player.m_localPlayer || !Settings.Enabled.Value || !Settings.ShowUnlockHint.Value) { return true; } string text = BuildMessage(); if (text == null) { return true; } if (Time.time - lastHintTime < Settings.HintCooldownSeconds.Value) { return false; } lastHintTime = Time.time; msg = text; type = (MessageType)((Settings.HintPositionSetting.Value == HintPosition.TopLeft) ? 1 : 2); return true; } } public static readonly List<ItemData> LastBlocked = new List<ItemData>(); private static float lastHintTime = float.NegativeInfinity; public static string BuildMessage() { List<Tier> tiers = Settings.Tiers; if (tiers.Count == 0) { return null; } Tier tier = tiers[tiers.Count - 1]; Tier tier2 = null; ItemData val = null; foreach (ItemData item in LastBlocked) { string text = Gate.PrefabName(item); if (text != null && Settings.NeverTeleportItems.Contains(text)) { continue; } Tier tier3; if (item.m_shared.m_toolTier >= 1000) { if (!Settings.FinalTierIncludesCheatTier.Value || !tier.AllowEverything) { continue; } tier3 = tier; } else { tier3 = ((text != null) ? Gate.TierFor(text) : null) ?? tier; } if (tier2 == null || tier3.Index < tier2.Index) { tier2 = tier3; val = item; } } if (tier2 == null || string.IsNullOrEmpty(tier2.Hint)) { return null; } return Settings.HintFormat.Value.Replace("{hint}", tier2.Hint).Replace("{item}", val.m_shared.m_name); } public static void LogUnmappedItems() { if ((Object)(object)ObjectDB.instance == (Object)null || !Settings.LogUnmappedItems.Value) { return; } List<string> list = new List<string>(); foreach (GameObject item in ObjectDB.instance.m_items) { ItemDrop val = (Object.op_Implicit((Object)(object)item) ? item.GetComponent<ItemDrop>() : null); if (!((Object)(object)val == (Object)null) && !val.m_itemData.m_shared.m_teleportable && !Settings.NeverTeleportItems.Contains(((Object)item).name) && Gate.TierFor(((Object)item).name) == null) { list.Add(((Object)item).name); } } if (list.Count == 0) { Logger.LogInfo((object)"LogUnmappedItems: every teleport-blocked item is listed in a tier."); return; } Logger.LogInfo((object)("LogUnmappedItems: teleport-blocked items not in any tier (locked until the final tier): " + string.Join(", ", list.OrderBy((string n) => n)))); } } public enum HintPosition { Centered, TopLeft } internal static class Settings { private class TierEntries { public string Name; public ConfigEntry<string> GlobalKey; public ConfigEntry<string> BossItem; public ConfigEntry<string> Items; public ConfigEntry<string> Hint; public ConfigEntry<string> UnlockMessage; public ConfigEntry<bool> AllowEverything; } public static ConfigEntry<bool> Enabled; public static ConfigEntry<bool> RequireWorldKey; public static ConfigEntry<bool> RequirePlayerBossItem; public static ConfigEntry<bool> CumulativeTiers; public static ConfigEntry<string> NeverTeleport; public static ConfigEntry<bool> HildirChestsByTier; public static ConfigEntry<bool> GateListedVanillaItems; public static ConfigEntry<bool> FinalTierIncludesCheatTier; public static ConfigEntry<bool> LogUnmappedItems; public static ConfigEntry<bool> ValidateItemIds; public static ConfigEntry<bool> ShowUnlockHint; public static ConfigEntry<string> HintFormat; public static ConfigEntry<HintPosition> HintPositionSetting; public static ConfigEntry<float> HintCooldownSeconds; public static ConfigEntry<bool> AccurateTooltips; public static ConfigEntry<bool> AnnounceTierUnlock; public static ConfigEntry<bool> AdminBypass; public static ConfigEntry<bool> EnableStatusCommand; public static ConfigEntry<bool> RespectVanillaAllowAll; public static ConfigEntry<bool> GateAttachedCartCargo; public static ConfigEntry<bool> WarnOnConflictingMods; public static ConfigEntry<bool> LogPortalModDetection; public static List<Tier> Tiers = new List<Tier>(); public static HashSet<string> NeverTeleportItems = new HashSet<string>(); private static readonly List<TierEntries> tierEntries = new List<TierEntries>(); private static readonly string[][] TierDefaults = new string[8][] { new string[6] { "Meadows", "defeated_eikthyr", "HardAntler", "", "", "" }, new string[6] { "Black Forest", "defeated_gdking", "CryptKey", "CopperOre, Copper, CopperScrap, TinOre, Tin, Bronze, BronzeScrap", "Something old still stirs beneath the Black Forest canopy.", "The portals hum. Metals of the forest may now pass." }, new string[6] { "Swamp", "defeated_bonemass", "Wishbone", "IronOre, IronScrap, Iron, Ironpit", "The Swamp's guardian has not yet been put to rest.", "The portals hum. Iron from the Swamp may now pass." }, new string[6] { "Mountains", "defeated_dragon", "DragonTear", "SilverOre, Silver, DragonEgg", "The Mountain peaks answer to a ruler you have not faced.", "The portals hum. Treasures of the Mountains may now pass." }, new string[6] { "Plains", "defeated_goblinking", "YagluthDrop", "BlackMetalScrap, BlackMetal", "The Plains still bow to their fallen king.", "The portals hum. Black metal may now pass." }, new string[6] { "Mistlands", "defeated_queen", "QueenDrop", "MechanicalSpring, DvergrNeedle", "Something waits deep within the Mistlands.", "The portals hum. Relics of the Mistlands may now pass." }, new string[6] { "Ashlands", "defeated_fader", "FaderDrop", "FlametalOreNew, FlametalNew, FlametalOre, Flametal, CharredCogwheel", "The Ashlands' guardian still smolders.", "The portals hum. Flametal may now pass." }, new string[6] { "Deep North", "defeated_frozenking_p3", "FrozenKingDrop", "GoldOre, Gold", "The portal will not carry this until the far North is conquered.", "The portals roar. Nothing is barred from passage now." } }; private static readonly Dictionary<string, int> HildirChestTiers = new Dictionary<string, int> { { "chest_hildir1", 1 }, { "chest_hildir2", 3 }, { "chest_hildir3", 4 } }; private static ConfigFile config; private static int order; public static bool EffectiveRequireWorldKey { get { if (!RequireWorldKey.Value) { return !RequirePlayerBossItem.Value; } return true; } } public static void Bind(ConfigFile cfg) { config = cfg; bool saveOnConfigSet = cfg.SaveOnConfigSet; cfg.SaveOnConfigSet = false; try { BindEntries(); } finally { cfg.Save(); cfg.SaveOnConfigSet = saveOnConfigSet; } Parse(); } private static void BindEntries() { Enabled = Admin("0.1 - General", "Enabled", value: true, "Master switch for the mod."); RequireWorldKey = Admin("0.1 - General", "RequireWorldKey", value: true, "The boss must have been killed ON THIS WORLD (world-wide global key, saved on the server)."); RequirePlayerBossItem = Admin("0.1 - General", "RequirePlayerBossItem", value: true, "The individual player must also have picked up that boss's drop at least once. Makes EVERY player follow progression.\nSafety rule: if this and RequireWorldKey are BOTH false, the world key is still required and a warning is logged."); CumulativeTiers = Admin("0.1 - General", "CumulativeTiers", value: false, "If true, unlocking a later tier also unlocks every earlier tier for that player. false = each tier must be earned on its own."); NeverTeleport = Admin("0.2 - Item Rules", "NeverTeleport", "", "Items that can NEVER be teleported, even after the final tier. Prefab names, comma-separated. Example: DragonEgg"); HildirChestsByTier = Admin("0.2 - Item Rules", "HildirChestsByTier", value: false, "If true, Hildir's chests unlock with the tier of the biome they're found in (Brass = Black Forest, Silver = Mountains, Bronze = Plains). false = they never teleport, as in vanilla."); GateListedVanillaItems = Admin("0.2 - Item Rules", "GateListedVanillaItems", value: false, "If true, items listed in a tier are gated even if vanilla normally lets them through portals. false = tiers only UNLOCK items vanilla already blocks."); FinalTierIncludesCheatTier = Admin("0.2 - Item Rules", "FinalTierIncludesCheatTier", value: false, "Vanilla blocks tool-tier 1000+ items at every portal. The only such item is FrozenKingDrop. If true, the final tier also unlocks those."); LogUnmappedItems = Admin("0.2 - Item Rules", "LogUnmappedItems", value: true, "At startup, log every teleport-blocked item not listed in any tier. Unlisted blocked items stay locked until the final tier."); ValidateItemIds = Admin("0.2 - Item Rules", "ValidateItemIds", value: true, "At startup, warn about any item or boss-drop ID in this file that doesn't exist in the game."); ShowUnlockHint = Admin("0.3 - Messages", "ShowUnlockHint", value: true, "Show a hint when a blocked item stops teleporting. Never names a boss."); HintFormat = Admin("0.3 - Messages", "HintFormat", "An item blocks the portal. {hint}", "Message text. {hint} = the tier's Hint. Optional {item} names the blocked item. If several tiers are locked, only the earliest locked tier's hint is shown."); HintPositionSetting = Client("0.3 - Messages", "HintPosition", HintPosition.Centered, "Where hints appear. Client setting: each player can choose."); HintCooldownSeconds = Client("0.3 - Messages", "HintCooldownSeconds", 5f, "Minimum seconds between repeat hints while standing in a portal. Client setting.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 60f)); AccurateTooltips = Admin("0.3 - Messages", "AccurateTooltips", value: true, "Item tooltips show whether the item can be teleported RIGHT NOW for this player, using the same check the portal uses."); AnnounceTierUnlock = Admin("0.3 - Messages", "AnnounceTierUnlock", value: true, "When a boss's world key is first set, announce to everyone that portals accept more goods. Uses the tier's UnlockMessage; never names a boss."); AdminBypass = Admin("0.4 - Admin", "AdminBypass", value: false, "Server admins ignore the gate (useful for building/testing)."); EnableStatusCommand = Admin("0.4 - Admin", "EnableStatusCommand", value: true, "Admin console command 'portalgate status': lists each tier as locked/unlocked for the world and for the calling player. Admin-only; shows boss names."); RespectVanillaAllowAll = Admin("0.5 - Compatibility", "RespectVanillaAllowAll", value: true, "Never block a portal that already lets everything through in vanilla (the Stone Portal), and respect the 'Portals' world modifier when it allows all items."); GateAttachedCartCargo = Admin("0.5 - Compatibility", "GateAttachedCartCargo", value: true, "If a cart-teleport mod carries an attached cart through, gate its cargo with the same check."); WarnOnConflictingMods = Admin("0.5 - Compatibility", "WarnOnConflictingMods", value: true, "Log a warning at startup if another mod that changes item teleport rules is installed."); LogPortalModDetection = Admin("0.6 - XPortal Compatibility", "LogPortalModDetection", value: true, "Log the detected XPortal / XPortalNetworks version at startup, and warn if AnyPortal is present."); for (int i = 0; i < TierDefaults.Length; i++) { string[] array = TierDefaults[i]; string section = $"T{i} - {array[0]}"; bool flag = i == TierDefaults.Length - 1; order = 0; tierEntries.Add(new TierEntries { Name = array[0], GlobalKey = Admin(section, "GlobalKey", array[1], "World kill flag for this tier's boss."), BossItem = Admin(section, "BossItem", array[2], "Boss drop (prefab name) the player must have held."), AllowEverything = (flag ? Admin(section, "AllowEverything", value: true, "Final tier: unlocks every teleport-blocked item, including unlisted ones (except NeverTeleport, and cheat-tier unless FinalTierIncludesCheatTier).") : null), Items = Admin(section, "Items", array[3], "Item prefab names this tier unlocks, comma-separated."), Hint = Admin(section, "Hint", array[4], flag ? "Hint while locked. Also used for any unlisted blocked item. Never name a boss." : "Hint while locked. Never name a boss."), UnlockMessage = Admin(section, "UnlockMessage", array[5], "Server-wide message when this tier's world key is first set. Never name a boss.") }); } } public static void Parse() { List<Tier> list = new List<Tier>(); for (int i = 0; i < Settings.tierEntries.Count; i++) { TierEntries tierEntries = Settings.tierEntries[i]; list.Add(new Tier { Index = i, Name = tierEntries.Name, GlobalKey = tierEntries.GlobalKey.Value.Trim().ToLowerInvariant(), BossItem = tierEntries.BossItem.Value.Trim(), Items = SplitList(tierEntries.Items.Value), Hint = tierEntries.Hint.Value.Trim(), UnlockMessage = tierEntries.UnlockMessage.Value.Trim(), AllowEverything = (tierEntries.AllowEverything != null && tierEntries.AllowEverything.Value) }); } NeverTeleportItems = SplitList(NeverTeleport.Value); foreach (KeyValuePair<string, int> hildirChestTier in HildirChestTiers) { if (HildirChestsByTier.Value) { list[hildirChestTier.Value].Items.Add(hildirChestTier.Key); } else { NeverTeleportItems.Add(hildirChestTier.Key); } } Tiers = list; if (!((Object)(object)ObjectDB.instance != (Object)null)) { return; } foreach (Tier item in list) { item.BossItemToken = GetItemToken(item.BossItem); } } public static void LogSummary() { if (!RequireWorldKey.Value && !RequirePlayerBossItem.Value) { Logger.LogWarning((object)"RequireWorldKey and RequirePlayerBossItem are both false. Ignoring that: the boss's world key is still required for each tier."); } List<string> list = new List<string> { $"Settings: Enabled={Enabled.Value}, WorldKey={EffectiveRequireWorldKey}, " + $"PlayerBossItem={RequirePlayerBossItem.Value}, Cumulative={CumulativeTiers.Value}, " + $"GateListedVanillaItems={GateListedVanillaItems.Value}, AdminBypass={AdminBypass.Value}", "NeverTeleport: " + Describe(NeverTeleportItems), "Tiers:" }; foreach (Tier tier in Tiers) { string text = ((tier.BossItemToken != null) ? (" (" + tier.BossItemToken + ")") : ""); string text2 = (tier.AllowEverything ? ("EVERYTHING" + ((tier.Items.Count > 0) ? (" + " + Describe(tier.Items)) : "")) : Describe(tier.Items)); list.Add($" T{tier.Index} {tier.Name,-12} key={tier.GlobalKey} bossItem={tier.BossItem}{text} items={text2}"); } Logger.LogInfo((object)string.Join("\n", list)); } public static void Validate() { if ((Object)(object)ObjectDB.instance == (Object)null) { return; } foreach (Tier tier in Tiers) { tier.BossItemToken = GetItemToken(tier.BossItem); } if (!ValidateItemIds.Value) { return; } int problems = 0; foreach (string neverTeleportItem in NeverTeleportItems) { Check(neverTeleportItem, "NeverTeleport"); } Dictionary<string, int> dictionary = new Dictionary<string, int>(); foreach (Tier tier2 in Tiers) { string text = $"[T{tier2.Index} - {tier2.Name}]"; if (string.IsNullOrEmpty(tier2.GlobalKey)) { problems++; Logger.LogWarning((object)("ValidateItemIds: " + text + " has no GlobalKey.")); } if (string.IsNullOrEmpty(tier2.BossItem)) { if (RequirePlayerBossItem.Value) { problems++; Logger.LogWarning((object)("ValidateItemIds: " + text + " has no BossItem.")); } } else { Check(tier2.BossItem, text + " BossItem"); } foreach (string item in tier2.Items) { Check(item, text + " Items"); if (dictionary.TryGetValue(item, out var value)) { problems++; Logger.LogWarning((object)$"ValidateItemIds: '{item}' is listed in both T{value} and T{tier2.Index}."); } else { dictionary[item] = tier2.Index; } } } if (problems == 0) { Logger.LogInfo((object)"ValidateItemIds: all item and boss-drop IDs exist."); } void Check(string id, string where) { if (!((Object)(object)ObjectDB.instance.GetItemPrefab(id) != (Object)null)) { problems++; Logger.LogWarning((object)("ValidateItemIds: '" + id + "' in " + where + " is not an item in this version of Valheim.")); } } } private static string GetItemToken(string prefab) { if (string.IsNullOrEmpty(prefab)) { return null; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefab); if (!Object.op_Implicit((Object)(object)itemPrefab)) { return null; } ItemDrop component = itemPrefab.GetComponent<ItemDrop>(); if (!Object.op_Implicit((Object)(object)component)) { return null; } return component.m_itemData.m_shared.m_name; } private static HashSet<string> SplitList(string value) { return new HashSet<string>(from s in value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries) select s.Trim() into s where s.Length > 0 select s); } private static string Describe(HashSet<string> set) { if (set.Count != 0) { return string.Join(", ", set); } return "(none)"; } private static ConfigEntry<T> Admin<T>(string section, string key, T value, string description, AcceptableValueBase range = null) { return Bind(section, key, value, description, range, adminOnly: true); } private static ConfigEntry<T> Client<T>(string section, string key, T value, string description, AcceptableValueBase range = null) { return Bind(section, key, value, description, range, adminOnly: false); } private static ConfigEntry<T> Bind<T>(string section, string key, T value, string description, AcceptableValueBase range, bool adminOnly) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: 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_0026: Expected O, but got Unknown //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown ConfigurationManagerAttributes val = new ConfigurationManagerAttributes { IsAdminOnly = adminOnly, Order = (order -= 1) }; if (!adminOnly) { description += " (Not synced.)"; } return config.Bind<T>(section, key, value, new ConfigDescription(description, range, new object[1] { val })); } } internal class StatusCommand : ConsoleCommand { public override string Name => "portalgate"; public override string Help => "portalgate status - BossGatedPortals tiers for the world and for you (admin only)"; public override List<string> CommandOptionList() { return new List<string> { "status" }; } public override void Run(string[] args, Terminal context) { if (args.Length == 0 || args[0].ToLowerInvariant() != "status") { context.AddString("Usage: portalgate status"); return; } if (!Settings.EnableStatusCommand.Value) { context.AddString("portalgate status is turned off (EnableStatusCommand)."); return; } if (!SynchronizationManager.Instance.PlayerIsAdmin) { context.AddString("portalgate status is for server admins only."); return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null) { context.AddString("Join a world first."); return; } context.AddString(string.Format("BossGatedPortals {0} - Enabled={1}, ", "0.1.4", Settings.Enabled.Value) + $"WorldKey={Settings.EffectiveRequireWorldKey}, PlayerBossItem={Settings.RequirePlayerBossItem.Value}, " + $"Cumulative={Settings.CumulativeTiers.Value}, AdminBypass={Settings.AdminBypass.Value}"); foreach (Tier tier in Settings.Tiers) { bool flag = !string.IsNullOrEmpty(tier.GlobalKey) && ZoneSystem.instance.GetGlobalKey(tier.GlobalKey); bool flag2 = !string.IsNullOrEmpty(tier.BossItemToken) && localPlayer.IsMaterialKnown(tier.BossItemToken); bool flag3 = Gate.IsTierUnlocked(localPlayer, tier); context.AddString(string.Format("T{0} {1}: world {2} ({3}), ", tier.Index, tier.Name, flag ? "UNLOCKED" : "locked", tier.GlobalKey) + "you " + (flag2 ? "have" : "have not") + " held " + tier.BossItem + " -> " + (flag3 ? "UNLOCKED" : "LOCKED") + " for you"); } } } internal class Tier { public int Index; public string Name; public string GlobalKey; public string BossItem; public string BossItemToken; public HashSet<string> Items = new HashSet<string>(); public string Hint; public string UnlockMessage; public bool AllowEverything; } internal static class Tooltips { [HarmonyPatch(typeof(ItemData), "GetTooltip", new Type[] { typeof(ItemData), typeof(int), typeof(bool), typeof(float), typeof(int), typeof(bool) })] private static class GetTooltipPatch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O, but got Unknown //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Expected O, but got Unknown //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Expected O, but got Unknown //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Expected O, but got Unknown //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Expected O, but got Unknown //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Expected O, but got Unknown //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Expected O, but got Unknown //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Expected O, but got Unknown //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Expected O, but got Unknown //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Expected O, but got Unknown FieldInfo fieldInfo = AccessTools.Field(typeof(ItemData), "m_shared"); FieldInfo fieldInfo2 = AccessTools.Field(typeof(SharedData), "m_teleportable"); MethodInfo methodInfo = AccessTools.Method(typeof(ZoneSystem), "GetGlobalKey", new Type[1] { typeof(GlobalKeys) }, (Type[])null); CodeMatcher val = new CodeMatcher(instructions, (ILGenerator)null).MatchStartForward((CodeMatch[])(object)new CodeMatch[10] { new CodeMatch((OpCode?)OpCodes.Ldarg_0, (object)null, (string)null), new CodeMatch((OpCode?)OpCodes.Ldfld, (object)fieldInfo, (string)null), new CodeMatch((OpCode?)OpCodes.Ldfld, (object)fieldInfo2, (string)null), new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction i) => i.opcode == OpCodes.Brtrue || i.opcode == OpCodes.Brtrue_S), (string)null), new CodeMatch((OpCode?)OpCodes.Call, (object)null, (string)null), new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction i) => i.opcode == OpCodes.Ldc_I4 || i.opcode == OpCodes.Ldc_I4_S), (string)null), new CodeMatch((OpCode?)OpCodes.Callvirt, (object)methodInfo, (string)null), new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction i) => i.opcode == OpCodes.Brtrue || i.opcode == OpCodes.Brtrue_S), (string)null), new CodeMatch((OpCode?)OpCodes.Ldsfld, (object)null, (string)null), new CodeMatch((OpCode?)OpCodes.Ldstr, (object)"\n<color=orange>$item_noteleport</color>", (string)null) }); if (val.IsInvalid) { Logger.LogWarning((object)"AccurateTooltips: couldn't find the teleport line in the item tooltip (game update?). Tooltips will show vanilla text."); return instructions; } object operand = val.InstructionAt(3).operand; val.Advance(1).RemoveInstructions(7).Insert((CodeInstruction[])(object)new CodeInstruction[2] { new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(Tooltips), "ShowNoTeleport", (Type[])null, (Type[])null)), new CodeInstruction(OpCodes.Brfalse, operand) }); return val.InstructionEnumeration(); } } [HarmonyPatch(typeof(InventoryGrid), "UpdateGui")] private static class InventoryGridIconPatch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Expected O, but got Unknown //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Expected O, but got Unknown //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Expected O, but got Unknown //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Expected O, but got Unknown //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Expected O, but got Unknown //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Expected O, but got Unknown //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Expected O, but got Unknown //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Expected O, but got Unknown //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Expected O, but got Unknown //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Expected O, but got Unknown //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Expected O, but got Unknown //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Expected O, but got Unknown //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_0222: Expected O, but got Unknown FieldInfo fieldInfo = AccessTools.Field(typeof(InventoryElement), "m_noteleport"); FieldInfo fieldInfo2 = AccessTools.Field(typeof(ItemData), "m_shared"); FieldInfo fieldInfo3 = AccessTools.Field(typeof(SharedData), "m_teleportable"); MethodInfo methodInfo = AccessTools.Method(typeof(ZoneSystem), "GetGlobalKey", new Type[1] { typeof(GlobalKeys) }, (Type[])null); CodeMatcher val = new CodeMatcher(instructions, (ILGenerator)null).MatchStartForward((CodeMatch[])(object)new CodeMatch[13] { new CodeMatch((OpCode?)OpCodes.Ldfld, (object)fieldInfo, (string)null), new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction i) => CodeInstructionExtensions.IsLdloc(i, (LocalBuilder)null)), (string)null), new CodeMatch((OpCode?)OpCodes.Ldfld, (object)fieldInfo2, (string)null), new CodeMatch((OpCode?)OpCodes.Ldfld, (object)fieldInfo3, (string)null), new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction i) => i.opcode == OpCodes.Brtrue || i.opcode == OpCodes.Brtrue_S), (string)null), new CodeMatch((OpCode?)OpCodes.Call, (object)null, (string)null), new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction i) => i.opcode == OpCodes.Ldc_I4 || i.opcode == OpCodes.Ldc_I4_S), (string)null), new CodeMatch((OpCode?)OpCodes.Callvirt, (object)methodInfo, (string)null), new CodeMatch((OpCode?)OpCodes.Ldc_I4_0, (object)null, (string)null), new CodeMatch((OpCode?)OpCodes.Ceq, (object)null, (string)null), new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction i) => i.opcode == OpCodes.Br || i.opcode == OpCodes.Br_S), (string)null), new CodeMatch((OpCode?)OpCodes.Ldc_I4_0, (object)null, (string)null), new CodeMatch((OpCode?)OpCodes.Callvirt, (object)null, (string)null) }); if (val.IsInvalid) { Logger.LogWarning((object)"AccurateTooltips: couldn't find the no-teleport icon in the inventory grid (game update?). Slot icons will show vanilla state."); return instructions; } val.Advance(2).RemoveInstructions(10).Insert((CodeInstruction[])(object)new CodeInstruction[1] { new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(Tooltips), "ShowNoTeleport", (Type[])null, (Type[])null)) }); return val.InstructionEnumeration(); } } public static bool ShowNoTeleport(ItemData item) { try { return Decide(item); } catch (Exception e) { Failsafe.Report("Teleport tooltip/icon", e); try { return Vanilla(item); } catch { return false; } } } [MethodImpl(MethodImplOptions.NoInlining)] private static bool Decide(ItemData item) { Player localPlayer = Player.m_localPlayer; if (!Settings.Enabled.Value || !Settings.AccurateTooltips.Value || (Object)(object)localPlayer == (Object)null) { return Vanilla(item); } return !Gate.CanTeleport(localPlayer, item); } [MethodImpl(MethodImplOptions.NoInlining)] private static bool Vanilla(ItemData item) { if (!item.m_shared.m_teleportable) { return !Failsafe.TeleportAllSet(); } return false; } }