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.1
UsefulRunestones.dll
Decompiled a week 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.Cryptography; 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 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.1")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.1.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.1")] [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.1"; 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.1", MinimumRequiredVersion = "1.0.1" }; private readonly Harmony _harmony = new Harmony("sighsorry.UsefulRunestones"); private CustomSyncedValue<string> _syncedYaml; private FileSystemWatcher? _watcher; private float _reloadDueAt = -1f; private int _yamlReloadQueued; private ConfigEntry<Toggle> _lockConfiguration; private ConfigEntry<Toggle> _enableRunestoneGlobalPins; private ConfigEntry<Toggle> _enableVegvisirGlobalEffects; internal static UsefulRunestonesPlugin? Instance { get; private set; } private static string ConfigDirectoryPath => Paths.ConfigPath; private static string YamlFilePath => Path.Combine(ConfigDirectoryPath, "UsefulRunestones.yml"); private static bool IsSourceOfTruth => Sync.IsSourceOfTruth; internal static bool IsRunestoneGlobalPinsEnabled() { UsefulRunestonesPlugin? instance = Instance; if (instance == null) { return false; } return instance._enableRunestoneGlobalPins?.Value == Toggle.On; } internal static bool IsVegvisirGlobalEffectsEnabled() { UsefulRunestonesPlugin? instance = Instance; if (instance == null) { return false; } return instance._enableVegvisirGlobalEffects?.Value == Toggle.On; } private void Awake() { Directory.CreateDirectory(ConfigDirectoryPath); EnsureDefaultYamlExists(); BindConfiguration(); Instance = this; _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; } if (_syncedYaml != 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 = BindOrderedSyncedConfigEntry("1 - General", "Lock Configuration", Toggle.On, "If on, synced configuration can be changed by server admins only.", 300); _enableRunestoneGlobalPins = BindOrderedSyncedConfigEntry("1 - General", "Enable Runestone Global Pins", Toggle.On, "If on, pinless RuneStones can reveal one saved map pin from UsefulRunestones.yml runestoneGlobalPins rows. Local selections are stable per loaded RuneStone; server-resolved selections are stable per RuneStone position for the server session.", 200); _enableVegvisirGlobalEffects = BindOrderedSyncedConfigEntry("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.", 100); Sync.AddLockingConfigEntry<Toggle>(_lockConfiguration); } finally { ((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet; } } private ConfigEntry<T> BindOrderedSyncedConfigEntry<T>(string group, string name, T value, string description, int configManagerOrder) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown ConfigDescription val = new ConfigDescription(description + " [Synced with Server]", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = configManagerOrder } }); ConfigEntry<T> val2 = ((BaseUnityPlugin)this).Config.Bind<T>(group, name, value, val); Sync.AddConfigEntry<T>(val2).SynchronizedConfig = true; return val2; } 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) { Interlocked.Exchange(ref _yamlReloadQueued, 1); } private void ProcessQueuedYamlReload() { if (Interlocked.Exchange(ref _yamlReloadQueued, 0) == 1) { if (!IsSourceOfTruth) { _reloadDueAt = -1f; return; } _reloadDueAt = Time.realtimeSinceStartup + 0.25f; } if (!(_reloadDueAt < 0f)) { if (!IsSourceOfTruth) { _reloadDueAt = -1f; } else if (!(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) { UsefulRunestonesConfiguration configuration; try { configuration = UsefulRunestonesConfiguration.Parse(yaml); } catch (Exception ex) { UsefulRunestonesLogger.LogError((object)("Rejected UsefulRunestones YAML from " + source + ". Keeping the previous configuration. " + ex.GetType().Name + ": " + ex.Message)); return false; } UsefulRunestonesRuntime.SetConfiguration(configuration); UsefulRunestonesLogger.LogInfo((object)("Loaded UsefulRunestones YAML from " + source + ".")); 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 UsefulRunestonesConfiguration Parse(string yaml) { UsefulRunestonesConfiguration obj = (string.IsNullOrWhiteSpace(yaml) ? new UsefulRunestonesConfiguration() : (Deserializer.Deserialize<UsefulRunestonesConfiguration>(yaml) ?? new UsefulRunestonesConfiguration())); Normalize(obj); return obj; } 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 = ShorthandScalar.NormalizeOptionalString(targetLocation.PinName, acceptNullLiteral: false); targetLocation.PinType = ShorthandScalar.NormalizeOptionalString(targetLocation.PinType, acceptNullLiteral: false); 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 = ShorthandScalar.NormalizeOptionalString(effect.EffectPrefab, acceptNullLiteral: false); } private static void NormalizeLocalization(VegvisirGlobalEffectsLocalizationDefinition? localization) { if (localization != null) { localization.MessageVegvisirEffectReceived = ShorthandScalar.NormalizeOptionalString(localization.MessageVegvisirEffectReceived, acceptNullLiteral: false); localization.MessageVegvisirClearStatus = ShorthandScalar.NormalizeOptionalString(localization.MessageVegvisirClearStatus, acceptNullLiteral: false); localization.MessageVegvisirBuffCooldown = ShorthandScalar.NormalizeOptionalString(localization.MessageVegvisirBuffCooldown, acceptNullLiteral: false); localization.MessageVegvisirAlreadyActive = ShorthandScalar.NormalizeOptionalString(localization.MessageVegvisirAlreadyActive, acceptNullLiteral: false); } } 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 string? PinName { get; set; } [YamlMember(Order = 4)] public string? PinType { get; set; } [YamlMember(Order = 5)] public List<string>? SourceBiomes { 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.NormalizeOptionalString(list[2], acceptNullLiteral: true) : null); PinType = ((list.Count > 3) ? ShorthandScalar.NormalizeOptionalString(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? DurationSeconds { get; set; } [YamlIgnore] public float? CooldownSeconds { get; set; } [YamlIgnore] public float? Weight { 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.NormalizeOptionalString(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) || float.IsNaN(result) || float.IsInfinity(result)) { throw new YamlException(context + " value '" + text + "' is not a valid " + fieldName + " number."); } return result; } internal static string FormatOptionalFloat(float? value) { if (!value.HasValue) { return ""; } return value.Value.ToString("R", CultureInfo.InvariantCulture); } internal static string? NormalizeOptionalString(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 DefinitionKey { get; set; } = ""; public ResolvedRunestoneGlobalPin? Pin { get; set; } } private sealed class RunestoneGlobalPinRequestState { public long LastAcceptedTimestamp { 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 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 object RuntimeWarningLock = new object(); private static readonly HashSet<string> RuntimeWarningLogs = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static UsefulRunestonesConfiguration _configuration = new UsefulRunestonesConfiguration(); private const int RunestoneGlobalPinRpcVersion = 1; private const string RunestoneGlobalPinRequestRpc = "UsefulRunestones Runestone GlobalPin Request"; private const string RunestoneGlobalPinResponseRpc = "UsefulRunestones Runestone GlobalPin Response"; private const float RunestoneGlobalPinRpcInteractionDistance = 10f; private const double RunestoneGlobalPinRpcRequestIntervalSeconds = 1.0; private const float RunestoneGlobalPinServerRollCellSize = 16f; private static readonly ConditionalWeakTable<RuneStone, RunestoneGlobalPinsRollState> RunestoneGlobalPinsRolls = new ConditionalWeakTable<RuneStone, RunestoneGlobalPinsRollState>(); private static readonly ConditionalWeakTable<ZNetPeer, RunestoneGlobalPinRequestState> RunestoneGlobalPinRequestStates = new ConditionalWeakTable<ZNetPeer, RunestoneGlobalPinRequestState>(); private static readonly object RunestoneGlobalPinsLock = new object(); private static readonly Random RunestoneGlobalPinsRandom = new Random(); private static readonly byte[] RunestoneGlobalPinServerRollSecret = CreateRunestoneGlobalPinServerRollSecret(); private static readonly Dictionary<string, string> RunestoneGlobalPinDefaultPinNames = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); private static RunestoneGlobalPinLocationIndex? RunestoneGlobalPinLocationIndexCache; private static ZRoutedRpc? RunestoneGlobalPinRegisteredRpcInstance; 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>(); internal static void SetConfiguration(UsefulRunestonesConfiguration configuration) { _configuration = configuration; ClearRuntimeWarnings(); ResetRunestoneGlobalPinsRuntimeState(); } 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 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) { WarnOnce(message, message); } private static void WarnOnce(string warningKey, string message) { lock (RuntimeWarningLock) { if (!RuntimeWarningLogs.Add(warningKey)) { return; } } UsefulRunestonesPlugin.UsefulRunestonesLogger.LogWarning((object)message); } private static void ClearRuntimeWarnings() { lock (RuntimeWarningLock) { RuntimeWarningLogs.Clear(); } } internal static void TryApplyRunestoneGlobalPins(RuneStone? runestone, bool hold, string? originalLocationName) { BossStone val = default(BossStone); if (hold || (Object)(object)runestone == (Object)null || ((Component)runestone).TryGetComponent<BossStone>(ref val) || !UsefulRunestonesPlugin.IsRunestoneGlobalPinsEnabled() || !string.IsNullOrWhiteSpace(originalLocationName ?? runestone.m_locationName) || TryRequestServerResolvedRunestoneGlobalPin(runestone, originalLocationName)) { return; } RunestoneGlobalPinsDefinition runestoneGlobalPins = _configuration.RunestoneGlobalPins; if (runestoneGlobalPins?.TargetLocations != null && runestoneGlobalPins.TargetLocations.Count != 0) { ResolvedRunestoneGlobalPin orRollRunestoneGlobalPin = GetOrRollRunestoneGlobalPin(runestone, runestoneGlobalPins); if (orRollRunestoneGlobalPin != null) { AddOrShowRunestoneGlobalPin(orRollRunestoneGlobalPin); } } } 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) { RunestoneGlobalPinDefaultPinNames.Clear(); RunestoneGlobalPinLocationIndexCache = null; } } private static ResolvedRunestoneGlobalPin? GetOrRollRunestoneGlobalPin(RuneStone runestone, RunestoneGlobalPinsDefinition definition) { //IL_0068: Unknown result type (might be due to invalid IL or missing references) if (ShouldWaitForRunestoneGlobalPinSourceBiomeResolution(definition)) { return null; } string text = CreateRunestoneGlobalPinsDefinitionKey(definition); lock (RunestoneGlobalPinsLock) { if (!RunestoneGlobalPinsRolls.TryGetValue(runestone, out RunestoneGlobalPinsRollState value)) { value = new RunestoneGlobalPinsRollState(); RunestoneGlobalPinsRolls.Add(runestone, value); } if (value.DefinitionKey == text) { return value.Pin; } value.DefinitionKey = text; value.Pin = RollRunestoneGlobalPin(((Component)runestone).transform.position, definition); return value.Pin; } } private static ResolvedRunestoneGlobalPin? RollServerRunestoneGlobalPin(Vector3 runestonePosition, RunestoneGlobalPinsDefinition definition) { //IL_000a: 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) if (ShouldWaitForRunestoneGlobalPinSourceBiomeResolution(definition)) { return null; } double value = CreateRunestoneGlobalPinServerSelectionRoll(runestonePosition, definition); lock (RunestoneGlobalPinsLock) { return RollRunestoneGlobalPin(runestonePosition, definition, value); } } private static ResolvedRunestoneGlobalPin? RollRunestoneGlobalPin(Vector3 runestonePosition, RunestoneGlobalPinsDefinition definition, double? selectionRoll = null) { //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_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_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007d: 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 valueOrDefault = targetLocation.Chance.GetValueOrDefault(); if (targetLocation.LocationName.Length != 0 && !(valueOrDefault <= 0f)) { Biome allowedSourceBiomeMask = ResolveRunestoneGlobalPinSourceBiomeMask(targetLocation); if (TryFindClosestRunestoneGlobalPinLocation(targetLocation.LocationName, runestonePosition, runestoneGlobalPinBiome, allowedSourceBiomeMask, out var closest)) { ResolvedRunestoneGlobalPin pin = CreateResolvedRunestoneGlobalPin(targetLocation, closest); list.Add(new RunestoneGlobalPinCandidate { Chance = valueOrDefault, Pin = pin }); } } } return SelectRunestoneGlobalPinCandidate(list, selectionRoll); } private static ResolvedRunestoneGlobalPin? SelectRunestoneGlobalPinCandidate(List<RunestoneGlobalPinCandidate> candidates, double? selectionRoll) { float num = 0f; foreach (RunestoneGlobalPinCandidate candidate in candidates) { num += candidate.Chance; } if (num <= 0f) { return null; } if (num > 1f) { WarnOnce("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 = (selectionRoll ?? 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_0046: 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_0065: 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_006d: Unknown result type (might be due to invalid IL or missing references) string pinName; if (!string.IsNullOrWhiteSpace(target.PinName)) { pinName = target.PinName.Trim(); } else { string runestoneGlobalPinConfiguredDefaultName = GetRunestoneGlobalPinConfiguredDefaultName(target.LocationName); pinName = ((runestoneGlobalPinConfiguredDefaultName.Length > 0) ? runestoneGlobalPinConfiguredDefaultName : 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 void AddOrShowRunestoneGlobalPin(ResolvedRunestoneGlobalPin pin) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) Minimap instance = Minimap.instance; if (instance != null) { instance.DiscoverLocation(pin.Position, pin.PinType, pin.PinName, 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; } WarnOnce("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(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 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_0035: 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) 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) && Enum.IsDefined(typeof(PinType), result)) { return result; } WarnOnce("globalpin|invalid-pintype|" + locationName + "|" + text, "Runestone global pin target '" + locationName + "' has invalid pinType '" + text + "'. Using Icon3."); return (PinType)3; } 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); val.Write(((Component)runestone).transform.position); val.Write(runestoneLocationName ?? runestone.m_locationName ?? ""); long serverPeerID = ZRoutedRpc.instance.GetServerPeerID(); if (serverPeerID != 0L) { ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerID, "UsefulRunestones Runestone GlobalPin Request", new object[1] { val }); } return true; } private static void OnRunestoneGlobalPinRequestRpc(long sender, ZPackage package) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: 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(sender, 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(long sender, Vector3 runestonePosition, string? runestoneLocationName) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) if (!UsefulRunestonesPlugin.IsRunestoneGlobalPinsEnabled() || !IsValidServerRunestoneGlobalPinRequest(sender, runestonePosition, runestoneLocationName)) { return null; } RunestoneGlobalPinsDefinition runestoneGlobalPins = _configuration.RunestoneGlobalPins; if (runestoneGlobalPins?.TargetLocations == null || runestoneGlobalPins.TargetLocations.Count == 0) { return null; } return RollServerRunestoneGlobalPin(runestonePosition, runestoneGlobalPins); } private static bool IsValidServerRunestoneGlobalPinRequest(long sender, Vector3 runestonePosition, string? runestoneLocationName) { //IL_001f: 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_0031: Unknown result type (might be due to invalid IL or missing references) ZRoutedRpc instance = ZRoutedRpc.instance; ZNetPeer val = ((instance != null) ? instance.GetPeer(sender) : null); if (val == null || !val.IsReady() || !IsFiniteRunestoneGlobalPinVector3(val.GetRefPos()) || Vector3.Distance(val.GetRefPos(), runestonePosition) > 10f || !string.IsNullOrWhiteSpace(runestoneLocationName)) { return false; } long timestamp = Stopwatch.GetTimestamp(); lock (RunestoneGlobalPinsLock) { RunestoneGlobalPinRequestState orCreateValue = RunestoneGlobalPinRequestStates.GetOrCreateValue(val); if (orCreateValue.LastAcceptedTimestamp != 0L && (double)(timestamp - orCreateValue.LastAcceptedTimestamp) / (double)Stopwatch.Frequency < 1.0) { return false; } orCreateValue.LastAcceptedTimestamp = timestamp; return true; } } 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) //IL_0030: Unknown result type (might be due to invalid IL or missing references) runestonePosition = default(Vector3); runestoneLocationName = ""; try { if (package.ReadInt() != 1) { return false; } runestonePosition = package.ReadVector3(); runestoneLocationName = package.ReadString(); return IsFiniteRunestoneGlobalPinVector3(runestonePosition); } catch (Exception ex) { WarnOnce("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); package.Write(pin.Position); } } private static void OnRunestoneGlobalPinResponseRpc(long sender, ZPackage package) { if (!((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer() && !((Object)(object)Minimap.instance == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null) && UsefulRunestonesPlugin.IsRunestoneGlobalPinsEnabled()) { List<RunestoneGlobalPinTargetDefinition> list = _configuration.RunestoneGlobalPins?.TargetLocations; if (list != null && list.Count > 0 && IsRunestoneGlobalPinServerRoutedSender(sender) && TryReadRunestoneGlobalPinResponse(package, out ResolvedRunestoneGlobalPin pin) && pin != null) { AddOrShowRunestoneGlobalPin(pin); } } } private static bool IsRunestoneGlobalPinServerRoutedSender(long sender) { if (ZRoutedRpc.instance != null) { return sender == ZRoutedRpc.instance.GetServerPeerID(); } return false; } private static bool TryReadRunestoneGlobalPinResponse(ZPackage package, out ResolvedRunestoneGlobalPin? pin) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //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_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) pin = null; try { if (package.ReadInt() != 1 || !package.ReadBool()) { return true; } string locationName = package.ReadString(); string pinName = package.ReadString(); PinType val = (PinType)package.ReadInt(); Vector3 val2 = package.ReadVector3(); if (!Enum.IsDefined(typeof(PinType), val) || !IsFiniteRunestoneGlobalPinVector3(val2)) { return false; } pin = new ResolvedRunestoneGlobalPin { LocationName = locationName, PinName = pinName, PinType = val, Position = val2 }; return true; } catch (Exception ex) { WarnOnce("globalpin|rpc-response-invalid", "Received invalid runestone global pin response RPC. Ignoring it. " + ex.GetType().Name + ": " + ex.Message); return false; } } private static bool IsFiniteRunestoneGlobalPinVector3(Vector3 value) { //IL_0000: 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_001a: 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_0034: 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) if (!float.IsNaN(value.x) && !float.IsInfinity(value.x) && !float.IsNaN(value.y) && !float.IsInfinity(value.y) && !float.IsNaN(value.z)) { return !float.IsInfinity(value.z); } return false; } private static string CreateRunestoneGlobalPinsDefinitionKey(RunestoneGlobalPinsDefinition definition) { StringBuilder stringBuilder = new StringBuilder(); if (definition.TargetLocations != null) { foreach (RunestoneGlobalPinTargetDefinition targetLocation in definition.TargetLocations) { stringBuilder.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'); } } return stringBuilder.ToString(); } private static double CreateRunestoneGlobalPinServerSelectionRoll(Vector3 runestonePosition, RunestoneGlobalPinsDefinition definition) { //IL_0008: 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) string s = Mathf.FloorToInt(runestonePosition.x / 16f).ToString(CultureInfo.InvariantCulture) + "|" + Mathf.FloorToInt(runestonePosition.z / 16f).ToString(CultureInfo.InvariantCulture) + "\n" + CreateRunestoneGlobalPinsDefinitionKey(definition); byte[] bytes = Encoding.UTF8.GetBytes(s); byte[] value; using (HMACSHA256 hMACSHA = new HMACSHA256(RunestoneGlobalPinServerRollSecret)) { value = hMACSHA.ComputeHash(bytes); } return (double)(BitConverter.ToUInt64(value, 0) >> 11) / 9007199254740992.0; } private static byte[] CreateRunestoneGlobalPinServerRollSecret() { byte[] array = new byte[32]; using RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create(); randomNumberGenerator.GetBytes(array); return array; } internal static void TryApplyVegvisirGlobalEffects(Vegvisir? vegvisir, Humanoid? character, bool hold, bool interactionSucceeded) { VegvisirGlobalEffectsDefinition vegvisirGlobalEffects = _configuration.VegvisirGlobalEffects; 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) && vegvisirGlobalEffects?.Biomes != null && vegvisirGlobalEffects.Biomes.Count != 0) { TryApplyVegvisirGlobalEffects(val, vegvisir, vegvisirGlobalEffects); } } } private static void TryApplyVegvisirGlobalEffects(Player player, Vegvisir vegvisir, VegvisirGlobalEffectsDefinition definition) { //IL_000f: 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) if (ShouldWaitForVegvisirGlobalEffectsBiomeResolution(definition)) { return; } List<VegvisirGlobalEffectCandidate> candidates = BuildVegvisirGlobalEffectCandidates(GetVegvisirGlobalEffectBiome(((Component)vegvisir).transform.position), definition); 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) 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 = 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", 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), 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) { StringBuilder stringBuilder = new StringBuilder(); foreach (VegvisirGlobalEffectCandidate candidate in candidates) { stringBuilder.Append('|').Append(candidate.EffectKey).Append(':') .Append(candidate.Weight.ToString("R", CultureInfo.InvariantCulture)) .Append(':') .Append(GetEffectiveVegvisirGlobalEffectCooldownSeconds(candidate).ToString("R", CultureInfo.InvariantCulture)) .Append(':') .Append(candidate.Definition.DurationSeconds?.ToString("R", CultureInfo.InvariantCulture) ?? "") .Append(':') .Append(candidate.Definition.EffectPrefab ?? ""); } return stringBuilder.ToString(); } private static bool MatchesVegvisirGlobalEffectBiome(Biome sourceBiome, VegvisirGlobalEffectsBiomeDefinition definition) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Invalid comparison between Unknown and I4 //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Invalid comparison between Unknown and I4 string text = (definition.Biome ?? "").Trim(); if (text.Length == 0) { return true; } 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 && BiomeResolutionSupport.ShouldWaitForExpandWorldDataBiomeResolution(new string[1] { text })) { 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; } List<StatusEffect> list = new List<StatusEffect>(); string category = statusEffect.m_category; if (!string.IsNullOrWhiteSpace(category)) { int num = statusEffect.NameHash(); foreach (StatusEffect statusEffect2 in sEMan.GetStatusEffects()) { if ((Object)(object)statusEffect2 != (Object)null && statusEffect2.NameHash() != num && string.Equals(statusEffect2.m_category, category, StringComparison.Ordinal)) { list.Add(statusEffect2); } } } StatusEffect val = sEMan.AddStatusEffect(statusEffect, true, 0, 0f) ?? sEMan.GetStatusEffect(statusEffect.NameHash()); if ((Object)(object)val == (Object)null) { return false; } foreach (StatusEffect item in list) { if (sEMan.GetStatusEffect(item.NameHash()) == item) { sEMan.RemoveStatusEffect(item, true); } } 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 num = ((definition.DurationSeconds.HasValue && definition.DurationSeconds.Value > 0f) ? definition.DurationSeconds.Value : ((activeStatusEffect.m_ttl > 0f) ? activeStatusEffect.m_ttl : 0f)); if (!(num <= 0f)) { activeStatusEffect.m_ttl = num; activeStatusEffect.m_time = 0f; } } private static void SpawnVegvisirGlobalEffectPrefab(Vegvisir vegvisir, VegvisirGlobalEffectDefinition definition) { //IL_0095: 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) string text = (definition.EffectPrefab ?? "").Trim(); if (text.Length == 0 || (Object)(object)ZNetScene.instance == (Object)null) { return; } if (!IsAllowedVegvisirGlobalEffectPrefabName(text)) { WarnOnce("vegvisir|invalid-effect-prefab|" + text, "vegvisirGlobalEffects effectPrefab '" + text + "' is ignored. effectPrefab must start with vfx_, sfx_, or fx_ (case-insensitive)."); return; } GameObject prefab = ZNetScene.instance.GetPrefab(text); if ((Object)(object)prefab == (Object)null) { WarnOnce("vegvisir|missing-effect-prefab|" + text, "vegvisirGlobalEffects references unknown effect prefab '" + 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 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) { if (candidate.Definition.CooldownSeconds.HasValue) { return candidate.Definition.CooldownSeconds.Value; } float num = candidate.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 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 localization = _configuration.Localization; if (candidate.ClearsStatusEffects) { QueueVegvisirGlobalEffectCenterMessage(player, GetVegvisirGlobalEffectLocalizedText(localization?.MessageVegvisirClearStatus, "You got bamboozled")); } else if (!((Object)(object)candidate.StatusEffect == (Object)null)) { QueueVegvisirGlobalEffectCenterMessage(player, FormatVegvisirGlobalEffectNamedMessage(GetVegvisirGlobalEffectLocalizedText(localization?.MessageVegvisirEffectReceived, "You have received {name}"), GetVegvisirGlobalEffectDisplayName(candidate.StatusEffect))); } } private static void QueueVegvisirGlobalEffectCooldownMessage(Player player, int remainingCooldownSeconds) { QueueVegvisirGlobalEffectCenterMessage(player, FormatVegvisirGlobalEffectCooldownMessage(GetVegvisirGlobalEffectLocalizedText(_configuration.Localization?.MessageVegvisirBuffCooldown, "Buff Cooldown {seconds}s"), Mathf.Max(1, remainingCooldownSeconds))); } private static void QueueVegvisirGlobalEffectAlreadyActiveMessage(Player player, StatusEffect statusEffect) { QueueVegvisirGlobalEffectCenterMessage(player, FormatVegvisirGlobalEffectNamedMessage(GetVegvisirGlobalEffectLocalizedText(_configuration.Localization?.MessageVegvisirAlreadyActive, "Already active {name}"), GetVegvisirGlobalEffectDisplayName(statusEffect))); } 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 static void Prefix(RuneStone __instance, ref string? __state) { __state = __instance.m_locationName; } private static void Postfix(RuneStone __instance, bool hold, string? __state) { UsefulRunestonesRuntime.TryApplyRunestoneGlobalPins(__instance, hold, __state); } } [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; } 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) { if (!HasConfiguredBiomeTokens(configuredBiomes)) { return false; } if (TryResolveBiomeMask(configuredBiomes, out var _) || !IsExpandWorldDataPresent() || IsExpandWorldDataReadyOrUnavailable()) { return false; } return true; } 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 (ExpandWorldDataTryGetBiomeMethod == null) { biome = (Biome)0; return false; } object[] array = new object[2] { configuredBiome, (object)(Biome)0 }; object obj = ExpandWorldDataTryGetBiomeMethod.Invoke(null, array); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0 && array[1] is Biome val) { biome = (Biome)(int)val; return true; } biome = (Biome)0; return false; } } } namespace ServerSync { [PublicAPI] internal abstract class OwnConfigEntryBase { public object? LocalBaseValue; public bool SynchronizedConfig = true; public abstract ConfigEntryBase BaseConfig { get; } } [PublicAPI] internal class SyncedConfigEntry<T>(ConfigEntry<T> sourceConfig) : OwnConfigEntryBase() { public readonly ConfigEntry<T> SourceConfig = sourceConfig; public override ConfigEntryBase BaseConfig => (ConfigEntryBase)(object)SourceConfig; public T Value { get { return SourceConfig.Value; } set { SourceConfig.Value = value; } } public void AssignLocalValue(T value) { if (LocalBaseValue == null) { Value = value; } else { LocalBaseValue = value; } } } internal abstract class CustomSyncedValueBase { public object? LocalBaseValue; public readonly string Identifier; public readonly Type Type; private object? boxedValue; protected bool localIsOwner; public readonly int Priority; public object? BoxedValue { get { return boxedValue; } set { boxedValue = value; this.ValueChanged?.Invoke(); } } public event Action? ValueChanged; protected CustomSyncedValueBase(ConfigSync configSync, string identifier, Type type, int priority) { Priority = priority; Identifier = identifier; Type = type; configSync.AddCustomValue(this); localIsOwner = configSync.IsSourceOfTruth; configSync.SourceOfTruthChanged += delegate(bool truth) { localIsOwner = truth; }; } } [PublicAPI] internal sealed class CustomSyncedValue<T> : CustomSyncedValueBase { public T Value { get { return (T)base.BoxedValue; } set { base.BoxedValue = value; } } public CustomSyncedValue(ConfigSync configSync, string identifier, T value = default(T), int priority = 0) : base(configSync, identifier, typeof(T), priority) { Value = value; } public void AssignLocalValue(T value) { if (localIsOwner) { Value = value; } else { LocalBaseValue = value; } } } internal class ConfigurationManagerAttributes { [UsedImplicitly] public bool? ReadOnly = false; } [PublicAPI] internal class ConfigSync { [HarmonyPatch(typeof(ZRpc), "HandlePackage")] private static class SnatchCurrentlyHandlingRPC { public static ZRpc? currentRpc; [HarmonyPrefix] private static void Prefix(ZRpc __instance) { currentRpc = __instance; } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class RegisterRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance) { isServer = __instance.IsServer(); foreach (ConfigSync configSync2 in configSyncs) { ZRoutedRpc.instance.Register<ZPackage>(configSync2.Name + " ConfigSync", (Action<long, ZPackage>)configSync2.RPC_FromOtherClientConfigSync); if (isServer) { configSync2.InitialSyncDone = true; Debug.Log((object)("Registered '" + configSync2.Name + " ConfigSync' RPC - waiting for incoming connections")); } } if (isServer) { ((MonoBehaviour)__instance).StartCoroutine(WatchAdminListChanges()); } static void SendAdmin(List<ZNetPeer> peers, bool isAdmin) { ZPackage package = ConfigsToPackage(null, null, new PackageEntry[1] { new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = isAdmin } }); ConfigSync configSync = configSyncs.First(); if (configSync != null) { ((MonoBehaviour)ZNet.instance).StartCoroutine(configSync.sendZPackage(peers, package)); } } static IEnumerator WatchAdminListChanges() { MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); List<string> CurrentList = new List<string>(adminList.GetList()); while (true) { yield return (object)new WaitForSeconds(30f); if (!adminList.GetList().SequenceEqual(CurrentList)) { CurrentList = new List<string>(adminList.GetList()); List<ZNetPeer> adminPeer = ZNet.instance.GetPeers().Where(delegate(ZNetPeer p) { string hostName = p.m_rpc.GetSocket().GetHostName(); return ((object)listContainsId == null) ? adminList.Contains(hostName) : ((bool)listContainsId.Invoke(ZNet.instance, new object[2] { adminList, hostName })); }).ToList(); List<ZNetPeer> nonAdminPeer = ZNet.instance.GetPeers().Except(adminPeer).ToList(); SendAdmin(nonAdminPeer, isAdmin: false); SendAdmin(adminPeer, isAdmin: true); } } } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class RegisterClientRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance, ZNetPeer peer) { if (__instance.IsServer()) { return; } foreach (ConfigSync configSync in configSyncs) { peer.m_rpc.Register<ZPackage>(configSync.Name + " ConfigSync", (Action<ZRpc, ZPackage>)configSync.RPC_FromServerConfigSync); } } } private class ParsedConfigs { public readonly Dictionary<OwnConfigEntryBase, object?> configValues = new Dictionary<OwnConfigEntryBase, object>(); public readonly Dictionary<CustomSyncedValueBase, object?> customValues = new Dictionary<CustomSyncedValueBase, object>(); } [HarmonyPatch(typeof(ZNet), "Shutdown")] private class ResetConfigsOnShutdown { [HarmonyPostfix] private static void Postfix() { ProcessingServerUpdate = true; foreach (ConfigSync configSync in configSyncs) { configSync.resetConfigsFromServer(); configSync.IsSourceOfTruth = true; configSync.InitialSyncDone = false; } ProcessingServerUpdate = false; } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private class SendConfigsAfterLogin { private class BufferingSocket : ZPlayFabSocket, ISocket { public volatile bool finished = false; public volatile int versionMatchQueued = -1; public readonly List<ZPackage> Package = new List<ZPackage>(); public readonly ISocket Original; public BufferingSocket(ISocket original) { Original = original; ((ZPlayFabSocket)this)..ctor(); } public bool IsConnected() { return Original.IsConnected(); } public ZPackage Recv() { return Original.Recv(); } public int GetSendQueueSize() { return Original.GetSendQueueSize(); } public int GetCurrentSendRate() { return Original.GetCurrentSendR