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 UsefulRunestones v1.0.0
UsefulRunestones.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using ServerSync; using Splatform; using TMPro; using UnityEngine; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Core.ObjectPool; using YamlDotNet.Core.Tokens; using YamlDotNet.Helpers; using YamlDotNet.Serialization; using YamlDotNet.Serialization.BufferedDeserialization; using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators; using YamlDotNet.Serialization.Callbacks; using YamlDotNet.Serialization.Converters; using YamlDotNet.Serialization.EventEmitters; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NodeDeserializers; using YamlDotNet.Serialization.NodeTypeResolvers; using YamlDotNet.Serialization.ObjectFactories; using YamlDotNet.Serialization.ObjectGraphTraversalStrategies; using YamlDotNet.Serialization.ObjectGraphVisitors; using YamlDotNet.Serialization.Schemas; using YamlDotNet.Serialization.TypeInspectors; using YamlDotNet.Serialization.TypeResolvers; using YamlDotNet.Serialization.Utilities; using YamlDotNet.Serialization.ValueDeserializers; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("UsefulRunestones")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("sighsorry")] [assembly: AssemblyProduct("UsefulRunestones")] [assembly: AssemblyCopyright("Copyright (c) 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("B89A0DCE-224B-4815-9A25-BB6FBE6B15BF")] [assembly: AssemblyFileVersion("1.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 UsefulRunestones { [BepInPlugin("sighsorry.UsefulRunestones", "UsefulRunestones", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class UsefulRunestonesPlugin : BaseUnityPlugin { public enum Toggle { On = 1, Off = 0 } private sealed class ConfigurationManagerAttributes { public int? Order; } internal const string ModName = "UsefulRunestones"; internal const string ModVersion = "1.0.0"; internal const string Author = "sighsorry"; internal const string ModGUID = "sighsorry.UsefulRunestones"; internal const string YamlFileName = "UsefulRunestones.yml"; private const float FileReloadDebounceSeconds = 0.25f; internal static readonly ManualLogSource UsefulRunestonesLogger = Logger.CreateLogSource("UsefulRunestones"); private static readonly ConfigSync Sync = new ConfigSync("sighsorry.UsefulRunestones") { DisplayName = "UsefulRunestones", CurrentVersion = "1.0.0", MinimumRequiredVersion = "1.0.0" }; private readonly Harmony _harmony = new Harmony("sighsorry.UsefulRunestones"); private CustomSyncedValue<string> _syncedYaml; private FileSystemWatcher? _watcher; private float _reloadDueAt = -1f; private ConfigEntry<Toggle> _lockConfiguration; private ConfigEntry<Toggle> _enableRunestoneGlobalPins; private ConfigEntry<Toggle> _enableVegvisirGlobalEffects; internal static UsefulRunestonesPlugin? Instance { get; private set; } internal static string ConfigDirectoryPath => Paths.ConfigPath; internal static string YamlFilePath => Path.Combine(ConfigDirectoryPath, "UsefulRunestones.yml"); internal static bool IsSourceOfTruth => Sync.IsSourceOfTruth; internal static bool IsRunestoneGlobalPinsEnabled() { UsefulRunestonesPlugin? instance = Instance; if (instance == null) { return true; } return instance._enableRunestoneGlobalPins.Value != Toggle.Off; } internal static bool IsVegvisirGlobalEffectsEnabled() { UsefulRunestonesPlugin? instance = Instance; if (instance == null) { return true; } return instance._enableVegvisirGlobalEffects.Value != Toggle.Off; } private void Awake() { Instance = this; Directory.CreateDirectory(ConfigDirectoryPath); EnsureDefaultYamlExists(); BindConfiguration(); _syncedYaml = new CustomSyncedValue<string>(Sync, "configuration-yaml", "", 50); _syncedYaml.ValueChanged += HandleSyncedYamlChanged; Sync.SourceOfTruthChanged += HandleSourceOfTruthChanged; LoadLocalYamlAndPublish("startup"); _harmony.PatchAll(typeof(UsefulRunestonesPlugin).Assembly); InitializeWatcher(); ((BaseUnityPlugin)this).Config.Save(); } private void Update() { UsefulRunestonesRuntime.EnsureRunestoneGlobalPinRpcRegistered(); ProcessQueuedYamlReload(); } private void OnDestroy() { if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } _syncedYaml.ValueChanged -= HandleSyncedYamlChanged; Sync.SourceOfTruthChanged -= HandleSourceOfTruthChanged; _watcher?.Dispose(); _watcher = null; _harmony.UnpatchSelf(); ((BaseUnityPlugin)this).Config.Save(); } private void BindConfiguration() { bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet; ((BaseUnityPlugin)this).Config.SaveOnConfigSet = false; try { _lockConfiguration = BindConfigEntry("1 - General", "Lock Configuration", Toggle.On, "If on, synced configuration can be changed by server admins only.", synchronizedSetting: true, 300); _enableRunestoneGlobalPins = BindConfigEntry("1 - General", "Enable Runestone Global Pins", Toggle.On, "If on, pinless RuneStones can reveal one saved map pin from UsefulRunestones.yml runestoneGlobalPins rows. The selected target rolls once per loaded RuneStone instance, and zone unload/reload can roll again.", synchronizedSetting: true, 200); _enableVegvisirGlobalEffects = BindConfigEntry("1 - General", "Enable Vegvisir Global Effects", Toggle.On, "If on, Vegvisirs can grant weighted status effects from UsefulRunestones.yml vegvisirGlobalEffects rows when a Vegvisir interaction succeeds. The selected effect, optional shared visual effect, and per-player cooldowns live on the loaded Vegvisir instance and reset when it unloads.", synchronizedSetting: true, 100); Sync.AddLockingConfigEntry<Toggle>(_lockConfiguration); } finally { ((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet; } } internal ConfigEntry<T> BindConfigEntry<T>(string group, string name, T value, string description, bool synchronizedSetting = true, int? configManagerOrder = null) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown ConfigDescription val = new ConfigDescription(description + (synchronizedSetting ? " [Synced with Server]" : " [Not Synced with Server]"), (AcceptableValueBase)null, BuildConfigDescriptionTags(configManagerOrder)); ConfigEntry<T> val2 = ((BaseUnityPlugin)this).Config.Bind<T>(group, name, value, val); Sync.AddConfigEntry<T>(val2).SynchronizedConfig = synchronizedSetting; return val2; } private static object[] BuildConfigDescriptionTags(int? configManagerOrder) { if (!configManagerOrder.HasValue) { return Array.Empty<object>(); } return new object[1] { new ConfigurationManagerAttributes { Order = configManagerOrder.Value } }; } private void EnsureDefaultYamlExists() { if (!File.Exists(YamlFilePath)) { File.WriteAllText(YamlFilePath, UsefulRunestonesConfiguration.BuildDefaultYaml()); UsefulRunestonesLogger.LogInfo((object)("Created " + YamlFilePath + ".")); } } private void InitializeWatcher() { _watcher = new FileSystemWatcher(ConfigDirectoryPath, "UsefulRunestones.yml") { IncludeSubdirectories = false, NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.CreationTime) }; _watcher.Changed += QueueYamlReload; _watcher.Created += QueueYamlReload; _watcher.Renamed += QueueYamlReload; _watcher.EnableRaisingEvents = true; } private void QueueYamlReload(object sender, FileSystemEventArgs args) { if (IsSourceOfTruth) { _reloadDueAt = Time.realtimeSinceStartup + 0.25f; } } private void ProcessQueuedYamlReload() { if (!(_reloadDueAt < 0f) && !(Time.realtimeSinceStartup < _reloadDueAt)) { _reloadDueAt = -1f; LoadLocalYamlAndPublish("file change"); } } private void HandleSourceOfTruthChanged(bool sourceOfTruth) { if (sourceOfTruth) { LoadLocalYamlAndPublish("authority change"); } else { ApplyYaml(_syncedYaml.Value ?? "", "server sync"); } } private void HandleSyncedYamlChanged() { if (!IsSourceOfTruth) { ApplyYaml(_syncedYaml.Value ?? "", "server sync"); } } private void LoadLocalYamlAndPublish(string source) { EnsureDefaultYamlExists(); string text; try { text = File.ReadAllText(YamlFilePath); } catch (Exception ex) { UsefulRunestonesLogger.LogError((object)("Failed to read " + YamlFilePath + ". " + ex.GetType().Name + ": " + ex.Message)); return; } if (ApplyYaml(text, source) && !string.Equals(_syncedYaml.Value ?? "", text, StringComparison.Ordinal)) { _syncedYaml.AssignLocalValue(text); } } private static bool ApplyYaml(string yaml, string source) { if (!UsefulRunestonesConfiguration.TryParse(yaml, source, out UsefulRunestonesConfiguration configuration)) { return false; } UsefulRunestonesRuntime.SetConfiguration(configuration, yaml); return true; } } internal sealed class UsefulRunestonesConfiguration { private static readonly IDeserializer Deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build(); [YamlMember(Order = 1)] public RunestoneGlobalPinsDefinition? RunestoneGlobalPins { get; set; } [YamlMember(Order = 2)] public VegvisirGlobalEffectsDefinition? VegvisirGlobalEffects { get; set; } [YamlMember(Order = 3)] public VegvisirGlobalEffectsLocalizationDefinition? Localization { get; set; } internal static bool TryParse(string yaml, string source, out UsefulRunestonesConfiguration configuration) { configuration = new UsefulRunestonesConfiguration(); try { configuration = (string.IsNullOrWhiteSpace(yaml) ? new UsefulRunestonesConfiguration() : (Deserializer.Deserialize<UsefulRunestonesConfiguration>(yaml) ?? new UsefulRunestonesConfiguration())); Normalize(configuration); UsefulRunestonesPlugin.UsefulRunestonesLogger.LogInfo((object)("Loaded UsefulRunestones YAML from " + source + ".")); return true; } catch (Exception ex) { UsefulRunestonesPlugin.UsefulRunestonesLogger.LogError((object)("Rejected UsefulRunestones YAML from " + source + ". Keeping the previous configuration. " + ex.GetType().Name + ": " + ex.Message)); return false; } } private static void Normalize(UsefulRunestonesConfiguration configuration) { NormalizeRunestoneGlobalPins(configuration.RunestoneGlobalPins); NormalizeVegvisirGlobalEffects(configuration.VegvisirGlobalEffects); NormalizeLocalization(configuration.Localization); } private static void NormalizeRunestoneGlobalPins(RunestoneGlobalPinsDefinition? definition) { if (definition?.TargetLocations == null) { return; } foreach (RunestoneGlobalPinTargetDefinition targetLocation in definition.TargetLocations) { targetLocation.LocationName = (targetLocation.LocationName ?? "").Trim(); targetLocation.PinName = NormalizeOptionalString(targetLocation.PinName); targetLocation.PinType = NormalizeOptionalString(targetLocation.PinType); targetLocation.SourceBiomes = (from value in targetLocation.SourceBiomes?.Select((string value) => (value ?? "").Trim()) where value.Length > 0 select value).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToList(); targetLocation.Chance = Mathf.Clamp01(targetLocation.Chance ?? 1f); } } private static void NormalizeVegvisirGlobalEffects(VegvisirGlobalEffectsDefinition? definition) { if (definition?.Biomes == null) { return; } foreach (VegvisirGlobalEffectsBiomeDefinition biome in definition.Biomes) { string text = (biome.Biome ?? "").Trim(); biome.Biome = (string.Equals(text, "All", StringComparison.OrdinalIgnoreCase) ? null : ((text.Length > 0) ? text : null)); if (biome.StatusEffects == null) { continue; } foreach (VegvisirGlobalEffectDefinition statusEffect in biome.StatusEffects) { NormalizeVegvisirGlobalEffect(statusEffect); } } } private static void NormalizeVegvisirGlobalEffect(VegvisirGlobalEffectDefinition effect) { effect.StatusEffect = (effect.StatusEffect ?? "").Trim(); if (effect.Weight.HasValue) { effect.Weight = Mathf.Max(0f, effect.Weight.Value); } if (effect.DurationSeconds.HasValue && effect.DurationSeconds.Value <= 0f) { effect.DurationSeconds = null; } effect.EffectPrefab = NormalizeOptionalString(effect.EffectPrefab); } private static void NormalizeLocalization(VegvisirGlobalEffectsLocalizationDefinition? localization) { if (localization != null) { localization.MessageVegvisirEffectReceived = NormalizeOptionalString(localization.MessageVegvisirEffectReceived); localization.MessageVegvisirClearStatus = NormalizeOptionalString(localization.MessageVegvisirClearStatus); localization.MessageVegvisirBuffCooldown = NormalizeOptionalString(localization.MessageVegvisirBuffCooldown); localization.MessageVegvisirAlreadyActive = NormalizeOptionalString(localization.MessageVegvisirAlreadyActive); } } private static string? NormalizeOptionalString(string? value) { string text = (value ?? "").Trim(); if (text.Length != 0) { return text; } return null; } internal static string BuildDefaultYaml() { return "# UsefulRunestones\n#\n# runestoneGlobalPins: pinless RuneStones can add map pins for configured locations.\n# Row: locationName, chance, pinName, pinType, sourceBiomes\n# ex) Vendor_BlackForest, 0.5, Some pin name, Icon3, [Meadows]\n# chance is the final selection chance. Remaining chance means no pin; totals over 1 are normalized.\n# normalized ex) three targets at 0.5 total 1.5, so each selected target has 0.5 / 1.5 = 33.3%.\n# sourceBiomes adds extra RuneStone source biomes; [] means only the target location's own biome.\n# pinName defaults to target Location.m_discoverLabel, then child Teleport.m_enterText, then locationName.\n# pinType defaults to Icon3. Options: Icon0, Icon1, Icon2, Icon3, Death, Bed, Icon4, Shout, None, Boss, Player, RandomEvent, Ping, EventArea, Hildir1, Hildir2, Hildir3.\nrunestoneGlobalPins:\n - Vendor_BlackForest, 0.5, Haldor, Icon3\n - CombatRuin01, 0.5\n - Hildir_camp, 0.5\n - BogWitch_Camp, 0.5\n - SunkenCrypt4, 0.5\n - MountainCave02, 0.5\n - StoneHenge1, 0.5\n - StoneHenge3, 0.5\n - StoneHenge4, 0.5\n - StoneHenge5, 0.5\n - Mistlands_DvergrTownEntrance1, 0.5\n - Mistlands_DvergrTownEntrance2, 0.5\n - Mistlands_Excavation1, 0.5\n - Mistlands_Excavation2, 0.5\n - Mistlands_Excavation3, 0.5\n - PlaceofMystery1, 0.5\n - PlaceofMystery2, 0.5\n - PlaceofMystery3, 0.5\n\n# vegvisirGlobalEffects: weighted rewards when a normal Vegvisir interaction succeeds.\n# Row: StatusEffect, durationSeconds, cooldownSeconds, weight, effectPrefab\n# All is pooled with the current biome row; every other key must be one biome name.\n# Each loaded Vegvisir locks one weighted pick until unload; unload/load resets its pick and cooldown state.\n# Omitted duration/cooldown use StatusEffect prefab values; omitted weight defaults to 1.\n# cooldown 0 = none; cooldown < 0 = once per loaded Vegvisir/player.\n# VRS_ClearStatus ignores duration, clears current status effects, and shows \"You got bamboozled\".\n# If the selected StatusEffect is already active, it is not reapplied and no cooldown/fx starts.\n# effectPrefab is optional and must start with vfx_, sfx_, or fx_ case-insensitively.\nvegvisirGlobalEffects:\n - All:\n - CorpseRun, 60, 120, 0.1, vfx_StaminaUpgrade\n - SoftDeath, 120, 240, 0.5, vfx_HealthUpgrade\n - VRS_ClearStatus, 0, 5, 0.1, sfx_goblin_idle\n - Meadows:\n - Rested, 240, 480, 1, vfx_HealthUpgrade\n - BeltStrength, 120, 240, 1, vfx_HealthUpgrade\n - AdrenalineRush, 120, 240, 1\n # - Lightning, 5, 10, 1000, fx_eikthyr_stomp\n - BlackForest:\n - GP_Eikthyr, 120, 240, 1\n - Potion_health_minor\n - Potion_stamina_minor\n - Potion_tasty, 10, 20, 1, vfx_StaminaUpgrade\n - SetEffect_BerserkerArmor, 120, 240, 1, vfx_HealthUpgrade\n - SetEffect_TrollArmor, 120, 240, 1, vfx_StaminaUpgrade\n - TrinketBronzeHealth, 60, 120, 1, fx_Adrenaline1\n - TrinketBronzeStamina, 60, 120, 1, fx_Adrenaline1\n - AdrenalineRush, 120, 240, 1\n - Wet, 60, -1, 0.5, sfx_gdking_scream\n - Swamp:\n - GP_TheElder, 120, 240, 1\n - Potion_hasty, 120, 240, 1\n - Potion_strength, 120, 240, 1\n - Potion_swimmer, 240, 360, 1\n - Potion_TrollPheromones, 240, 360, 1\n - Potion_poisonresist, 120, 240, 1\n - Potion_health_medium\n - Potion_stamina_minor\n - SetEffect_RootArmor, 120, 240, 1\n - TrinketIronHealth, 60, 120, 0.5, fx_Adrenaline1\n - TrinketIronStamina, 60, 120, 0.5, fx_Adrenaline1\n - AdrenalineRush2, 120, 240, 1, fx_Adrenaline1\n - Puke, 5, 10, 0.5, fx_Bonemass_aoe_start\n - Mountain:\n - GP_Bonemass, 120, 240, 1\n - Potion_frostresist, 120, 240, 1\n - Potion_tamer, 240, 480, 1\n - SetEffect_FenringArmor, 120, 240, 1, vfx_StaminaUpgrade\n - SetEffect_WolfArmor, 120, 240, 1, vfx_HealthUpgrade\n - SetEffect_FishingHat, 240, 480, 1, vfx_StaminaUpgrade\n - SetEffect_HarvesterArmor, 240, 480, 1, vfx_StaminaUpgrade\n - TrinketChitinSwim, 120, 240, 0.5, fx_Adrenaline1\n - TrinketSilverDamage, 60, 120, 0.5, fx_Adrenaline1\n - TrinketSilverResist, 60, 120, 0.5, fx_Adrenaline1\n - AdrenalineRush2, 120, 240, 1, fx_Adrenaline1\n - Frost, 5, 10, 0.5, sfx_dragon_scream\n - Plains:\n - GP_Moder, 120, 240, 1\n - Potion_BugRepellent, 120, 240, 1\n - Potion_bzerker, 30, 60, 1\n - Potion_barleywine, 120, 240, 1\n - Potion_stamina_medium\n - SetEffect_BerserkerUndeadArmor, 120, 240, 1, vfx_StaminaUpgrade\n - TrinketBlackDamageHealth, 60, 120, 0.5, fx_Adrenaline1\n - TrinketBlackStamina, 60, 120, 0.5, fx_Adrenaline1\n - AdrenalineRush3, 120, 240, 1, fx_Adrenaline1\n - Tared, 5, 10, 0.5, sfx_goblinking_taunt\n - Mistlands:\n - GP_Yagluth, 120, 240, 1\n - Potion_eitr_minor\n - Potion_health_major\n - Potion_LightFoot, 120, 240, 1\n - Potion_stamina_lingering, 120, 240, 1\n - SetEffect_MageArmor, 120, 240, 1, vfx_StaminaUpgrade\n - SE_Dvergr_buff, 60, 120, 1, vfx_HealthUpgrade\n - SlowFall, 60, 120, 1, vfx_StaminaUpgrade\n - Staff_shield, 120, 240, 1, vfx_HealthUpgrade\n - TrinketCarapaceEitr, 60, 120, 0.5, fx_Adrenaline1\n - TrinketScaleStaminaDamage, 60, 120, 0.5, fx_Adrenaline1\n - Demister, 120, 240, 1\n - AdrenalineRush3, 120, 240, 1, fx_Adrenaline1\n - Slimed, 5, 10, 0.5, sfx_HiveQueen_callout\n - AshLands:\n - GP_Queen, 120, 240, 1\n - Potion_eitr_lingering, 120, 240, 1\n - Potion_health_lingering, 120, 240, 1\n - SetEffect_AshlandsMediumArmor, 120, 240, 1, vfx_StaminaUpgrade\n - WindRun, 120, 240, 1, vfx_StaminaUpgrade\n - TrinketFlametalEitr, 60, 120, 0.5, fx_Adrenaline1\n - TrinketFlametalStaminaHealth, 60, 120, 0.5, fx_Adrenaline1\n - Warm, 60, 120, 0.5, vfx_HealthUpgrade\n - AdrenalineRush4, 120, 240, 1, fx_Adrenaline1\n - Immobilized, 5, 10, 0.5, sfx_fader_taunt\n - DeepNorth:\n - GP_Fader, 120, 240, 1\n - AdrenalineRush4, 120, 240, 1, fx_Adrenaline1\n\nlocalization:\n messageVegvisirEffectReceived: \"You have received {name}\"\n messageVegvisirClearStatus: \"You got bamboozled\"\n messageVegvisirBuffCooldown: \"Buff Cooldown {seconds}s\"\n messageVegvisirAlreadyActive: \"Already active {name}\""; } } internal sealed class RunestoneGlobalPinsDefinition : IYamlConvertible { [YamlMember(Order = 1)] public List<RunestoneGlobalPinTargetDefinition>? TargetLocations { get; set; } void IYamlConvertible.Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer) { List<RunestoneGlobalPinTargetDefinition> list = new List<RunestoneGlobalPinTargetDefinition>(); parser.Consume<SequenceStart>(); SequenceEnd @event; while (!parser.Accept<SequenceEnd>(out @event)) { RunestoneGlobalPinTargetDefinition runestoneGlobalPinTargetDefinition = (RunestoneGlobalPinTargetDefinition)nestedObjectDeserializer(typeof(RunestoneGlobalPinTargetDefinition)); if (runestoneGlobalPinTargetDefinition != null) { list.Add(runestoneGlobalPinTargetDefinition); } } parser.Consume<SequenceEnd>(); TargetLocations = list; } void IYamlConvertible.Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer) { emitter.Emit(new SequenceStart(null, null, isImplicit: false, SequenceStyle.Block)); IEnumerable<RunestoneGlobalPinTargetDefinition> targetLocations = TargetLocations; foreach (RunestoneGlobalPinTargetDefinition item in targetLocations ?? Enumerable.Empty<RunestoneGlobalPinTargetDefinition>()) { nestedObjectSerializer(item); } emitter.Emit(new SequenceEnd()); } } internal sealed class RunestoneGlobalPinTargetDefinition : IYamlConvertible { [YamlMember(Order = 1)] public string LocationName { get; set; } = ""; [YamlMember(Order = 2)] public float? Chance { get; set; } [YamlMember(Order = 3)] public List<string>? SourceBiomes { get; set; } [YamlMember(Order = 4)] public string? PinName { get; set; } [YamlMember(Order = 5)] public string? PinType { get; set; } void IYamlConvertible.Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer) { if (!parser.TryConsume<YamlDotNet.Core.Events.Scalar>(out var @event)) { throw new YamlException("runestoneGlobalPins rows must use scalar shorthand: locationName, chance, pinName, pinType, sourceBiomes."); } ReadShorthand(@event.Value); } void IYamlConvertible.Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer) { emitter.Emit(new YamlDotNet.Core.Events.Scalar(FormatShorthand())); } private void ReadShorthand(string? rawValue) { List<string> list = SplitShorthand(rawValue); if (list.Count > 5) { throw new YamlException("runestoneGlobalPins rows support at most five comma-separated values: locationName, chance, pinName, pinType, sourceBiomes."); } LocationName = ((list.Count > 0) ? list[0].Trim() : ""); Chance = ((list.Count > 1) ? ShorthandScalar.ParseOptionalFloat(list[1], "chance", "runestoneGlobalPins", acceptNullLiteral: true) : ((float?)null)); PinName = ((list.Count > 2) ? ShorthandScalar.ParseOptionalString(list[2], acceptNullLiteral: true) : null); PinType = ((list.Count > 3) ? ShorthandScalar.ParseOptionalString(list[3], acceptNullLiteral: true) : null); SourceBiomes = ((list.Count > 4) ? ParseSourceBiomes(list[4]) : null); } private string FormatShorthand() { List<string> list = new List<string> { LocationName ?? "" }; List<string> sourceBiomes = SourceBiomes; int num = ((sourceBiomes != null && sourceBiomes.Count > 0) ? 4 : ((!string.IsNullOrWhiteSpace(PinType)) ? 3 : ((!string.IsNullOrWhiteSpace(PinName)) ? 2 : (Chance.HasValue ? 1 : 0)))); if (num >= 1) { list.Add(ShorthandScalar.FormatOptionalFloat(Chance)); } if (num >= 2) { list.Add(PinName ?? ""); } if (num >= 3) { list.Add(PinType ?? ""); } if (num >= 4) { list.Add(FormatSourceBiomes(SourceBiomes)); } return string.Join(", ", list); } private static List<string> SplitShorthand(string? rawValue) { List<string> list = new List<string>(); StringBuilder stringBuilder = new StringBuilder(); int num = 0; string text = rawValue ?? ""; foreach (char c in text) { switch (c) { case '[': num++; stringBuilder.Append(c); continue; case ']': num--; if (num < 0) { throw new YamlException("runestoneGlobalPins sourceBiomes has an unmatched ']'."); } stringBuilder.Append(c); continue; case ',': if (num == 0) { list.Add(stringBuilder.ToString().Trim()); stringBuilder.Clear(); continue; } break; } stringBuilder.Append(c); } if (num != 0) { throw new YamlException("runestoneGlobalPins sourceBiomes has an unmatched '['."); } list.Add(stringBuilder.ToString().Trim()); return list; } private static List<string>? ParseSourceBiomes(string? rawValue) { string text = (rawValue ?? "").Trim(); if (text.Length == 0 || string.Equals(text, "null", StringComparison.OrdinalIgnoreCase)) { return null; } if (text.StartsWith("[", StringComparison.Ordinal) || text.EndsWith("]", StringComparison.Ordinal)) { if (!text.StartsWith("[", StringComparison.Ordinal) || !text.EndsWith("]", StringComparison.Ordinal)) { throw new YamlException("runestoneGlobalPins sourceBiomes must use [Biome] or [Biome, Biome] when brackets are used."); } string text2 = text.Substring(1, text.Length - 2).Trim(); if (text2.Length == 0) { return new List<string>(); } return (from value in text2.Split(new char[1] { ',' }) select value.Trim() into value where value.Length > 0 select value).ToList(); } return new List<string> { text }; } private static string FormatSourceBiomes(List<string>? sourceBiomes) { if (sourceBiomes == null || sourceBiomes.Count == 0) { return "[]"; } return "[" + string.Join(", ", sourceBiomes) + "]"; } } internal sealed class VegvisirGlobalEffectsDefinition : IYamlConvertible { [YamlMember(Order = 1)] public List<VegvisirGlobalEffectsBiomeDefinition>? Biomes { get; set; } void IYamlConvertible.Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer) { List<VegvisirGlobalEffectsBiomeDefinition> list = new List<VegvisirGlobalEffectsBiomeDefinition>(); parser.Consume<SequenceStart>(); SequenceEnd @event; while (!parser.Accept<SequenceEnd>(out @event)) { parser.Consume<MappingStart>(); MappingEnd event2; while (!parser.Accept<MappingEnd>(out event2)) { string rawKey = (parser.Consume<YamlDotNet.Core.Events.Scalar>().Value ?? "").Trim(); List<VegvisirGlobalEffectDefinition> statusEffects = (List<VegvisirGlobalEffectDefinition>)nestedObjectDeserializer(typeof(List<VegvisirGlobalEffectDefinition>)); list.Add(new VegvisirGlobalEffectsBiomeDefinition { Biome = ParseVegvisirGlobalEffectsBiomeKey(rawKey), StatusEffects = statusEffects }); } parser.Consume<MappingEnd>(); } parser.Consume<SequenceEnd>(); Biomes = list; } void IYamlConvertible.Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer) { emitter.Emit(new SequenceStart(null, null, isImplicit: false, SequenceStyle.Block)); IEnumerable<VegvisirGlobalEffectsBiomeDefinition> biomes = Biomes; foreach (VegvisirGlobalEffectsBiomeDefinition item in biomes ?? Enumerable.Empty<VegvisirGlobalEffectsBiomeDefinition>()) { emitter.Emit(new MappingStart(null, null, isImplicit: false, MappingStyle.Block)); emitter.Emit(new YamlDotNet.Core.Events.Scalar(FormatVegvisirGlobalEffectsBiomeKey(item.Biome))); nestedObjectSerializer(item.StatusEffects ?? new List<VegvisirGlobalEffectDefinition>()); emitter.Emit(new MappingEnd()); } emitter.Emit(new SequenceEnd()); } private static string? ParseVegvisirGlobalEffectsBiomeKey(string rawKey) { string text = rawKey.Trim(); if (text.Length == 0) { throw new YamlException("vegvisirGlobalEffects biome key cannot be empty. Use All to match every biome."); } if (text.StartsWith("[", StringComparison.Ordinal) || text.EndsWith("]", StringComparison.Ordinal)) { throw new YamlException("vegvisirGlobalEffects biome key '" + text + "' is invalid. Use one biome name per row, or All to match every biome."); } if (Enumerable.Contains(text, ',')) { throw new YamlException("vegvisirGlobalEffects biome key '" + text + "' is invalid. Use one biome name per row instead of comma-separated biome names."); } if (text == "*") { throw new YamlException("vegvisirGlobalEffects uses '*' as a biome wildcard. Use All instead."); } if (!string.Equals(text, "all", StringComparison.OrdinalIgnoreCase)) { return text; } return null; } private static string FormatVegvisirGlobalEffectsBiomeKey(string? biome) { string text = (biome ?? "").Trim(); if (text.Length != 0) { return text; } return "All"; } } internal sealed class VegvisirGlobalEffectsLocalizationDefinition { [YamlMember(Order = 1)] public string? MessageVegvisirEffectReceived { get; set; } [YamlMember(Order = 2)] public string? MessageVegvisirClearStatus { get; set; } [YamlMember(Order = 3)] public string? MessageVegvisirBuffCooldown { get; set; } [YamlMember(Order = 4)] public string? MessageVegvisirAlreadyActive { get; set; } } internal sealed class VegvisirGlobalEffectsBiomeDefinition { [YamlMember(Order = 1)] public string? Biome { get; set; } [YamlMember(Order = 2)] public List<VegvisirGlobalEffectDefinition>? StatusEffects { get; set; } } internal sealed class VegvisirGlobalEffectDefinition : IYamlConvertible { [YamlIgnore] public string StatusEffect { get; set; } = ""; [YamlIgnore] public float? Weight { get; set; } [YamlIgnore] public float? CooldownSeconds { get; set; } [YamlIgnore] public float? DurationSeconds { get; set; } [YamlIgnore] public string? EffectPrefab { get; set; } void IYamlConvertible.Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer) { if (!parser.TryConsume<YamlDotNet.Core.Events.Scalar>(out var @event)) { throw new YamlException("vegvisirGlobalEffects status effect rows must use scalar shorthand: StatusEffect, durationSeconds, cooldownSeconds, weight, effectPrefab."); } ReadShorthand(@event.Value); } void IYamlConvertible.Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer) { emitter.Emit(new YamlDotNet.Core.Events.Scalar(FormatShorthand())); } private void ReadShorthand(string? rawValue) { string[] array = (rawValue ?? "").Split(new char[1] { ',' }); if (array.Length > 5) { throw new YamlException("vegvisirGlobalEffects status effect rows support at most five comma-separated values: StatusEffect, durationSeconds, cooldownSeconds, weight, effectPrefab."); } StatusEffect = ((array.Length != 0) ? array[0].Trim() : ""); DurationSeconds = ((array.Length > 1) ? ShorthandScalar.ParseOptionalFloat(array[1], "durationSeconds", "vegvisirGlobalEffects", acceptNullLiteral: false) : ((float?)null)); CooldownSeconds = ((array.Length > 2) ? ShorthandScalar.ParseOptionalFloat(array[2], "cooldownSeconds", "vegvisirGlobalEffects", acceptNullLiteral: false) : ((float?)null)); Weight = ((array.Length > 3) ? ShorthandScalar.ParseOptionalFloat(array[3], "weight", "vegvisirGlobalEffects", acceptNullLiteral: false) : ((float?)null)); EffectPrefab = ((array.Length > 4) ? ShorthandScalar.ParseOptionalString(array[4], acceptNullLiteral: false) : null); } private string FormatShorthand() { List<string> list = new List<string> { StatusEffect ?? "" }; int num = ((!string.IsNullOrWhiteSpace(EffectPrefab)) ? 4 : (Weight.HasValue ? 3 : (CooldownSeconds.HasValue ? 2 : (DurationSeconds.HasValue ? 1 : 0)))); if (num >= 1) { list.Add(ShorthandScalar.FormatOptionalFloat(DurationSeconds)); } if (num >= 2) { list.Add(ShorthandScalar.FormatOptionalFloat(CooldownSeconds)); } if (num >= 3) { list.Add(ShorthandScalar.FormatOptionalFloat(Weight)); } if (num >= 4) { list.Add(EffectPrefab ?? ""); } return string.Join(", ", list); } } internal static class ShorthandScalar { internal static float? ParseOptionalFloat(string? rawValue, string fieldName, string context, bool acceptNullLiteral) { string text = (rawValue ?? "").Trim(); if (text.Length == 0 || (acceptNullLiteral && string.Equals(text, "null", StringComparison.OrdinalIgnoreCase))) { return null; } if (float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return result; } throw new YamlException(context + " value '" + text + "' is not a valid " + fieldName + " number."); } internal static string FormatOptionalFloat(float? value) { if (!value.HasValue) { return ""; } return value.Value.ToString("R", CultureInfo.InvariantCulture); } internal static string? ParseOptionalString(string? rawValue, bool acceptNullLiteral) { string text = (rawValue ?? "").Trim(); if (text.Length != 0 && (!acceptNullLiteral || !string.Equals(text, "null", StringComparison.OrdinalIgnoreCase))) { return text; } return null; } } internal static class UsefulRunestonesRuntime { private sealed class RunestoneGlobalPinsRollState { public string RollKey { get; set; } = ""; public ResolvedRunestoneGlobalPin? Pin { get; set; } } private sealed class RunestoneGlobalPinCandidate { public float Chance { get; set; } public ResolvedRunestoneGlobalPin Pin { get; set; } = new ResolvedRunestoneGlobalPin(); } private sealed class ResolvedRunestoneGlobalPin { public string LocationName { get; set; } = ""; public string PinName { get; set; } = ""; public PinType PinType { get; set; } = (PinType)3; public Vector3 Position { get; set; } } private sealed class RunestoneGlobalPinLocationIndex { public int ZoneSystemId { get; set; } public int LocationInstanceCount { get; set; } public Dictionary<string, List<LocationInstance>> InstancesByName { get; } = new Dictionary<string, List<LocationInstance>>(StringComparer.OrdinalIgnoreCase); } private sealed class VegvisirGlobalEffectCandidate { public VegvisirGlobalEffectDefinition Definition { get; set; } = new VegvisirGlobalEffectDefinition(); public StatusEffect? StatusEffect { get; set; } public Biome SourceBiome { get; set; } public float Weight { get; set; } public bool ClearsStatusEffects { get; set; } public string EffectKey { get; set; } = ""; } private sealed class VegvisirGlobalEffectSelectionState { public string CandidateSignature { get; set; } = ""; public int SelectedIndex { get; set; } = -1; public Dictionary<long, DateTime> LastGrantedByPlayer { get; } = new Dictionary<long, DateTime>(); } private static readonly HashSet<string> InvalidEntryWarnings = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static RunestoneGlobalPinsDefinition? _runestoneGlobalPins; private static VegvisirGlobalEffectsDefinition? _vegvisirGlobalEffects; private static VegvisirGlobalEffectsLocalizationDefinition? _localization; private static string? _configurationSignature; private const int RunestoneGlobalPinRpcVersion = 1; private const string RunestoneGlobalPinRequestRpc = "UsefulRunestones Runestone GlobalPin Request"; private const string RunestoneGlobalPinResponseRpc = "UsefulRunestones Runestone GlobalPin Response"; private static readonly ConditionalWeakTable<RuneStone, RunestoneGlobalPinsRollState> RunestoneGlobalPinsRolls = new ConditionalWeakTable<RuneStone, RunestoneGlobalPinsRollState>(); private static readonly object RunestoneGlobalPinsLock = new object(); private static readonly Random RunestoneGlobalPinsRandom = new Random(); private static readonly Dictionary<string, ResolvedRunestoneGlobalPin?> RunestoneGlobalPinServerRolls = new Dictionary<string, ResolvedRunestoneGlobalPin>(StringComparer.Ordinal); private static readonly Dictionary<string, string> RunestoneGlobalPinDefaultPinNames = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); private static readonly HashSet<string> RunestoneGlobalPinWarningLogs = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static readonly FieldRef<ZRoutedRpc, long> RunestoneGlobalPinRoutedRpcIdRef = AccessTools.FieldRefAccess<ZRoutedRpc, long>("m_id"); private static RunestoneGlobalPinLocationIndex? RunestoneGlobalPinLocationIndexCache; private static ZRoutedRpc? RunestoneGlobalPinRegisteredRpcInstance; private static readonly FieldInfo? MinimapPinsField = AccessTools.Field(typeof(Minimap), "m_pins"); private const string VegvisirGlobalEffectClearStatusKey = "VRS_ClearStatus"; private static readonly object VegvisirGlobalEffectsLock = new object(); private static readonly Random VegvisirGlobalEffectsRandom = new Random(); private static readonly ConditionalWeakTable<Vegvisir, VegvisirGlobalEffectSelectionState> VegvisirGlobalEffectSelections = new ConditionalWeakTable<Vegvisir, VegvisirGlobalEffectSelectionState>(); private static readonly HashSet<string> VegvisirGlobalEffectMissingEffectPrefabWarnings = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static readonly HashSet<string> VegvisirGlobalEffectInvalidEffectPrefabWarnings = new HashSet<string>(StringComparer.OrdinalIgnoreCase); internal static void SetConfiguration(UsefulRunestonesConfiguration configuration, string sourceYaml) { _runestoneGlobalPins = configuration.RunestoneGlobalPins; _vegvisirGlobalEffects = configuration.VegvisirGlobalEffects; _localization = configuration.Localization; _configurationSignature = ComputeConfigurationSignature(sourceYaml); InvalidEntryWarnings.Clear(); RunestoneGlobalPinWarningLogs.Clear(); VegvisirGlobalEffectMissingEffectPrefabWarnings.Clear(); VegvisirGlobalEffectInvalidEffectPrefabWarnings.Clear(); ResetRunestoneGlobalPinsRuntimeState(); } private static string ComputeConfigurationSignature(string sourceYaml) { int num = 17; string text = sourceYaml ?? ""; foreach (char c in text) { num = num * 31 + c; } return num.ToString("X8", CultureInfo.InvariantCulture); } private static bool IsGameDataReady() { if ((Object)(object)ZoneSystem.instance != (Object)null && (Object)(object)ZNetScene.instance != (Object)null) { return (Object)(object)ObjectDB.instance != (Object)null; } return false; } private static bool HasRunestoneGlobalPinsOverride(RunestoneGlobalPinsDefinition? definition) { if (definition?.TargetLocations != null) { return definition.TargetLocations.Count > 0; } return false; } private static bool HasVegvisirGlobalEffectsOverride(VegvisirGlobalEffectsDefinition? definition) { if (definition?.Biomes != null) { return definition.Biomes.Count > 0; } return false; } private static string GetZoneLocationPrefabName(ZoneLocation? location) { return (location?.m_prefabName ?? location?.m_prefab.Name ?? "").Trim(); } private static StatusEffect? ResolveStatusEffect(string? statusEffectName, string? warnContext) { string trimmedName = (statusEffectName ?? "").Trim(); if (trimmedName.Length == 0) { return null; } ObjectDB instance = ObjectDB.instance; StatusEffect val = ((instance != null) ? instance.GetStatusEffect(StringExtensionMethods.GetStableHashCode(trimmedName)) : null); if ((Object)(object)val != (Object)null) { return val; } val = ((IEnumerable<StatusEffect>)ObjectDB.instance?.m_StatusEffects).FirstOrDefault((Func<StatusEffect, bool>)((StatusEffect effect) => string.Equals(((Object)effect).name, trimmedName, StringComparison.OrdinalIgnoreCase) || string.Equals(effect.m_name, trimmedName, StringComparison.OrdinalIgnoreCase))); if ((Object)(object)val != (Object)null) { return val; } if (!string.IsNullOrWhiteSpace(warnContext)) { WarnInvalidEntry("Entry '" + warnContext + "' references unknown status effect '" + trimmedName + "'."); } return null; } private static void WarnInvalidEntry(string message) { if (InvalidEntryWarnings.Add(message)) { UsefulRunestonesPlugin.UsefulRunestonesLogger.LogWarning((object)message); } } internal static void TryApplyRunestoneGlobalPins(RuneStone? runestone, bool hold, string? originalLocationName) { if (hold || (Object)(object)runestone == (Object)null || (Object)(object)((Component)runestone).gameObject == (Object)null || HasBossStoneComponent(runestone) || !UsefulRunestonesPlugin.IsRunestoneGlobalPinsEnabled() || !string.IsNullOrWhiteSpace(originalLocationName ?? runestone.m_locationName) || TryRequestServerResolvedRunestoneGlobalPin(runestone, originalLocationName)) { return; } RunestoneGlobalPinsDefinition effectiveRunestoneGlobalPinsDefinition = GetEffectiveRunestoneGlobalPinsDefinition(); if (effectiveRunestoneGlobalPinsDefinition?.TargetLocations != null && effectiveRunestoneGlobalPinsDefinition.TargetLocations.Count != 0) { ResolvedRunestoneGlobalPin orRollRunestoneGlobalPin = GetOrRollRunestoneGlobalPin(runestone, effectiveRunestoneGlobalPinsDefinition, originalLocationName); if (orRollRunestoneGlobalPin != null) { TryAddRunestoneGlobalPin(orRollRunestoneGlobalPin); } } } private static bool HasBossStoneComponent(RuneStone runestone) { BossStone val = default(BossStone); return ((Component)runestone).TryGetComponent<BossStone>(ref val); } internal static void EnsureRunestoneGlobalPinRpcRegistered() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && instance != RunestoneGlobalPinRegisteredRpcInstance) { if (RunestoneGlobalPinRegisteredRpcInstance != null) { ResetRunestoneGlobalPinsRuntimeState(); } instance.Register<ZPackage>("UsefulRunestones Runestone GlobalPin Request", (Action<long, ZPackage>)OnRunestoneGlobalPinRequestRpc); instance.Register<ZPackage>("UsefulRunestones Runestone GlobalPin Response", (Action<long, ZPackage>)OnRunestoneGlobalPinResponseRpc); RunestoneGlobalPinRegisteredRpcInstance = instance; } } private static void ResetRunestoneGlobalPinsRuntimeState() { lock (RunestoneGlobalPinsLock) { RunestoneGlobalPinServerRolls.Clear(); } RunestoneGlobalPinLocationIndexCache = null; } private static RunestoneGlobalPinsDefinition? GetEffectiveRunestoneGlobalPinsDefinition() { if (!HasRunestoneGlobalPinsOverride(_runestoneGlobalPins)) { return null; } return _runestoneGlobalPins; } private static ResolvedRunestoneGlobalPin? GetOrRollRunestoneGlobalPin(RuneStone runestone, RunestoneGlobalPinsDefinition definition, string? originalLocationName) { if (ShouldWaitForRunestoneGlobalPinSourceBiomeResolution(definition)) { return null; } string text = CreateRunestoneGlobalPinsRollKey(runestone, definition, originalLocationName); lock (RunestoneGlobalPinsLock) { if (!RunestoneGlobalPinsRolls.TryGetValue(runestone, out RunestoneGlobalPinsRollState value)) { value = new RunestoneGlobalPinsRollState(); RunestoneGlobalPinsRolls.Add(runestone, value); } if (value.RollKey == text) { return value.Pin; } value.RollKey = text; value.Pin = RollRunestoneGlobalPin(runestone, definition); return value.Pin; } } private static ResolvedRunestoneGlobalPin? GetOrRollServerRunestoneGlobalPin(Vector3 runestonePosition, RunestoneGlobalPinsDefinition definition, string? runestoneLocationName) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (ShouldWaitForRunestoneGlobalPinSourceBiomeResolution(definition)) { return null; } string key = CreateRunestoneGlobalPinsServerRollKey(runestonePosition, definition, runestoneLocationName); lock (RunestoneGlobalPinsLock) { if (RunestoneGlobalPinServerRolls.TryGetValue(key, out ResolvedRunestoneGlobalPin value)) { return value; } ResolvedRunestoneGlobalPin resolvedRunestoneGlobalPin = RollRunestoneGlobalPin(runestonePosition, definition); RunestoneGlobalPinServerRolls[key] = resolvedRunestoneGlobalPin; return resolvedRunestoneGlobalPin; } } private static ResolvedRunestoneGlobalPin? RollRunestoneGlobalPin(RuneStone runestone, RunestoneGlobalPinsDefinition definition) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) return RollRunestoneGlobalPin(((Component)runestone).transform.position, definition); } private static ResolvedRunestoneGlobalPin? RollRunestoneGlobalPin(Vector3 runestonePosition, RunestoneGlobalPinsDefinition definition) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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_0075: 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_0082: Unknown result type (might be due to invalid IL or missing references) if (definition.TargetLocations == null || (Object)(object)ZoneSystem.instance == (Object)null) { return null; } List<RunestoneGlobalPinCandidate> list = new List<RunestoneGlobalPinCandidate>(); Biome runestoneGlobalPinBiome = GetRunestoneGlobalPinBiome(runestonePosition); foreach (RunestoneGlobalPinTargetDefinition targetLocation in definition.TargetLocations) { float num = Mathf.Clamp01(targetLocation.Chance.GetValueOrDefault()); if (targetLocation.LocationName.Length == 0 || num <= 0f) { continue; } Biome allowedSourceBiomeMask = ResolveRunestoneGlobalPinSourceBiomeMask(targetLocation); if (TryFindClosestRunestoneGlobalPinLocation(targetLocation.LocationName, runestonePosition, runestoneGlobalPinBiome, allowedSourceBiomeMask, out var closest)) { ResolvedRunestoneGlobalPin resolvedRunestoneGlobalPin = CreateResolvedRunestoneGlobalPin(targetLocation, closest); if (resolvedRunestoneGlobalPin != null) { list.Add(new RunestoneGlobalPinCandidate { Chance = num, Pin = resolvedRunestoneGlobalPin }); } } } return SelectRunestoneGlobalPinCandidate(list); } private static ResolvedRunestoneGlobalPin? SelectRunestoneGlobalPinCandidate(List<RunestoneGlobalPinCandidate> candidates) { float num = 0f; foreach (RunestoneGlobalPinCandidate candidate in candidates) { num += candidate.Chance; } if (num <= 0f) { return null; } if (num > 1f) { WarnRunestoneGlobalPin("globalpin|chance-total-over-1|" + num.ToString("R", CultureInfo.InvariantCulture), "Runestone global pin chance values add up to " + num.ToString("0.###", CultureInfo.InvariantCulture) + " after filtering. Normalizing them, so exactly one candidate can be selected."); } float num2 = Math.Max(1f, num); double num3 = RunestoneGlobalPinsRandom.NextDouble() * (double)num2; float num4 = 0f; foreach (RunestoneGlobalPinCandidate candidate2 in candidates) { num4 += candidate2.Chance; if (num3 < (double)num4) { return candidate2.Pin; } } return null; } private static ResolvedRunestoneGlobalPin? CreateResolvedRunestoneGlobalPin(RunestoneGlobalPinTargetDefinition target, LocationInstance locationInstance) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) string pinName = ((!string.IsNullOrWhiteSpace(target.PinName)) ? target.PinName.Trim() : GetRunestoneGlobalPinDefaultPinName(target.LocationName)); PinType pinType = ParseRunestoneGlobalPinType(target.PinType, target.LocationName); return new ResolvedRunestoneGlobalPin { LocationName = target.LocationName, PinName = pinName, PinType = pinType, Position = locationInstance.m_position }; } private static bool TryAddRunestoneGlobalPin(ResolvedRunestoneGlobalPin pin) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: 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_007a: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Minimap.instance == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null) { return false; } PinType pinType = pin.PinType; if (HasSimilarRunestoneGlobalPin(pin.Position, pinType, pin.PinName, save: true)) { ((Character)Player.m_localPlayer).Message((MessageType)2, "$msg_pin_exist", 0, (Sprite)null); Minimap.instance.ShowPointOnMap(pin.Position); return false; } PinData val = Minimap.instance.AddPin(pin.Position, pinType, pin.PinName, true, false, 0L, default(PlatformUserID)); ((Character)Player.m_localPlayer).Message((MessageType)1, "$msg_pin_added: " + pin.PinName, 0, val.m_icon); Minimap.instance.ShowPointOnMap(pin.Position); return true; } private static bool TryFindClosestRunestoneGlobalPinLocation(string locationName, Vector3 runestonePosition, Biome runestoneBiome, Biome allowedSourceBiomeMask, out LocationInstance closest) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_004b: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0067: 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) closest = default(LocationInstance); if (!TryGetRunestoneGlobalPinLocationInstances(locationName, out List<LocationInstance> instances)) { return false; } float num = float.MaxValue; bool flag = false; foreach (LocationInstance item in instances) { if (RunestoneGlobalPinBiomesMatch(GetRunestoneGlobalPinBiome(item.m_position), runestoneBiome) || RunestoneGlobalPinAllowsSourceBiome(runestoneBiome, allowedSourceBiomeMask)) { float num2 = Utils.DistanceXZ(runestonePosition, item.m_position); if (!flag || num2 < num) { num = num2; closest = item; flag = true; } } } return flag; } private static bool TryGetRunestoneGlobalPinLocationInstances(string locationName, out List<LocationInstance> instances) { instances = null; string text = (locationName ?? "").Trim(); if (text.Length == 0 || !TryGetRunestoneGlobalPinLocationIndex(out RunestoneGlobalPinLocationIndex index)) { return false; } if (index.InstancesByName.TryGetValue(text, out instances)) { return instances.Count > 0; } return false; } private static bool TryGetRunestoneGlobalPinLocationIndex(out RunestoneGlobalPinLocationIndex index) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) index = null; ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null) { RunestoneGlobalPinLocationIndexCache = null; return false; } int instanceID = ((Object)instance).GetInstanceID(); int count = instance.m_locationInstances.Count; if (RunestoneGlobalPinLocationIndexCache != null && RunestoneGlobalPinLocationIndexCache.ZoneSystemId == instanceID && RunestoneGlobalPinLocationIndexCache.LocationInstanceCount == count) { index = RunestoneGlobalPinLocationIndexCache; return true; } RunestoneGlobalPinLocationIndex runestoneGlobalPinLocationIndex = new RunestoneGlobalPinLocationIndex { ZoneSystemId = instanceID, LocationInstanceCount = count }; foreach (LocationInstance value2 in instance.m_locationInstances.Values) { string zoneLocationPrefabName = GetZoneLocationPrefabName(value2.m_location); if (zoneLocationPrefabName.Length != 0) { if (!runestoneGlobalPinLocationIndex.InstancesByName.TryGetValue(zoneLocationPrefabName, out List<LocationInstance> value)) { value = new List<LocationInstance>(); runestoneGlobalPinLocationIndex.InstancesByName[zoneLocationPrefabName] = value; } value.Add(value2); } } RunestoneGlobalPinLocationIndexCache = runestoneGlobalPinLocationIndex; index = runestoneGlobalPinLocationIndex; return true; } private static Biome ResolveRunestoneGlobalPinSourceBiomeMask(RunestoneGlobalPinTargetDefinition target) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) if (target.SourceBiomes == null || target.SourceBiomes.Count == 0) { return (Biome)0; } if (BiomeResolutionSupport.TryResolveBiomeMask(target.SourceBiomes, out var biomeMask)) { return biomeMask; } WarnRunestoneGlobalPin("globalpin|invalid-source-biome|" + target.LocationName + "|" + string.Join(",", target.SourceBiomes), "Runestone global pin target '" + target.LocationName + "' has invalid sourceBiomes value. Matching-biome RuneStones can still target it."); return (Biome)0; } private static bool ShouldWaitForRunestoneGlobalPinSourceBiomeResolution(RunestoneGlobalPinsDefinition definition) { if (definition.TargetLocations == null) { return false; } foreach (RunestoneGlobalPinTargetDefinition targetLocation in definition.TargetLocations) { if (BiomeResolutionSupport.ShouldWaitForExpandWorldDataBiomeResolution(resolvedBiomeMask: BiomeResolutionSupport.ResolveBiomeMaskOrNull(targetLocation.SourceBiomes), configuredBiomes: targetLocation.SourceBiomes)) { return true; } } return false; } private static bool RunestoneGlobalPinBiomesMatch(Biome targetBiome, Biome runestoneBiome) { //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_0004: 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_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000b: 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_000e: Invalid comparison between Unknown and I4 if (targetBiome != runestoneBiome) { if ((int)targetBiome != 0 && (int)runestoneBiome != 0) { return (targetBiome & runestoneBiome) > 0; } return false; } return true; } private static bool RunestoneGlobalPinAllowsSourceBiome(Biome runestoneBiome, Biome allowedSourceBiomeMask) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000b: 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_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 if ((int)allowedSourceBiomeMask != 895) { if ((int)allowedSourceBiomeMask != 0 && (int)runestoneBiome != 0) { return (allowedSourceBiomeMask & runestoneBiome) > 0; } return false; } return true; } private static Biome GetRunestoneGlobalPinBiome(Vector3 position) { //IL_0013: 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) //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) if (WorldGenerator.instance == null) { return Heightmap.FindBiome(position); } return WorldGenerator.instance.GetBiome(position); } private static string GetRunestoneGlobalPinDefaultPinName(string locationName) { string runestoneGlobalPinConfiguredDefaultName = GetRunestoneGlobalPinConfiguredDefaultName(locationName); if (runestoneGlobalPinConfiguredDefaultName.Length <= 0) { return locationName; } return runestoneGlobalPinConfiguredDefaultName; } private static string GetRunestoneGlobalPinConfiguredDefaultName(string locationName) { //IL_0060: Unknown result type (might be due to invalid IL or missing references) if (RunestoneGlobalPinDefaultPinNames.TryGetValue(locationName, out string value)) { return value; } string text = ""; if ((Object)(object)ZoneSystem.instance != (Object)null) { foreach (ZoneLocation location in ZoneSystem.instance.m_locations) { if (string.Equals(GetZoneLocationPrefabName(location), locationName, StringComparison.OrdinalIgnoreCase) && location.m_prefab.IsValid) { location.m_prefab.Load(); try { text = GetRunestoneGlobalPinConfiguredDefaultName(location.m_prefab.Asset); } finally { location.m_prefab.Release(); } break; } } } RunestoneGlobalPinDefaultPinNames[locationName] = text; return text; } private static string GetRunestoneGlobalPinConfiguredDefaultName(GameObject? locationPrefab) { if ((Object)(object)locationPrefab == (Object)null) { return ""; } string text = (locationPrefab.GetComponent<Location>()?.m_discoverLabel ?? "").Trim(); if (text.Length > 0) { return text; } Teleport[] componentsInChildren = locationPrefab.GetComponentsInChildren<Teleport>(true); for (int i = 0; i < componentsInChildren.Length; i++) { string text2 = (componentsInChildren[i]?.m_enterText ?? "").Trim(); if (text2.Length > 0) { return text2; } } return ""; } private static PinType ParseRunestoneGlobalPinType(string? rawPinType, string locationName) { //IL_0011: 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) string text = (string.IsNullOrWhiteSpace(rawPinType) ? ((object)(PinType)3/*cast due to .constrained prefix*/).ToString() : rawPinType.Trim()); if (Enum.TryParse<PinType>(text, ignoreCase: true, out PinType result)) { return result; } WarnRunestoneGlobalPin("globalpin|invalid-pintype|" + locationName + "|" + text, "Runestone global pin target '" + locationName + "' has invalid pinType '" + text + "'. Using Icon3."); return (PinType)3; } private static bool HasSimilarRunestoneGlobalPin(Vector3 position, PinType pinType, string pinName, bool save) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: 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) //IL_0066: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Minimap.instance == (Object)null || !(MinimapPinsField?.GetValue(Minimap.instance) is List<PinData> list)) { return false; } foreach (PinData item in list) { if (item.m_type == pinType && item.m_save == save && string.Equals(item.m_name, pinName, StringComparison.Ordinal) && Utils.DistanceXZ(item.m_pos, position) < 1f) { return true; } } return false; } private static bool TryRequestServerResolvedRunestoneGlobalPin(RuneStone runestone, string? runestoneLocationName) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_003b: Unknown result type (might be due to invalid IL or missing references) EnsureRunestoneGlobalPinRpcRegistered(); if (ZRoutedRpc.instance == null || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { return false; } ZPackage val = new ZPackage(); val.Write(1); WriteRunestoneGlobalPinVector3(val, ((Component)runestone).transform.position); val.Write(runestoneLocationName ?? runestone.m_locationName ?? ""); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "UsefulRunestones Runestone GlobalPin Request", new object[1] { val }); return true; } private static void OnRunestoneGlobalPinRequestRpc(long sender, ZPackage package) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown if (ZRoutedRpc.instance != null && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && TryReadRunestoneGlobalPinRequest(package, out Vector3 runestonePosition, out string runestoneLocationName)) { ResolvedRunestoneGlobalPin pin = ResolveServerRunestoneGlobalPin(runestonePosition, runestoneLocationName); ZPackage val = new ZPackage(); WriteRunestoneGlobalPinResponse(val, pin); ZRoutedRpc.instance.InvokeRoutedRPC(sender, "UsefulRunestones Runestone GlobalPin Response", new object[1] { val }); } } private static ResolvedRunestoneGlobalPin? ResolveServerRunestoneGlobalPin(Vector3 runestonePosition, string? runestoneLocationName) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) if (!UsefulRunestonesPlugin.IsRunestoneGlobalPinsEnabled() || !string.IsNullOrWhiteSpace(runestoneLocationName)) { return null; } RunestoneGlobalPinsDefinition effectiveRunestoneGlobalPinsDefinition = GetEffectiveRunestoneGlobalPinsDefinition(); if (effectiveRunestoneGlobalPinsDefinition?.TargetLocations == null || effectiveRunestoneGlobalPinsDefinition.TargetLocations.Count == 0) { return null; } return GetOrRollServerRunestoneGlobalPin(runestonePosition, effectiveRunestoneGlobalPinsDefinition, runestoneLocationName); } private static bool TryReadRunestoneGlobalPinRequest(ZPackage package, out Vector3 runestonePosition, out string runestoneLocationName) { //IL_0001: 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_0022: Unknown result type (might be due to invalid IL or missing references) runestonePosition = default(Vector3); runestoneLocationName = ""; try { if (package.ReadInt() != 1) { return false; } runestonePosition = ReadRunestoneGlobalPinVector3(package); runestoneLocationName = package.ReadString(); return true; } catch (Exception ex) { WarnRunestoneGlobalPin("globalpin|rpc-request-invalid", "Received invalid runestone global pin request RPC. Ignoring it. " + ex.GetType().Name + ": " + ex.Message); return false; } } private static void WriteRunestoneGlobalPinResponse(ZPackage package, ResolvedRunestoneGlobalPin? pin) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected I4, but got Unknown //IL_004d: Unknown result type (might be due to invalid IL or missing references) package.Write(1); package.Write(pin != null); if (pin != null) { package.Write(pin.LocationName ?? ""); package.Write(pin.PinName ?? ""); package.Write((int)pin.PinType); WriteRunestoneGlobalPinVector3(package, pin.Position); } } private static void OnRunestoneGlobalPinResponseRpc(long sender, ZPackage package) { if (IsRunestoneGlobalPinServerRoutedSender(sender) && TryReadRunestoneGlobalPinResponse(package, out ResolvedRunestoneGlobalPin pin) && pin != null) { TryAddRunestoneGlobalPin(pin); } } private static bool IsRunestoneGlobalPinServerRoutedSender(long sender) { if (ZRoutedRpc.instance == null || (Object)(object)ZNet.instance == (Object)null) { return false; } if (ZNet.instance.IsServer()) { return RunestoneGlobalPinRoutedRpcIdRef.Invoke(ZRoutedRpc.instance) == sender; } ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer != null) { return serverPeer.m_uid == sender; } return false; } private static bool TryReadRunestoneGlobalPinResponse(ZPackage package, out ResolvedRunestoneGlobalPin? pin) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) pin = null; try { if (package.ReadInt() != 1 || !package.ReadBool()) { return true; } pin = new ResolvedRunestoneGlobalPin { LocationName = package.ReadString(), PinName = package.ReadString(), PinType = (PinType)package.ReadInt(), Position = ReadRunestoneGlobalPinVector3(package) }; return true; } catch (Exception ex) { WarnRunestoneGlobalPin("globalpin|rpc-response-invalid", "Received invalid runestone global pin response RPC. Ignoring it. " + ex.GetType().Name + ": " + ex.Message); return false; } } private static void WriteRunestoneGlobalPinVector3(ZPackage package, Vector3 value) { //IL_0001: 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_0019: Unknown result type (might be due to invalid IL or missing references) package.Write(value.x); package.Write(value.y); package.Write(value.z); } private static Vector3 ReadRunestoneGlobalPinVector3(ZPackage package) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) return new Vector3(package.ReadSingle(), package.ReadSingle(), package.ReadSingle()); } private static string CreateRunestoneGlobalPinsRollKey(RuneStone runestone, RunestoneGlobalPinsDefinition definition, string? originalLocationName) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(((Object)runestone).GetInstanceID().ToString(CultureInfo.InvariantCulture)).Append('\n'); Vector3 position = ((Component)runestone).transform.position; stringBuilder.Append(position.x.ToString("R", CultureInfo.InvariantCulture)).Append('|').Append(position.y.ToString("R", CultureInfo.InvariantCulture)) .Append('|') .Append(position.z.ToString("R", CultureInfo.InvariantCulture)) .Append('\n'); stringBuilder.Append(originalLocationName ?? runestone.m_locationName ?? "").Append('\n'); AppendRunestoneGlobalPinsDefinitionRollKey(stringBuilder, definition); return stringBuilder.ToString(); } private static string CreateRunestoneGlobalPinsServerRollKey(Vector3 runestonePosition, RunestoneGlobalPinsDefinition definition, string? runestoneLocationName) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(runestonePosition.x.ToString("R", CultureInfo.InvariantCulture)).Append('|').Append(runestonePosition.y.ToString("R", CultureInfo.InvariantCulture)) .Append('|') .Append(runestonePosition.z.ToString("R", CultureInfo.InvariantCulture)) .Append('\n'); stringBuilder.Append(runestoneLocationName ?? "").Append('\n'); AppendRunestoneGlobalPinsDefinitionRollKey(stringBuilder, definition); return stringBuilder.ToString(); } private static void AppendRunestoneGlobalPinsDefinitionRollKey(StringBuilder builder, RunestoneGlobalPinsDefinition definition) { if (definition.TargetLocations == null) { return; } foreach (RunestoneGlobalPinTargetDefinition targetLocation in definition.TargetLocations) { builder.Append(targetLocation.LocationName).Append('|').Append(targetLocation.Chance?.ToString("R", CultureInfo.InvariantCulture) ?? "") .Append('|') .Append((targetLocation.SourceBiomes == null) ? "" : string.Join(",", targetLocation.SourceBiomes)) .Append('|') .Append(targetLocation.PinName ?? "") .Append('|') .Append(targetLocation.PinType ?? "") .Append('\n'); } } private static void WarnRunestoneGlobalPin(string warningKey, string message) { if (RunestoneGlobalPinWarningLogs.Add(warningKey)) { UsefulRunestonesPlugin.UsefulRunestonesLogger.LogWarning((object)message); } } internal static void TryApplyVegvisirGlobalEffects(Vegvisir? vegvisir, Humanoid? character, bool hold, bool interactionSucceeded) { if (!(!interactionSucceeded || hold) && !((Object)(object)vegvisir == (Object)null)) { Player val = (Player)(object)((character is Player) ? character : null); if (val != null && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && UsefulRunestonesPlugin.IsVegvisirGlobalEffectsEnabled() && IsGameDataReady() && VegvisirCanOpenMap(vegvisir) && HasVegvisirGlobalEffectsConfiguration()) { TryApplyVegvisirGlobalEffects(val, vegvisir); } } } private static VegvisirGlobalEffectsDefinition? GetEffectiveVegvisirGlobalEffectsDefinition() { if (!HasVegvisirGlobalEffectsOverride(_vegvisirGlobalEffects)) { return null; } return _vegvisirGlobalEffects; } private static void TryApplyVegvisirGlobalEffects(Player player, Vegvisir vegvisir) { //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) if ((Object)(object)player != (Object)(object)Player.m_localPlayer || !UsefulRunestonesPlugin.IsVegvisirGlobalEffectsEnabled() || !IsGameDataReady()) { return; } VegvisirGlobalEffectsDefinition effectiveVegvisirGlobalEffectsDefinition = GetEffectiveVegvisirGlobalEffectsDefinition(); if (effectiveVegvisirGlobalEffectsDefinition?.Biomes == null || effectiveVegvisirGlobalEffectsDefinition.Biomes.Count == 0 || ShouldWaitForVegvisirGlobalEffectsBiomeResolution(effectiveVegvisirGlobalEffectsDefinition)) { return; } List<VegvisirGlobalEffectCandidate> candidates = BuildVegvisirGlobalEffectCandidates(GetVegvisirGlobalEffectBiome(((Component)vegvisir).transform.position), effectiveVegvisirGlobalEffectsDefinition); VegvisirGlobalEffectSelectionState orCreateValue = VegvisirGlobalEffectSelections.GetOrCreateValue(vegvisir); VegvisirGlobalEffectCandidate vegvisirGlobalEffectCandidate = SelectVegvisirGlobalEffectCandidate(orCreateValue, candidates); if (vegvisirGlobalEffectCandidate == null) { return; } long playerID = player.GetPlayerID(); bool alreadyActive; if (IsVegvisirGlobalEffectOnCooldown(orCreateValue, playerID, vegvisirGlobalEffectCandidate, out var remainingCooldownSeconds, out var alreadyReceived)) { if (alreadyReceived) { QueueVegvisirGlobalEffectMessage(player, vegvisirGlobalEffectCandidate); } else { QueueVegvisirGlobalEffectCooldownMessage(player, remainingCooldownSeconds); } } else if (!TryGrantVegvisirGlobalEffect(player, vegvisirGlobalEffectCandidate, out alreadyActive)) { if (alreadyActive && (Object)(object)vegvisirGlobalEffectCandidate.StatusEffect != (Object)null) { QueueVegvisirGlobalEffectAlreadyActiveMessage(player, vegvisirGlobalEffectCandidate.StatusEffect); } } else { SetVegvisirGlobalEffectLastGranted(orCreateValue, playerID, vegvisirGlobalEffectCandidate); SpawnVegvisirGlobalEffectPrefab(vegvisir, vegvisirGlobalEffectCandidate.Definition); QueueVegvisirGlobalEffectMessage(player, vegvisirGlobalEffectCandidate); } } private static List<VegvisirGlobalEffectCandidate> BuildVegvisirGlobalEffectCandidates(Biome sourceBiome, VegvisirGlobalEffectsDefinition definition) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) List<VegvisirGlobalEffectCandidate> list = new List<VegvisirGlobalEffectCandidate>(); IEnumerable<VegvisirGlobalEffectsBiomeDefinition> biomes = definition.Biomes; foreach (VegvisirGlobalEffectsBiomeDefinition item in biomes ?? Enumerable.Empty<VegvisirGlobalEffectsBiomeDefinition>()) { if (!MatchesVegvisirGlobalEffectBiome(sourceBiome, item)) { continue; } IEnumerable<VegvisirGlobalEffectDefinition> statusEffects = item.StatusEffects; foreach (VegvisirGlobalEffectDefinition item2 in statusEffects ?? Enumerable.Empty<VegvisirGlobalEffectDefinition>()) { float num = Mathf.Max(0f, item2.Weight ?? 1f); if (num <= 0f) { continue; } string statusEffectName = (item2.StatusEffect ?? "").Trim(); if (IsVegvisirGlobalClearStatusEffect(statusEffectName)) { list.Add(new VegvisirGlobalEffectCandidate { Definition = item2, ClearsStatusEffects = true, EffectKey = "VRS_ClearStatus", SourceBiome = sourceBiome, Weight = num }); continue; } StatusEffect val = ResolveStatusEffect(statusEffectName, "vegvisirGlobalEffects/statusEffects/statusEffect"); if (!((Object)(object)val == (Object)null)) { list.Add(new VegvisirGlobalEffectCandidate { Definition = item2, StatusEffect = val, EffectKey = GetVegvisirGlobalEffectNameKey(val), SourceBiome = sourceBiome, Weight = num }); } } } return list; } private static VegvisirGlobalEffectCandidate? SelectVegvisirGlobalEffectCandidate(VegvisirGlobalEffectSelectionState state, List<VegvisirGlobalEffectCandidate> candidates) { if (candidates.Count == 0) { return null; } string text = BuildVegvisirGlobalEffectCandidateSignature(candidates); lock (VegvisirGlobalEffectsLock) { if (string.Equals(state.CandidateSignature, text, StringComparison.Ordinal) && state.SelectedIndex >= 0 && state.SelectedIndex < candidates.Count) { return candidates[state.SelectedIndex]; } state.CandidateSignature = text; state.SelectedIndex = SelectVegvisirGlobalEffectCandidateIndex(candidates); state.LastGrantedByPlayer.Clear(); return (state.SelectedIndex >= 0) ? candidates[state.SelectedIndex] : null; } } private static int SelectVegvisirGlobalEffectCandidateIndex(List<VegvisirGlobalEffectCandidate> candidates) { float num = 0f; foreach (VegvisirGlobalEffectCandidate candidate in candidates) { num += candidate.Weight; } if (num <= 0f) { return -1; } double num2 = VegvisirGlobalEffectsRandom.NextDouble() * (double)num; float num3 = 0f; for (int i = 0; i < candidates.Count; i++) { num3 += candidates[i].Weight; if (num2 < (double)num3) { return i; } } return candidates.Count - 1; } private static string BuildVegvisirGlobalEffectCandidateSignature(List<VegvisirGlobalEffectCandidate> candidates) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected I4, but got Unknown StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(_configurationSignature ?? ""); foreach (VegvisirGlobalEffectCandidate candidate in candidates) { stringBuilder.Append('|').Append(((int)candidate.SourceBiome).ToString(CultureInfo.InvariantCulture)).Append(':') .Append(candidate.EffectKey) .Append(':') .Append(candidate.Weight.ToString("R", CultureInfo.InvariantCulture)) .Append(':') .Append(GetEffectiveVegvisirGlobalEffectCooldownSeconds(candidate).ToString("R", CultureInfo.InvariantCulture)) .Append(':') .Append(FormatVegvisirGlobalEffectSignatureFloat(candidate.Definition.DurationSeconds)) .Append(':') .Append(candidate.Definition.EffectPrefab ?? ""); } return stringBuilder.ToString(); } private static string FormatVegvisirGlobalEffectSignatureFloat(float? value) { if (!value.HasValue) { return ""; } return value.Value.ToString("R", CultureInfo.InvariantCulture); } private static bool MatchesVegvisirGlobalEffectBiome(Biome sourceBiome, VegvisirGlobalEffectsBiomeDefinition definition) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Invalid comparison between Unknown and I4 //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Invalid comparison between Unknown and I4 string text = (definition.Biome ?? "").Trim(); if (text.Length == 0) { return true; } if (Enumerable.Contains(text, ',') || text.StartsWith("[", StringComparison.Ordinal) || text.EndsWith("]", StringComparison.Ordinal)) { WarnInvalidEntry("vegvisirGlobalEffects biome key '" + text + "' is ignored. Use one biome name per row, or All to match every biome."); return false; } if (text == "*") { WarnInvalidEntry("vegvisirGlobalEffects uses '*' as a biome wildcard. Use All instead."); return false; } if (!BiomeResolutionSupport.TryResolveBiomeToken(text, out var biome)) { WarnInvalidEntry("vegvisirGlobalEffects uses unknown biome '" + text + "'. That biome block is ignored."); return false; } if ((int)biome != 895) { return (sourceBiome & biome) > 0; } return true; } private static bool ShouldWaitForVegvisirGlobalEffectsBiomeResolution(VegvisirGlobalEffectsDefinition definition) { IEnumerable<VegvisirGlobalEffectsBiomeDefinition> biomes = definition.Biomes; foreach (VegvisirGlobalEffectsBiomeDefinition item in biomes ?? Enumerable.Empty<VegvisirGlobalEffectsBiomeDefinition>()) { string text = (item.Biome ?? "").Trim(); if (text.Length != 0) { Biome? resolvedBiomeMask = BiomeResolutionSupport.ResolveBiomeMaskOrNull(new string[1] { text }); if (BiomeResolutionSupport.ShouldWaitForExpandWorldDataBiomeResolution(new string[1] { text }, resolvedBiomeMask)) { return true; } } } return false; } private static bool TryGrantVegvisirGlobalEffect(Player player, VegvisirGlobalEffectCandidate candidate, out bool alreadyActive) { alreadyActive = false; SEMan sEMan = ((Character)player).GetSEMan(); if (candidate.ClearsStatusEffects) { ClearVegvisirGlobalStatusEffects(player, sEMan); return true; } StatusEffect statusEffect = candidate.StatusEffect; if ((Object)(object)statusEffect == (Object)null) { return false; } if ((Object)(object)sEMan.GetStatusEffect(statusEffect.NameHash()) != (Object)null) { alreadyActive = true; return false; } RemoveVegvisirGlobalEffectCategoryConflicts(sEMan, statusEffect); StatusEffect val = sEMan.AddStatusEffect(statusEffect, true, 0, 0f) ?? sEMan.GetStatusEffect(statusEffect.NameHash()); if ((Object)(object)val == (Object)null) { return false; } ApplyVegvisirGlobalEffectDuration(val, candidate.Definition); return true; } private static void ClearVegvisirGlobalStatusEffects(Player player, SEMan seMan) { player.ClearHardDeath(); seMan.RemoveAllStatusEffects(true); } private static void ApplyVegvisirGlobalEffectDuration(StatusEffect activeStatusEffect, VegvisirGlobalEffectDefinition definition) { float effectiveVegvisirGlobalEffectDurationSeconds = GetEffectiveVegvisirGlobalEffectDurationSeconds(definition, activeStatusEffect); if (!(effectiveVegvisirGlobalEffectDurationSeconds <= 0f)) { activeStatusEffect.m_ttl = effectiveVegvisirGlobalEffectDurationSeconds; activeStatusEffect.m_time = 0f; } } private static float GetEffectiveVegvisirGlobalEffectDurationSeconds(VegvisirGlobalEffectDefinition definition, StatusEffect statusEffect) { if (definition.DurationSeconds.HasValue && definition.DurationSeconds.Value > 0f) { return definition.DurationSeconds.Value; } if (!(statusEffect.m_ttl > 0f)) { return 0f; } return statusEffect.m_ttl; } private static void SpawnVegvisirGlobalEffectPrefab(Vegvisir vegvisir, VegvisirGlobalEffectDefinition definition) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) string text = (definition.EffectPrefab ?? "").Trim(); if (text.Length == 0 || (Object)(object)ZNetScene.instance == (Object)null) { return; } if (!IsAllowedVegvisirGlobalEffectPrefabName(text)) { WarnInvalidVegvisirGlobalEffectPrefab(text); return; } GameObject prefab = ZNetScene.instance.GetPrefab(text); if ((Object)(object)prefab == (Object)null) { WarnMissingVegvisirGlobalEffectPrefab(text); } else { ZNetScene.instance.SpawnObject(((Component)vegvisir).transform.position, ((Component)vegvisir).transform.rotation, prefab); } } private static bool IsAllowedVegvisirGlobalEffectPrefabName(string effectPrefabName) { if (!effectPrefabName.StartsWith("vfx_", StringComparison.OrdinalIgnoreCase) && !effectPrefabName.StartsWith("sfx_", StringComparison.OrdinalIgnoreCase)) { return effectPrefabName.StartsWith("fx_", StringComparison.OrdinalIgnoreCase); } return true; } private static void WarnInvalidVegvisirGlobalEffectPrefab(string effectPrefabName) { lock (VegvisirGlobalEffectsLock) { if (!VegvisirGlobalEffectInvalidEffectPrefabWarnings.Add(effectPrefabName)) { return; } } UsefulRunestonesPlugin.UsefulRunestonesLogger.LogWarning((object)("vegvisirGlobalEffects effectPrefab '" + effectPrefabName + "' is ignored. effectPrefab must start with vfx_, sfx_, or fx_ (case-insensitive).")); } private static void WarnMissingVegvisirGlobalEffectPrefab(string effectPrefabName) { lock (VegvisirGlobalEffectsLock) { if (!VegvisirGlobalEffectMissingEffectPrefabWarnings.Add(effectPrefabName)) { return; } } UsefulRunestonesPlugin.UsefulRunestonesLogger.LogWarning((object)("vegvisirGlobalEffects references unknown effect prefab '" + effectPrefabName + "'.")); } private static void RemoveVegvisirGlobalEffectCategoryConflicts(SEMan seMan, StatusEffect statusEffect) { string category = statusEffect.m_category; if (string.IsNullOrWhiteSpace(category)) { return; } int num = statusEffect.NameHash(); List<StatusEffect> statusEffects = seMan.GetStatusEffects(); for (int num2 = statusEffects.Count - 1; num2 >= 0; num2--) { StatusEffect val = statusEffects[num2]; if ((Object)(object)val != (Object)null && val.NameHash() != num && string.Equals(val.m_category, category, StringComparison.Ordinal)) { seMan.RemoveStatusEffect(val, true); } } } private static bool IsVegvisirGlobalEffectOnCooldown(VegvisirGlobalEffectSelectionState state, long playerId, VegvisirGlobalEffectCandidate candidate, out int remainingCooldownSeconds, out bool alreadyReceived) { remainingCooldownSeconds = 0; alreadyReceived = false; float effectiveVegvisirGlobalEffectCooldownSeconds = GetEffectiveVegvisirGlobalEffectCooldownSeconds(candidate); if (effectiveVegvisirGlobalEffectCooldownSeconds == 0f) { return false; } DateTime value; lock (VegvisirGlobalEffectsLock) { if (!state.LastGrantedByPlayer.TryGetValue(playerId, out value)) { return false; } } if (effectiveVegvisirGlobalEffectCooldownSeconds < 0f) { alreadyReceived = true; return true; } double totalSeconds = (GetVegvisirGlobalEffectNow() - value).TotalSeconds; if (totalSeconds < 0.0) { remainingCooldownSeconds = Mathf.CeilToInt(effectiveVegvisirGlobalEffectCooldownSeconds); return true; } double num = (double)effectiveVegvisirGlobalEffectCooldownSeconds - totalSeconds; if (num <= 0.0) { return false; } remainingCooldownSeconds = Mathf.Max(1, Mathf.CeilToInt((float)num)); return true; } private static void SetVegvisirGlobalEffectLastGranted(VegvisirGlobalEffectSelectionState state, long playerId, VegvisirGlobalEffectCandidate candidate) { if (GetEffectiveVegvisirGlobalEffectCooldownSeconds(candidate) == 0f) { lock (VegvisirGlobalEffectsLock) { state.LastGrantedByPlayer.Remove(playerId); return; } } lock (VegvisirGlobalEffectsLock) { state.LastGrantedByPlayer[playerId] = GetVegvisirGlobalEffectNow(); } } private static float GetEffectiveVegvisirGlobalEffectCooldownSeconds(VegvisirGlobalEffectCandidate candidate) { return GetEffectiveVegvisirGlobalEffectCooldownSeconds(candidate.Definition, candidate.StatusEffect); } private static float GetEffectiveVegvisirGlobalEffectCooldownSeconds(VegvisirGlobalEffectDefinition definition, StatusEffect? statusEffect) { if (definition.CooldownSeconds.HasValue) { return definition.CooldownSeconds.Value; } float num = statusEffect?.m_cooldown ?? 0f; if (!(num > 0f)) { return 0f; } return num; } private static string GetVegvisirGlobalEffectNameKey(StatusEffect statusEffect) { if (!string.IsNullOrWhiteSpace(((Object)statusEffect).name)) { return ((Object)statusEffect).name; } return statusEffect.m_name; } private static bool IsVegvisirGlobalClearStatusEffect(string? statusEffectName) { return string.Equals((statusEffectName ?? "").Trim(), "VRS_ClearStatus", StringComparison.OrdinalIgnoreCase); } private static bool HasVegvisirGlobalEffectsConfiguration() { VegvisirGlobalEffectsDefinition effectiveVegvisirGlobalEffectsDefinition = GetEffectiveVegvisirGlobalEffectsDefinition(); if (effectiveVegvisirGlobalEffectsDefinition?.Biomes != null) { return effectiveVegvisirGlobalEffectsDefinition.Biomes.Count > 0; } return false; } private static bool VegvisirCanOpenMap(Vegvisir vegvisir) { if (vegvisir.m_locations != null) { return vegvisir.m_locations.Any((VegvisrLocation location) => location?.m_showMap ?? false); } return false; } private static DateTime GetVegvisirGlobalEffectNow() { if (!((Object)(object)ZNet.instance != (Object)null)) { return DateTime.UtcNow; } return ZNet.instance.GetTime(); } private static Biome GetVegvisirGlobalEffectBiome(Vector3 position) { //IL_0013: 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) //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) if (WorldGenerator.instance == null) { return Heightmap.FindBiome(position); } return WorldGenerator.instance.GetBiome(position); } private static void QueueVegvisirGlobalEffectMessage(Player player, VegvisirGlobalEffectCandidate candidate) { VegvisirGlobalEffectsLocalizationDefinition vegvisirGlobalEffectsLocalization = GetVegvisirGlobalEffectsLocalization(); if (candidate.ClearsStatusEffects) { QueueVegvisirGlobalEffectCenterMessage(player, GetVegvisirGlobalEffectLocalizedText(vegvisirGlobalEffectsLocalization?.MessageVegvisirClearStatus, "You got bamboozled")); } else if (!((Object)(object)candidate.StatusEffect == (Object)null)) { QueueVegvisirGlobalEffectCenterMessage(player, FormatVegvisirGlobalEffectNamedMessage(GetVegvisirGlobalEffectLocalizedText(vegvisirGlobalEffectsLocalization?.MessageVegvisirEffectReceived, "You have received {name}"), GetVegvisirGlobalEffectDisplayName(candidate.StatusEffect))); } } private static void QueueVegvisirGlobalEffectCooldownMessage(Player player, int remainingCooldownSeconds) { QueueVegvisirGlobalEffectCenterMessage(player, FormatVegvisirGlobalEffectCooldownMessage(GetVegvisirGlobalEffectLocalizedText(GetVegvisirGlobalEffectsLocalization()?.MessageVegvisirBuffCooldown, "Buff Cooldown {seconds}s"), Mathf.Max(1, remainingCooldownSeconds))); } private static void QueueVegvisirGlobalEffectAlreadyActiveMessage(Player player, StatusEffect statusEffect) { QueueVegvisirGlobalEffectCenterMessage(player, FormatVegvisirGlobalEffectNamedMessage(GetVegvisirGlobalEffectLocalizedText(GetVegvisirGlobalEffectsLocalization()?.MessageVegvisirAlreadyActive, "Already active {name}"), GetVegvisirGlobalEffectDisplayName(statusEffect))); } private static VegvisirGlobalEffectsLocalizationDefinition? GetVegvisirGlobalEffectsLocalization() { return _localization; } private static string GetVegvisirGlobalEffectLocalizedText(string? configuredText, string fallbackText) { string text = (configuredText ?? "").Trim(); if (text.Length != 0) { return text; } return fallbackText; } private static string FormatVegvisirGlobalEffectNamedMessage(string message, string displayName) { if (message.IndexOf("{name}", StringComparison.Ordinal) < 0) { return message + " \"" + displayName + "\""; } return message.Replace("{name}", displayName); } private static string FormatVegvisirGlobalEffectCooldownMessage(string message, int remainingCooldownSeconds) { string text = remainingCooldownSeconds.ToString(CultureInfo.InvariantCulture); if (message.IndexOf("{seconds}", StringComparison.Ordinal) >= 0) { return message.Replace("{seconds}", text); } if (message.IndexOf('N') >= 0) { return message.Replace("N", text); } return message + " " + text + "s"; } private static void QueueVegvisirGlobalEffectCenterMessage(Player player, string message) { UsefulRunestonesPlugin? instance = UsefulRunestonesPlugin.Instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(ShowVegvisirGlobalEffectCenterMessageAfterDelay(player.GetPlayerID(), message)); } } private static IEnumerator ShowVegvisirGlobalEffectCenterMessageAfterDelay(long playerId, string message) { yield return (object)new WaitForSeconds(1f); Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && localPlayer.GetPlayerID() == playerId) { ((Character)localPlayer).Message((MessageType)2, message, 0, (Sprite)null); } } private static string GetVegvisirGlobalEffectDisplayName(StatusEffect statusEffect) { string text = (string.IsNullOrWhiteSpace(statusEffect.m_name) ? GetVegvisirGlobalEffectNameKey(statusEffect) : statusEffect.m_name); string text2 = ((Localization.instance != null) ? Localization.instance.Localize(text) : text); if (!string.IsNullOrWhiteSpace(text2)) { return text2; } return GetVegvisirGlobalEffectNameKey(statusEffect); } } [HarmonyPatch(typeof(RuneStone), "Interact")] internal static class RuneStoneInteractGlobalPinsPatch { private struct RunestoneInteractionState { public bool Hold { get; set; } public string? OriginalLocationName { get; set; } } private static void Prefix(RuneStone __instance, bool hold, ref RunestoneInteractionState __state) { __state.Hold = hold; __state.OriginalLocationName = __instance.m_locationName; } private static void Postfix(RuneStone __instance, RunestoneInteractionState __state) { UsefulRunestonesRuntime.TryApplyRunestoneGlobalPins(__instance, __state.Hold, __state.OriginalLocationName); } } [HarmonyPatch(typeof(Vegvisir), "Interact")] internal static class VegvisirInteractGlobalEffectsPatch { private static void Postfix(Vegvisir __instance, Humanoid character, bool hold, bool __result) { UsefulRunestonesRuntime.TryApplyVegvisirGlobalEffects(__instance, character, hold, __result); } } internal static class BiomeResolutionSupport { private static readonly Dictionary<string, Biome> VanillaBiomeLookup = BuildVanillaBiomeLookup(); private static readonly Type? ExpandWorldDataDataManagerType = Type.GetType("ExpandWorldData.DataManager, ExpandWorldData"); private static readonly PropertyInfo? ExpandWorldDataIsReadyProperty = ExpandWorldDataDataManagerType?.GetProperty("IsReady", BindingFlags.Static | BindingFlags.Public); private static readonly MethodInfo? ExpandWorldDataTryGetBiomeMethod = Type.GetType("ExpandWorldData.BiomeManager, ExpandWorldData")?.GetMethod("TryGetBiome", BindingFlags.Static | BindingFlags.Public, null, new Type[2] { typeof(string), typeof(Biome).MakeByRefType() }, null); internal static bool TryResolveBiomeToken(string? configuredBiome, out Biome biome) { string text = (configuredBiome ?? "").Trim(); if (text.Length == 0) { biome = (Biome)0; return false; } if (string.Equals(text, "All", StringComparison.OrdinalIgnoreCase)) { biome = (Biome)895; return true; } if (int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && result != 0) { biome = (Biome)result; return true; } if (Enum.TryParse<Biome>(text, ignoreCase: true, out biome)) { return true; } if (VanillaBiomeLookup.TryGetValue(NormalizeBiomeToken(text), out biome)) { return true; } if (TryResolveExpandWorldDataBiome(text, out biome)) { return true; } biome = (Biome)0; return false; } internal static bool TryResolveBiomeMask(IEnumerable<string>? configuredBiomes, out Biome biomeMask) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Invalid comparison between Unknown and I4 //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected I4, but got Unknown biomeMask = (Biome)0; bool result = false; foreach (string item in configuredBiomes ?? Array.Empty<string>()) { string text = (item ?? "").Trim(); if (text.Length != 0) { result = true; if (!TryResolveBiomeToken(text, out var biome)) { biomeMask = (Biome)0; return false; } if ((int)biome == 895) { biomeMask = (Biome)895; return true; } biomeMask |= biome; } } return result; } internal static Biome? ResolveBiomeMaskOrNull(IEnumerable<string>? configuredBiomes) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (!TryResolveBiomeMask(configuredBiomes, out var biomeMask)) { return null; } return biomeMask; } private static bool IsExpandWorldDataPresent() { if (!(ExpandWorldDataTryGetBiomeMethod != null)) { return ExpandWorldDataIsReadyProperty != null; } return true; } private static bool IsExpandWorldDataReadyOrUnavailable() { if (ExpandWorldDataIsReadyProperty == null) { return true; } try { return (ExpandWorldDataIsReadyProperty.GetValue(null) as bool?) ?? true; } catch { return true; } } internal static bool ShouldWaitForExpandWorldDataBiomeResolution(IEnumerable<string>? configuredBiomes, Biome? resolvedBiomeMask) { if (!HasConfiguredBiomeTokens(configuredBiomes)) { return false; } if (resolvedBiomeMask.HasValue || !IsExpandWorldDataPresent() || IsExpandWorldDataReadyOrUnavailable()) { return false; } Biome biomeMask; return !TryResolveBiomeMask(configuredBiomes, out biomeMask); } private static bool HasConfiguredBiomeTokens(IEnumerable<string>? configuredBiomes) { foreach (string item in configuredBiomes ?? Array.Empty<string>()) { if (!string.IsNullOrWhiteSpace(item)) { return true; } } return false; } private static string NormalizeBiomeToken(string? value) { StringBuilder stringBuilder = new StringBuilder(); string text = (value ?? "").Trim(); foreach (char c in text) { if (char.IsLetterOrDigit(c)) { stringBuilder.Append(char.ToLowerInvariant(c)); } } return stringBuilder.ToString(); } private static Dictionary<string, Biome> BuildVanillaBiomeLookup() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) Dictionary<string, Biome> dictionary = new Dictionary<string, Biome>(StringComparer.OrdinalIgnoreCase); foreach (Biome value in Enum.GetValues(typeof(Biome))) { dictionary[NormalizeBiomeToken(((object)value/*cast due to .constrained prefix*/).ToString())] = value; } dictionary["ashlands"] = (Biome)32; return dictionary; } private static bool TryResolveExpandWorldDataBiome(string configuredBiome, out Biome biome) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected I4, but got Unknown if (ExpandWorl