using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Managers; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.Networking; using UnityEngine.UI; using ValheimServerGuide.Commands; using ValheimServerGuide.Config; using ValheimServerGuide.Discord; using ValheimServerGuide.Display; using ValheimServerGuide.Net; using ValheimServerGuide.Rewards; using ValheimServerGuide.State; using ValheimServerGuide.Triggers; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("ValheimServerGuide")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0+b1a17eae6dd79343542054ca652eae4f00be5557")] [assembly: AssemblyProduct("ValheimServerGuide")] [assembly: AssemblyTitle("ValheimServerGuide")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 ValheimServerGuide { [BepInPlugin("com.valheimserverguide", "ValheimServerGuide", "0.5.2")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.valheimserverguide"; public const string PluginName = "ValheimServerGuide"; public const string PluginVersion = "0.5.2"; private Harmony _harmony; private static GuidanceConfigLoader _loader; private static readonly object _loaderLock = new object(); private static string _configDir; public static Plugin Instance { get; private set; } public static ManualLogSource Log { get; private set; } public static GuidanceConfig CurrentConfig { get; internal set; } = GuidanceConfig.Empty; public static ConfigEntry RavenEnabled { get; private set; } public static ConfigEntry IntroMusicName { get; private set; } public static ConfigEntry IntroMusicDuration { get; private set; } public static ConfigEntry IntroFadeInDuration { get; private set; } public static ConfigEntry IntroPreDelay { get; private set; } public static ConfigEntry ChatColor { get; private set; } public static ConfigEntry CodexEnabled { get; private set; } public static ConfigEntry CodexKey { get; private set; } public static ConfigEntry TrackerEnabled { get; private set; } public static ConfigEntry TrackerPosition { get; private set; } public static ConfigEntry TrackerMaxVisible { get; private set; } public static ConfigEntry TrackerHotkey { get; private set; } public static ConfigEntry TrackerBadgeEnabled { get; private set; } public static ConfigEntry DiscordWebhookUrl { get; private set; } public static ConfigEntry DiscordDefaultTemplate { get; private set; } public static ConfigEntry DiscordBotUsername { get; private set; } public static ConfigEntry DiscordGuideEnabled { get; private set; } public static ConfigEntry DiscordGuideFormat { get; private set; } private void Awake() { //IL_029c: Unknown result type (might be due to invalid IL or missing references) //IL_02a6: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; RavenEnabled = ((BaseUnityPlugin)this).Config.Bind("Display", "RavenEnabled", true, "Enable raven (Hugin) popup mode. Independent of Valheim's 'Tutorials' setting — this mod's raven popups will fire even when vanilla raven hints are turned off in game options."); IntroMusicName = ((BaseUnityPlugin)this).Config.Bind("Display", "IntroMusicName", "intro", "Music track to play while a guidance is shown in 'intro' display mode. 'intro' is the vanilla Valkyrie-intro track."); IntroMusicDuration = ((BaseUnityPlugin)this).Config.Bind("Display", "IntroMusicDuration", 60f, "Seconds the intro music stays pinned once it starts. The music plays for at least this long even if the player dismisses the on-screen text early. After the duration elapses, vanilla MusicMan resumes normal environment-based selection."); IntroFadeInDuration = ((BaseUnityPlugin)this).Config.Bind("Display", "IntroFadeInDuration", 3f, "Seconds to fade the screen to black before the intro text + music start. Uses vanilla Hud.m_loadingScreen so no custom assets are needed. Set 0 to disable."); IntroPreDelay = ((BaseUnityPlugin)this).Config.Bind("Display", "IntroPreDelay", 1f, "Seconds to hold on a black screen after the fade-in, before the intro text appears. Adds dramatic weight to the transition."); ChatColor = ((BaseUnityPlugin)this).Config.Bind("Display", "ChatColor", "#E0C078", "Hex color (with or without leading '#') applied to chat-mode guidance messages, so they read distinct from regular say (white) and shout (yellow). Set to empty string to disable coloring."); TrackerEnabled = ((BaseUnityPlugin)this).Config.Bind("HudTracker", "TrackerEnabled", true, "Show the objective tracker widget on the HUD. Set false to hide it entirely (the widget GameObject remains in the scene — just inactive)."); TrackerPosition = ((BaseUnityPlugin)this).Config.Bind("HudTracker", "TrackerPosition", "TopRight", "Corner the tracker widget anchors to: TopRight | TopLeft | BottomRight | BottomLeft. Takes effect on next session start (Hud.Awake)."); TrackerMaxVisible = ((BaseUnityPlugin)this).Config.Bind("HudTracker", "TrackerMaxVisible", 3, "Maximum number of active guide chains shown simultaneously. Chains beyond this limit are collapsed into a '+N more' label."); TrackerHotkey = ((BaseUnityPlugin)this).Config.Bind("HudTracker", "TrackerHotkey", "F10", "KeyCode name for the tracker toggle hotkey (e.g. F9, F10, H). See UnityEngine.KeyCode enum for valid values. YAML tracker.hotkey wins when set."); TrackerBadgeEnabled = ((BaseUnityPlugin)this).Config.Bind("HudTracker", "TrackerBadgeEnabled", true, "Show the persistent corner hint badge (e.g. '[F9] Quests (2)') even when the main tracker panel is hidden. YAML tracker.badge_enabled wins when set."); CodexEnabled = ((BaseUnityPlugin)this).Config.Bind("Codex", "CodexEnabled", true, "Enable the in-game Guide Codex panel. Set false to disable the keybind and skip instantiating the panel entirely."); CodexKey = ((BaseUnityPlugin)this).Config.Bind("Codex", "CodexKey", "F3", "KeyCode name for the Codex toggle hotkey (e.g. F2, F3). See UnityEngine.KeyCode enum for valid values."); DiscordWebhookUrl = ((BaseUnityPlugin)this).Config.Bind("Discord", "WebhookUrl", "", "Discord webhook URL. Set on the server only — never share this with clients. Leave empty to disable all discord announcements."); DiscordDefaultTemplate = ((BaseUnityPlugin)this).Config.Bind("Discord", "DefaultTemplate", "**{playerName}** triggered **{topic}**", "Default message template when a guidance entry has `announce: { discord: \"\" }` (empty string = use default). Tokens: {playerName}, {id}, {topic}, {text}."); DiscordBotUsername = ((BaseUnityPlugin)this).Config.Bind("Discord", "BotUsername", "ValheimServerGuide", "Username shown for webhook messages in Discord."); DiscordGuideEnabled = ((BaseUnityPlugin)this).Config.Bind("Discord", "DiscordGuideEnabled", true, "Enable guide-completion webhook POSTs (discord_on_complete). Set false to suppress these without affecting kill/event POSTs."); DiscordGuideFormat = ((BaseUnityPlugin)this).Config.Bind("Discord", "DiscordGuideFormat", "plain", "Format for guide-completion messages: 'plain' (content string) or 'embed' (rich embed)."); _harmony = new Harmony("com.valheimserverguide"); _harmony.PatchAll(Assembly.GetExecutingAssembly()); foreach (MethodBase patchedMethod in _harmony.GetPatchedMethods()) { Log.LogInfo((object)("Harmony patched: " + patchedMethod.DeclaringType?.Name + "." + patchedMethod.Name)); } _configDir = Path.Combine(Paths.ConfigPath, "ValheimServerGuide"); GuidanceSync.Register(); GuidanceDisplay.Initialize(); AdminCommands.Register(); if (Application.isBatchMode) { Log.LogInfo((object)"Running in batch mode (dedicated server). Loading guidance YAML now."); EnsureLoaderStarted(); } else { Log.LogInfo((object)"Client process. Guidance YAML will be loaded only if this session hosts a world."); } Log.LogInfo((object)"ValheimServerGuide v0.5.2 loaded."); } public static void EnsureLoaderStarted() { lock (_loaderLock) { if (_loader == null) { Directory.CreateDirectory(_configDir); _loader = new GuidanceConfigLoader(_configDir); _loader.ConfigChanged += Instance.OnConfigChanged; _loader.Start(); Log.LogInfo((object)("Guidance YAML loader started (" + _configDir + ").")); } } } public static void ShutdownLoader() { lock (_loaderLock) { if (_loader != null) { _loader.Dispose(); _loader = null; Log.LogInfo((object)"Guidance YAML loader stopped."); } } } private void OnConfigChanged(GuidanceConfig newConfig) { if (!((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer()) { Log.LogInfo((object)"Local YAML edit ignored — remote server's config takes priority."); return; } CurrentConfig = newConfig; GuidanceDisplay.RegisterTutorials(newConfig); TimedTrigger.OnConfigChanged(newConfig); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { GuidanceSync.BroadcastToClients(newConfig); } GuidanceHudTracker.Instance?.ApplyLayout(); GuidanceHudTracker.Instance?.Refresh(); ItemAcquiredTrigger.CheckAllCountGoals(); if ((Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)MessageHud.instance != (Object)null) { SynchronizationManager instance = SynchronizationManager.Instance; if (instance != null && instance.PlayerIsAdmin) { MessageHud.instance.ShowMessage((MessageType)1, $"[VSG] Guide config reloaded — {newConfig.Guidances.Count} entries loaded.", 0, (Sprite)null, false); } } } private void Update() { GuidanceConfigLoader loader; lock (_loaderLock) { loader = _loader; } loader?.Tick(); GuidanceDisplay.Tick(); } private void OnDestroy() { ShutdownLoader(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } } namespace ValheimServerGuide.Triggers { [HarmonyPatch(typeof(Player), "Update")] internal static class BiomeTrigger { private const float CheckInterval = 2f; private static float _nextCheck; private static Biome _lastBiome; internal static void Reset() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) _lastBiome = (Biome)0; } [HarmonyPostfix] private static void Postfix(Player __instance) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: 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) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Invalid comparison between Unknown and I4 //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer || Time.time < _nextCheck) { return; } _nextCheck = Time.time + 2f; Biome currentBiome = __instance.GetCurrentBiome(); if (currentBiome != _lastBiome) { Biome lastBiome = _lastBiome; _lastBiome = currentBiome; if ((int)currentBiome != 0) { Plugin.Log.LogInfo((object)$"[biome] entered '{currentBiome}' (was '{lastBiome}')."); GuidanceDispatcher.Raise(new TriggerEvent { Type = "biome", Subject = ((object)(Biome)(ref currentBiome)).ToString() }); } } } } [HarmonyPatch(typeof(Player), "OnSpawned")] internal static class BiomeTriggerSpawnReset { [HarmonyPostfix] private static void Postfix(Player __instance) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { BiomeTrigger.Reset(); } } } [HarmonyPatch(typeof(Character), "OnDeath")] internal static class BossDefeatedTrigger { [HarmonyPostfix] private static void Postfix(Character __instance) { if ((Object)(object)Player.m_localPlayer == (Object)null || !__instance.IsBoss()) { return; } object obj; if (__instance == null) { obj = null; } else { HitData lastHit = __instance.m_lastHit; obj = ((lastHit != null) ? lastHit.GetAttacker() : null); } Character val = (Character)obj; if (!((Object)(object)val != (Object)(object)Player.m_localPlayer)) { GameObject gameObject = ((Component)__instance).gameObject; string text = TriggerUtils.NormalizePrefabName((gameObject != null) ? ((Object)gameObject).name : null); if (!string.IsNullOrEmpty(text)) { GuidanceDispatcher.Raise(new TriggerEvent { Type = "boss_defeated", Subject = text, DisplayName = __instance.m_name }); } } } } [HarmonyPatch(typeof(Player), "TryPlacePiece")] internal static class BuildTrigger { [HarmonyPostfix] private static void Postfix(Player __instance, Piece piece, bool __result) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && __result && !((Object)(object)piece == (Object)null)) { GameObject gameObject = ((Component)piece).gameObject; string text = TriggerUtils.NormalizePrefabName((gameObject != null) ? ((Object)gameObject).name : null); if (!string.IsNullOrEmpty(text)) { Plugin.Log.LogInfo((object)("[build] subject='" + text + "' (display='" + piece.m_name + "').")); GuidanceDispatcher.Raise(new TriggerEvent { Type = "build", Subject = text, DisplayName = piece.m_name }); } } } } [HarmonyPatch(typeof(Container), "Interact")] internal static class ChestOpenedTrigger { private const string GuardKey = "chest_opened_fired"; [HarmonyPostfix] private static void Postfix(Humanoid character, bool hold, bool __result) { if (!hold && __result && !((Object)(object)character != (Object)(object)Player.m_localPlayer)) { Player localPlayer = Player.m_localPlayer; if (!SeenTracker.HasFired(localPlayer, "chest_opened_fired")) { SeenTracker.MarkFired(localPlayer, "chest_opened_fired"); GuidanceDispatcher.Raise(new TriggerEvent { Type = "chest_opened", Subject = "" }); } } } } [HarmonyPatch(typeof(InventoryGui), "DoCrafting")] internal static class CraftTrigger { [HarmonyPostfix] private static void Postfix(InventoryGui __instance, Player player) { if ((Object)(object)player != (Object)(object)Player.m_localPlayer) { Plugin.Log.LogDebug((object)"[craft] postfix fired for non-local player; ignoring."); return; } Recipe craftRecipe = __instance.m_craftRecipe; object obj; if (craftRecipe == null) { obj = null; } else { ItemDrop item = craftRecipe.m_item; obj = ((item != null) ? ((Component)item).gameObject : null); } GameObject val = (GameObject)obj; if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning((object)"[craft] DoCrafting completed but m_craftRecipe/m_item was null."); return; } string name = ((Object)val).name; Plugin.Log.LogInfo((object)("[craft] subject='" + name + "' (display='" + craftRecipe.m_item.m_itemData?.m_shared?.m_name + "')")); GuidanceDispatcher.Raise(new TriggerEvent { Type = "craft", Subject = name, DisplayName = craftRecipe.m_item.m_itemData?.m_shared?.m_name }); } } [HarmonyPatch(typeof(Player), "Update")] internal static class DistanceTrigger { private const float CheckInterval = 5f; private const float DefaultRadius = 50f; private const string KeyPrefix = "dist_"; private static float _nextCheck; [HarmonyPostfix] private static void Postfix(Player __instance) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer || (Object)(object)ZoneSystem.instance == (Object)null || Time.time < _nextCheck) { return; } _nextCheck = Time.time + 5f; GuidanceConfig currentConfig = Plugin.CurrentConfig; if (currentConfig?.Guidances == null) { return; } Vector3 position = ((Component)__instance).transform.position; foreach (KeyValuePair locationInstance in ZoneSystem.instance.m_locationInstances) { LocationInstance value = locationInstance.Value; if (!value.m_placed) { continue; } string text = value.m_location?.m_prefabName; if (!string.IsNullOrEmpty(text)) { string id = "dist_" + text; if (!SeenTracker.HasFired(__instance, id) && AnyEntryInRange(currentConfig, text, position, value.m_position)) { SeenTracker.MarkFired(__instance, id); Plugin.Log.LogInfo((object)("[distance] entered range of '" + text + "'.")); GuidanceDispatcher.Raise(new TriggerEvent { Type = "distance", Subject = text }); } } } } private static bool AnyEntryInRange(GuidanceConfig config, string prefabName, Vector3 playerPos, Vector3 locPos) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_006e: 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) foreach (GuidanceEntry guidance in config.Guidances) { if (CheckTrigger(guidance.Trigger, prefabName, playerPos, locPos)) { return true; } if (guidance.Steps == null) { continue; } foreach (GuidanceStep step in guidance.Steps) { if (CheckTrigger(step?.Trigger, prefabName, playerPos, locPos)) { return true; } } } return false; } private static bool CheckTrigger(TriggerSpec t, string prefabName, Vector3 playerPos, Vector3 locPos) { //IL_005d: 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) if (t == null) { return false; } if (!string.Equals(t.Type, "distance", StringComparison.OrdinalIgnoreCase)) { return false; } if (!LocationMatches(t.Location, prefabName)) { return false; } float num = ((t.Radius > 0f) ? t.Radius : 50f); return Vector3.Distance(playerPos, locPos) <= num; } private static bool LocationMatches(string pattern, string value) { if (string.IsNullOrEmpty(pattern) || string.IsNullOrEmpty(value)) { return false; } if (pattern.EndsWith("*")) { return value.StartsWith(pattern.Substring(0, pattern.Length - 1), StringComparison.OrdinalIgnoreCase); } return string.Equals(pattern, value, StringComparison.OrdinalIgnoreCase); } } [HarmonyPatch(typeof(Humanoid), "EquipItem")] internal static class EquipTrigger { [HarmonyPostfix] private static void Postfix(Humanoid __instance, ItemData item, bool __result) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && __result && item != null) { string text = ResolveItemName(item); if (!string.IsNullOrEmpty(text)) { Plugin.Log.LogInfo((object)("[equip] subject='" + text + "' (token='" + item.m_shared?.m_name + "').")); GuidanceDispatcher.Raise(new TriggerEvent { Type = "equip", Subject = text, DisplayName = item.m_shared?.m_name }); } } } private static string ResolveItemName(ItemData item) { GameObject dropPrefab = item.m_dropPrefab; string text = ((dropPrefab != null) ? ((Object)dropPrefab).name : null); if (!string.IsNullOrEmpty(text)) { return TriggerUtils.NormalizePrefabName(text); } return TriggerUtils.NormalizePrefabName(item.m_shared?.m_name ?? ""); } } [HarmonyPatch(typeof(Player), "OnSpawned")] internal static class FirstLoginTrigger { private const string GuardKey = "first_login_fired"; [HarmonyPostfix] private static void Postfix(Player __instance) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && !SeenTracker.HasFired(__instance, "first_login_fired")) { SeenTracker.MarkFired(__instance, "first_login_fired"); GuidanceDispatcher.Raise(new TriggerEvent { Type = "first_login", Subject = "" }); } } } public static class GuidanceDispatcher { public static void Raise(TriggerEvent evt) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { Plugin.Log.LogDebug((object)("[dispatch] " + evt.Type + "/" + evt.Subject + " ignored: no local player.")); return; } GuidanceConfig currentConfig = Plugin.CurrentConfig; if (currentConfig?.Guidances == null || currentConfig.Guidances.Count == 0) { Plugin.Log.LogDebug((object)("[dispatch] " + evt.Type + "/" + evt.Subject + " ignored: empty config.")); return; } int num = 0; List list = new List(); foreach (GuidanceEntry guidance in currentConfig.Guidances) { if (guidance.Steps != null && guidance.Steps.Count > 0) { if (HandleChain(guidance, evt, localPlayer, list)) { num++; } } else { if (!Matches(guidance, evt)) { continue; } if (string.Equals(evt.Type, "item_acquired", StringComparison.OrdinalIgnoreCase) && guidance.Trigger != null && ItemAcquiredTrigger.GetEffectiveGoals(guidance.Trigger) != null) { Plugin.Log.LogDebug((object)("[dispatch] '" + guidance.Id + "' item_acquired count-goal — delegated to count path.")); continue; } if (!RequirementsMet(guidance, localPlayer)) { Plugin.Log.LogInfo((object)("[dispatch] '" + guidance.Id + "' skipped: requires not met.")); continue; } if (StopConditionMet(guidance, localPlayer)) { Plugin.Log.LogInfo((object)("[dispatch] '" + guidance.Id + "' skipped: stop_when met.")); continue; } if (guidance.Once && SeenTracker.HasFired(localPlayer, guidance.Id, guidance.Scope)) { Plugin.Log.LogInfo((object)("[dispatch] '" + guidance.Id + "' skipped: already fired (once).")); continue; } if (!SeenTracker.CooldownReady(guidance.Id, guidance.Cooldown, Time.time)) { Plugin.Log.LogInfo((object)("[dispatch] '" + guidance.Id + "' skipped: cooldown.")); continue; } int num2 = guidance.Trigger?.MaxFires ?? 0; if (num2 > 0 && SeenTracker.GetFireCount(localPlayer, guidance.Id) >= num2) { Plugin.Log.LogInfo((object)$"[dispatch] '{guidance.Id}' skipped: max_fires ({num2}) reached."); continue; } if (SeenTracker.IsGlobalScope(guidance.Scope)) { Plugin.Log.LogInfo((object)("[dispatch] '" + guidance.Id + "' (global) -> server.")); GuidanceSync.SendTriggerGlobal(guidance.Id, localPlayer.GetPlayerName()); SeenTracker.MarkCooldown(guidance.Id, guidance.Cooldown, Time.time); num++; continue; } Plugin.Log.LogInfo((object)("[dispatch] firing '" + guidance.Id + "' via mode '" + guidance.Display?.Mode + "'.")); string template = ((!string.IsNullOrEmpty(guidance.Message)) ? guidance.Message : guidance.Display?.Text); string renderedText = TemplateText(template, evt, localPlayer.GetPlayerName()); GuidanceDisplay.Show(guidance, renderedText); if (guidance.Once) { SeenTracker.MarkFired(localPlayer, guidance.Id, guidance.Scope); } if (num2 > 0) { SeenTracker.IncrementFireCount(localPlayer, guidance.Id); } SeenTracker.MarkCooldown(guidance.Id, guidance.Cooldown, Time.time); if (guidance.Announce?.Discord != null) { GuidanceSync.SendAnnounceRequest(guidance.Id, localPlayer.GetPlayerName()); } if (guidance.DiscordOnComplete) { GuidanceSync.SendCompleteAnnounce(guidance.Id, localPlayer.GetPlayerName()); } if (guidance.Rewards != null && guidance.Rewards.Count > 0) { RewardDispatcher.Grant(guidance.Rewards, localPlayer); } list.Add(guidance.Id); num++; } } if (num == 0) { Plugin.Log.LogInfo((object)("[dispatch] " + evt.Type + "/" + evt.Subject + " matched no guidance entries.")); } foreach (string item in list) { Raise(new TriggerEvent { Type = "entry_finished", Subject = item }); } } private static bool HandleChain(GuidanceEntry entry, TriggerEvent evt, Player player, List completedIds) { if (ChainState.IsComplete(player, entry.Id)) { return false; } if (!PrerequisiteChecker.AllSatisfied(entry.Requires, player, Plugin.CurrentConfig)) { Plugin.Log.LogDebug((object)("[chain] '" + entry.Id + "' prerequisites not met.")); return false; } int step = ChainState.GetStep(player, entry.Id); if (step >= entry.Steps.Count) { ChainState.MarkComplete(player, entry.Id); return false; } GuidanceStep guidanceStep = entry.Steps[step]; if (guidanceStep?.Trigger == null) { return false; } if (guidanceStep.ProgressGoal > 0) { return HandleCounterStep(entry, guidanceStep, step, evt, player, completedIds); } return HandleNormalStep(entry, guidanceStep, step, evt, player, completedIds); } private static bool HandleNormalStep(GuidanceEntry entry, GuidanceStep step, int stepIndex, TriggerEvent evt, Player player, List completedIds) { if (!MatchesTrigger(step.Trigger, evt)) { return false; } FireStepDisplay(entry, step, stepIndex, evt, player); AdvanceChain(entry, stepIndex, player, completedIds); return true; } private static bool HandleCounterStep(GuidanceEntry entry, GuidanceStep step, int stepIndex, TriggerEvent evt, Player player, List completedIds) { if (step.ProgressTrigger == null) { Plugin.Log.LogWarning((object)($"[chain] '{entry.Id}' step {stepIndex} has progress_goal " + "but no progress_trigger — treating as normal step.")); return HandleNormalStep(entry, step, stepIndex, evt, player, completedIds); } int counter = ChainState.GetCounter(player, entry.Id, stepIndex); if (counter < 0) { if (!MatchesTrigger(step.Trigger, evt)) { return false; } int num = 0; if (step.ProgressTrigger != null && string.Equals(step.ProgressTrigger.Type, "item_acquired", StringComparison.OrdinalIgnoreCase)) { num = Math.Min(ItemAcquiredTrigger.CountInInventory(player, step.ProgressTrigger.Item), step.ProgressGoal); } ChainState.SetCounter(player, entry.Id, stepIndex, num); Plugin.Log.LogInfo((object)$"[chain] '{entry.Id}' step {stepIndex} counter activated (seed: {num}/{step.ProgressGoal})."); GuidanceSync.SendChainStepUpdate(player.GetPlayerName(), entry.Id + ":" + stepIndex, num.ToString()); GuidanceHudTracker.Instance?.Refresh(fromProgress: true); if (num >= step.ProgressGoal) { FireStepDisplay(entry, step, stepIndex, evt, player); ChainState.ClearCounter(player, entry.Id, stepIndex); AdvanceChain(entry, stepIndex, player, completedIds); } return true; } if (!MatchesTrigger(step.ProgressTrigger, evt)) { return false; } int num2 = Math.Min(counter + 1, step.ProgressGoal); ChainState.SetCounter(player, entry.Id, stepIndex, num2); Plugin.Log.LogInfo((object)$"[chain] '{entry.Id}' step {stepIndex} counter: {num2}/{step.ProgressGoal}."); GuidanceSync.SendChainStepUpdate(player.GetPlayerName(), entry.Id + ":" + stepIndex, num2.ToString()); GuidanceHudTracker.Instance?.Refresh(fromProgress: true); if (num2 >= step.ProgressGoal) { FireStepDisplay(entry, step, stepIndex, evt, player); ChainState.ClearCounter(player, entry.Id, stepIndex); AdvanceChain(entry, stepIndex, player, completedIds); } return true; } private static void FireStepDisplay(GuidanceEntry entry, GuidanceStep step, int stepIndex, TriggerEvent evt, Player player) { DisplaySpec displaySpec = step.Display ?? entry.Display ?? new DisplaySpec(); string template = ((!string.IsNullOrEmpty(step.Message)) ? step.Message : displaySpec.Text); string text = TemplateText(template, evt, player.GetPlayerName(), stepIndex + 1, entry.Steps.Count); string id = entry.Id + "_s" + stepIndex; GuidanceEntry entry2 = new GuidanceEntry { Id = id, Display = new DisplaySpec { Mode = displaySpec.Mode, Topic = displaySpec.Topic, Text = text, Position = displaySpec.Position }, Scope = entry.Scope, Once = false }; Plugin.Log.LogInfo((object)$"[chain] '{entry.Id}' step {stepIndex} firing via '{displaySpec.Mode}'."); GuidanceDisplay.Show(entry2, text); if (entry.Announce?.Discord != null) { GuidanceSync.SendAnnounceRequest(entry.Id, player.GetPlayerName()); } } private static void AdvanceChain(GuidanceEntry entry, int stepIndex, Player player, List completedIds) { int num = stepIndex + 1; if (num >= entry.Steps.Count) { ChainState.MarkComplete(player, entry.Id); ChainState.SetCompletedVersion(player, entry.Id, entry.Version); Plugin.Log.LogInfo((object)$"[chain] '{entry.Id}' complete (all {entry.Steps.Count} steps done)."); GuidanceSync.SendChainStepUpdate(player.GetPlayerName(), entry.Id, "done"); GuidanceHudTracker.Instance?.FlashCompletion(entry.Id); if (entry.DiscordOnComplete) { GuidanceSync.SendCompleteAnnounce(entry.Id, player.GetPlayerName()); } if (entry.Rewards != null && entry.Rewards.Count > 0) { RewardDispatcher.Grant(entry.Rewards, player); } completedIds.Add(entry.Id); } else { ChainState.SetStep(player, entry.Id, num); Plugin.Log.LogInfo((object)$"[chain] '{entry.Id}' advanced to step {num}/{entry.Steps.Count}."); GuidanceSync.SendChainStepUpdate(player.GetPlayerName(), entry.Id, num.ToString()); GuidanceHudTracker.Instance?.Refresh(fromProgress: true); } } public static void PlayGlobalReceived(string entryId, string sourcePlayerName) { GuidanceEntry guidanceEntry = Plugin.CurrentConfig?.Guidances?.Find((GuidanceEntry g) => g.Id == entryId); if (guidanceEntry == null) { Plugin.Log.LogWarning((object)("[global] received play for unknown id '" + entryId + "'.")); return; } Plugin.Log.LogInfo((object)("[global] showing '" + entryId + "' (triggered by " + sourcePlayerName + ").")); string template = ((!string.IsNullOrEmpty(guidanceEntry.Message)) ? guidanceEntry.Message : guidanceEntry.Display?.Text); string renderedText = TemplateText(template, null, sourcePlayerName); GuidanceDisplay.Show(guidanceEntry, renderedText); Raise(new TriggerEvent { Type = "entry_finished", Subject = entryId }); } internal static bool CheckGates(GuidanceEntry entry, Player player) { if (!RequirementsMet(entry, player)) { return false; } if (StopConditionMet(entry, player)) { return false; } if (entry.Once && SeenTracker.HasFired(player, entry.Id, entry.Scope)) { return false; } if (!SeenTracker.CooldownReady(entry.Id, entry.Cooldown, Time.time)) { return false; } int num = entry.Trigger?.MaxFires ?? 0; if (num > 0 && SeenTracker.GetFireCount(player, entry.Id) >= num) { return false; } return true; } internal static void FireById(string entryId) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } GuidanceEntry guidanceEntry = Plugin.CurrentConfig?.Guidances?.Find((GuidanceEntry g) => string.Equals(g.Id, entryId, StringComparison.OrdinalIgnoreCase)); if (guidanceEntry == null) { Plugin.Log.LogWarning((object)("[dispatch] FireById: entry '" + entryId + "' not found.")); return; } if (!CheckGates(guidanceEntry, localPlayer)) { Plugin.Log.LogInfo((object)("[dispatch] FireById: '" + entryId + "' gates blocked.")); return; } Plugin.Log.LogInfo((object)("[dispatch] FireById firing '" + entryId + "'.")); string template = ((!string.IsNullOrEmpty(guidanceEntry.Message)) ? guidanceEntry.Message : guidanceEntry.Display?.Text); string renderedText = TemplateText(template, null, localPlayer.GetPlayerName()); GuidanceDisplay.Show(guidanceEntry, renderedText); if (guidanceEntry.Once) { SeenTracker.MarkFired(localPlayer, guidanceEntry.Id, guidanceEntry.Scope); } int num = guidanceEntry.Trigger?.MaxFires ?? 0; if (num > 0) { SeenTracker.IncrementFireCount(localPlayer, guidanceEntry.Id); } SeenTracker.MarkCooldown(guidanceEntry.Id, guidanceEntry.Cooldown, Time.time); Raise(new TriggerEvent { Type = "entry_finished", Subject = entryId }); } internal static void FireEntry(GuidanceEntry entry, TriggerEvent evt) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || entry == null) { return; } if (SeenTracker.IsGlobalScope(entry.Scope)) { Plugin.Log.LogInfo((object)("[dispatch] FireEntry '" + entry.Id + "' (global) -> server.")); GuidanceSync.SendTriggerGlobal(entry.Id, localPlayer.GetPlayerName()); SeenTracker.MarkCooldown(entry.Id, entry.Cooldown, Time.time); return; } Plugin.Log.LogInfo((object)("[dispatch] FireEntry firing '" + entry.Id + "' via mode '" + entry.Display?.Mode + "'.")); string template = ((!string.IsNullOrEmpty(entry.Message)) ? entry.Message : entry.Display?.Text); string renderedText = TemplateText(template, evt, localPlayer.GetPlayerName()); GuidanceDisplay.Show(entry, renderedText); if (entry.Once) { SeenTracker.MarkFired(localPlayer, entry.Id, entry.Scope); } int num = entry.Trigger?.MaxFires ?? 0; if (num > 0) { SeenTracker.IncrementFireCount(localPlayer, entry.Id); } SeenTracker.MarkCooldown(entry.Id, entry.Cooldown, Time.time); if (entry.Announce?.Discord != null) { GuidanceSync.SendAnnounceRequest(entry.Id, localPlayer.GetPlayerName()); } if (entry.DiscordOnComplete) { GuidanceSync.SendCompleteAnnounce(entry.Id, localPlayer.GetPlayerName()); } if (entry.Rewards != null && entry.Rewards.Count > 0) { RewardDispatcher.Grant(entry.Rewards, localPlayer); } Raise(new TriggerEvent { Type = "entry_finished", Subject = entry.Id }); } private static bool Matches(GuidanceEntry entry, TriggerEvent evt) { if (entry.Trigger == null) { return false; } return MatchesTrigger(entry.Trigger, evt); } internal static bool MatchesTrigger(TriggerSpec t, TriggerEvent evt) { if (t == null) { return false; } if (!string.Equals(t.Type, evt.Type, StringComparison.OrdinalIgnoreCase)) { return false; } switch (evt.Type.ToLowerInvariant()) { case "craft": return Eq(t.Item, evt.Subject); case "pickup": return Eq(t.Item, evt.Subject); case "kill": return Eq(t.Creature, evt.Subject); case "build": return Eq(t.Piece, evt.Subject); case "biome": return Eq(t.Biome, evt.Subject); case "equip": return Eq(t.Item, evt.Subject); case "boss_defeated": return Eq(t.Creature, evt.Subject); case "item_acquired": return WildcardMatch(t.Item, evt.Subject); case "location_entered": return WildcardMatch(t.Location, evt.Subject); case "distance": return WildcardMatch(t.Location, evt.Subject); case "npc_interacted": case "npc_conversation": return Eq(t.Npc, evt.Subject); case "npc_item_submit": { if (!Eq(t.Npc, evt.Subject)) { return false; } if (string.IsNullOrEmpty(t.Item)) { return true; } string b = ((evt.Extra == null || !evt.Extra.ContainsKey("item")) ? null : evt.Extra["item"]?.ToString()); return Eq(t.Item, b); } case "skill_level": return MatchSkillLevel(t, evt.Subject); case "timed": return Eq(t.Id, evt.Subject); case "entry_finished": return Eq(t.Entry, evt.Subject); case "first_login": case "chest_opened": case "player_death": return true; default: return true; } } private static bool Eq(string a, string b) { return !string.IsNullOrEmpty(a) && string.Equals(a, b, StringComparison.OrdinalIgnoreCase); } private static bool WildcardMatch(string pattern, string value) { if (string.IsNullOrEmpty(pattern) || string.IsNullOrEmpty(value)) { return false; } if (pattern.EndsWith("*")) { string value2 = pattern.Substring(0, pattern.Length - 1); return value.StartsWith(value2, StringComparison.OrdinalIgnoreCase); } return string.Equals(pattern, value, StringComparison.OrdinalIgnoreCase); } private static bool MatchSkillLevel(TriggerSpec t, string subject) { if (string.IsNullOrEmpty(t.Skill) || string.IsNullOrEmpty(subject)) { return false; } int num = subject.IndexOf(':'); if (num < 0) { return false; } string b = subject.Substring(0, num); string s = subject.Substring(num + 1); if (!int.TryParse(s, out var result)) { return false; } return string.Equals(t.Skill, b, StringComparison.OrdinalIgnoreCase) && t.Level == result; } private static bool RequirementsMet(GuidanceEntry entry, Player player) { return PrerequisiteChecker.AllSatisfied(entry.Requires, player, Plugin.CurrentConfig); } private static bool StopConditionMet(GuidanceEntry entry, Player player) { if (entry.StopWhen == null || entry.StopWhen.Count == 0) { return false; } foreach (string item in entry.StopWhen) { if (SeenTracker.HasFired(player, item, "player")) { return true; } } return false; } internal static string TemplateText(string template, TriggerEvent evt, string playerName, int step = -1, int total = -1) { //IL_002f: 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) if (string.IsNullOrEmpty(template)) { return template; } string text = ""; Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { Biome currentBiome = localPlayer.GetCurrentBiome(); text = ((object)(Biome)(ref currentBiome)).ToString(); } string newValue = ""; string newValue2 = ""; if (evt != null && string.Equals(evt.Type, "skill_level", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(evt.Subject)) { int num = evt.Subject.IndexOf(':'); if (num >= 0) { newValue = evt.Subject.Substring(0, num); newValue2 = evt.Subject.Substring(num + 1); } } string text2 = template.Replace("{playerName}", playerName ?? "").Replace("{player_name}", playerName ?? "").Replace("{itemName}", evt?.DisplayName ?? evt?.Subject ?? "") .Replace("{creatureName}", evt?.DisplayName ?? evt?.Subject ?? "") .Replace("{biome}", (!string.IsNullOrEmpty(text)) ? text : (evt?.Subject ?? "")) .Replace("{skill}", newValue) .Replace("{level}", newValue2); if (step >= 0) { text2 = text2.Replace("{step}", step.ToString()); } if (total >= 0) { text2 = text2.Replace("{total}", total.ToString()); } return text2; } public static void CheckVersionUpdates(Player player, GuidanceConfig config) { if ((Object)(object)player == (Object)null || config?.Guidances == null) { return; } foreach (GuidanceEntry guidance in config.Guidances) { if (guidance.Steps == null || guidance.Steps.Count == 0 || !ChainState.IsComplete(player, guidance.Id)) { continue; } int completedVersion = ChainState.GetCompletedVersion(player, guidance.Id); if (guidance.Version > completedVersion) { GuidanceStep guidanceStep = guidance.Steps[guidance.Steps.Count - 1]; string template = ((!string.IsNullOrEmpty(guidanceStep.Message)) ? guidanceStep.Message : (guidanceStep.Display?.Text ?? guidance.Title ?? guidance.Id)); string text = TemplateText(template, null, player.GetPlayerName()); if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false); } Plugin.Log.LogInfo((object)("[dispatch] Version update for '" + guidance.Id + "': " + $"seen v{completedVersion}, current v{guidance.Version}. Re-delivered notification.")); ChainState.SetCompletedVersion(player, guidance.Id, guidance.Version); } } } } [HarmonyPatch(typeof(Player), "OnSpawned")] internal static class PlayerOnSpawnedDispatchPatch { private static void Postfix(Player __instance) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { GuidanceDispatcher.CheckVersionUpdates(__instance, Plugin.CurrentConfig); ItemAcquiredTrigger.CheckAllCountGoals(); SkillLevelTrigger.CheckAllSkillLevels(); } } } public class TriggerEvent { public string Type; public string Subject; public string DisplayName; public Dictionary Extra; } [HarmonyPatch(typeof(Humanoid), "Pickup")] internal static class ItemAcquiredTrigger { [HarmonyPostfix] private static void Postfix(Humanoid __instance, GameObject go) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && !((Object)(object)go == (Object)null)) { string text = TriggerUtils.NormalizePrefabName(((Object)go).name); if (!string.IsNullOrEmpty(text)) { string displayName = go.GetComponent()?.m_itemData?.m_shared?.m_name; GuidanceDispatcher.Raise(new TriggerEvent { Type = "item_acquired", Subject = text, DisplayName = displayName }); CheckCountGoals(text, displayName); } } } internal static List GetEffectiveGoals(TriggerSpec trigger) { if (trigger == null) { return null; } if (trigger.Goals != null && trigger.Goals.Count > 0) { return trigger.Goals; } if (!string.IsNullOrEmpty(trigger.Item) && trigger.Count > 1) { return new List { new ItemGoalSpec { Item = trigger.Item, Count = trigger.Count } }; } return null; } private static bool IsCountGoalEntry(GuidanceEntry entry) { if (entry.Trigger == null) { return false; } if (!string.Equals(entry.Trigger.Type, "item_acquired", StringComparison.OrdinalIgnoreCase)) { return false; } return GetEffectiveGoals(entry.Trigger) != null; } internal static void CheckAllCountGoals() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } GuidanceConfig currentConfig = Plugin.CurrentConfig; if (currentConfig?.Guidances == null) { return; } foreach (GuidanceEntry guidance in currentConfig.Guidances) { if (!IsCountGoalEntry(guidance) || !GuidanceDispatcher.CheckGates(guidance, localPlayer)) { continue; } List effectiveGoals = GetEffectiveGoals(guidance.Trigger); bool flag = true; bool flag2 = false; foreach (ItemGoalSpec item in effectiveGoals) { int num = CountInInventory(localPlayer, item.Item); Plugin.Log.LogInfo((object)$"[item_acquired] '{guidance.Id}' seed {item.Item}: {num}/{item.Count}."); if (num < item.Count) { flag = false; } if (num > 0) { flag2 = true; } } if (flag) { Plugin.Log.LogInfo((object)("[item_acquired] '" + guidance.Id + "' all goals already met — firing.")); GoalStartedState.Clear(localPlayer, guidance.Id); GuidanceDispatcher.FireEntry(guidance, new TriggerEvent { Type = "item_acquired", Subject = effectiveGoals[0].Item }); GuidanceHudTracker.Instance?.FlashCompletion(guidance.Id); } else if (flag2 || GoalStartedState.IsStarted(localPlayer, guidance.Id)) { if (flag2) { GoalStartedState.MarkStarted(localPlayer, guidance.Id); } GuidanceHudTracker.Instance?.Refresh(fromProgress: true); } } } internal static void CheckCountGoals(string prefabName, string displayName) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } GuidanceConfig currentConfig = Plugin.CurrentConfig; if (currentConfig?.Guidances == null) { return; } foreach (GuidanceEntry guidance in currentConfig.Guidances) { if (!IsCountGoalEntry(guidance)) { continue; } List effectiveGoals = GetEffectiveGoals(guidance.Trigger); bool flag = false; foreach (ItemGoalSpec item in effectiveGoals) { if (ItemWildcardMatch(item.Item, prefabName)) { flag = true; break; } } if (!flag || !GuidanceDispatcher.CheckGates(guidance, localPlayer)) { continue; } bool flag2 = true; bool flag3 = false; foreach (ItemGoalSpec item2 in effectiveGoals) { int num = CountInInventory(localPlayer, item2.Item); Plugin.Log.LogInfo((object)$"[item_acquired] '{guidance.Id}' {item2.Item}: {num}/{item2.Count}."); if (num < item2.Count) { flag2 = false; } if (num > 0) { flag3 = true; } } if (flag2) { Plugin.Log.LogInfo((object)("[item_acquired] '" + guidance.Id + "' all goals reached — firing.")); GoalStartedState.Clear(localPlayer, guidance.Id); GuidanceDispatcher.FireEntry(guidance, new TriggerEvent { Type = "item_acquired", Subject = prefabName, DisplayName = displayName }); GuidanceHudTracker.Instance?.FlashCompletion(guidance.Id); } else { if (flag3) { GoalStartedState.MarkStarted(localPlayer, guidance.Id); } GuidanceHudTracker.Instance?.Refresh(fromProgress: true); } } } internal static int CountInInventory(Player player, string prefabPattern) { if (string.IsNullOrEmpty(prefabPattern)) { return 0; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null) { return 0; } int num = 0; foreach (ItemData allItem in inventory.GetAllItems()) { if (!((Object)(object)allItem?.m_dropPrefab == (Object)null)) { string value = TriggerUtils.NormalizePrefabName(((Object)allItem.m_dropPrefab).name); if (ItemWildcardMatch(prefabPattern, value)) { num += allItem.m_stack; } } } return num; } internal static string BuildGoalProgressText(Player player, List goals) { if ((Object)(object)player == (Object)null || goals == null || goals.Count == 0) { return ""; } StringBuilder stringBuilder = new StringBuilder(); foreach (ItemGoalSpec goal in goals) { int value = Math.Min(CountInInventory(player, goal.Item), goal.Count); if (stringBuilder.Length > 0) { stringBuilder.Append('\n'); } stringBuilder.Append(goal.Item).Append(": ").Append(value) .Append('/') .Append(goal.Count); } return stringBuilder.ToString(); } internal static bool ItemWildcardMatch(string pattern, string value) { if (string.IsNullOrEmpty(pattern) || string.IsNullOrEmpty(value)) { return false; } if (pattern.EndsWith("*")) { string value2 = pattern.Substring(0, pattern.Length - 1); return value.StartsWith(value2, StringComparison.OrdinalIgnoreCase); } return string.Equals(pattern, value, StringComparison.OrdinalIgnoreCase); } } [HarmonyPatch(typeof(InventoryGui), "DoCrafting")] internal static class ItemAcquiredCraftPatch { [HarmonyPostfix] private static void Postfix(InventoryGui __instance, Player player) { if ((Object)(object)player != (Object)(object)Player.m_localPlayer) { return; } Recipe craftRecipe = __instance.m_craftRecipe; if (!((Object)(object)craftRecipe?.m_item == (Object)null)) { string text = TriggerUtils.NormalizePrefabName(((Object)((Component)craftRecipe.m_item).gameObject).name); if (!string.IsNullOrEmpty(text)) { string displayName = craftRecipe.m_item.m_itemData?.m_shared?.m_name; ItemAcquiredTrigger.CheckCountGoals(text, displayName); } } } } [HarmonyPatch(typeof(Character), "OnDeath")] internal static class KillTrigger { [HarmonyPostfix] private static void Postfix(Character __instance) { if ((Object)(object)Player.m_localPlayer == (Object)null) { return; } object obj; if (__instance == null) { obj = null; } else { HitData lastHit = __instance.m_lastHit; obj = ((lastHit != null) ? lastHit.GetAttacker() : null); } Character val = (Character)obj; if (!((Object)(object)val == (Object)null) && !((Object)(object)val != (Object)(object)Player.m_localPlayer)) { GameObject gameObject = ((Component)__instance).gameObject; string text = TriggerUtils.NormalizePrefabName((gameObject != null) ? ((Object)gameObject).name : null); if (!string.IsNullOrEmpty(text)) { GuidanceDispatcher.Raise(new TriggerEvent { Type = "kill", Subject = text, DisplayName = __instance.m_name }); } } } } [HarmonyPatch(typeof(Player), "Update")] internal static class LocationEnteredTrigger { private const float CheckInterval = 5f; private const float DetectRadius = 40f; private const string KeyPrefix = "loc_"; private static float _nextCheck; [HarmonyPostfix] private static void Postfix(Player __instance) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_0202: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer || (Object)(object)ZoneSystem.instance == (Object)null || Time.time < _nextCheck) { return; } _nextCheck = Time.time + 5f; Vector3 position = ((Component)__instance).transform.position; HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (Location s_allLocation in Location.s_allLocations) { if ((Object)(object)s_allLocation == (Object)null || Vector3.Distance(position, ((Component)s_allLocation).transform.position) > 40f) { continue; } string text = ((Object)((Component)s_allLocation).gameObject).name; if (text.EndsWith("(Clone)")) { text = text.Substring(0, text.Length - 7).TrimEnd(Array.Empty()); } if (!string.IsNullOrEmpty(text)) { Plugin.Log.LogDebug((object)("[location_entered] Scene scan in range: '" + text + "'")); string id = "loc_" + text; if (!SeenTracker.HasFired(__instance, id)) { SeenTracker.MarkFired(__instance, id); hashSet.Add(text); GuidanceDispatcher.Raise(new TriggerEvent { Type = "location_entered", Subject = text }); } } } foreach (KeyValuePair locationInstance in ZoneSystem.instance.m_locationInstances) { LocationInstance value = locationInstance.Value; string text2 = value.m_location?.m_prefabName; if (string.IsNullOrEmpty(text2)) { text2 = value.m_location?.m_name; } if (string.IsNullOrEmpty(text2)) { continue; } float num = Vector3.Distance(position, value.m_position); if (num > 40f) { continue; } if (!value.m_placed) { Plugin.Log.LogDebug((object)$"[location_entered] ZoneSystem in range but unplaced: '{text2}' dist={num:F0}"); continue; } Plugin.Log.LogDebug((object)$"[location_entered] ZoneSystem in range (placed): '{text2}' dist={num:F0}"); if (!hashSet.Contains(text2)) { string id2 = "loc_" + text2; if (!SeenTracker.HasFired(__instance, id2)) { SeenTracker.MarkFired(__instance, id2); GuidanceDispatcher.Raise(new TriggerEvent { Type = "location_entered", Subject = text2 }); } } } } } internal static class NpcConvHoldState { internal const float HoldThreshold = 0.5f; internal static float HoldStart = -1f; internal static Trader PendingTrader = null; } [HarmonyPatch(typeof(Trader), "Interact")] internal static class NpcConversationTrigger { [HarmonyPrefix] private static bool Prefix(Trader __instance, Humanoid character, bool hold, ref bool __result) { NpcConversationHoldDetector.EnsureCreated(); Player val = (Player)(object)((character is Player) ? character : null); if ((Object)(object)val == (Object)null || (Object)(object)val != (Object)(object)Player.m_localPlayer) { return true; } GameObject gameObject = ((Component)__instance).gameObject; string npcSubject = TriggerUtils.NormalizePrefabName((gameObject != null) ? ((Object)gameObject).name : null); GuidanceEntry guidanceEntry = FindEntry(npcSubject, val); if (guidanceEntry == null) { return true; } if (!hold) { NpcConvHoldState.HoldStart = Time.time; NpcConvHoldState.PendingTrader = __instance; __result = true; return false; } __result = false; return false; } internal static GuidanceEntry FindEntry(string npcSubject, Player player) { if (string.IsNullOrEmpty(npcSubject) || (Object)(object)player == (Object)null) { return null; } GuidanceConfig currentConfig = Plugin.CurrentConfig; if (currentConfig?.Guidances == null) { return null; } foreach (GuidanceEntry guidance in currentConfig.Guidances) { if (guidance.Trigger == null || !string.Equals(guidance.Trigger.Type, "npc_conversation", StringComparison.OrdinalIgnoreCase) || !string.Equals(guidance.Trigger.Npc, npcSubject, StringComparison.OrdinalIgnoreCase) || !GuidanceDispatcher.CheckGates(guidance, player)) { continue; } return guidance; } return null; } } [HarmonyPatch(typeof(Trader), "GetHoverText")] internal static class TraderHoverTextPatch { [HarmonyPostfix] private static void Postfix(Trader __instance, ref string __result) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { GameObject gameObject = ((Component)__instance).gameObject; string npcSubject = TriggerUtils.NormalizePrefabName((gameObject != null) ? ((Object)gameObject).name : null); if (NpcConversationTrigger.FindEntry(npcSubject, localPlayer) != null) { __result += "\n[Hold E] Quest"; } } } } internal class NpcConversationHoldDetector : MonoBehaviour { private static NpcConversationHoldDetector _instance; internal static void EnsureCreated() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown if (!((Object)(object)_instance != (Object)null)) { GameObject val = new GameObject("VSG_NpcConvHold"); Object.DontDestroyOnLoad((Object)(object)val); _instance = val.AddComponent(); } } private void Update() { if ((Object)(object)NpcConvHoldState.PendingTrader == (Object)null) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { Reset(); } else if (!ZInput.GetButton("Use")) { Trader pendingTrader = NpcConvHoldState.PendingTrader; Reset(); if ((Object)(object)StoreGui.instance != (Object)null) { StoreGui.instance.Show(pendingTrader); } } else { if (!(Time.time - NpcConvHoldState.HoldStart >= 0.5f)) { return; } Trader pendingTrader2 = NpcConvHoldState.PendingTrader; Reset(); GameObject gameObject = ((Component)pendingTrader2).gameObject; string npcSubject = TriggerUtils.NormalizePrefabName((gameObject != null) ? ((Object)gameObject).name : null); GuidanceEntry guidanceEntry = NpcConversationTrigger.FindEntry(npcSubject, localPlayer); if (guidanceEntry == null) { if ((Object)(object)StoreGui.instance != (Object)null) { StoreGui.instance.Show(pendingTrader2); } } else { string template = ((!string.IsNullOrEmpty(guidanceEntry.Message)) ? guidanceEntry.Message : guidanceEntry.Display?.Text); string renderedText = GuidanceDispatcher.TemplateText(template, null, localPlayer.GetPlayerName()); GuidanceDisplay.Show(guidanceEntry, renderedText); } } } private static void Reset() { NpcConvHoldState.HoldStart = -1f; NpcConvHoldState.PendingTrader = null; } } [HarmonyPatch(typeof(StoreGui), "Show")] internal static class NpcInteractedTrigger { [HarmonyPostfix] private static void Postfix(Trader trader) { if (!((Object)(object)trader == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null)) { GameObject gameObject = ((Component)trader).gameObject; string text = TriggerUtils.NormalizePrefabName((gameObject != null) ? ((Object)gameObject).name : null); if (!string.IsNullOrEmpty(text)) { GuidanceDispatcher.Raise(new TriggerEvent { Type = "npc_interacted", Subject = text, DisplayName = trader.m_name }); } } } } [HarmonyPatch(typeof(Trader), "UseItem")] internal static class NpcItemSubmitTrigger { [HarmonyPrefix] private static bool Prefix(Trader __instance, Humanoid user, ItemData item, ref bool __result) { if (item == null) { return true; } if ((Object)(object)user == (Object)null || (Object)(object)user != (Object)(object)Player.m_localPlayer) { return true; } GameObject gameObject = ((Component)__instance).gameObject; string text = TriggerUtils.NormalizePrefabName((gameObject != null) ? ((Object)gameObject).name : null); if (string.IsNullOrEmpty(text)) { return true; } string text2 = ResolveItemName(item); Plugin.Log.LogInfo((object)("[item_submit] '" + ((object)user).GetType().Name + "' used '" + text2 + "' (token '" + item.m_shared?.m_name + "') on '" + text + "'.")); if (IsVanillaUseItem(__instance, item)) { Plugin.Log.LogInfo((object)("[item_submit] '" + text2 + "' is a vanilla quest item — deferring to vanilla.")); return true; } Player val = (Player)(object)((user is Player) ? user : null); if ((Object)(object)val == (Object)null) { return true; } GuidanceEntry guidanceEntry = FindEntry(text, text2, val); if (guidanceEntry != null) { HandleSubmission(__instance, val, item, text2, text, guidanceEntry); __result = true; return false; } if (__instance.m_useItems != null && __instance.m_useItems.Count > 0) { Plugin.Log.LogInfo((object)"[item_submit] no entry; NPC has vanilla useItems — deferring to vanilla rejection."); return true; } if (NpcHasConfiguredEntries(text)) { Plugin.Log.LogInfo((object)("[item_submit] no entry; suppressing vanilla 'can't use' on owned NPC '" + text + "'.")); __result = true; return false; } return true; } private static void HandleSubmission(Trader trader, Player player, ItemData item, string itemPrefabName, string npcSubject, GuidanceEntry entry) { int num = ((entry.Trigger.Count <= 0) ? 1 : entry.Trigger.Count); bool consume = entry.Trigger.Consume; string text = Localized(item); TriggerEvent evt = new TriggerEvent { Type = "npc_item_submit", Subject = npcSubject, DisplayName = text, Extra = new Dictionary { { "item", itemPrefabName } } }; if (num <= 1) { if (consume) { ConsumeItems(player, item, 1); } Plugin.Log.LogInfo((object)("[item_submit] firing entry '" + entry.Id + "' (single) for '" + itemPrefabName + "' -> '" + npcSubject + "'.")); GuidanceDispatcher.FireEntry(entry, evt); return; } int num2 = SubmitState.Get(player, entry.Id); int num3 = num - num2; if (num3 <= 0) { num3 = num; num2 = 0; } int val = Math.Max(1, item.m_stack); int num4 = Math.Min(num3, val); if (consume) { ConsumeItems(player, item, num4); } int num5 = num2 + num4; Plugin.Log.LogInfo((object)($"[item_submit] '{entry.Id}' progress {num5}/{num} " + $"(+{num4} {itemPrefabName}, consume={consume}).")); if (num5 >= num) { SubmitState.Clear(player, entry.Id); Plugin.Log.LogInfo((object)$"[item_submit] '{entry.Id}' complete ({num}/{num}) — firing."); GuidanceDispatcher.FireEntry(entry, evt); GuidanceHudTracker.Instance?.FlashCompletion(entry.Id); return; } SubmitState.Set(player, entry.Id, num5); string text2 = ((!string.IsNullOrEmpty(entry.Title)) ? entry.Title : text); ((Character)player).Message((MessageType)2, $"{text2}: {num5}/{num} {text}", 0, (Sprite)null); GuidanceHudTracker.Instance?.Refresh(fromProgress: true); } private static void ConsumeItems(Player player, ItemData item, int amount) { if (amount > 0) { Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory != null) { inventory.RemoveItem(item, amount); ((Character)player).ShowRemovedMessage(item, amount); } } } private static string Localized(ItemData item) { string text = item.m_shared?.m_name ?? ""; return (Localization.instance != null) ? Localization.instance.Localize(text) : text; } private static string ResolveItemName(ItemData item) { GameObject dropPrefab = item.m_dropPrefab; string text = ((dropPrefab != null) ? ((Object)dropPrefab).name : null); if (!string.IsNullOrEmpty(text)) { return TriggerUtils.NormalizePrefabName(text); } return TriggerUtils.NormalizePrefabName(item.m_shared?.m_name ?? ""); } private static bool IsVanillaUseItem(Trader trader, ItemData item) { if (trader.m_useItems == null || trader.m_useItems.Count == 0) { return false; } string text = item.m_shared?.m_name; if (string.IsNullOrEmpty(text)) { return false; } foreach (TraderUseItem useItem in trader.m_useItems) { string text2 = useItem?.m_prefab?.m_itemData?.m_shared?.m_name; if (!string.IsNullOrEmpty(text2) && string.Equals(text, text2, StringComparison.Ordinal)) { return true; } } return false; } internal static GuidanceEntry FindEntry(string npcSubject, string itemPrefabName, Player player) { if (string.IsNullOrEmpty(npcSubject) || (Object)(object)player == (Object)null) { return null; } GuidanceConfig currentConfig = Plugin.CurrentConfig; if (currentConfig?.Guidances == null) { return null; } GuidanceEntry guidanceEntry = null; foreach (GuidanceEntry guidance in currentConfig.Guidances) { if (guidance.Trigger == null || !string.Equals(guidance.Trigger.Type, "npc_item_submit", StringComparison.OrdinalIgnoreCase) || !string.Equals(guidance.Trigger.Npc, npcSubject, StringComparison.OrdinalIgnoreCase) || !GuidanceDispatcher.CheckGates(guidance, player)) { continue; } if (string.IsNullOrEmpty(guidance.Trigger.Item)) { if (guidanceEntry == null) { guidanceEntry = guidance; } } else if (string.Equals(guidance.Trigger.Item, itemPrefabName, StringComparison.OrdinalIgnoreCase)) { return guidance; } } return guidanceEntry; } internal static bool NpcHasConfiguredEntries(string npcSubject) { GuidanceConfig currentConfig = Plugin.CurrentConfig; if (currentConfig?.Guidances == null) { return false; } foreach (GuidanceEntry guidance in currentConfig.Guidances) { if (guidance.Trigger == null || !string.Equals(guidance.Trigger.Type, "npc_item_submit", StringComparison.OrdinalIgnoreCase) || !string.Equals(guidance.Trigger.Npc, npcSubject, StringComparison.OrdinalIgnoreCase)) { continue; } return true; } return false; } } [HarmonyPatch(typeof(Trader), "GetHoverText")] internal static class NpcItemSubmitHoverPatch { private const string GiveItemLine = "\n[1-8] $npc_giveitem"; [HarmonyPostfix] private static void Postfix(Trader __instance, ref string __result) { if (!((Object)(object)Player.m_localPlayer == (Object)null) && (__instance.m_useItems == null || __instance.m_useItems.Count <= 0)) { GameObject gameObject = ((Component)__instance).gameObject; string npcSubject = TriggerUtils.NormalizePrefabName((gameObject != null) ? ((Object)gameObject).name : null); if (NpcItemSubmitTrigger.NpcHasConfiguredEntries(npcSubject)) { string text = ((Localization.instance != null) ? Localization.instance.Localize("\n[1-8] $npc_giveitem") : "\n[1-8] $npc_giveitem"); __result += text; } } } } [HarmonyPatch(typeof(Player), "OnDeath")] internal static class PlayerDeathTrigger { [HarmonyPostfix] private static void Postfix(Player __instance) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { GuidanceDispatcher.Raise(new TriggerEvent { Type = "player_death", Subject = "" }); } } } [HarmonyPatch(typeof(Skills), "RaiseSkill")] internal static class SkillLevelTrigger { private static int _prevLevel; [HarmonyPrefix] private static void Prefix(Skills __instance, SkillType skillType) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Player.m_localPlayer == (Object)null) && !((Object)(object)Player.m_localPlayer.m_skills != (Object)(object)__instance)) { _prevLevel = (int)__instance.GetSkillLevel(skillType); } } [HarmonyPostfix] private static void Postfix(Skills __instance, SkillType skillType) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Player.m_localPlayer == (Object)null) && !((Object)(object)Player.m_localPlayer.m_skills != (Object)(object)__instance)) { int num = (int)__instance.GetSkillLevel(skillType); for (int i = _prevLevel + 1; i <= num; i++) { GuidanceDispatcher.Raise(new TriggerEvent { Type = "skill_level", Subject = $"{skillType}:{i}" }); } } } internal static void CheckAllSkillLevels() { //IL_0155: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } GuidanceConfig currentConfig = Plugin.CurrentConfig; if (currentConfig?.Guidances == null) { return; } List<(string, int)> list = new List<(string, int)>(); foreach (GuidanceEntry guidance in currentConfig.Guidances) { CollectThreshold(guidance.Trigger, list); if (guidance.Steps == null) { continue; } foreach (GuidanceStep step in guidance.Steps) { CollectThreshold(step?.Trigger, list); } } if (list.Count == 0) { return; } list.Sort(delegate((string skill, int level) a, (string skill, int level) b) { int num3 = string.Compare(a.skill, b.skill, StringComparison.OrdinalIgnoreCase); return (num3 != 0) ? num3 : a.level.CompareTo(b.level); }); foreach (var (text, num) in list) { if (Enum.TryParse(text, ignoreCase: true, out SkillType result)) { int num2 = (int)localPlayer.m_skills.GetSkillLevel(result); if (num2 >= num) { Plugin.Log.LogInfo((object)$"[skill_level] Login scan: {text}:{num} (player {num2}) — raising."); GuidanceDispatcher.Raise(new TriggerEvent { Type = "skill_level", Subject = $"{text}:{num}" }); } } } } private static void CollectThreshold(TriggerSpec t, List<(string, int)> list) { if (t != null && string.Equals(t.Type, "skill_level", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(t.Skill) && t.Level > 0) { list.Add((t.Skill, t.Level)); } } } internal static class TimedTrigger { [CompilerGenerated] private sealed class d__3 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public string entryId; public string triggerId; public float interval; public bool isGlobal; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__3(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(interval); <>1__state = 1; return true; case 1: <>1__state = -1; break; case 2: <>1__state = -1; break; } Plugin.Log.LogInfo((object)("[timed] '" + entryId + "' firing.")); if (isGlobal && Application.isBatchMode) { GuidanceSync.BroadcastTimedGuidance(entryId); } else { GuidanceDispatcher.Raise(new TriggerEvent { Type = "timed", Subject = triggerId }); } <>2__current = (object)new WaitForSeconds(interval); <>1__state = 2; return true; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static readonly Dictionary _coroutines = new Dictionary(); public static void OnConfigChanged(GuidanceConfig config) { StopAll(); if (config?.Guidances == null) { return; } bool flag = Application.isBatchMode && IsServerOrHost(); bool flag2 = !IsServerOrHost(); foreach (GuidanceEntry guidance in config.Guidances) { if (guidance.Trigger == null || !string.Equals(guidance.Trigger.Type, "timed", StringComparison.OrdinalIgnoreCase)) { continue; } float num = ParseInterval(guidance.Trigger.Interval); if (num <= 0f) { Plugin.Log.LogWarning((object)("[timed] '" + guidance.Id + "' has invalid interval '" + guidance.Trigger.Interval + "'; skipping.")); continue; } bool flag3 = SeenTracker.IsGlobalScope(guidance.Scope); if ((!flag || flag3) && !(flag2 && flag3)) { string id = guidance.Id; string triggerId = guidance.Trigger.Id ?? guidance.Id; Coroutine value = ((MonoBehaviour)Plugin.Instance).StartCoroutine(TimerRoutine(id, triggerId, num, flag3)); _coroutines[id] = value; Plugin.Log.LogInfo((object)string.Format("[timed] scheduled '{0}' every {1}s ({2}).", id, num, flag3 ? "global" : "player")); } } } private static void StopAll() { if ((Object)(object)Plugin.Instance == (Object)null) { return; } foreach (Coroutine value in _coroutines.Values) { if (value != null) { ((MonoBehaviour)Plugin.Instance).StopCoroutine(value); } } _coroutines.Clear(); } [IteratorStateMachine(typeof(d__3))] private static IEnumerator TimerRoutine(string entryId, string triggerId, float interval, bool isGlobal) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__3(0) { entryId = entryId, triggerId = triggerId, interval = interval, isGlobal = isGlobal }; } private static float ParseInterval(string s) { if (string.IsNullOrEmpty(s)) { return 0f; } if (string.Equals(s, "daily", StringComparison.OrdinalIgnoreCase)) { return 86400f; } if (string.Equals(s, "hourly", StringComparison.OrdinalIgnoreCase)) { return 3600f; } float result; return float.TryParse(s, out result) ? result : 0f; } private static bool IsServerOrHost() { return (Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer(); } } internal static class TriggerUtils { private const string CloneSuffix = "(Clone)"; public static string NormalizePrefabName(string raw) { if (string.IsNullOrEmpty(raw)) { return raw; } return raw.EndsWith("(Clone)") ? raw.Substring(0, raw.Length - "(Clone)".Length) : raw; } } } namespace ValheimServerGuide.State { public static class ChainState { private const string StepPrefix = "VSG.cp."; private const string DonePrefix = "VSG.cd."; private const string CounterPrefix = "VSG.cc."; private const string VersionPrefix = "VSG.cv."; public static int GetStep(Player player, string chainId) { if (player?.m_customData == null || string.IsNullOrEmpty(chainId)) { return 0; } if (!player.m_customData.TryGetValue("VSG.cp." + chainId, out var value)) { return 0; } int result; return int.TryParse(value, out result) ? result : 0; } public static void SetStep(Player player, string chainId, int step) { if (player?.m_customData != null && !string.IsNullOrEmpty(chainId)) { player.m_customData["VSG.cp." + chainId] = step.ToString(); } } public static bool IsComplete(Player player, string chainId) { if (player?.m_customData == null || string.IsNullOrEmpty(chainId)) { return false; } string value; return player.m_customData.TryGetValue("VSG.cd." + chainId, out value) && value == "1"; } public static void MarkComplete(Player player, string chainId) { if (player?.m_customData != null && !string.IsNullOrEmpty(chainId)) { player.m_customData["VSG.cd." + chainId] = "1"; player.m_customData.Remove("VSG.cp." + chainId); } } public static void Reset(Player player, string chainId) { if (player?.m_customData == null || string.IsNullOrEmpty(chainId)) { return; } player.m_customData.Remove("VSG.cp." + chainId); player.m_customData.Remove("VSG.cd." + chainId); player.m_customData.Remove("VSG.cv." + chainId); string value = "VSG.cc." + chainId + ":"; List list = new List(); foreach (string key in player.m_customData.Keys) { if (key.StartsWith(value)) { list.Add(key); } } foreach (string item in list) { player.m_customData.Remove(item); } } public static void ResetAll(Player player) { if (player?.m_customData == null) { return; } List list = new List(); foreach (string key in player.m_customData.Keys) { if (key.StartsWith("VSG.cp.") || key.StartsWith("VSG.cd.") || key.StartsWith("VSG.cc.") || key.StartsWith("VSG.cv.")) { list.Add(key); } } foreach (string item in list) { player.m_customData.Remove(item); } } public static int GetCompletedVersion(Player player, string chainId) { if (player?.m_customData == null || string.IsNullOrEmpty(chainId)) { return 0; } if (!player.m_customData.TryGetValue("VSG.cv." + chainId, out var value)) { return 0; } int result; return int.TryParse(value, out result) ? result : 0; } public static void SetCompletedVersion(Player player, string chainId, int version) { if (player?.m_customData != null && !string.IsNullOrEmpty(chainId)) { player.m_customData["VSG.cv." + chainId] = version.ToString(); } } private static string CounterKey(string chainId, int stepIndex) { return "VSG.cc." + chainId + ":" + stepIndex; } public static int GetCounter(Player player, string chainId, int stepIndex) { if (player?.m_customData == null || string.IsNullOrEmpty(chainId)) { return -1; } if (!player.m_customData.TryGetValue(CounterKey(chainId, stepIndex), out var value)) { return -1; } int result; return int.TryParse(value, out result) ? result : (-1); } public static void SetCounter(Player player, string chainId, int stepIndex, int value) { if (player?.m_customData != null && !string.IsNullOrEmpty(chainId)) { player.m_customData[CounterKey(chainId, stepIndex)] = value.ToString(); } } public static void ClearCounter(Player player, string chainId, int stepIndex) { player?.m_customData?.Remove(CounterKey(chainId, stepIndex)); } } public static class GoalStartedState { private const string StartedPrefix = "VSG.ig."; private static string Key(string entryId) { return "VSG.ig." + entryId; } public static bool IsStarted(Player player, string entryId) { if (player?.m_customData == null || string.IsNullOrEmpty(entryId)) { return false; } return player.m_customData.ContainsKey(Key(entryId)); } public static void MarkStarted(Player player, string entryId) { if (player?.m_customData != null && !string.IsNullOrEmpty(entryId)) { player.m_customData[Key(entryId)] = "1"; } } public static void Clear(Player player, string entryId) { player?.m_customData?.Remove(Key(entryId)); } public static void ResetAll(Player player) { if (player?.m_customData == null) { return; } List list = new List(); foreach (string key in player.m_customData.Keys) { if (key.StartsWith("VSG.ig.")) { list.Add(key); } } foreach (string item in list) { player.m_customData.Remove(item); } } } public static class PrerequisiteChecker { public static bool AllSatisfied(List requires, Player player, GuidanceConfig config) { if (requires == null || requires.Count == 0) { return true; } foreach (string require in requires) { if (!IsSatisfied(require, player, config)) { return false; } } return true; } private static bool IsSatisfied(string reqId, Player player, GuidanceConfig config) { if (ChainState.IsComplete(player, reqId)) { return true; } if (SeenTracker.HasFired(player, reqId, "player")) { return true; } if (!(config?.Guidances?.Exists((GuidanceEntry e) => e.Id == reqId)).GetValueOrDefault()) { Plugin.Log.LogWarning((object)("[prereq] '" + reqId + "' not found in config — treating as unsatisfied.")); } return false; } } public static class SeenTracker { private const string Key = "VSG.fired"; private const string FireCountPrefix = "VSG.fc."; public const string GlobalKeyPrefix = "VSG."; private static readonly Dictionary CooldownExpiry = new Dictionary(); public static string GlobalKeyFor(string id) { return "VSG." + id; } public static bool IsGlobalScope(string scope) { return string.Equals(scope, "global", StringComparison.OrdinalIgnoreCase); } public static bool HasFired(Player player, string id, string scope) { if (string.IsNullOrEmpty(id)) { return false; } if (IsGlobalScope(scope)) { return (Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey(GlobalKeyFor(id)); } if ((Object)(object)player == (Object)null) { return false; } return GetSet(player).Contains(id); } public static bool HasFired(Player player, string id) { return HasFired(player, id, "player"); } public static void MarkFired(Player player, string id, string scope) { if (string.IsNullOrEmpty(id)) { return; } if (IsGlobalScope(scope)) { if ((Object)(object)ZoneSystem.instance != (Object)null && ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer())) { ZoneSystem.instance.SetGlobalKey(GlobalKeyFor(id)); } } else if (!((Object)(object)player == (Object)null)) { HashSet set = GetSet(player); if (set.Add(id)) { player.m_customData["VSG.fired"] = string.Join(",", set); } } } public static void MarkFired(Player player, string id) { MarkFired(player, id, "player"); } public static bool CooldownReady(string id, float cooldownSeconds, float now) { if (cooldownSeconds <= 0f) { return true; } if (!CooldownExpiry.TryGetValue(id, out var value)) { return true; } return now >= value; } public static void MarkCooldown(string id, float cooldownSeconds, float now) { if (!(cooldownSeconds <= 0f)) { CooldownExpiry[id] = now + cooldownSeconds; } } public static bool ClearFired(Player player, string id, string scope = "player") { if (string.IsNullOrEmpty(id)) { return false; } if (IsGlobalScope(scope)) { if ((Object)(object)ZoneSystem.instance == (Object)null) { return false; } if ((Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer()) { return false; } string text = GlobalKeyFor(id); if (!ZoneSystem.instance.GetGlobalKey(text)) { return false; } ZoneSystem.instance.RemoveGlobalKey(text); CooldownExpiry.Remove(id); return true; } if ((Object)(object)player == (Object)null) { return false; } bool result = ClearFireCount(player, id); CooldownExpiry.Remove(id); HashSet set = GetSet(player); if (!set.Remove(id)) { return result; } if (set.Count == 0) { player.m_customData.Remove("VSG.fired"); } else { player.m_customData["VSG.fired"] = string.Join(",", set); } return true; } public static bool ClearFireCount(Player player, string id) { if (player?.m_customData == null || string.IsNullOrEmpty(id)) { return false; } return player.m_customData.Remove("VSG.fc." + id); } public static int ClearAllFired(Player player) { if ((Object)(object)player == (Object)null) { return 0; } int count = GetSet(player).Count; player.m_customData.Remove("VSG.fired"); List list = player.m_customData.Keys.Where((string k) => k.StartsWith("VSG.fc.")).ToList(); foreach (string item in list) { player.m_customData.Remove(item); } CooldownExpiry.Clear(); return count; } public static int GetFireCount(Player player, string id) { if (player?.m_customData == null) { return 0; } string key = "VSG.fc." + id; if (!player.m_customData.TryGetValue(key, out var value)) { return 0; } int result; return int.TryParse(value, out result) ? result : 0; } public static void IncrementFireCount(Player player, string id) { if (player?.m_customData != null) { string key = "VSG.fc." + id; player.m_customData[key] = (GetFireCount(player, id) + 1).ToString(); } } public static IReadOnlyCollection GetFiredIds(Player player) { if ((Object)(object)player == (Object)null) { return (IReadOnlyCollection)(object)Array.Empty(); } return GetSet(player); } private static HashSet GetSet(Player player) { if (!player.m_customData.TryGetValue("VSG.fired", out var value) || string.IsNullOrEmpty(value)) { return new HashSet(); } return new HashSet(value.Split(new char[1] { ',' })); } } public static class SubmitState { private const string ProgressPrefix = "VSG.is."; private static string Key(string entryId) { return "VSG.is." + entryId; } public static int Get(Player player, string entryId) { if (player?.m_customData == null || string.IsNullOrEmpty(entryId)) { return 0; } if (!player.m_customData.TryGetValue(Key(entryId), out var value)) { return 0; } int result; return int.TryParse(value, out result) ? result : 0; } public static void Set(Player player, string entryId, int value) { if (player?.m_customData != null && !string.IsNullOrEmpty(entryId)) { player.m_customData[Key(entryId)] = value.ToString(); } } public static void Clear(Player player, string entryId) { player?.m_customData?.Remove(Key(entryId)); } public static void ResetAll(Player player) { if (player?.m_customData == null) { return; } List list = new List(); foreach (string key in player.m_customData.Keys) { if (key.StartsWith("VSG.is.")) { list.Add(key); } } foreach (string item in list) { player.m_customData.Remove(item); } } } } namespace ValheimServerGuide.Rewards { public static class RewardDispatcher { public static void Grant(List rewards, Player player) { if (rewards == null || rewards.Count == 0 || (Object)(object)player == (Object)null) { return; } foreach (RewardSpec reward in rewards) { switch (reward.Type?.ToLowerInvariant()) { case "item": GrantItem(reward, player); break; case "skill_exp": GrantSkillExp(reward, player); break; case "skill_level": GrantSkillLevel(reward, player); break; case "buff": GrantBuff(reward, player); break; default: Plugin.Log.LogWarning((object)("[rewards] Unknown reward type '" + reward.Type + "' — skipping.")); break; } } RewardNotification.Show(rewards); } public static void ValidateRewards(List rewards, string context) { if (rewards == null) { return; } foreach (RewardSpec reward in rewards) { switch (reward.Type?.ToLowerInvariant()) { case "item": if (!string.IsNullOrEmpty(reward.Item) && (Object)(object)ZNetScene.instance != (Object)null) { GameObject prefab = ZNetScene.instance.GetPrefab(reward.Item); if ((Object)(object)prefab == (Object)null) { Plugin.Log.LogWarning((object)("[rewards] " + context + ": item prefab '" + reward.Item + "' not found.")); } else if ((Object)(object)prefab.GetComponent() == (Object)null) { Plugin.Log.LogWarning((object)("[rewards] " + context + ": prefab '" + reward.Item + "' has no ItemDrop.")); } } break; case "skill_exp": case "skill_level": { if (!string.IsNullOrEmpty(reward.Skill) && !Enum.TryParse(reward.Skill, ignoreCase: true, out SkillType _)) { Plugin.Log.LogWarning((object)("[rewards] " + context + ": unknown skill '" + reward.Skill + "'.")); } break; } case "buff": { if (string.IsNullOrEmpty(reward.Effect) || ObjectDB.instance?.m_StatusEffects == null) { break; } string text = NormalizeEffectName(reward.Effect); bool flag = false; foreach (StatusEffect statusEffect in ObjectDB.instance.m_StatusEffects) { if ((Object)(object)statusEffect == (Object)null || (!(NormalizeEffectName(((Object)statusEffect).name) == text) && !(NormalizeEffectName(statusEffect.m_name) == text))) { continue; } flag = true; break; } if (!flag) { Plugin.Log.LogWarning((object)("[rewards] " + context + ": status effect '" + reward.Effect + "' not found in ObjectDB.")); } break; } } } } private static void GrantItem(RewardSpec reward, Player player) { //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(reward.Item)) { Plugin.Log.LogWarning((object)"[rewards] item reward missing 'item' field — skipping."); return; } ZNetScene instance = ZNetScene.instance; GameObject val = ((instance != null) ? instance.GetPrefab(reward.Item) : null); if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning((object)("[rewards] item prefab '" + reward.Item + "' not found — skipping.")); return; } ItemDrop component = val.GetComponent(); if ((Object)(object)component == (Object)null) { Plugin.Log.LogWarning((object)("[rewards] prefab '" + reward.Item + "' has no ItemDrop — skipping.")); return; } int num = Mathf.Clamp(reward.Quality, 1, component.m_itemData.m_shared.m_maxQuality); ItemData val2 = ((Humanoid)player).GetInventory().AddItem(reward.Item, reward.Amount, num, 0, 0L, player.GetPlayerName(), false); if (val2 == null) { Vector3 val3 = ((Component)player).transform.position + ((Component)player).transform.forward * 1.5f; GameObject val4 = Object.Instantiate(val, val3, Quaternion.identity); ItemDrop component2 = val4.GetComponent(); if ((Object)(object)component2 != (Object)null) { component2.m_itemData.m_stack = reward.Amount; component2.m_itemData.m_quality = num; } Plugin.Log.LogInfo((object)$"[rewards] Inventory full — dropped '{reward.Item}' x{reward.Amount} Q{num} in front of player."); } else { Plugin.Log.LogInfo((object)$"[rewards] Granted '{reward.Item}' x{reward.Amount} Q{num}."); } } private static void GrantSkillExp(RewardSpec reward, Player player) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) if (TryParseSkill(reward.Skill, out var skillType)) { float num = ((reward.SkillExp > 0f) ? reward.SkillExp : ((float)reward.Amount)); ((Character)player).GetSkills().RaiseSkill(skillType, num); Plugin.Log.LogInfo((object)$"[rewards] Raised {reward.Skill} by {num} XP."); } } private static void GrantSkillLevel(RewardSpec reward, Player player) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) if (!TryParseSkill(reward.Skill, out var skillType)) { return; } Skill skill = ((Character)player).GetSkills().GetSkill(skillType); if (skill == null) { Plugin.Log.LogWarning((object)("[rewards] could not resolve skill '" + reward.Skill + "' — skipping.")); return; } int num = Mathf.Clamp(reward.Level, 1, 100); if ((float)num <= skill.m_level) { Plugin.Log.LogInfo((object)$"[rewards] {reward.Skill} already at {skill.m_level} >= target {num}; skipping."); return; } skill.m_level = num; skill.m_accumulator = 0f; Plugin.Log.LogInfo((object)$"[rewards] Set {reward.Skill} to level {num}."); } private static void GrantBuff(RewardSpec reward, Player player) { if (string.IsNullOrEmpty(reward.Effect)) { Plugin.Log.LogWarning((object)"[rewards] buff reward missing 'effect' field — skipping."); return; } ObjectDB instance = ObjectDB.instance; StatusEffect val = null; string text = NormalizeEffectName(reward.Effect); if (instance?.m_StatusEffects != null) { foreach (StatusEffect statusEffect in instance.m_StatusEffects) { if ((Object)(object)statusEffect == (Object)null || (!(NormalizeEffectName(((Object)statusEffect).name) == text) && !(NormalizeEffectName(statusEffect.m_name) == text))) { continue; } val = statusEffect; break; } } if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning((object)("[rewards] status effect '" + reward.Effect + "' not found in ObjectDB — skipping.")); return; } StatusEffect val2 = ((Character)player).GetSEMan().AddStatusEffect(val, true, 0, 0f); if (reward.DurationOverride.HasValue && (Object)(object)val2 != (Object)null) { val2.m_ttl = reward.DurationOverride.Value; } Plugin.Log.LogInfo((object)("[rewards] Applied buff '" + reward.Effect + "'" + (reward.DurationOverride.HasValue ? $" ({reward.DurationOverride.Value}s)" : "") + ".")); } internal static string NormalizeEffectName(string s) { if (string.IsNullOrEmpty(s)) { return ""; } s = s.ToLowerInvariant(); if (s.StartsWith("$")) { s = s.Substring(1); } if (s.StartsWith("se_")) { s = s.Substring(3); } return s; } private static bool TryParseSkill(string skill, out SkillType skillType) { if (string.IsNullOrEmpty(skill)) { Plugin.Log.LogWarning((object)"[rewards] skill reward missing 'skill' field — skipping."); skillType = (SkillType)0; return false; } if (!Enum.TryParse(skill, ignoreCase: true, out skillType)) { Plugin.Log.LogWarning((object)("[rewards] Unknown skill '" + skill + "' — skipping.")); return false; } return true; } } public static class RewardNotification { public static void Show(List rewards) { if (rewards == null || rewards.Count == 0 || (Object)(object)MessageHud.instance == (Object)null) { return; } List list = new List(); foreach (RewardSpec reward in rewards) { string text = Describe(reward); if (!string.IsNullOrEmpty(text)) { list.Add(text); } } if (list.Count != 0) { string text2 = "Received: " + string.Join(", ", list); MessageHud.instance.ShowMessage((MessageType)2, text2, 0, (Sprite)null, false); } } private static string Describe(RewardSpec reward) { return reward.Type?.ToLowerInvariant() switch { "item" => DescribeItem(reward), "skill_exp" => DescribeSkillExp(reward), "skill_level" => DescribeSkillLevel(reward), "buff" => DescribeBuff(reward), _ => null, }; } private static string DescribeItem(RewardSpec reward) { if (string.IsNullOrEmpty(reward.Item)) { return null; } string value = LocalizeItemName(reward.Item); StringBuilder stringBuilder = new StringBuilder(value); if (reward.Amount > 1) { stringBuilder.Append(" x").Append(reward.Amount); } if (reward.Quality > 1) { stringBuilder.Append(" (Q").Append(reward.Quality).Append(")"); } return stringBuilder.ToString(); } private static string DescribeSkillExp(RewardSpec reward) { if (string.IsNullOrEmpty(reward.Skill)) { return null; } float num = ((reward.SkillExp > 0f) ? reward.SkillExp : ((float)reward.Amount)); return $"+{num:0.#} {reward.Skill} XP"; } private static string DescribeSkillLevel(RewardSpec reward) { if (string.IsNullOrEmpty(reward.Skill)) { return null; } return $"{reward.Skill} level {reward.Level}"; } private static string DescribeBuff(RewardSpec reward) { if (string.IsNullOrEmpty(reward.Effect)) { return null; } string text = LocalizeBuffName(reward.Effect); if (reward.DurationOverride.HasValue) { float value = reward.DurationOverride.Value; string text2 = ((value >= 60f) ? $"{value / 60f:0.#} min" : $"{value:0}s"); return text + " buff (" + text2 + ")"; } return text + " buff"; } private static string LocalizeItemName(string prefabName) { ZNetScene instance = ZNetScene.instance; GameObject val = ((instance != null) ? instance.GetPrefab(prefabName) : null); string text = (((Object)(object)val != (Object)null) ? val.GetComponent() : null)?.m_itemData?.m_shared?.m_name; if (!string.IsNullOrEmpty(text) && Localization.instance != null) { return Localization.instance.Localize(text); } return prefabName; } private static string LocalizeBuffName(string effect) { ObjectDB instance = ObjectDB.instance; if (instance?.m_StatusEffects != null) { string text = RewardDispatcher.NormalizeEffectName(effect); foreach (StatusEffect statusEffect in instance.m_StatusEffects) { if ((Object)(object)statusEffect == (Object)null || (!(RewardDispatcher.NormalizeEffectName(((Object)statusEffect).name) == text) && !(RewardDispatcher.NormalizeEffectName(statusEffect.m_name) == text))) { continue; } if (!string.IsNullOrEmpty(statusEffect.m_name) && Localization.instance != null) { return Localization.instance.Localize(statusEffect.m_name); } break; } } return effect; } } } namespace ValheimServerGuide.Net { public static class GuidanceSync { [HarmonyPatch(typeof(ZNet), "Awake")] private static class ZNetAwakePatch { private static void Postfix(ZNet __instance) { EnsureRegistered(); if (__instance.IsServer()) { Plugin.Log.LogInfo((object)"ZNet started as server/host — loading guidance YAML."); Plugin.EnsureLoaderStarted(); } else { Plugin.Log.LogInfo((object)"ZNet started as pure client — waiting for server config push."); } } } [HarmonyPatch(typeof(ZNet), "OnDestroy")] private static class ZNetOnDestroyPatch { private static void Postfix() { _rpcsBound = false; _playerChainData.Clear(); if (!Application.isBatchMode) { Plugin.ShutdownLoader(); Plugin.CurrentConfig = GuidanceConfig.Empty; } } } [HarmonyPatch(typeof(Player), "OnSpawned")] private static class PlayerSpawnedPatch { private static void Postfix(Player __instance) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { RequestChainState(__instance.GetPlayerName()); } } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private static class PeerInfoPatch { private static void Postfix(ZNet __instance, ZRpc rpc) { if (__instance.IsServer()) { ZNetPeer peer = __instance.GetPeer(rpc); if (peer != null) { EnsureRegistered(); SendToPeer(peer.m_uid, Plugin.CurrentConfig); } } } } [CompilerGenerated] private sealed class d__46 : IEnumerable, IEnumerable, IEnumerator, IDisposable, IEnumerator { private int <>1__state; private string <>2__current; private int <>l__initialThreadId; private List.Enumerator <>s__1; private ZNetPeer

5__2; string IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__46(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <>s__1 = default(List.Enumerator);

5__2 = null; <>1__state = -2; } private bool MoveNext() { try { int num = <>1__state; if (num != 0) { if (num != 1) { return false; } <>1__state = -3; goto IL_00aa; } <>1__state = -1; if ((Object)(object)ZNet.instance == (Object)null) { return false; } <>s__1 = ZNet.instance.GetPeers().GetEnumerator(); <>1__state = -3; goto IL_00b1; IL_00aa:

5__2 = null; goto IL_00b1; IL_00b1: if (<>s__1.MoveNext()) {

5__2 = <>s__1.Current; if (!string.IsNullOrEmpty(

5__2.m_playerName)) { <>2__current =

5__2.m_playerName; <>1__state = 1; return true; } goto IL_00aa; } <>m__Finally1(); <>s__1 = default(List.Enumerator); return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; ((IDisposable)<>s__1).Dispose(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; return this; } return new d__46(0); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } private const string RpcName = "VSG_SyncConfig"; private const string RpcTriggerGlobal = "VSG_TriggerGlobal"; private const string RpcPlayGlobal = "VSG_PlayGlobal"; private const string RpcAnnounce = "VSG_AnnounceRequest"; private const string RpcAdminResetGlobal = "VSG_AdminResetGlobal"; private const string RpcTimedGuidance = "VSG_TimedGuidance"; private const string RpcChainStepUpdate = "VSG_ChainStepUpdate"; private const string RpcChainStateRequest = "VSG_ChainStateRequest"; private const string RpcChainStatePush = "VSG_ChainStatePush"; private const string RpcCompleteAnnounce = "VSG_CompleteAnnounce"; private const string RpcAdminPlayerListReq = "VSG_APListReq"; private const string RpcAdminPlayerListFwd = "VSG_APListFwd"; private const string RpcAdminPlayerListResp = "VSG_APListResp"; private const string RpcAdminPlayerListOut = "VSG_APListOut"; private const string RpcAdminPlayerResetReq = "VSG_APResetReq"; private const string RpcAdminPlayerResetFwd = "VSG_APResetFwd"; private const string RpcAdminPlayerResetAck = "VSG_APResetAck"; private const string RpcAdminPlayerResetOut = "VSG_APResetOut"; private static bool _registered; private static bool _rpcsBound; private static readonly Dictionary> _playerChainData = new Dictionary>(); public static void Register() { if (!_registered) { _registered = true; } } private static void EnsureRegistered() { if (!_rpcsBound && ZRoutedRpc.instance != null) { ZRoutedRpc.instance.Register("VSG_SyncConfig", (Action)OnReceive); ZRoutedRpc.instance.Register("VSG_TriggerGlobal", (Action)OnTriggerGlobal); ZRoutedRpc.instance.Register("VSG_PlayGlobal", (Action)OnPlayGlobal); ZRoutedRpc.instance.Register("VSG_AnnounceRequest", (Action)OnAnnounceRequest); ZRoutedRpc.instance.Register("VSG_AdminResetGlobal", (Action)OnAdminResetGlobal); ZRoutedRpc.instance.Register("VSG_TimedGuidance", (Action)OnTimedGuidance); ZRoutedRpc.instance.Register("VSG_ChainStepUpdate", (Action)OnChainStepUpdate); ZRoutedRpc.instance.Register("VSG_ChainStateRequest", (Action)OnChainStateRequest); ZRoutedRpc.instance.Register("VSG_ChainStatePush", (Action)OnChainStatePush); ZRoutedRpc.instance.Register("VSG_CompleteAnnounce", (Action)OnCompleteAnnounce); ZRoutedRpc.instance.Register("VSG_APListReq", (Action)OnAdminPlayerListReq); ZRoutedRpc.instance.Register("VSG_APListFwd", (Action)OnAdminPlayerListFwd); ZRoutedRpc.instance.Register("VSG_APListResp", (Action)OnAdminPlayerListResp); ZRoutedRpc.instance.Register("VSG_APListOut", (Action)OnAdminPlayerListOut); ZRoutedRpc.instance.Register("VSG_APResetReq", (Action)OnAdminPlayerResetReq); ZRoutedRpc.instance.Register("VSG_APResetFwd", (Action)OnAdminPlayerResetFwd); ZRoutedRpc.instance.Register("VSG_APResetAck", (Action)OnAdminPlayerResetAck); ZRoutedRpc.instance.Register("VSG_APResetOut", (Action)OnAdminPlayerResetOut); _rpcsBound = true; Plugin.Log.LogInfo((object)"RPCs registered with ZRoutedRpc."); } } private static void OnReceive(long sender, ZPackage pkg) { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { return; } string yaml = pkg.ReadString(); try { GuidanceConfig guidanceConfig2 = (Plugin.CurrentConfig = Deserialize(yaml)); GuidanceDisplay.RegisterTutorials(guidanceConfig2); Plugin.Log.LogInfo((object)$"Received guidance config from server: {guidanceConfig2.Guidances.Count} entries."); } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to apply synced config: " + ex.Message)); } } public static void BroadcastToClients(GuidanceConfig config) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown if (ZRoutedRpc.instance != null) { ZPackage val = new ZPackage(); val.Write(Serialize(config)); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "VSG_SyncConfig", new object[1] { val }); } } public static void SendToPeer(long peerId, GuidanceConfig config) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown if (ZRoutedRpc.instance != null) { ZPackage val = new ZPackage(); val.Write(Serialize(config)); ZRoutedRpc.instance.InvokeRoutedRPC(peerId, "VSG_SyncConfig", new object[1] { val }); } } public static void SendTriggerGlobal(string entryId, string playerName) { if (ZRoutedRpc.instance != null) { long serverPeerID = ZRoutedRpc.instance.GetServerPeerID(); ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerID, "VSG_TriggerGlobal", new object[2] { entryId, playerName ?? "" }); } } private static void OnTriggerGlobal(long sender, string entryId, string playerName) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } GuidanceEntry guidanceEntry = Plugin.CurrentConfig?.Guidances?.Find((GuidanceEntry g) => g.Id == entryId); if (guidanceEntry == null) { Plugin.Log.LogWarning((object)$"[global] server got trigger for unknown id '{entryId}' from {sender}."); return; } if (!SeenTracker.IsGlobalScope(guidanceEntry.Scope)) { Plugin.Log.LogWarning((object)("[global] server got trigger for non-global id '" + entryId + "'; ignoring.")); return; } if (guidanceEntry.Once && SeenTracker.HasFired(null, guidanceEntry.Id, guidanceEntry.Scope)) { Plugin.Log.LogInfo((object)("[global] '" + entryId + "' already fired world-wide; ignoring duplicate trigger from " + playerName + ".")); return; } SeenTracker.MarkFired(null, guidanceEntry.Id, guidanceEntry.Scope); Plugin.Log.LogInfo((object)("[global] '" + entryId + "' marked & broadcasting (triggered by " + playerName + ").")); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "VSG_PlayGlobal", new object[2] { entryId, playerName ?? "" }); if (guidanceEntry.Announce?.Discord != null) { DiscordAnnouncer.Announce(guidanceEntry, playerName); } } private static void OnPlayGlobal(long sender, string entryId, string playerName) { if (!((Object)(object)Player.m_localPlayer == (Object)null)) { GuidanceDispatcher.PlayGlobalReceived(entryId, playerName); } } public static void SendAnnounceRequest(string entryId, string playerName) { if (ZRoutedRpc.instance != null) { long serverPeerID = ZRoutedRpc.instance.GetServerPeerID(); ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerID, "VSG_AnnounceRequest", new object[2] { entryId, playerName ?? "" }); } } private static void OnAnnounceRequest(long sender, string entryId, string playerName) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { GuidanceEntry guidanceEntry = Plugin.CurrentConfig?.Guidances?.Find((GuidanceEntry g) => g.Id == entryId); if (guidanceEntry?.Announce?.Discord != null) { DiscordAnnouncer.Announce(guidanceEntry, playerName); } } } public static void SendCompleteAnnounce(string entryId, string playerName) { if (ZRoutedRpc.instance != null) { long serverPeerID = ZRoutedRpc.instance.GetServerPeerID(); ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerID, "VSG_CompleteAnnounce", new object[2] { entryId ?? "", playerName ?? "" }); } } private static void OnCompleteAnnounce(long sender, string entryId, string playerName) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { GuidanceEntry guidanceEntry = Plugin.CurrentConfig?.Guidances?.Find((GuidanceEntry g) => g.Id == entryId); if (guidanceEntry != null && guidanceEntry.DiscordOnComplete) { DiscordAnnouncer.AnnounceChainComplete(playerName, guidanceEntry.Title ?? entryId); } } } public static void BroadcastTimedGuidance(string entryId) { if (ZRoutedRpc.instance != null) { Plugin.Log.LogInfo((object)("[timed] server broadcasting '" + entryId + "' to all clients.")); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "VSG_TimedGuidance", new object[1] { entryId }); } } private static void OnTimedGuidance(long sender, string entryId) { if (!((Object)(object)Player.m_localPlayer == (Object)null)) { GuidanceEntry guidanceEntry = Plugin.CurrentConfig?.Guidances?.Find((GuidanceEntry g) => g.Id == entryId); if (guidanceEntry?.Trigger != null) { string subject = guidanceEntry.Trigger.Id ?? entryId; GuidanceDispatcher.Raise(new TriggerEvent { Type = "timed", Subject = subject }); } } } public static void SendChainStepUpdate(string playerName, string entryId, string value) { if (ZRoutedRpc.instance != null) { long serverPeerID = ZRoutedRpc.instance.GetServerPeerID(); ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerID, "VSG_ChainStepUpdate", new object[3] { playerName ?? "", entryId ?? "", value ?? "" }); } } private static void OnChainStepUpdate(long sender, string playerName, string entryId, string value) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && !string.IsNullOrEmpty(playerName) && !string.IsNullOrEmpty(entryId)) { if (!_playerChainData.TryGetValue(playerName, out var value2)) { value2 = (_playerChainData[playerName] = new Dictionary()); } value2[entryId] = value; Plugin.Log.LogInfo((object)("[chain-sync] stored '" + entryId + "'='" + value + "' for player '" + playerName + "'.")); } } public static void RequestChainState(string playerName) { if (ZRoutedRpc.instance != null) { long serverPeerID = ZRoutedRpc.instance.GetServerPeerID(); ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerID, "VSG_ChainStateRequest", new object[1] { playerName ?? "" }); } } private static void OnChainStateRequest(long sender, string playerName) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || !_playerChainData.TryGetValue(playerName, out var value) || value.Count == 0) { return; } List list = new List(value.Count); foreach (KeyValuePair item in value) { list.Add(item.Key + "=" + item.Value); } string text = string.Join("|", list); ZRoutedRpc.instance.InvokeRoutedRPC(sender, "VSG_ChainStatePush", new object[2] { playerName, text }); } private static void OnChainStatePush(long sender, string playerName, string encoded) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || localPlayer.GetPlayerName() != playerName || string.IsNullOrEmpty(encoded)) { return; } string[] array = encoded.Split(new char[1] { '|' }); foreach (string text in array) { int num = text.IndexOf('='); if (num < 0) { continue; } string text2 = text.Substring(0, num); string text3 = text.Substring(num + 1); int num2 = text2.IndexOf(':'); if (num2 >= 0) { string text4 = text2.Substring(0, num2); string s = text2.Substring(num2 + 1); if (int.TryParse(s, out var result) && int.TryParse(text3, out var result2)) { int counter = ChainState.GetCounter(localPlayer, text4, result); if (result2 > counter) { ChainState.SetCounter(localPlayer, text4, result, result2); Plugin.Log.LogInfo((object)$"[chain-sync] server pushed counter {result2} for '{text4}' step {result} (was {counter})."); } } continue; } string text5 = text2; int result3; if (text3 == "done") { if (!ChainState.IsComplete(localPlayer, text5)) { ChainState.MarkComplete(localPlayer, text5); Plugin.Log.LogInfo((object)("[chain-sync] server pushed complete for '" + text5 + "'.")); } } else if (int.TryParse(text3, out result3)) { int step = ChainState.GetStep(localPlayer, text5); if (result3 > step) { ChainState.SetStep(localPlayer, text5, result3); Plugin.Log.LogInfo((object)$"[chain-sync] server pushed step {result3} for '{text5}' (was {step})."); } } } GuidanceHudTracker.Instance?.Refresh(); } public static void SendAdminResetGlobal(string entryId) { if (ZRoutedRpc.instance != null) { long serverPeerID = ZRoutedRpc.instance.GetServerPeerID(); ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerID, "VSG_AdminResetGlobal", new object[1] { entryId }); } } private static void OnAdminResetGlobal(long sender, string entryId) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } ZNetPeer peer = ZNet.instance.GetPeer(sender); object obj; if (peer == null) { obj = null; } else { ISocket socket = peer.m_socket; obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; if (string.IsNullOrEmpty(text) || !ZNet.instance.IsAdmin(text)) { Plugin.Log.LogWarning((object)$"[admin-reset] non-admin sender ({sender}, host='{text}') tried to reset '{entryId}' — denied."); return; } GuidanceEntry guidanceEntry = Plugin.CurrentConfig?.Guidances?.Find((GuidanceEntry g) => g.Id == entryId); if (guidanceEntry == null) { Plugin.Log.LogWarning((object)("[admin-reset] '" + entryId + "' not in current config; ignoring.")); return; } if (!SeenTracker.IsGlobalScope(guidanceEntry.Scope)) { Plugin.Log.LogWarning((object)("[admin-reset] '" + entryId + "' is not global-scope; ignoring.")); return; } bool flag = SeenTracker.ClearFired(null, entryId, "global"); Plugin.Log.LogInfo((object)(flag ? ("[admin-reset] cleared global '" + entryId + "' for admin " + text + ".") : ("[admin-reset] global '" + entryId + "' was not set; nothing to clear (request by " + text + ")."))); } public static bool ListPlayerForLocalAdmin(string targetName) { ZNetPeer val = FindPeerByPlayerName(targetName); if (val == null) { return false; } ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(val.m_uid, "VSG_APListFwd", new object[1] { "server" }); } return true; } public static bool ResetPlayerForLocalAdmin(string targetName, string resetArg) { ZNetPeer val = FindPeerByPlayerName(targetName); if (val == null) { return false; } ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(val.m_uid, "VSG_APResetFwd", new object[2] { "server", resetArg ?? "all" }); } return true; } public static void SendAdminPlayerListReq(string targetName) { if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "VSG_APListReq", new object[1] { targetName ?? "" }); } } public static void SendAdminPlayerResetReq(string targetName, string resetArg) { if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "VSG_APResetReq", new object[2] { targetName ?? "", resetArg ?? "all" }); } } [IteratorStateMachine(typeof(d__46))] public static IEnumerable GetOnlinePeerNames() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__46(-2); } private static void OnAdminPlayerListReq(long sender, string targetName) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } ZNetPeer peer = ZNet.instance.GetPeer(sender); object obj; if (peer == null) { obj = null; } else { ISocket socket = peer.m_socket; obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; if (string.IsNullOrEmpty(text) || !ZNet.instance.IsAdmin(text)) { Plugin.Log.LogWarning((object)$"[admin-plist] non-admin ({sender}) tried to list '{targetName}' — denied."); return; } ZNetPeer val = FindPeerByPlayerName(targetName); if (val == null) { ZRoutedRpc.instance.InvokeRoutedRPC(sender, "VSG_APListOut", new object[1] { "vsg_list_player: '" + targetName + "' is not currently online." }); } else { ZRoutedRpc.instance.InvokeRoutedRPC(val.m_uid, "VSG_APListFwd", new object[1] { sender.ToString() }); } } private static void OnAdminPlayerListFwd(long sender, string adminMarker) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { string text = CollectPlayerStatePayload(localPlayer); if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "VSG_APListResp", new object[2] { adminMarker, text }); } } } private static void OnAdminPlayerListResp(long sender, string adminMarker, string payload) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } long result; if (adminMarker == "server") { string[] array = payload.Split(new char[1] { '\n' }); foreach (string text in array) { Console instance = Console.instance; if (instance != null) { ((Terminal)instance).AddString(text); } } } else if (long.TryParse(adminMarker, out result)) { ZRoutedRpc.instance.InvokeRoutedRPC(result, "VSG_APListOut", new object[1] { payload }); } } private static void OnAdminPlayerListOut(long sender, string payload) { if ((Object)(object)Player.m_localPlayer == (Object)null) { return; } string[] array = payload.Split(new char[1] { '\n' }); foreach (string text in array) { Console instance = Console.instance; if (instance != null) { ((Terminal)instance).AddString(text); } } } private static void OnAdminPlayerResetReq(long sender, string targetName, string resetArg) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } ZNetPeer peer = ZNet.instance.GetPeer(sender); object obj; if (peer == null) { obj = null; } else { ISocket socket = peer.m_socket; obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; if (string.IsNullOrEmpty(text) || !ZNet.instance.IsAdmin(text)) { Plugin.Log.LogWarning((object)$"[admin-preset] non-admin ({sender}) tried to reset '{targetName}' — denied."); return; } ZNetPeer val = FindPeerByPlayerName(targetName); if (val == null) { ZRoutedRpc.instance.InvokeRoutedRPC(sender, "VSG_APResetOut", new object[1] { "vsg_reset_player: '" + targetName + "' is not currently online." }); } else { ZRoutedRpc.instance.InvokeRoutedRPC(val.m_uid, "VSG_APResetFwd", new object[2] { sender.ToString(), resetArg }); } } private static void OnAdminPlayerResetFwd(long sender, string adminMarker, string resetArg) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } string text; if (string.Equals(resetArg, "all", StringComparison.OrdinalIgnoreCase)) { int num = SeenTracker.ClearAllFired(localPlayer); ChainState.ResetAll(localPlayer); SubmitState.ResetAll(localPlayer); GoalStartedState.ResetAll(localPlayer); GuidanceDisplay.ClearAllVsgTutorialSeen(); GuidanceDisplay.ClearRavenState(); GuidanceHudTracker.Instance?.Refresh(); text = $"vsg_reset_player: cleared {num} fired id(s) + all chain/submit/goal state for {localPlayer.GetPlayerName()}."; } else { GuidanceEntry guidanceEntry = Plugin.CurrentConfig?.Guidances?.Find((GuidanceEntry g) => g.Id == resetArg); bool flag = guidanceEntry != null && guidanceEntry.Steps?.Count > 0; if (flag) { ChainState.Reset(localPlayer, resetArg); } GuidanceDisplay.ClearVsgTutorialSeenForEntry(resetArg); GuidanceDisplay.ClearRavenQueueForId(resetArg); bool flag2 = SeenTracker.ClearFired(localPlayer, resetArg); bool flag3 = SubmitState.Get(localPlayer, resetArg) > 0; if (flag3) { SubmitState.Clear(localPlayer, resetArg); } bool flag4 = GoalStartedState.IsStarted(localPlayer, resetArg); if (flag4) { GoalStartedState.Clear(localPlayer, resetArg); } if (flag2 || flag || flag3 || flag4) { GuidanceHudTracker.Instance?.Refresh(); text = "vsg_reset_player: cleared '" + resetArg + "'" + (flag ? " (chain)" : "") + (flag3 ? " (submit)" : "") + (flag4 ? " (goal)" : "") + " for " + localPlayer.GetPlayerName() + "."; } else { text = "vsg_reset_player: '" + resetArg + "' was not set for " + localPlayer.GetPlayerName() + " (nothing cleared)."; } } Plugin.Log.LogInfo((object)("[admin-preset] " + text)); if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "VSG_APResetAck", new object[2] { adminMarker, text }); } } private static void OnAdminPlayerResetAck(long sender, string adminMarker, string resultMsg) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } long result; if (adminMarker == "server") { Console instance = Console.instance; if (instance != null) { ((Terminal)instance).AddString(resultMsg); } } else if (long.TryParse(adminMarker, out result)) { ZRoutedRpc.instance.InvokeRoutedRPC(result, "VSG_APResetOut", new object[1] { resultMsg }); } } private static void OnAdminPlayerResetOut(long sender, string resultMsg) { if (!((Object)(object)Player.m_localPlayer == (Object)null)) { Console instance = Console.instance; if (instance != null) { ((Terminal)instance).AddString(resultMsg); } } } private static string CollectPlayerStatePayload(Player player) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("=== ValheimServerGuide (" + player.GetPlayerName() + ") ==="); List list = (from s in SeenTracker.GetFiredIds(player) orderby s select s).ToList(); stringBuilder.AppendLine($"Fired ({list.Count}):"); if (list.Count == 0) { stringBuilder.AppendLine(" (none)"); } else { foreach (string item in list) { stringBuilder.AppendLine(" - " + item); } } List list2 = (from k in player.m_customData.Keys where k.StartsWith("VSG.fc.") orderby k select k).ToList(); if (list2.Count > 0) { stringBuilder.AppendLine($"Fire counts ({list2.Count}):"); foreach (string item2 in list2) { stringBuilder.AppendLine(" - " + item2.Substring("VSG.fc.".Length) + " = " + player.m_customData[item2]); } } List list3 = (from k in player.m_customData.Keys where k.StartsWith("VSG.cd.") orderby k select k).ToList(); List list4 = (from k in player.m_customData.Keys where k.StartsWith("VSG.cp.") orderby k select k).ToList(); if (list3.Count > 0 || list4.Count > 0) { stringBuilder.AppendLine("Chain state:"); foreach (string item3 in list3) { stringBuilder.AppendLine(" - " + item3.Substring("VSG.cd.".Length) + ": complete"); } foreach (string item4 in list4) { stringBuilder.AppendLine(" - " + item4.Substring("VSG.cp.".Length) + ": step " + player.m_customData[item4]); } } List list5 = (from k in player.m_customData.Keys where k.StartsWith("VSG.is.") orderby k select k).ToList(); if (list5.Count > 0) { stringBuilder.AppendLine("Submit state:"); foreach (string item5 in list5) { stringBuilder.AppendLine(" - " + item5.Substring("VSG.is.".Length) + ": " + player.m_customData[item5] + " submitted"); } } List list6 = (from k in player.m_customData.Keys where k.StartsWith("VSG.ig.") orderby k select k).ToList(); if (list6.Count > 0) { stringBuilder.AppendLine("Goal state:"); foreach (string item6 in list6) { stringBuilder.AppendLine(" - " + item6.Substring("VSG.ig.".Length) + ": started"); } } return stringBuilder.ToString().TrimEnd('\n', '\r'); } private static ZNetPeer FindPeerByPlayerName(string name) { if ((Object)(object)ZNet.instance == (Object)null || string.IsNullOrEmpty(name)) { return null; } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (string.Equals(peer.m_playerName, name, StringComparison.OrdinalIgnoreCase)) { return peer; } } return null; } private static string Serialize(GuidanceConfig config) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown ISerializer val = ((BuilderSkeleton)new SerializerBuilder()).WithNamingConvention(UnderscoredNamingConvention.Instance).Build(); using StringWriter stringWriter = new StringWriter(); val.Serialize((TextWriter)stringWriter, (object)config); return stringWriter.ToString(); } private static GuidanceConfig Deserialize(string yaml) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown IDeserializer val = ((BuilderSkeleton)new DeserializerBuilder()).WithNamingConvention(UnderscoredNamingConvention.Instance).IgnoreUnmatchedProperties().Build(); return val.Deserialize(yaml) ?? GuidanceConfig.Empty; } } } namespace ValheimServerGuide.Display { public class GuidanceCodex : MonoBehaviour { private GameObject _uiRoot; private GameObject _panel; private Transform _leftContent; private readonly List<(GuidanceEntry entry, GameObject row, TMP_Text label)> _guideRows = new List<(GuidanceEntry, GameObject, TMP_Text)>(); private GuidanceEntry _selected; private TMP_Text _titleText; private TMP_Text _badgeText; private TMP_Text _bodyText; private RectTransform _bodyContentRect; private Transform _upcomingContent; private TMP_FontAsset _font; private static readonly Color ColGold = new Color(1f, 0.82f, 0.42f); private static readonly Color ColText = new Color(0.9f, 0.88f, 0.82f); private static readonly Color ColLocked = new Color(0.5f, 0.5f, 0.5f); private static readonly Color ColGreen = new Color(0.6f, 0.95f, 0.6f); private static readonly Color ColBg = new Color(0.06f, 0.05f, 0.04f, 0.96f); private static readonly Color ColPanelBg = new Color(0.1f, 0.08f, 0.06f, 0.92f); private static readonly Color ColHdrBar = new Color(0.12f, 0.1f, 0.08f, 1f); private static readonly Color ColDivider = new Color(0.3f, 0.25f, 0.2f, 1f); private static readonly Color ColRowSel = new Color(0.22f, 0.17f, 0.1f, 0.9f); public static GuidanceCodex Instance { get; internal set; } public bool IsOpen { get; private set; } public void BuildPanel() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Expected O, but got Unknown //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Expected O, but got Unknown //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Expected O, but got Unknown //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) _uiRoot = new GameObject("VSG_CodexRoot"); Canvas val = _uiRoot.AddComponent(); val.renderMode = (RenderMode)0; val.sortingOrder = 1100; object obj; if (!((Object)(object)Hud.instance != (Object)null)) { obj = null; } else { Canvas componentInParent = ((Component)Hud.instance).GetComponentInParent(); obj = ((componentInParent != null) ? ((Component)componentInParent).GetComponent() : null); } CanvasScaler val2 = (CanvasScaler)obj; CanvasScaler val3 = _uiRoot.AddComponent(); if ((Object)(object)val2 != (Object)null) { val3.uiScaleMode = val2.uiScaleMode; val3.referenceResolution = val2.referenceResolution; val3.screenMatchMode = val2.screenMatchMode; val3.matchWidthOrHeight = val2.matchWidthOrHeight; } _uiRoot.AddComponent(); _uiRoot.SetActive(false); GameObject val4 = new GameObject("VSG_CodexBackdrop"); val4.transform.SetParent(_uiRoot.transform, false); RectTransform val5 = val4.AddComponent(); val5.anchorMin = Vector2.zero; val5.anchorMax = Vector2.one; val5.offsetMin = Vector2.zero; val5.offsetMax = Vector2.zero; Image val6 = val4.AddComponent(); ((Graphic)val6).color = new Color(0f, 0f, 0f, 0.6f); Button val7 = val4.AddComponent