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 ScrollOfEscape v1.0.0
BepInEx/plugins/ScrollOfEscape/ScrollOfEscape.dll
Decompiled 4 days agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ScrollOfEscape")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+11fde89c7cc8ad9a9b4a9d9be327658002dfaae9")] [assembly: AssemblyProduct("ScrollOfEscape")] [assembly: AssemblyTitle("ScrollOfEscape")] [assembly: AssemblyVersion("1.0.0.0")] namespace ScrollOfEscape; [BepInPlugin("local.valheim.scrollofescape", "Scroll of Escape", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { [HarmonyPatch(typeof(Player), "ConsumeItem")] private static class ConsumePatch { [HarmonyPrefix] private static bool Prefix(Player __instance, Inventory inventory, ItemData item, ref bool __result) { if (item == null || item.m_shared == null || item.m_shared.m_name != "$item_scrollofescape") { return true; } __result = false; try { Recall.TryStart(__instance, inventory); } catch (Exception ex) { Log.LogError((object)("Reading the scroll failed: " + ex)); } return false; } } [HarmonyPatch(typeof(Trader), "GetAvailableItems")] private static class TraderPatch { [HarmonyPostfix] private static void Postfix(Trader __instance, List<TradeItem> __result) { //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Expected O, but got Unknown //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Expected O, but got Unknown if (__result == null || !vendorEnabled.Value || scroll == null || (Object)(object)scroll.ItemDrop == (Object)null) { return; } string text = ((Object)((Component)__instance).gameObject).name; int num = text.IndexOf("(Clone)", StringComparison.Ordinal); if (num >= 0) { text = text.Substring(0, num); } if (!string.Equals(text.Trim(), vendor.Value, StringComparison.OrdinalIgnoreCase)) { return; } string text2 = ((vendorKey.Value == null) ? "" : vendorKey.Value.Trim()); if (text2.Length <= 0 || (!((Object)(object)ZoneSystem.instance == (Object)null) && ZoneSystem.instance.GetGlobalKey(text2))) { if (offer == null) { offer = new TradeItem { m_buyPlayerEffects = new EffectList(), m_tooltip = "", m_name = "" }; } offer.m_prefab = scroll.ItemDrop; offer.m_stack = 1; offer.m_price = vendorPrice.Value; if (!__result.Contains(offer)) { __result.Add(offer); } } } } [HarmonyPatch(typeof(Player), "OnSpawned")] private static class SpawnPatch { [HarmonyPostfix] private static void Postfix(Player __instance) { try { Recall.ShowCooldown(__instance); } catch (Exception ex) { Log.LogWarning((object)("Could not show the scroll cooldown: " + ex.Message)); } } } [HarmonyPatch(typeof(Player), "SetControls")] private static class ControlsPatch { [HarmonyPrefix] private static void Prefix(Player __instance, ref Vector3 movedir, ref bool attack, ref bool attackHold, ref bool secondaryAttack, ref bool secondaryAttackHold, ref bool block, ref bool blockHold, ref bool jump, ref bool crouch, ref bool run, ref bool autoRun, ref bool dodge) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) if (!LockMovement.Value || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || (Object)(object)Recall.ChannelEffect == (Object)null) { return; } RecallEffect recallEffect = Recall.ActiveChannel(__instance); if (!((Object)(object)recallEffect == (Object)null)) { if (jump) { recallEffect.Interrupt("$msg_scrollofescape_cancelled"); } movedir = Vector3.zero; attack = (attackHold = (secondaryAttack = (secondaryAttackHold = false))); block = (blockHold = (jump = (crouch = (run = (autoRun = (dodge = false)))))); } } } public const string PluginGuid = "local.valheim.scrollofescape"; private const string ItemPrefab = "ScrollOfEscape"; private const string FallbackPrefab = "LeatherScraps"; private const string DefaultStation = "Workbench"; private const string DefaultRequirements = "Feathers:2, GreydwarfEye:3, SurtlingCore:1"; internal static Plugin Instance; internal static ManualLogSource Log; internal static ConfigEntry<float> ChannelSeconds; internal static ConfigEntry<float> CooldownMinutes; internal static ConfigEntry<bool> LockMovement; internal static ConfigEntry<bool> CancelOnDamage; internal static ConfigEntry<bool> AllowInDungeons; internal static ConfigEntry<string> ChannelEffects; internal static ConfigEntry<string> DepartEffects; internal static ConfigEntry<string> ChannelEmote; private static ConfigEntry<bool> vendorEnabled; private static ConfigEntry<string> vendor; private static ConfigEntry<int> vendorPrice; private static ConfigEntry<string> vendorKey; private static ConfigEntry<bool> craftable; private static ConfigEntry<string> station; private static ConfigEntry<int> stationLevel; private static ConfigEntry<string> requirements; private static ConfigEntry<int> amount; private static CustomItem scroll; private static TradeItem offer; private static Sprite icon; private static Sprite cooldownIcon; private static string lastRecipeWarning; private void Awake() { //IL_00b4: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; BindConfig(); icon = LoadSprite("scroll.png"); cooldownIcon = LoadSprite("scroll_cooldown.png"); AddLocalization(); AddStatusEffects(); PrefabManager.OnVanillaPrefabsAvailable += AddItem; ItemManager.OnItemsRegistered += ApplyRecipe; SynchronizationManager.OnConfigurationSynchronized += delegate { ApplyRecipe(); }; ((BaseUnityPlugin)this).Config.SettingChanged += delegate { ApplyRecipe(); }; new Harmony("local.valheim.scrollofescape").PatchAll(typeof(Plugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Scroll of Escape loaded."); } private void BindConfig() { vendorEnabled = Bind("Vendor", "Enabled", value: true, "A vendor sells the scroll."); vendor = Bind("Vendor", "Trader", "Haldor", "Which vendor sells it.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "Haldor", "Hildir", "BogWitch" })); vendorPrice = Bind("Vendor", "Price", 100, "Coins per scroll.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 9999)); vendorKey = Bind("Vendor", "RequiredKey", "", "World key needed before the vendor offers it, such as defeated_eikthyr, defeated_gdking or defeated_bonemass. Empty sells it from the start."); craftable = Bind("Recipe", "Enabled", value: false, "The scroll can also be crafted with the recipe below."); station = Bind("Recipe", "CraftingStation", "Workbench", "Where the scroll is crafted. None crafts it from the inventory.", (AcceptableValueBase)(object)CraftingStations.GetAcceptableValueList()); stationLevel = Bind("Recipe", "StationLevel", 2, "Crafting station level required.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10)); requirements = Bind("Recipe", "Requirements", "Feathers:2, GreydwarfEye:3, SurtlingCore:1", "Comma-separated PrefabName:Amount pairs. An invalid list falls back to the default."); amount = Bind("Recipe", "Amount", 1, "Scrolls made per craft.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 50)); ChannelSeconds = Bind("Recall", "ChannelSeconds", 10f, "Seconds the scroll takes before teleporting. 0 teleports at once.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 60f)); CooldownMinutes = Bind("Recall", "CooldownMinutes", 10f, "Real-time minutes before another scroll can be read. 0 disables the cooldown.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1440f)); LockMovement = Bind("Recall", "LockMovement", value: true, "Hold the player in place while recalling; jump or read the scroll again to cancel. When false, moving cancels the recall instead."); CancelOnDamage = Bind("Recall", "CancelOnDamage", value: true, "Taking damage during the channel interrupts it."); AllowInDungeons = Bind("Recall", "AllowInDungeons", value: true, "Scrolls work inside dungeons and other interiors."); ChannelEffects = ((BaseUnityPlugin)this).Config.Bind<string>("Effects", "ChannelEffects", "fx_GP_Activation, vfx_Potion_eitr_minor", "Comma-separated vanilla effect prefabs attached to the player while recalling. They must be networked effects (vfx_/fx_ prefabs); unknown names are skipped."); DepartEffects = ((BaseUnityPlugin)this).Config.Bind<string>("Effects", "DepartEffects", "vfx_odin_despawn", "Comma-separated vanilla effect prefabs played where the player vanishes."); ChannelEmote = ((BaseUnityPlugin)this).Config.Bind<string>("Effects", "ChannelEmote", "kneel", "Emote played when the recall starts, such as kneel, bow or cheer. Empty for none."); } private ConfigEntry<T> Bind<T>(string section, string key, T value, string description, AcceptableValueBase range = null) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown return ((BaseUnityPlugin)this).Config.Bind<T>(section, key, value, new ConfigDescription(description, range, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); } private static Sprite LoadSprite(string file) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) using Stream stream = typeof(Plugin).Assembly.GetManifestResourceStream("ScrollOfEscape." + file); using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); Texture2D val = AssetUtils.LoadImage(memoryStream.ToArray()); return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f)); } private static void AddLocalization() { CustomLocalization localization = LocalizationManager.Instance.GetLocalization(); foreach (KeyValuePair<string, Dictionary<string, string>> item in Translations.All) { string key = item.Key; localization.AddTranslation(ref key, item.Value); } } private static void AddStatusEffects() { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected O, but got Unknown //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O, but got Unknown RecallEffect recallEffect = ScriptableObject.CreateInstance<RecallEffect>(); ((Object)recallEffect).name = "SE_ScrollOfEscape_Recall"; ((StatusEffect)recallEffect).m_name = "$se_scrollofescape_recall"; ((StatusEffect)recallEffect).m_tooltip = "$se_scrollofescape_recall_tooltip"; ((StatusEffect)recallEffect).m_icon = icon; ((StatusEffect)recallEffect).m_ttl = 10f; StatusEffect val = ScriptableObject.CreateInstance<StatusEffect>(); ((Object)val).name = "SE_ScrollOfEscape_Cooldown"; val.m_name = "$se_scrollofescape_cooldown"; val.m_tooltip = "$se_scrollofescape_cooldown_tooltip"; val.m_icon = cooldownIcon; val.m_ttl = 60f; ItemManager.Instance.AddStatusEffect(new CustomStatusEffect((StatusEffect)(object)recallEffect, false)); ItemManager.Instance.AddStatusEffect(new CustomStatusEffect(val, false)); Recall.ChannelEffect = (StatusEffect)(object)recallEffect; Recall.CooldownEffect = val; } private void AddItem() { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Expected O, but got Unknown PrefabManager.OnVanillaPrefabsAvailable -= AddItem; UseVanillaIcons(); bool flag = (Object)(object)PrefabManager.Instance.GetPrefab("MooseHide") != (Object)null; ItemConfig val = new ItemConfig(); val.Name = "$item_scrollofescape"; val.Description = "$item_scrollofescape_desc"; val.Icons = (Sprite[])(object)new Sprite[1] { icon }; val.CraftingStation = CraftingStations.GetInternalName("Workbench"); val.MinStationLevel = 2; val.Amount = 1; val.StackSize = 10; val.Weight = 0.5f; ItemConfig val2 = val; val2.AddRequirement("Feathers", 2, 0); string text = (flag ? "MooseHide" : "LeatherScraps"); scroll = new CustomItem("ScrollOfEscape", text, val2); if ((Object)(object)scroll.ItemDrop == (Object)null) { ((BaseUnityPlugin)this).Logger.LogError((object)("Base item " + text + " is missing; the scroll was not added.")); return; } if (flag) { try { VanillaArt.TintModel(scroll.ItemPrefab); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Dropped scroll keeps the hide's color: " + ex.Message)); } } SharedData shared = scroll.ItemDrop.m_itemData.m_shared; shared.m_itemType = (ItemType)2; shared.m_food = 0f; shared.m_foodStamina = 0f; shared.m_foodEitr = 0f; shared.m_consumeStatusEffect = null; shared.m_teleportable = true; shared.m_questItem = false; shared.m_value = 0; ItemManager.Instance.AddItem(scroll); } private void UseVanillaIcons() { try { if (!VanillaArt.TryCreateIcons(out var val, out var cooldown)) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Icon MooseHide was not found; using the built-in scroll icon."); return; } icon = val; Recall.ChannelEffect.m_icon = val; Recall.CooldownEffect.m_icon = cooldown; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not recolor the MooseHide icon; using the built-in scroll icon. " + ex.Message)); } } private static void ApplyRecipe() { Recipe val = ((scroll == null || scroll.Recipe == null) ? null : scroll.Recipe.Recipe); ObjectDB instance = ObjectDB.instance; if ((Object)(object)val == (Object)null || (Object)(object)instance == (Object)null) { return; } List<string> list = new List<string>(); string internalName = CraftingStations.GetInternalName(station.Value); CraftingStation val2 = null; if (!string.IsNullOrEmpty(internalName)) { val2 = Cache.GetPrefab<CraftingStation>(internalName); if ((Object)(object)val2 == (Object)null) { list.Add("crafting station " + station.Value + " was not found; using the Workbench"); val2 = Cache.GetPrefab<CraftingStation>(CraftingStations.GetInternalName("Workbench")); } } string error; Requirement[] array = BuildRequirements(requirements.Value, instance, out error); if (array == null) { list.Add("requirements " + error + "; using Feathers:2, GreydwarfEye:3, SurtlingCore:1"); array = BuildRequirements("Feathers:2, GreydwarfEye:3, SurtlingCore:1", instance, out var _); } val.m_enabled = craftable.Value; val.m_craftingStation = val2; val.m_minStationLevel = stationLevel.Value; val.m_amount = amount.Value; if (array != null) { val.m_resources = array; } string text = ((list.Count == 0) ? null : ("Scroll of Escape recipe: " + string.Join("; ", list) + ".")); if (text != null && text != lastRecipeWarning) { Log.LogWarning((object)text); } lastRecipeWarning = text; } private static Requirement[] BuildRequirements(string text, ObjectDB db, out string error) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected O, but got Unknown if (!RecallRules.TryParseRequirements(text, out var result, out error)) { return null; } Requirement[] array = (Requirement[])(object)new Requirement[result.Count]; for (int i = 0; i < result.Count; i++) { GameObject itemPrefab = db.GetItemPrefab(result[i].Key); ItemDrop val = (((Object)(object)itemPrefab == (Object)null) ? null : itemPrefab.GetComponent<ItemDrop>()); if ((Object)(object)val == (Object)null) { error = "name an unknown item '" + result[i].Key + "'"; return null; } array[i] = new Requirement { m_resItem = val, m_amount = result[i].Value, m_recover = true }; } return array; } } internal static class Recall { internal const string ItemToken = "$item_scrollofescape"; private const string CooldownKey = "ScrollOfEscape.ReadyAtUtcTicks"; private const float MoveLimit = 1f; internal static StatusEffect ChannelEffect; internal static StatusEffect CooldownEffect; private static readonly Action<Player> StopEmote = AccessTools.MethodDelegate<Action<Player>>(AccessTools.Method(typeof(Player), "StopEmote", (Type[])null, (Type[])null), (object)null, true); private static readonly FieldRef<Player, bool> AutoRun = AccessTools.FieldRefAccess<Player, bool>("m_autoRun"); private static readonly Dictionary<long, long> SessionReadyAt = new Dictionary<long, long>(); private static readonly HashSet<string> WarnedEffects = new HashSet<string>(); private static double CooldownSeconds => (double)Plugin.CooldownMinutes.Value * 60.0; internal static void TryStart(Player player, Inventory inventory) { if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || ((Character)player).IsDead() || ((Character)player).IsTeleporting()) { return; } RecallEffect recallEffect = ActiveChannel(player); if ((Object)(object)recallEffect != (Object)null) { recallEffect.Interrupt("$msg_scrollofescape_cancelled"); return; } if (inventory != ((Humanoid)player).GetInventory()) { Say(player, "$msg_scrollofescape_inventory"); return; } double num = CooldownRemaining(player); if (num > 0.0) { Say(player, "$msg_scrollofescape_cooldown " + RecallRules.FormatDuration(num)); return; } string text = Blocker(player); if (text != null) { Say(player, text); return; } float value = Plugin.ChannelSeconds.Value; if (!(value > 0f) || float.IsInfinity(value)) { Complete(player); return; } ChannelEffect.m_startEffects = Effects(Plugin.ChannelEffects.Value, attach: true); StatusEffect val = seman(player).AddStatusEffect(ChannelEffect, false, 0, 0f, (short)(-1)); if (!((Object)(object)val == (Object)null)) { val.m_ttl = value; AutoRun.Invoke(player) = false; string value2 = Plugin.ChannelEmote.Value; if (!string.IsNullOrWhiteSpace(value2)) { player.StartEmote(value2.Trim().ToLowerInvariant(), true); } Say(player, Plugin.LockMovement.Value ? "$msg_scrollofescape_start_locked" : "$msg_scrollofescape_start_move"); } } internal static RecallEffect ActiveChannel(Player player) { RecallEffect recallEffect = seman(player).GetStatusEffect(ChannelEffect.NameHash()) as RecallEffect; if (!((Object)(object)recallEffect != (Object)null) || ((StatusEffect)recallEffect).IsDone()) { return null; } return recallEffect; } private static string Blocker(Player player) { if (((Character)player).IsAttached()) { return "$msg_scrollofescape_busy"; } PlayerProfile val = (((Object)(object)Game.instance == (Object)null) ? null : Game.instance.GetPlayerProfile()); if (val == null || !val.HaveCustomSpawnPoint()) { return "$msg_scrollofescape_nobed"; } ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null) { return "$msg_scrollofescape_busy"; } if (instance.GetGlobalKey((GlobalKeys)32)) { return "$msg_blocked"; } float num = default(float); if (instance.GetGlobalKey((GlobalKeys)33) && (((Object)(object)RandEventSystem.instance != (Object)null && RandEventSystem.instance.GetBossEvent() != null) || (instance.GetGlobalKey((GlobalKeys)47, ref num) && num > 0f))) { return "$msg_blockedbyboss"; } if (!((Humanoid)player).IsTeleportable(false)) { return "$msg_noteleport"; } if (!Plugin.AllowInDungeons.Value && ((Character)player).InInterior()) { return "$msg_scrollofescape_dungeon"; } return null; } internal static void Complete(Player player) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_007e: 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_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || ((Character)player).IsDead()) { return; } string text = Blocker(player); if (text != null) { Say(player, text); return; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory.CountItems("$item_scrollofescape", -1, false) < 1) { Say(player, "$msg_scrollofescape_inventory"); return; } Vector3 position = ((Component)player).transform.position; Quaternion rotation = ((Component)player).transform.rotation; Vector3 customSpawnPoint = Game.instance.GetPlayerProfile().GetCustomSpawnPoint(); if (!((Character)player).TeleportTo(customSpawnPoint, rotation, true)) { Say(player, "$msg_scrollofescape_busy"); return; } Effects(Plugin.DepartEffects.Value, attach: false).Create(position, rotation, (Transform)null, 1f, -1, default(ZDOID)); inventory.RemoveItem("$item_scrollofescape", 1, -1, false); StartCooldown(player); if ((Object)(object)Plugin.Instance != (Object)null) { ((MonoBehaviour)Plugin.Instance).StartCoroutine(CheckBedAfterArrival(player)); } } private static IEnumerator CheckBedAfterArrival(Player player) { while ((Object)(object)player != (Object)null && ((Character)player).IsTeleporting()) { yield return null; } if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || ((Character)player).IsDead()) { yield break; } Bed[] array = Object.FindObjectsByType<Bed>((FindObjectsSortMode)0); for (int i = 0; i < array.Length; i++) { if (array[i].IsCurrent()) { yield break; } } Game.instance.GetPlayerProfile().ClearCustomSpawnPoint(); Say(player, "$msg_scrollofescape_bedgone"); } private static EffectList Effects(string names, bool attach) { List<string> list = new List<string>(); EffectList result = VanillaArt.Effects(names, attach, list); foreach (string item in list) { if (WarnedEffects.Add(item)) { Plugin.Log.LogWarning((object)("Effect " + item + " was not found" + (attach ? " or cannot be attached" : "") + "; skipping it.")); } } return result; } private static double CooldownRemaining(Player player) { string value; long val = (player.m_customData.TryGetValue("ScrollOfEscape.ReadyAtUtcTicks", out value) ? RecallRules.ParseTicks(value) : 0); SessionReadyAt.TryGetValue(PlayerId(), out var value2); return RecallRules.CooldownRemaining(Math.Max(val, value2), DateTime.UtcNow.Ticks, CooldownSeconds); } private static void StartCooldown(Player player) { double cooldownSeconds = CooldownSeconds; if (cooldownSeconds > 0.0 && !double.IsInfinity(cooldownSeconds)) { long value = DateTime.UtcNow.Ticks + (long)(cooldownSeconds * 10000000.0); player.m_customData["ScrollOfEscape.ReadyAtUtcTicks"] = value.ToString(CultureInfo.InvariantCulture); SessionReadyAt[PlayerId()] = value; ShowCooldown(player, cooldownSeconds); } } internal static void ShowCooldown(Player player) { if ((Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer) { ShowCooldown(player, CooldownRemaining(player)); } } private static void ShowCooldown(Player player, double seconds) { float ttl = ((seconds > 0.0) ? ((float)seconds) : 0.001f); StatusEffect statusEffect = seman(player).GetStatusEffect(CooldownEffect.NameHash()); if ((Object)(object)statusEffect != (Object)null) { statusEffect.m_ttl = ttl; statusEffect.ResetTime(); } else if (seconds > 0.0) { statusEffect = seman(player).AddStatusEffect(CooldownEffect, false, 0, 0f, (short)(-1)); if ((Object)(object)statusEffect != (Object)null) { statusEffect.m_ttl = ttl; } } } internal static void EndEmote(Player player) { if ((Object)(object)player != (Object)null && ((Character)player).InEmote()) { StopEmote(player); } } private static SEMan seman(Player player) { return ((Character)player).GetSEMan(); } private static long PlayerId() { PlayerProfile val = (((Object)(object)Game.instance == (Object)null) ? null : Game.instance.GetPlayerProfile()); if (val != null) { return val.GetPlayerID(); } return 0L; } private static void Say(Player player, string message) { ((Character)player).Message((MessageType)2, message, 0, (Sprite)null, false); } internal static bool IsMoved(Vector3 from, Vector3 to) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: 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_0014: Unknown result type (might be due to invalid IL or missing references) Vector3 val = to - from; return RecallRules.Moved(val.x, val.y, val.z, 1f); } } public sealed class RecallEffect : StatusEffect { private Vector3 start; private bool finished; public override void Setup(Character character) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) ((StatusEffect)this).Setup(character); start = ((Component)character).transform.position; character.m_onDamaged = (Action<float, Character>)Delegate.Combine(character.m_onDamaged, new Action<float, Character>(OnDamaged)); } public override void Stop() { if ((Object)(object)base.m_character != (Object)null) { Character character = base.m_character; character.m_onDamaged = (Action<float, Character>)Delegate.Remove(character.m_onDamaged, new Action<float, Character>(OnDamaged)); Character character2 = base.m_character; Recall.EndEmote((Player)(object)((character2 is Player) ? character2 : null)); } ((StatusEffect)this).Stop(); } private void OnDamaged(float damage, Character attacker) { if (damage > 0f && Plugin.CancelOnDamage.Value) { Interrupt("$msg_scrollofescape_interrupted"); } } public override void UpdateStatusEffect(float dt) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) ((StatusEffect)this).UpdateStatusEffect(dt); if (!finished) { Character character = base.m_character; Player val = (Player)(object)((character is Player) ? character : null); if ((Object)(object)val == (Object)null || (Object)(object)val != (Object)(object)Player.m_localPlayer || ((Character)val).IsDead()) { finished = true; } else if (!Plugin.LockMovement.Value && Recall.IsMoved(start, ((Component)val).transform.position)) { Interrupt("$msg_scrollofescape_interrupted"); } else if (base.m_time >= base.m_ttl) { finished = true; Recall.Complete(val); } } } internal void Interrupt(string message) { if (!finished) { finished = true; if ((Object)(object)base.m_character != (Object)null) { base.m_character.Message((MessageType)2, message, 0, (Sprite)null, false); } } } public override bool IsDone() { if (!finished) { return ((StatusEffect)this).IsDone(); } return true; } } internal static class RecallRules { internal static double CooldownRemaining(long readyAtTicks, long nowTicks, double cooldownSeconds) { if (!(cooldownSeconds > 0.0) || double.IsInfinity(cooldownSeconds)) { return 0.0; } double num = ((double)readyAtTicks - (double)nowTicks) / 10000000.0; if (!(num > 0.0)) { return 0.0; } return Math.Min(num, cooldownSeconds); } internal static long ParseTicks(string value) { if (!long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return 0L; } return result; } internal static string FormatDuration(double seconds) { int num = ((seconds > 0.0) ? ((int)Math.Ceiling(Math.Min(seconds, 2147483647.0))) : 0); if (num < 60) { return num + "s"; } if (num < 3600) { if (num % 60 != 0) { return num / 60 + "m " + num % 60 + "s"; } return num / 60 + "m"; } return num / 3600 + "h " + num % 3600 / 60 + "m"; } internal static bool TryParseRequirements(string text, out List<KeyValuePair<string, int>> result, out string error) { result = new List<KeyValuePair<string, int>>(); error = null; if (string.IsNullOrWhiteSpace(text)) { error = "the list is empty"; return false; } string[] array = text.Split(','); foreach (string obj in array) { string text2 = obj.Trim(); if (text2.Length != 0) { int num = text2.IndexOf(':'); string name = ((num < 0) ? text2 : text2.Substring(0, num)).Trim(); int result2 = 1; bool flag = num < 0 || int.TryParse(text2.Substring(num + 1).Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result2); if (name.Length == 0 || name.IndexOf(' ') >= 0 || !flag || result2 < 1 || result2 > 9999) { error = "'" + text2 + "'"; return false; } int num2 = result.FindIndex((KeyValuePair<string, int> pair) => pair.Key == name); if (num2 < 0) { result.Add(new KeyValuePair<string, int>(name, result2)); } else { result[num2] = new KeyValuePair<string, int>(name, result[num2].Value + result2); } } } if (result.Count == 0) { error = "the list is empty"; return false; } return true; } internal static bool Moved(float dx, float dy, float dz, float limit) { return !(dx * dx + dy * dy + dz * dz <= limit * limit); } } internal static class IconRecolor { private static readonly float[] Dark = new float[3] { 0.3f, 0.21f, 0.13f }; private static readonly float[] Mid = new float[3] { 0.72f, 0.61f, 0.45f }; private static readonly float[] Light = new float[3] { 0.97f, 0.92f, 0.8f }; private static readonly float[] Glow = new float[3] { 0.3f, 0.95f, 0.82f }; internal static byte[] Scroll(byte[] rgba, int width, int height) { byte[] array = new byte[rgba.Length]; for (int i = 0; i < rgba.Length; i += 4) { float num = Luminance(rgba, i); float[] array2 = ((num < 0.5f) ? Dark : Mid); float[] array3 = ((num < 0.5f) ? Mid : Light); float num2 = ((num < 0.5f) ? (num * 2f) : ((num - 0.5f) * 2f)); for (int j = 0; j < 3; j++) { array[i + j] = ToByte(array2[j] + (array3[j] - array2[j]) * num2); } array[i + 3] = rgba[i + 3]; } AddGlow(array, width, height, 6); return array; } internal static byte[] Cooldown(byte[] rgba) { byte[] array = new byte[rgba.Length]; for (int i = 0; i < rgba.Length; i += 4) { byte b = ToByte(0.15f + Luminance(rgba, i) * 0.45f); array[i] = (array[i + 1] = (array[i + 2] = b)); array[i + 3] = rgba[i + 3]; } return array; } private static float Luminance(byte[] rgba, int i) { return (0.299f * (float)(int)rgba[i] + 0.587f * (float)(int)rgba[i + 1] + 0.114f * (float)(int)rgba[i + 2]) / 255f; } private static void AddGlow(byte[] rgba, int width, int height, int radius) { byte[] array = new byte[width * height]; for (int i = 0; i < array.Length; i++) { array[i] = rgba[i * 4 + 3]; } for (int j = 0; j < height; j++) { for (int k = 0; k < width; k++) { int num = j * width + k; if (array[num] >= 250) { continue; } float num2 = float.MaxValue; for (int l = -radius; l <= radius; l++) { for (int m = -radius; m <= radius; m++) { int num3 = k + m; int num4 = j + l; if (num3 >= 0 && num4 >= 0 && num3 < width && num4 < height && array[num4 * width + num3] >= 128) { num2 = Math.Min(num2, m * m + l * l); } } } if (num2 == float.MaxValue) { continue; } float num5 = 1f - (float)Math.Sqrt(num2) / (float)(radius + 1); float num6 = num5 * num5 * 0.85f; float num7 = (float)(int)array[num] / 255f; float num8 = num7 + num6 * (1f - num7); if (!(num8 <= 0f)) { int num9 = num * 4; for (int n = 0; n < 3; n++) { rgba[num9 + n] = ToByte(((float)(int)rgba[num9 + n] / 255f * num7 + Glow[n] * num6 * (1f - num7)) / num8); } rgba[num9 + 3] = ToByte(num8); } } } } private static byte ToByte(float value) { return (byte)Math.Round(Math.Max(0f, Math.Min(1f, value)) * 255f); } } internal static class VanillaArt { internal const string SourceItem = "MooseHide"; private static readonly Color Parchment = new Color(0.96f, 0.86f, 0.66f); internal static bool TryCreateIcons(out Sprite scroll, out Sprite cooldown) { scroll = (cooldown = null); Sprite prefab = Cache.GetPrefab<Sprite>("MooseHide"); if ((Object)(object)prefab == (Object)null || (Object)(object)prefab.texture == (Object)null) { return false; } int width; int height; byte[] array = ReadPixels(prefab, out width, out height); if (array == null || array.Length != width * height * 4) { return false; } scroll = ToSprite(IconRecolor.Scroll(array, width, height), width, height, "ScrollOfEscape_icon"); cooldown = ToSprite(IconRecolor.Cooldown(array), width, height, "ScrollOfEscape_cooldown_icon"); return true; } private static byte[] ReadPixels(Sprite sprite, out int width, out int height) { //IL_0008: 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_0095: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Expected O, but got Unknown Texture2D texture = sprite.texture; Rect textureRect = sprite.textureRect; width = Mathf.RoundToInt(((Rect)(ref textureRect)).width); height = Mathf.RoundToInt(((Rect)(ref textureRect)).height); if (width <= 0 || height <= 0) { return null; } RenderTexture temporary = RenderTexture.GetTemporary(width, height, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)2); RenderTexture active = RenderTexture.active; try { Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(((Rect)(ref textureRect)).width / (float)((Texture)texture).width, ((Rect)(ref textureRect)).height / (float)((Texture)texture).height); Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(((Rect)(ref textureRect)).x / (float)((Texture)texture).width, ((Rect)(ref textureRect)).y / (float)((Texture)texture).height); Graphics.Blit((Texture)(object)texture, temporary, val, val2); RenderTexture.active = temporary; Texture2D val3 = new Texture2D(width, height, (TextureFormat)4, false); val3.ReadPixels(new Rect(0f, 0f, (float)width, (float)height), 0, 0); val3.Apply(); byte[] rawTextureData = val3.GetRawTextureData(); Object.Destroy((Object)val3); return rawTextureData; } finally { RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); } } private static Sprite ToSprite(byte[] rgba, int width, int height, string name) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0010: 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_002b: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown Texture2D val = new Texture2D(width, height, (TextureFormat)4, false) { name = name }; val.LoadRawTextureData(rgba); val.Apply(); Sprite obj = Sprite.Create(val, new Rect(0f, 0f, (float)width, (float)height), new Vector2(0.5f, 0.5f)); ((Object)obj).name = name; return obj; } internal static void TintModel(GameObject prefab) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) Renderer[] componentsInChildren = prefab.GetComponentsInChildren<Renderer>(true); foreach (Renderer val in componentsInChildren) { Material[] sharedMaterials = val.sharedMaterials; for (int j = 0; j < sharedMaterials.Length; j++) { if (!((Object)(object)sharedMaterials[j] == (Object)null) && sharedMaterials[j].HasProperty("_Color")) { Material val2 = new Material(sharedMaterials[j]); val2.color = sharedMaterials[j].color * Parchment; sharedMaterials[j] = val2; } } val.sharedMaterials = sharedMaterials; } } internal static EffectList Effects(string names, bool attach, List<string> missing) { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Expected O, but got Unknown //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Expected O, but got Unknown List<EffectData> list = new List<EffectData>(); string[] array = (names ?? "").Split(','); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0) { GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(text) : null); if ((Object)(object)val == (Object)null) { val = Cache.GetPrefab<GameObject>(text); } if ((Object)(object)val == (Object)null || (attach && (Object)(object)val.GetComponent<ZNetView>() == (Object)null)) { missing.Add(text); continue; } list.Add(new EffectData { m_prefab = val, m_enabled = true, m_attach = attach }); } } return new EffectList { m_effectPrefabs = list.ToArray() }; } } internal static class Translations { internal static readonly Dictionary<string, Dictionary<string, string>> All = new Dictionary<string, Dictionary<string, string>> { { "English", new Dictionary<string, string> { { "item_scrollofescape", "Scroll of Escape" }, { "item_scrollofescape_desc", "Read it to return to your bed. Ore and anything else a portal refuses must stay behind." }, { "se_scrollofescape_recall", "Recalling" }, { "se_scrollofescape_recall_tooltip", "Returning to your bed. Read the scroll again to cancel." }, { "se_scrollofescape_cooldown", "Recall weariness" }, { "se_scrollofescape_cooldown_tooltip", "You cannot read another Scroll of Escape yet." }, { "msg_scrollofescape_start_locked", "The scroll begins to glow. Jump to cancel" }, { "msg_scrollofescape_start_move", "The scroll begins to glow. Stand still" }, { "msg_scrollofescape_interrupted", "Recall interrupted" }, { "msg_scrollofescape_cancelled", "Recall cancelled" }, { "msg_scrollofescape_cooldown", "You can read another scroll in" }, { "msg_scrollofescape_nobed", "You have no bed to return to" }, { "msg_scrollofescape_bedgone", "Your bed is gone" }, { "msg_scrollofescape_inventory", "The scroll must be in your inventory" }, { "msg_scrollofescape_dungeon", "The scroll's magic cannot reach you here" }, { "msg_scrollofescape_busy", "You cannot read the scroll right now" } } }, { "Spanish", new Dictionary<string, string> { { "item_scrollofescape", "Pergamino de escape" }, { "item_scrollofescape_desc", "Léelo para volver a tu cama. El mineral y todo lo que un portal no deja pasar debe quedarse atrás." }, { "se_scrollofescape_recall", "Regresando" }, { "se_scrollofescape_recall_tooltip", "Volviendo a tu cama. Lee el pergamino otra vez para cancelar." }, { "se_scrollofescape_cooldown", "Fatiga del regreso" }, { "se_scrollofescape_cooldown_tooltip", "Todavía no puedes leer otro Pergamino de escape." }, { "msg_scrollofescape_start_locked", "El pergamino empieza a brillar. Salta para cancelar" }, { "msg_scrollofescape_start_move", "El pergamino empieza a brillar. No te muevas" }, { "msg_scrollofescape_interrupted", "Regreso interrumpido" }, { "msg_scrollofescape_cancelled", "Regreso cancelado" }, { "msg_scrollofescape_cooldown", "Podrás leer otro pergamino en" }, { "msg_scrollofescape_nobed", "No tienes una cama a la que volver" }, { "msg_scrollofescape_bedgone", "Tu cama ha desaparecido" }, { "msg_scrollofescape_inventory", "El pergamino debe estar en tu inventario" }, { "msg_scrollofescape_dungeon", "La magia del pergamino no puede alcanzarte aquí" }, { "msg_scrollofescape_busy", "Ahora no puedes leer el pergamino" } } } }; }