Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ValheimQuest v1.1.0
Valheimquest.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Logging; using HarmonyLib; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("Valheimquest")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Valheimquest")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("25bf4d3e-bb53-4126-8370-18af0311e6c7")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.0.0")] namespace ValheimQuest; [BepInPlugin("com.demonforge.valheimquest", "Valheim Quest", "1.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class ValheimQuest : BaseUnityPlugin { public const string PluginGUID = "com.demonforge.valheimquest"; public const string PluginName = "Valheim Quest"; public const string PluginVersion = "1.1.0"; public static ManualLogSource Log; private readonly Harmony harmony = new Harmony("com.demonforge.valheimquest"); private void Awake() { Log = ((BaseUnityPlugin)this).Logger; Log.LogInfo((object)"Valheim Quest 1.1.0 loading..."); try { harmony.PatchAll(); } catch (Exception arg) { Log.LogError((object)$"Harmony patching failed: {arg}"); } PrefabManager.OnVanillaPrefabsAvailable += RegisterAll; PrefabManager.OnPrefabsRegistered += ValidatePrefabNames; } private void RegisterAll() { Log.LogInfo((object)"Registering quest objects..."); QuestGiver.Register(); PrefabManager.OnVanillaPrefabsAvailable -= RegisterAll; } private void ValidatePrefabNames() { PrefabManager.OnPrefabsRegistered -= ValidatePrefabNames; List<string> list = new List<string>(); foreach (string enemyPrefabName in QuestGiverInteract.GetEnemyPrefabNames()) { if (!PrefabExists(enemyPrefabName)) { list.Add("enemy: " + enemyPrefabName); } } foreach (string rewardPrefabName in QuestGiverInteract.GetRewardPrefabNames()) { if (!PrefabExists(rewardPrefabName)) { list.Add("reward: " + rewardPrefabName); } } if (list.Count == 0) { Log.LogInfo((object)"Prefab check passed - all quest targets and rewards resolved."); return; } Log.LogWarning((object)$"Prefab check found {list.Count} unresolved name(s):"); foreach (string item in list) { Log.LogWarning((object)(" " + item)); } } private static bool PrefabExists(string name) { if (PrefabManager.Instance != null && (Object)(object)PrefabManager.Instance.GetPrefab(name) != (Object)null) { return true; } if ((Object)(object)ZNetScene.instance != (Object)null && (Object)(object)ZNetScene.instance.GetPrefab(name) != (Object)null) { return true; } return false; } } public class QuestGiver { private const string BasePrefab = "darkwood_raven"; public static void Register() { //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Expected O, but got Unknown //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Expected O, but got Unknown //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Expected O, but got Unknown //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Expected O, but got Unknown GameObject val = PrefabManager.Instance.CreateClonedPrefab("QuestGiverRaven", "darkwood_raven"); if ((Object)(object)val == (Object)null) { ManualLogSource log = ValheimQuest.Log; if (log != null) { log.LogError((object)"Failed to clone 'darkwood_raven' - the Raven Oracle will not be available. The base prefab may have been renamed or removed."); } return; } Interactable[] componentsInChildren = val.GetComponentsInChildren<Interactable>(true); foreach (Interactable val2 in componentsInChildren) { Object.DestroyImmediate((Object)(object)((val2 is Object) ? val2 : null), true); } Hoverable[] componentsInChildren2 = val.GetComponentsInChildren<Hoverable>(true); foreach (Hoverable val3 in componentsInChildren2) { Object.DestroyImmediate((Object)(object)((val3 is Object) ? val3 : null), true); } QuestGiverInteract questGiverInteract = val.AddComponent<QuestGiverInteract>(); if ((Object)(object)questGiverInteract == (Object)null) { ManualLogSource log2 = ValheimQuest.Log; if (log2 != null) { log2.LogError((object)"Failed to add QuestGiverInteract component! This usually means an interface member is missing."); } } PieceConfig val4 = new PieceConfig(); val4.Name = "Raven Oracle"; val4.Description = "Place your own Oracle to begin your personal quest chain. Each player must place their own."; val4.PieceTable = "Hammer"; val4.AddRequirement(new RequirementConfig("Wood", 50, 0, true)); val4.AddRequirement(new RequirementConfig("Stone", 20, 0, true)); PieceManager.Instance.AddPiece(new CustomPiece(val, false, val4)); ManualLogSource log3 = ValheimQuest.Log; if (log3 != null) { log3.LogInfo((object)("Raven Oracle registered. " + $"Interact component present: {(Object)(object)val.GetComponent<QuestGiverInteract>() != (Object)null}")); } } } public class QuestGiverInteract : MonoBehaviour, Interactable, Hoverable { private struct QuestData { public string enemy; public int count; public string startMessage; public string progressMessage; public string completeMessage; public (string item, int amount)[] rewards; } private const int StateNoQuest = 0; private static int state = 0; private static int currentQuest = 0; private static readonly QuestData[] Quests = new QuestData[18] { new QuestData { enemy = "Boar", count = 5, startMessage = "Slay 5 boars that plague these lands.", progressMessage = "boars", completeMessage = "The boars are dealt with. Take these supplies!", rewards = new(string, int)[3] { ("CookedMeat", 10), ("Bread", 5), ("Coins", 20) } }, new QuestData { enemy = "Greydwarf", count = 10, startMessage = "Now slay 10 Greydwarfs lurking in the forest.", progressMessage = "Greydwarfs", completeMessage = "The forest is safer. Here are your Surtling Cores!", rewards = new(string, int)[1] { ("SurtlingCore", 5) } }, new QuestData { enemy = "Troll", count = 2, startMessage = "Prove your strength. Slay 2 Trolls.", progressMessage = "Trolls", completeMessage = "Impressive! Take this bronze and gold.", rewards = new(string, int)[2] { ("Bronze", 10), ("Coins", 200) } }, new QuestData { enemy = "Draugr", count = 10, startMessage = "The dead walk. Slay 10 Draugr.", progressMessage = "Draugr", completeMessage = "The dead rest now. Take your reward.", rewards = new(string, int)[2] { ("SurtlingCore", 5), ("Coins", 100) } }, new QuestData { enemy = "Blob", count = 3, startMessage = "Slay 3 Slimes from the swamp.", progressMessage = "Slimes", completeMessage = "The swamp is cleaner. Here is your Guck.", rewards = new(string, int)[2] { ("Guck", 10), ("Coins", 60) } }, new QuestData { enemy = "Wraith", count = 3, startMessage = "Face the darkness. Slay 3 Wraiths.", progressMessage = "Wraiths", completeMessage = "The spirits are vanquished. Take these chains.", rewards = new(string, int)[2] { ("Chain", 5), ("Coins", 150) } }, new QuestData { enemy = "Wolf", count = 10, startMessage = "The mountains are dangerous. Slay 10 Wolves.", progressMessage = "Wolves", completeMessage = "The mountains are safer. Take this iron.", rewards = new(string, int)[2] { ("Iron", 20), ("Coins", 100) } }, new QuestData { enemy = "Hatchling", count = 5, startMessage = "Slay 5 Drakes from the mountain peaks.", progressMessage = "Drakes", completeMessage = "The skies are clear. Take this trophy and gold.", rewards = new(string, int)[2] { ("TrophyHatchling", 1), ("Coins", 50) } }, new QuestData { enemy = "Fenring", count = 1, startMessage = "Face the beast of the night. Slay 1 Fenring.", progressMessage = "Fenrings", completeMessage = "You are truly brave. Take this crystal and gold.", rewards = new(string, int)[2] { ("Coins", 100), ("Crystal", 20) } }, new QuestData { enemy = "Goblin", count = 20, startMessage = "The plains are overrun. Slay 20 Fulings.", progressMessage = "Fulings", completeMessage = "The plains breathe again. Take this silver and gold.", rewards = new(string, int)[2] { ("Silver", 20), ("Coins", 200) } }, new QuestData { enemy = "Deathsquito", count = 10, startMessage = "Slay 10 Deathsquitos from the plains.", progressMessage = "Deathsquitos", completeMessage = "No more buzzing. Take this Lox pelt and gold.", rewards = new(string, int)[2] { ("LoxPelt", 10), ("Coins", 50) } }, new QuestData { enemy = "Lox", count = 5, startMessage = "Slay 5 Lox from the plains.", progressMessage = "Lox", completeMessage = "Mighty warrior! Take this gold.", rewards = new(string, int)[1] { ("Coins", 300) } }, new QuestData { enemy = "Tick", count = 10, startMessage = "Slay 10 Ticks from the Mistlands.", progressMessage = "Ticks", completeMessage = "The Mistlands are safer. Take these Black Cores.", rewards = new(string, int)[2] { ("Coins", 100), ("BlackCore", 3) } }, new QuestData { enemy = "Seeker", count = 10, startMessage = "Slay 10 Seekers in the Mistlands.", progressMessage = "Seekers", completeMessage = "The seekers fall. Take these Magecaps and gold.", rewards = new(string, int)[2] { ("Coins", 200), ("Magecap", 10) } }, new QuestData { enemy = "Gjall", count = 1, startMessage = "Face the great Gjall. Slay 1 of them.", progressMessage = "Gjalls", completeMessage = "Legendary! Take this gold and sap.", rewards = new(string, int)[2] { ("Coins", 200), ("Sap", 20) } }, new QuestData { enemy = "Charred_Melee", count = 10, startMessage = "Slay 10 Charred Warriors in the Ashlands.", progressMessage = "Charred Warriors", completeMessage = "The ash settles. Take your reward.", rewards = new(string, int)[2] { ("Coins", 200), ("BlackCore", 3) } }, new QuestData { enemy = "Charred_Twitcher", count = 10, startMessage = "Slay 10 Charred Twitchers.", progressMessage = "Charred Twitchers", completeMessage = "They twitch no more. Take your reward.", rewards = new(string, int)[2] { ("Coins", 300), ("BlackCore", 3) } }, new QuestData { enemy = "Charred_Archer", count = 10, startMessage = "Slay 10 Charred Marksmen.", progressMessage = "Charred Marksmen", completeMessage = "Their arrows fly no more. Take your final reward!", rewards = new(string, int)[2] { ("Coins", 400), ("MoltenCore", 5) } } }; private static int StateAllComplete => Quests.Length * 2 + 1; public static IEnumerable<string> GetEnemyPrefabNames() { QuestData[] quests = Quests; for (int i = 0; i < quests.Length; i++) { QuestData quest = quests[i]; yield return quest.enemy; } } public static IEnumerable<string> GetRewardPrefabNames() { HashSet<string> seen = new HashSet<string>(); QuestData[] quests = Quests; for (int i = 0; i < quests.Length; i++) { QuestData quest = quests[i]; if (quest.rewards == null) { continue; } (string item, int amount)[] rewards = quest.rewards; for (int j = 0; j < rewards.Length; j++) { (string item, int amount) reward = rewards[j]; if (seen.Add(reward.item)) { yield return reward.item; } } } } public static void LoadState(int savedState, int savedQuest) { state = savedState; currentQuest = Mathf.Clamp(savedQuest, 0, Quests.Length - 1); if (IsActiveState()) { QuestData questData = Quests[currentQuest]; QuestTracker.StartTracking(questData.enemy, questData.count); } } public static void ResetState() { state = 0; currentQuest = 0; ManualLogSource log = ValheimQuest.Log; if (log != null) { log.LogInfo((object)"Quest state reset (no saved progress for this character)."); } } public static int GetStateAsInt() { return state; } public static int GetCurrentQuest() { return currentQuest; } private static bool IsActiveState() { if (state == 0 || state == StateAllComplete) { return false; } return state % 2 == 1; } private static bool IsCompleteState() { if (state == 0 || state == StateAllComplete) { return false; } return state % 2 == 0; } private static void SetActiveState() { state = currentQuest * 2 + 1; } private static void SetCompleteState() { state = currentQuest * 2 + 2; } public bool Interact(Humanoid user, bool hold, bool alt) { if (hold) { return false; } Player val = (Player)(object)((user is Player) ? user : null); if ((Object)(object)val == (Object)null) { return false; } if (state == StateAllComplete) { Message("Raven Oracle: You have completed all my quests. You are a legend!"); return true; } if (state == 0 || IsCompleteState()) { if (IsCompleteState()) { if (currentQuest >= Quests.Length - 1) { state = StateAllComplete; Message("Raven Oracle: You have completed all my quests. You are a legend!"); QuestTracker.StopTracking(); QuestSaveData.Save(GetStateAsInt(), QuestTracker.GetKillCount(), currentQuest); return true; } currentQuest++; } QuestData questData = Quests[currentQuest]; Message("Raven Oracle: " + questData.startMessage); QuestTracker.StartTracking(questData.enemy, questData.count); SetActiveState(); QuestSaveData.Save(GetStateAsInt(), 0, currentQuest); } else if (IsActiveState()) { QuestData quest = Quests[currentQuest]; int killCount = QuestTracker.GetKillCount(); if (killCount < quest.count) { Message($"Raven Oracle: You have slain {killCount}/{quest.count} {quest.progressMessage}. Keep going!"); return true; } if (!HasRoomForReward(val, quest)) { Message("Raven Oracle: You have no room to carry your reward. Return when your pack is lighter."); return true; } Message("Raven Oracle: " + quest.completeMessage); GiveReward(val, quest); SetCompleteState(); QuestSaveData.Save(GetStateAsInt(), killCount, currentQuest); } return true; } public bool UseItem(Humanoid user, ItemData item) { return false; } public string GetHoverText() { if (state == StateAllComplete) { return "Raven Oracle\n[E] Speak"; } if (IsActiveState()) { QuestData questData = Quests[currentQuest]; return $"Raven Oracle\n[E] Check Progress ({QuestTracker.GetKillCount()}/{questData.count} {questData.progressMessage})"; } return "Raven Oracle\n[E] Speak"; } public string GetHoverName() { return "Raven Oracle"; } public float GetHoverOffset() { return 0f; } private static bool HasRoomForReward(Player player, QuestData quest) { if (quest.rewards == null) { return true; } (string, int)[] rewards = quest.rewards; for (int i = 0; i < rewards.Length; i++) { (string, int) tuple = rewards[i]; GameObject val = ResolvePrefab(tuple.Item1); if ((Object)(object)val == (Object)null) { ManualLogSource log = ValheimQuest.Log; if (log != null) { log.LogError((object)("Reward prefab '" + tuple.Item1 + "' could not be resolved.")); } } else if (!((Humanoid)player).GetInventory().CanAddItem(val, tuple.Item2)) { return false; } } return true; } private static void GiveReward(Player player, QuestData quest) { if (quest.rewards == null) { return; } (string, int)[] rewards = quest.rewards; for (int i = 0; i < rewards.Length; i++) { (string, int) tuple = rewards[i]; if (!TryGiveItem(player, tuple.Item1, tuple.Item2)) { ManualLogSource log = ValheimQuest.Log; if (log != null) { log.LogWarning((object)$"Failed to grant reward {tuple.Item1} x{tuple.Item2}"); } } } } private static bool TryGiveItem(Player player, string prefabName, int amount) { GameObject val = ResolvePrefab(prefabName); if ((Object)(object)val == (Object)null) { return false; } ItemDrop component = val.GetComponent<ItemDrop>(); if ((Object)(object)component == (Object)null) { return false; } Inventory inventory = ((Humanoid)player).GetInventory(); int num = Mathf.Max(1, component.m_itemData.m_shared.m_maxStackSize); int num2 = amount; while (num2 > 0) { int num3 = Mathf.Min(num2, num); ItemData val2 = component.m_itemData.Clone(); val2.m_stack = num3; val2.m_dropPrefab = val; if (!inventory.AddItem(val2)) { return num2 < amount; } num2 -= num3; } return true; } private static GameObject ResolvePrefab(string prefabName) { GameObject val = null; if (PrefabManager.Instance != null) { val = PrefabManager.Instance.GetPrefab(prefabName); } if ((Object)(object)val == (Object)null && (Object)(object)ZNetScene.instance != (Object)null) { val = ZNetScene.instance.GetPrefab(prefabName); } return val; } private static void Message(string text) { if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false, true); } } } public class QuestSaveData { private const string QuestStateKey = "ValheimQuest_State"; private const string KillCountKey = "ValheimQuest_KillCount"; private const string CurrentQuestKey = "ValheimQuest_CurrentQuest"; public static void Save(int questState, int killCount, int currentQuest) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { localPlayer.m_customData["ValheimQuest_State"] = questState.ToString(); localPlayer.m_customData["ValheimQuest_KillCount"] = killCount.ToString(); localPlayer.m_customData["ValheimQuest_CurrentQuest"] = currentQuest.ToString(); ManualLogSource log = ValheimQuest.Log; if (log != null) { log.LogInfo((object)$"Quest progress saved - state {questState}, kills {killCount}, quest {currentQuest}"); } } } public static bool Load(out int questState, out int killCount, out int currentQuest) { questState = 0; killCount = 0; currentQuest = 0; Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || localPlayer.m_customData == null) { return false; } if (!localPlayer.m_customData.ContainsKey("ValheimQuest_State")) { return false; } questState = ReadInt(localPlayer, "ValheimQuest_State"); killCount = ReadInt(localPlayer, "ValheimQuest_KillCount"); currentQuest = ReadInt(localPlayer, "ValheimQuest_CurrentQuest"); ManualLogSource log = ValheimQuest.Log; if (log != null) { log.LogInfo((object)$"Quest progress loaded - state {questState}, kills {killCount}, quest {currentQuest}"); } return true; } private static int ReadInt(Player player, string key) { if (!player.m_customData.TryGetValue(key, out var value)) { return 0; } int result; return int.TryParse(value, out result) ? result : 0; } } [HarmonyPatch(typeof(Player), "OnSpawned")] public class PlayerSpawnPatch { private static bool Prepare() { bool flag = AccessTools.Method(typeof(Player), "OnSpawned", (Type[])null, (Type[])null) != null; if (!flag) { ManualLogSource log = ValheimQuest.Log; if (log != null) { log.LogError((object)"Player.OnSpawned not found - quest progress will not load."); } } return flag; } private static void Postfix(Player __instance) { if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } if (QuestSaveData.Load(out var questState, out var killCount, out var currentQuest)) { QuestTracker.LoadState(killCount); QuestGiverInteract.LoadState(questState, currentQuest); ManualLogSource log = ValheimQuest.Log; if (log != null) { log.LogInfo((object)"Quest state restored."); } } else { QuestTracker.ResetState(); QuestGiverInteract.ResetState(); } } } public class QuestTracker { private const float KillCreditRange = 60f; private static int killCount = 0; private static bool isTracking = false; private static string targetEnemy = ""; private static int killsRequired = 0; private static HashSet<int> countedKills = new HashSet<int>(); public static void StartTracking(string enemyName, int required) { killCount = 0; isTracking = true; targetEnemy = enemyName; killsRequired = required; countedKills.Clear(); ManualLogSource log = ValheimQuest.Log; if (log != null) { log.LogInfo((object)$"Quest started - tracking {enemyName} kills (need {required})."); } } public static void LoadState(int savedKillCount) { killCount = savedKillCount; isTracking = true; countedKills.Clear(); ManualLogSource log = ValheimQuest.Log; if (log != null) { log.LogInfo((object)$"Kill count loaded: {killCount}"); } } public static void ResetState() { killCount = 0; isTracking = false; targetEnemy = ""; killsRequired = 0; countedKills.Clear(); } public static void StopTracking() { isTracking = false; } public static int GetKillCount() { return killCount; } public static string GetTargetEnemy() { return targetEnemy; } public static bool IsTracking() { return isTracking; } public static void AddKill(int instanceId, string enemyName) { if (isTracking && !string.IsNullOrEmpty(targetEnemy) && enemyName.Contains(targetEnemy) && countedKills.Add(instanceId)) { killCount++; ManualLogSource log = ValheimQuest.Log; if (log != null) { log.LogInfo((object)$"{targetEnemy} killed - total {killCount}/{killsRequired}"); } if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)1, $"{targetEnemy}s slain: {killCount}/{killsRequired}", 0, (Sprite)null, false, true); } QuestSaveData.Save(QuestGiverInteract.GetStateAsInt(), killCount, QuestGiverInteract.GetCurrentQuest()); } } } [HarmonyPatch(typeof(CharacterDrop), "GenerateDropList")] public class EnemyDeathPatch { private static bool Prepare() { bool flag = AccessTools.Method(typeof(CharacterDrop), "GenerateDropList", (Type[])null, (Type[])null) != null; if (!flag) { ManualLogSource log = ValheimQuest.Log; if (log != null) { log.LogError((object)"CharacterDrop.GenerateDropList not found - kills will not be counted."); } } return flag; } private static void Postfix(CharacterDrop __instance) { //IL_0033: 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) if (!((Object)(object)Player.m_localPlayer == (Object)null) && QuestTracker.IsTracking() && !((Object)(object)__instance == (Object)null)) { float num = Vector3.Distance(((Component)__instance).transform.position, ((Component)Player.m_localPlayer).transform.position); if (!(num > 60f)) { QuestTracker.AddKill(((Object)((Component)__instance).gameObject).GetInstanceID(), ((Object)((Component)__instance).gameObject).name); } } } }