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 AllfathersEye v0.23.7
plugins/AllfathersEye.dll
Decompiled 5 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Net.Security; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Security.Permissions; using System.Text; using System.Threading; using AllfathersEye.Feed; using AllfathersEye.Naming; using AllfathersEye.Overlay; using AllfathersEye.Patches; using AllfathersEye.Points; using AllfathersEye.Shop; using AllfathersEye.Twitch; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Splatform; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("AllfathersEye")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Twitch chatters take over creatures in Valheim, with an on-screen event feed.")] [assembly: AssemblyFileVersion("0.23.7.0")] [assembly: AssemblyInformationalVersion("0.23.7+b373ed48b5df476739c0495f553d361d6d3501ae")] [assembly: AssemblyProduct("Allfather's Eye")] [assembly: AssemblyTitle("AllfathersEye")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.23.7.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } public sealed class ConfigurationManagerAttributes { public int? Order; public bool? IsAdvanced; public Action<ConfigEntryBase> CustomDrawer; public bool? HideDefaultButton; public bool? HideSettingName; } namespace AllfathersEye { public static class ColorSpec { public static string Sanitize(string raw) { if (string.IsNullOrEmpty(raw)) { return ""; } string text = raw.Trim(); if (text.Length > 0 && text[0] == '#') { text = text.Substring(1); } if (text.Length != 3 && text.Length != 6 && text.Length != 8) { return ""; } StringBuilder stringBuilder = new StringBuilder(text.Length + 1); stringBuilder.Append('#'); foreach (char c in text) { if ((c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F')) { return ""; } stringBuilder.Append(c); } return stringBuilder.ToString(); } public static Dictionary<string, string> ParseMap(string csv) { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrEmpty(csv)) { return dictionary; } string[] array = csv.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0) { continue; } int num = text.LastIndexOf(':'); if (num > 0 && num != text.Length - 1) { string text2 = NormalizeChannel(text.Substring(0, num)); string text3 = Sanitize(text.Substring(num + 1)); if (text2.Length != 0 && text3.Length != 0) { dictionary[text2] = text3; } } } return dictionary; } public static string NormalizeChannel(string channel) { if (string.IsNullOrEmpty(channel)) { return ""; } return channel.Trim().TrimStart(new char[1] { '#' }).Trim() .ToLowerInvariant(); } } internal static class ConsoleCommands { [CompilerGenerated] private static class <>O { public static ConsoleEvent <0>__Execute; } private static readonly List<Character> Scratch = new List<Character>(128); private static bool _registered; public static void Register() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_0072: 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_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown if (!_registered) { _registered = true; object obj = <>O.<0>__Execute; if (obj == null) { ConsoleEvent val = Execute; <>O.<0>__Execute = val; obj = (object)val; } new ConsoleCommand("ae", "Allfather's Eye: status | test [name] | fake <login> [message] | nearby | clear [name|all] | points [<login> [amount]] | shop [verify|<command>] | tier [0-5|off] | feed | fonts | raven [sub|resub|gift|gifts|raid|cheer] | twitch [connect|disconnect] | pause | resume | reconnect", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj2 = <>O.<0>__Execute; if (obj2 == null) { ConsoleEvent val2 = Execute; <>O.<0>__Execute = val2; obj2 = (object)val2; } new ConsoleCommand("allfatherseye", "Allfather's Eye: status | test [name] | fake <login> [message] | nearby | clear [name|all] | points [<login> [amount]] | shop [verify|<command>] | tier [0-5|off] | feed | fonts | raven [sub|resub|gift|gifts|raid|cheer] | twitch [connect|disconnect] | pause | resume | reconnect", (ConsoleEvent)obj2, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } } private static void Execute(ConsoleEventArgs args) { try { string text = Arg(args, 1).ToLowerInvariant(); if (text.Length == 0) { text = "status"; } switch (text) { case "status": Status(args); break; case "test": Test(args); break; case "fake": Fake(args); break; case "nearby": Nearby(args); break; case "clear": Clear(args); break; case "pause": SetPaused(args, paused: true); break; case "resume": SetPaused(args, paused: false); break; case "reconnect": Reconnect(args); break; case "feed": PlayFeedDemo(args); break; case "fonts": ListFonts(args); break; case "sound": TestSound(args); break; case "points": PointsCommand(args); break; case "shop": ShopCommand(args); break; case "actions": ShopCommand(args); break; case "tier": TierCommand(args); break; case "twitch": TwitchAccount(args); break; case "raven": RavenCommand(args); break; case "debug": ToggleDebug(args); break; default: Reply(args, "Unknown subcommand '" + text + "'. Try: status, test, fake, nearby, clear, points, shop, tier, feed, fonts, sound, twitch, debug, pause, resume, reconnect"); break; } } catch (Exception ex) { Reply(args, "ae failed: " + ex.Message); AllfathersEyePlugin.Log.LogError((object)("Console command failed: " + ex)); } } private static void Status(ConsoleEventArgs args) { AllfathersEyePlugin instance = AllfathersEyePlugin.Instance; if ((Object)(object)instance == (Object)null) { Reply(args, "Plugin instance is gone."); return; } NameAssigner assigner = instance.Assigner; TwitchChatClient client = instance.Client; string normalizedChannel = ModConfig.NormalizedChannel; Reply(args, "Allfather's Eye 0.23.7 | enabled: " + ModConfig.Enabled.Value + " | assignment: " + (assigner.Paused ? "PAUSED" : "running") + " | feed: " + (ModConfig.FeedEnabled.Value ? "on" : "off") + "/" + ModConfig.FeedScopeSetting.Value.ToString() + (ModConfig.FeedPreview.Value ? " (PREVIEW)" : "")); if (normalizedChannel.Length == 0) { Reply(args, "Channel: (none) - display only, no Twitch connection."); } else { string text = ((client == null) ? "not started" : client.State.ToString()); string text2 = ((client != null && client.IsAuthenticated) ? "signed in, can write" : "anonymous, read only"); long num = client?.MessagesReceived ?? 0; Reply(args, "Channel: #" + normalizedChannel + " | connection: " + text + " (" + text2 + ") | messages received: " + num); } AllfathersEyePlugin instance2 = AllfathersEyePlugin.Instance; Reply(args, "Twitch account: " + (((Object)(object)instance != (Object)null && instance.Auth != null) ? (instance.Auth.State.ToString() + ((instance.Auth.Login.Length > 0) ? (" (" + instance.Auth.Login + ")") : "")) : "unknown")); Reply(args, "Feed RPC: " + (((Object)(object)instance2 != (Object)null && instance2.Network != null && instance2.Network.IsRegistered) ? "registered (events go to all players)" : "not registered (events stay local)")); Reply(args, "Trigger: " + ModConfig.Trigger.Value.ToString() + " | radius: " + ModConfig.Radius.Value.ToString("0") + "m | names in use nearby: " + NameStore.TrackedCount + " | remembered: " + NameStore.RememberedCount + " | queued: " + assigner.QueueCount); if (assigner.QueueCount > 0) { StringBuilder stringBuilder = new StringBuilder("Waiting: "); bool flag = true; foreach (string queuedName in assigner.QueuedNames) { if (!flag) { stringBuilder.Append(", "); } stringBuilder.Append(queuedName); flag = false; } Reply(args, stringBuilder.ToString()); } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { Reply(args, "No local player - assignment is idle."); return; } CollectInRange(localPlayer); int num2 = 0; for (int i = 0; i < Scratch.Count; i++) { if (NameAssigner.IsEligible(Scratch[i])) { num2++; } } Reply(args, "Creatures in radius: " + Scratch.Count + ", free for a name: " + num2); Scratch.Clear(); } private static void Test(ConsoleEventArgs args) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { Reply(args, "Not in a world."); return; } string text = ((args.Length > 2) ? Arg(args, 2) : "TestViewer"); if (!NameSanitizer.TryClean(text, out var result)) { Reply(args, "'" + text + "' is not a usable name (allowed: letters, digits, underscore)."); return; } Character val = NameStore.FindHolder(result); if ((Object)(object)val != (Object)null) { Reply(args, result + " is already on " + NameAssigner.DescribeCreature(val) + "."); return; } Character val2 = FindNearestEligible(localPlayer); string failureReason; if ((Object)(object)val2 == (Object)null) { Reply(args, "No free creature within " + ModConfig.Radius.Value.ToString("0") + "m. Run 'ae nearby' to see why."); } else if (NameStore.TryAssign(val2, result, ModConfig.NormalizedChannel, out failureReason)) { Reply(args, "Assigned " + result + " to " + NameAssigner.DescribeCreature(val2) + "."); } else { Reply(args, "Could not assign " + result + ": " + failureReason); } } private static void Fake(ConsoleEventArgs args) { if (args.Length < 3) { Reply(args, "Usage: ae fake <login> [message]"); return; } string text = Arg(args, 2).ToLowerInvariant(); string text2 = ""; if (args.Length > 3) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 3; i < args.Length; i++) { if (stringBuilder.Length > 0) { stringBuilder.Append(' '); } stringBuilder.Append(Arg(args, i)); } text2 = stringBuilder.ToString(); } AllfathersEyePlugin instance = AllfathersEyePlugin.Instance; if ((Object)(object)instance == (Object)null) { Reply(args, "Plugin instance is gone."); return; } ChatMessage message = new ChatMessage(text, text, ModConfig.NormalizedChannel, text2, ChatBadges.None); instance.DispatchChatMessage(message); Reply(args, "Injected chat message from " + text + ((text2.Length > 0) ? (": " + text2) : " (empty)") + ". Queued: " + instance.Assigner.QueueCount); } private static void PointsCommand(ConsoleEventArgs args) { AllfathersEyePlugin instance = AllfathersEyePlugin.Instance; PointService pointService = (((Object)(object)instance != (Object)null) ? instance.Points : null); if (pointService == null) { Reply(args, "The points service is not running."); return; } if (pointService.LoadedChannel.Length == 0) { Reply(args, "No channel is loaded, so there is no ledger. Set a channel or sign in under F1."); return; } if (args.Length <= 2) { Reply(args, "Points for " + pointService.LoadedChannel + " | enabled " + ModConfig.PointsEnabled.Value + " | only when live " + ModConfig.OnlyWhenLive.Value + " | " + pointService.ViewerCount + " viewer(s)"); Reply(args, "Earning: " + ModConfig.PresencePoints.Value + " VP / " + ModConfig.PresenceIntervalMinutes.Value + " min present, " + ModConfig.ChatBonusPoints.Value + " VP / " + ModConfig.ChatBonusCooldownMinutes.Value + " min for writing"); if (pointService.NeedsReconnectForLurkers) { Reply(args, "Lurkers cannot be seen: this sign-in predates that permission. Disconnect and connect again under F1 -> 2 - Twitch."); } string lastError = pointService.LastError; if (lastError.Length > 0) { Reply(args, "Last Twitch problem: " + lastError); } ListTopViewers(args, pointService); return; } string text = Arg(args, 2); if (args.Length <= 3) { Reply(args, text + ": " + pointService.Balance(text) + " VP"); return; } if (!int.TryParse(Arg(args, 3), out var result)) { Reply(args, "'" + Arg(args, 3) + "' is not a number. Use: ae points <login> [amount]"); return; } int remaining; if (result >= 0) { remaining = pointService.Ledger.Grant(text, result, DateTime.UtcNow); } else { pointService.Ledger.Revoke(text, -result, out remaining); } pointService.SaveNow(); Reply(args, text + ": " + remaining + " VP"); } private static void ListTopViewers(ConsoleEventArgs args, PointService points) { List<ViewerAccount> list = new List<ViewerAccount>(); foreach (ViewerAccount account in points.Ledger.Accounts) { if (account.Points > 0) { list.Add(account); } } if (list.Count == 0) { Reply(args, "Nobody has earned anything yet."); return; } list.Sort((ViewerAccount a, ViewerAccount b) => b.Points.CompareTo(a.Points)); int num = Math.Min(10, list.Count); for (int num2 = 0; num2 < num; num2++) { Reply(args, " " + list[num2].Login + ": " + list[num2].Points + " VP"); } if (list.Count > num) { Reply(args, " ... and " + (list.Count - num) + " more"); } } private static void ShopCommand(ConsoleEventArgs args) { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) string text = Arg(args, 2).ToLowerInvariant(); if (text == "verify") { ActionRunner.Verify(delegate(string line) { Reply(args, line); }); if ((Object)(object)RandEventSystem.instance != (Object)null) { Reply(args, "Raids this world qualifies for:"); RaidPicker.Describe(RandEventSystem.instance, RaidPicker.PlayersNear(((Object)(object)Player.m_localPlayer != (Object)null) ? ((Component)Player.m_localPlayer).transform.position : Vector3.zero), delegate(string line) { Reply(args, line); }); } return; } if (text.Length == 0) { Reply(args, "Shop: " + (ModConfig.ShopEnabled.Value ? "on" : "off") + " | world tier " + BossProgress.Current.ToString() + (BossProgress.Override.HasValue ? " (forced by 'ae tier')" : "") + " | viewer " + ModConfig.ViewerCooldownSeconds.Value + "s / action " + ModConfig.ActionCooldownSeconds.Value + "s / mischief " + ModConfig.MischiefCooldownSeconds.Value + "s"); SpawnRegistry.Count(out var hostile, out var harmless); Reply(args, "Alive from the shop: " + hostile + "/" + ModConfig.MaxHostileSpawns.Value + " hostile, " + harmless + "/" + ModConfig.MaxHarmlessSpawns.Value + " harmless"); BossTier current = BossProgress.Current; foreach (ActionDefinition action in ShopCatalog.Actions) { string text2 = ((!ModConfig.ShopActionEnabled(action)) ? "off" : ((current < action.UnlockedAt) ? ("locked until " + action.UnlockedAt) : "ready")); Reply(args, " " + ShopCatalog.PrimaryCommand(action).PadRight(11) + ModConfig.ShopPrice(action).ToString().PadLeft(5) + " VP " + action.Category.ToString() + " " + text2); } Reply(args, "Run one with 'ae shop <command>', e.g. ae shop !wood"); return; } ActionDefinition actionDefinition = ShopCatalog.Match(text.StartsWith("!") ? text : ("!" + text)); if (actionDefinition == null) { Reply(args, "No shop action matches '" + text + "'. Try 'ae shop' for the list."); return; } string failure; bool flag = ActionRunner.Run(actionDefinition, ModConfig.ShopAmount(actionDefinition), delegate(string line) { Reply(args, line); }, out failure); Reply(args, flag ? ("Ran " + actionDefinition.Id + ". No points were charged.") : ("Failed: " + failure + " - in chat this would have been refunded.")); } private static void TierCommand(ConsoleEventArgs args) { if (args.Length <= 2) { Reply(args, "World tier: " + BossProgress.Current.ToString() + (BossProgress.Override.HasValue ? " (forced; 'ae tier off' to read the world again)" : " (from the world)")); return; } string text = Arg(args, 2).ToLowerInvariant(); int result; if (text == "off" || text == "auto") { BossProgress.Override = null; Reply(args, "Reading the world again. Tier: " + BossProgress.Current); } else if (!int.TryParse(text, out result) || result < 0 || result > 5) { Reply(args, "Use a number from 0 (nothing beaten) to 5 (Yagluth), or 'off'."); } else { BossProgress.Override = (BossTier)result; Reply(args, "Pretending the world is at tier " + BossProgress.Override.ToString() + ". The world itself is untouched."); } } private static void TestSound(ConsoleEventArgs args) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { Reply(args, "Not in a world."); return; } CollectInRange(localPlayer); Vector3 position = ((Component)localPlayer).transform.position; Character val = null; float num = float.MaxValue; for (int i = 0; i < Scratch.Count; i++) { Character val2 = Scratch[i]; if (!((Object)(object)val2 == (Object)null) && !val2.IsPlayer() && !val2.IsDead()) { float num2 = Vector3.Distance(position, ((Component)val2).transform.position); if (!(num2 >= num)) { num = num2; val = val2; } } } Scratch.Clear(); if ((Object)(object)val == (Object)null) { Reply(args, "No creature within " + ModConfig.Radius.Value.ToString("0") + "m."); return; } Reply(args, "Nearest: " + NameAssigner.DescribeCreature(val) + " at " + num.ToString("0") + "m"); Reply(args, "SoundOnAssign: " + ModConfig.SoundOnAssign.Value + " | min gap " + ModConfig.SoundMinGapSeconds.Value.ToString("0.0") + "s"); int num3 = CreatureSound.Attempt(val, delegate(string line) { Reply(args, line); }); Reply(args, (num3 > 0) ? ("Played " + num3 + " effect(s). If you heard nothing, the game's own sound settings are the next place to look.") : "Nothing was played - the lines above say why."); } private static void Nearby(ConsoleEventArgs args) { //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { Reply(args, "Not in a world."); return; } CollectInRange(localPlayer); if (Scratch.Count == 0) { Reply(args, "Nothing within " + ModConfig.Radius.Value.ToString("0") + "m."); return; } Reply(args, "Creatures within " + ModConfig.Radius.Value.ToString("0") + "m:"); Vector3 position = ((Component)localPlayer).transform.position; int num = 0; for (int i = 0; i < Scratch.Count; i++) { if (num >= 30) { break; } Character val = Scratch[i]; if (!((Object)(object)val == (Object)null) && !val.IsPlayer()) { num++; float num2 = Vector3.Distance(position, ((Component)val).transform.position); ZNetView nview = val.m_nview; string text = " " + CreatureTraits.Describe(val); if (val.IsDead()) { text += " dead"; } text = (((Object)(object)nview == (Object)null || !nview.IsValid()) ? (text + " no-zdo") : (nview.IsOwner() ? (text + " mine") : (nview.HasOwner() ? (text + " other-owner") : (text + " unowned")))); string viewer = NameStore.GetViewer(val); if (viewer.Length > 0) { string channel = NameStore.GetChannel(val); text = text + " name=" + viewer + ((channel.Length > 0) ? ("@" + channel) : ""); } text += (NameAssigner.IsEligible(val) ? " FREE" : " skip"); Reply(args, " " + Utils.GetPrefabName(((Component)val).gameObject) + " " + num2.ToString("0.0") + "m" + text); } } if (Scratch.Count > num) { Reply(args, " ... and " + (Scratch.Count - num) + " more"); } Scratch.Clear(); } private static void Clear(ConsoleEventArgs args) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { Reply(args, "Not in a world."); return; } string text = ((args.Length > 2) ? Arg(args, 2) : "all"); if (!string.Equals(text, "all", StringComparison.OrdinalIgnoreCase)) { Character val = NameStore.FindHolder(text); if ((Object)(object)val == (Object)null) { Reply(args, "No loaded creature carries the name " + text + "."); return; } Reply(args, NameStore.TryClear(val) ? ("Cleared " + text + " from " + NameAssigner.DescribeCreature(val) + ".") : ("Could not clear " + text + " (not ours to write).")); return; } AllfathersEyePlugin instance = AllfathersEyePlugin.Instance; if ((Object)(object)instance == (Object)null) { Reply(args, "Plugin instance is gone."); return; } int refused; int num = instance.ResetCreatures(out refused); Reply(args, "Cleared " + num + " name(s) from every loaded creature" + ((refused > 0) ? (", " + refused + " refused (owned by another player)") : "") + ". Everyone may be given a new one."); } private static void PlayFeedDemo(ConsoleEventArgs args) { AllfathersEyePlugin instance = AllfathersEyePlugin.Instance; if ((Object)(object)instance == (Object)null || instance.Feed == null) { Reply(args, "Plugin instance is gone."); return; } instance.Feed.PlayTestSequence(); Reply(args, ModConfig.FeedEnabled.Value ? "Playing the feed test sequence." : "Playing the feed test sequence, but Feed/Enabled is off so nothing will show."); } private static void ListFonts(ConsoleEventArgs args) { Font[] array = EventFeed.AvailableFonts(); if (array.Length == 0) { Reply(args, "No usable UnityEngine.Font is loaded; the feed uses the built-in font."); return; } Reply(args, array.Length + " font(s) available for Feed/GameFontName:"); for (int i = 0; i < array.Length && i < 40; i++) { Reply(args, " " + ((Object)array[i]).name); } if (array.Length > 40) { Reply(args, " ... and " + (array.Length - 40) + " more"); } } private static void TwitchAccount(ConsoleEventArgs args) { AllfathersEyePlugin instance = AllfathersEyePlugin.Instance; if ((Object)(object)instance == (Object)null || instance.Auth == null) { Reply(args, "Plugin instance is gone."); return; } TwitchAuth auth = instance.Auth; string text = Arg(args, 2).ToLowerInvariant(); if (text.Length == 0) { text = "status"; } if (!(text == "connect")) { if (text == "disconnect") { auth.Disconnect(); Reply(args, "Disconnecting and revoking the token."); return; } Reply(args, "Twitch account: " + auth.State.ToString() + ((auth.Login.Length > 0) ? (" (" + auth.Login + ")") : "")); if (auth.StatusDetail.Length > 0) { Reply(args, " " + auth.StatusDetail); } if (auth.State == AuthState.WaitingForUser) { Reply(args, " Open F1 -> 2 - Twitch for the code."); } Reply(args, auth.IsConnected ? (" Chat is read as " + auth.Login + ".") : " Chat is read anonymously, which is all the naming needs."); } else if (auth.IsConnected) { Reply(args, "Already connected as " + auth.Login + ". Use 'ae twitch disconnect' first."); } else { auth.BeginConnect(); Reply(args, "Starting Twitch sign-in. Open F1 -> 2 - Twitch to see the code."); Reply(args, "The code is shown there only, never here: the console can be on stream."); } } private static void RavenCommand(ConsoleEventArgs args) { string text = Arg(args, 2).ToLowerInvariant(); int result = 0; int.TryParse(Arg(args, 3), out result); ChannelEventKind channelEventKind; switch (text) { case "sub": channelEventKind = ChannelEventKind.Subscription; break; case "resub": channelEventKind = ChannelEventKind.Resubscription; if (result <= 0) { result = 13; } break; case "gift": channelEventKind = ChannelEventKind.GiftSubscription; result = 1; break; case "gifts": channelEventKind = ChannelEventKind.GiftSubscription; if (result <= 0) { result = 20; } break; case "raid": channelEventKind = ChannelEventKind.Raid; if (result <= 0) { result = 42; } break; case "cheer": channelEventKind = ChannelEventKind.Cheer; if (result <= 0) { result = ModConfig.RavenMinimumBits.Value; } break; default: Reply(args, "Usage: ae raven <sub|resub|gift|gifts|raid|cheer> [amount]"); return; } if ((Object)(object)Player.m_localPlayer == (Object)null) { Reply(args, "Not in a world - the raven needs somewhere to land."); return; } string text2 = ((ModConfig.NormalizedChannel.Length > 0) ? ModConfig.NormalizedChannel : "TestViewer"); ChannelEvent channelEvent = new ChannelEvent(channelEventKind, text2, text2, ModConfig.NormalizedChannel, result); if (RavenHerald.Announce(channelEvent)) { Reply(args, "Hugin has it: " + channelEvent?.ToString() + ". Anything already waiting goes first, one every " + ModConfig.RavenGapSeconds.Value + "s."); } else if (!ModConfig.RavenEnabled.Value) { Reply(args, "12 - Hugin / Enabled is off."); } else if (channelEventKind == ChannelEventKind.Cheer && result < ModConfig.RavenMinimumBits.Value) { Reply(args, result + " bits is below MinimumBits (" + ModConfig.RavenMinimumBits.Value + ")."); } else { Reply(args, "Refused: the switch for this kind is off in 12 - Hugin."); } } private static void ToggleDebug(ConsoleEventArgs args) { if (args.Length > 2) { string text = Arg(args, 2).ToLowerInvariant(); ModConfig.DebugLogging = text == "on" || text == "true" || text == "1"; } else { ModConfig.DebugLogging = !ModConfig.DebugLogging; } Reply(args, "Verbose logging " + (ModConfig.DebugLogging ? "ON" : "off") + ". Resets when the game restarts."); } private static void SetPaused(ConsoleEventArgs args, bool paused) { AllfathersEyePlugin instance = AllfathersEyePlugin.Instance; if (!((Object)(object)instance == (Object)null)) { instance.Assigner.Paused = paused; Reply(args, "Name assignment " + (paused ? "paused" : "resumed") + "."); } } private static void Reconnect(ConsoleEventArgs args) { if (ModConfig.NormalizedChannel.Length == 0) { Reply(args, "No channel configured, nothing to reconnect."); return; } AllfathersEyePlugin instance = AllfathersEyePlugin.Instance; if ((Object)(object)instance == (Object)null) { Reply(args, "Plugin instance is gone."); return; } instance.RequestReconnect(); Reply(args, "Reconnecting to #" + ModConfig.NormalizedChannel + "."); } private static string Arg(ConsoleEventArgs args, int index) { if (args == null || args.Length <= index) { return ""; } string text = args[index]; if (text != null) { return text.Trim(); } return ""; } private static void CollectInRange(Player player) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) Scratch.Clear(); Character.GetCharactersInRange(((Component)player).transform.position, ModConfig.Radius.Value, Scratch); } private static Character FindNearestEligible(Player player) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //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) CollectInRange(player); Vector3 position = ((Component)player).transform.position; Character result = null; float num = float.MaxValue; for (int i = 0; i < Scratch.Count; i++) { Character val = Scratch[i]; if (NameAssigner.IsEligible(val)) { float num2 = Vector3.SqrMagnitude(((Component)val).transform.position - position); if (!(num2 >= num)) { num = num2; result = val; } } } Scratch.Clear(); return result; } private static void Reply(ConsoleEventArgs args, string text) { if (args != null && (Object)(object)args.Context != (Object)null) { args.Context.AddString(text); } else { AllfathersEyePlugin.Log.LogInfo((object)text); } } } public enum TriggerMode { AnyMessage, FirstMessage, Command } public static class ModConfig { public static ConfigEntry<bool> Enabled; public static ConfigEntry<bool> Paused; public static ConfigEntry<bool> ResetCreatures; public static ConfigEntry<string> Channel; public static ConfigEntry<bool> TwitchAccount; public static ConfigEntry<ModLanguage> Language; public static ConfigEntry<bool> AnnounceOnConnect; public static ConfigEntry<bool> AnnounceDeaths; public static ConfigEntry<bool> AnnounceWaiting; public static ConfigEntry<int> CommandCooldownSeconds; public static ConfigEntry<int> ReminderMinutes; public static ConfigEntry<TriggerMode> Trigger; public static ConfigEntry<string> Command; public static ConfigEntry<bool> OnlySubsAndVips; public static ConfigEntry<int> CooldownSeconds; public static ConfigEntry<int> RebirthCooldownSeconds; public static ConfigEntry<string> IgnoredUsers; public static ConfigEntry<float> Radius; public static ConfigEntry<bool> OverlayStatus; public static ConfigEntry<float> WanderRadius; public static ConfigEntry<int> SameSpeciesMinutes; public static ConfigEntry<bool> IncludeEnemies; public static ConfigEntry<bool> IncludeTamed; public static ConfigEntry<bool> IncludeBosses; public static ConfigEntry<string> PrefabBlacklist; public static ConfigEntry<string> NameFormat; public static ConfigEntry<string> ViewerColor; public static ConfigEntry<string> ColorPerChannel; public static ConfigEntry<bool> SoundOnAssign; public static ConfigEntry<float> SoundMinGapSeconds; public static ConfigEntry<int> CallOutMinutes; public static ConfigEntry<bool> MapPins; public static ConfigEntry<int> MapPinLimit; public const PinType MapPinIcon = (PinType)3; public static ConfigEntry<bool> RavenEnabled; public static ConfigEntry<bool> RavenOnSubscription; public static ConfigEntry<bool> RavenOnGift; public static ConfigEntry<bool> RavenOnRaid; public static ConfigEntry<bool> RavenOnCheer; public static ConfigEntry<int> RavenMinimumBits; public static ConfigEntry<int> RavenGapSeconds; public static ConfigEntry<bool> FeedPreview; public static ConfigEntry<bool> FeedPreviewAnimation; public static ConfigEntry<bool> FeedEnabled; public static ConfigEntry<bool> FeedShowSoulCount; public static ConfigEntry<bool> FeedShowNameAssigned; public static ConfigEntry<bool> FeedShowCreatureKilled; public static ConfigEntry<FeedAnchor> FeedAnchorPosition; public static ConfigEntry<float> FeedPositionX; public static ConfigEntry<float> FeedPositionY; public static ConfigEntry<int> FeedWidth; public static ConfigEntry<int> FeedFontSize; public static ConfigEntry<string> FeedTextColor; public static ConfigEntry<bool> FeedShadow; public static ConfigEntry<FeedScope> FeedScopeSetting; public static ConfigEntry<bool> FeedUseGameFont; public static ConfigEntry<string> FeedGameFontName; public static ConfigEntry<int> FeedMaxEntries; public static ConfigEntry<float> FeedLifetimeSeconds; public static ConfigEntry<float> FeedFadeSeconds; public static ConfigEntry<bool> PointsEnabled; public static ConfigEntry<bool> OnlyWhenLive; public static ConfigEntry<int> PresencePoints; public static ConfigEntry<int> PresenceIntervalMinutes; public static ConfigEntry<int> ChatBonusPoints; public static ConfigEntry<int> ChatBonusCooldownMinutes; public static ConfigEntry<bool> BroadcasterEarns; public static ConfigEntry<int> PresenceWindowMinutes; public static ConfigEntry<int> ForgetAfterDays; public static ConfigEntry<bool> ShopEnabled; public static ConfigEntry<int> ViewerCooldownSeconds; public static ConfigEntry<int> ActionCooldownSeconds; public static ConfigEntry<int> MischiefCooldownSeconds; public static ConfigEntry<int> MaxHostileSpawns; public static ConfigEntry<int> MaxHarmlessSpawns; public static ConfigEntry<int> SpawnLifetimeMinutes; public static ConfigEntry<bool> PauseWrathNearBoss; public static ConfigEntry<int> WrathMinHealthPercent; public static ConfigEntry<bool> FeedShowShopActions; public static ConfigEntry<bool> AllowOtherChannels; public static ConfigEntry<int> WorldEventCooldownSeconds; public static ConfigEntry<RaidDifficulty> RaidDifficultySetting; public static ConfigEntry<int> RaidSeconds; public static bool DebugLogging; public const string OverlayHost = "allfatherseye.sohneg.ch"; public const string OverlayKey = "M-9_CeQqAs4wJk-o13HDAzseJDY-oKKt"; private static readonly Dictionary<string, ConfigEntry<bool>> ShopEnabledEntries = new Dictionary<string, ConfigEntry<bool>>(StringComparer.Ordinal); private static readonly Dictionary<string, ConfigEntry<int>> ShopPriceEntries = new Dictionary<string, ConfigEntry<int>>(StringComparer.Ordinal); private static readonly Dictionary<string, ConfigEntry<int>> ShopAmountEntries = new Dictionary<string, ConfigEntry<int>>(StringComparer.Ordinal); public static Func<TwitchAuth> AuthAccessor; internal static Func<OverlaySync> OverlayAccessor; private static int _pendingPixelsX = -1; private static int _pendingPixelsY = -1; private static readonly string[] StaleEntries = new string[8] { "9 - Overlay", "Host", "9 - Overlay", "Token", "9 - Overlay", "Enabled", "5 - Display", "MapPinIcon" }; private static string _lastResetResult = ""; public static HashSet<string> IgnoredUserSet { get; private set; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase); public static HashSet<string> PrefabBlacklistSet { get; private set; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase); public static string SafeViewerColor { get; private set; } = ""; public static Dictionary<string, string> ChannelColors { get; private set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); public static string NormalizedChannel { get { TwitchAuth twitchAuth = ((AuthAccessor != null) ? AuthAccessor() : null); if (twitchAuth != null && twitchAuth.IsConnected) { string text = ColorSpec.NormalizeChannel(twitchAuth.Login); if (text.Length > 0) { return text; } } return ColorSpec.NormalizeChannel((Channel == null) ? "" : Channel.Value); } } public static event Action DisplayChanged; public static event Action PreviewAnimationRequested; public static void NotifyDisplayChanged() { RaiseDisplayChanged(); } public static void Bind(ConfigFile cfg) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Expected O, but got Unknown //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Expected O, but got Unknown //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Expected O, but got Unknown //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Expected O, but got Unknown //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_0236: Expected O, but got Unknown //IL_0268: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Expected O, but got Unknown //IL_02a4: Unknown result type (might be due to invalid IL or missing references) //IL_02ae: Expected O, but got Unknown //IL_02f7: Unknown result type (might be due to invalid IL or missing references) //IL_0301: Expected O, but got Unknown //IL_0332: Unknown result type (might be due to invalid IL or missing references) //IL_033c: Expected O, but got Unknown //IL_0371: Unknown result type (might be due to invalid IL or missing references) //IL_037b: Expected O, but got Unknown //IL_03ac: Unknown result type (might be due to invalid IL or missing references) //IL_03b6: Expected O, but got Unknown //IL_03f2: Unknown result type (might be due to invalid IL or missing references) //IL_03fc: Expected O, but got Unknown //IL_0438: Unknown result type (might be due to invalid IL or missing references) //IL_0442: Expected O, but got Unknown //IL_0477: Unknown result type (might be due to invalid IL or missing references) //IL_0481: Expected O, but got Unknown //IL_04af: Unknown result type (might be due to invalid IL or missing references) //IL_04b9: Expected O, but got Unknown //IL_04e7: Unknown result type (might be due to invalid IL or missing references) //IL_04f1: Expected O, but got Unknown //IL_0514: Unknown result type (might be due to invalid IL or missing references) //IL_051e: Expected O, but got Unknown //IL_0649: Unknown result type (might be due to invalid IL or missing references) //IL_0653: Expected O, but got Unknown //IL_0676: Unknown result type (might be due to invalid IL or missing references) //IL_0680: Expected O, but got Unknown //IL_06b2: Unknown result type (might be due to invalid IL or missing references) //IL_06bc: Expected O, but got Unknown //IL_06f8: Unknown result type (might be due to invalid IL or missing references) //IL_0702: Expected O, but got Unknown //IL_0729: Unknown result type (might be due to invalid IL or missing references) //IL_0733: Expected O, but got Unknown Enabled = cfg.Bind<bool>("1 - General", "Enabled", true, "Master switch. When off, no names are assigned and none are shown. / Hauptschalter. Aus heisst: keine Namen werden vergeben und keine angezeigt."); Paused = cfg.Bind<bool>("1 - General", "Paused", false, "Pauses handing out new names without dropping the Twitch connection. Names already on creatures stay visible and the feed keeps working. / Pausiert die Vergabe neuer Namen, ohne die Twitch-Verbindung zu trennen. Bereits vergebene Namen bleiben sichtbar, der Feed laeuft weiter."); TwitchAccount = cfg.Bind<bool>("2 - Twitch", "TwitchAccount", false, new ConfigDescription("Sign in to Twitch. Signed in, the mod uses your own channel and can write in your chat; signed out it still reads a channel anonymously and hands out names, it just cannot say anything. Tokens live outside this file and are never logged. / Bei Twitch anmelden. Angemeldet nutzt die Mod deinen eigenen Kanal und darf in deinen Chat schreiben; abgemeldet liest sie einen Kanal weiterhin anonym mit und vergibt Namen, kann aber nichts sagen. Tokens liegen ausserhalb dieser Datei und werden nie geloggt.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 95, HideDefaultButton = true, CustomDrawer = DrawTwitchAccount } })); OverlayStatus = cfg.Bind<bool>("2 - Twitch", "Overlay", false, new ConfigDescription("The shop as an overlay on your video, through the Twitch extension. Nothing to fill in: activate the extension on your channel, and the mod reports by itself once a viewer opens it. / Der Shop als Overlay ueber deinem Video, ueber die Twitch-Erweiterung. Hier ist nichts einzutragen: Erweiterung auf dem Kanal aktivieren, den Rest macht die Mod von selbst, sobald ein Zuschauer sie oeffnet.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 94, HideDefaultButton = true, CustomDrawer = DrawOverlayStatus } })); ResetCreatures = cfg.Bind<bool>("1 - General", "ResetCreatures", false, new ConfigDescription("Takes every name off every loaded creature and lets all viewers be given a new one. Useful while setting things up, when a named creature has wandered off. Creatures owned by another player are left alone. / Nimmt allen geladenen Kreaturen ihren Namen ab; alle Zuschauer koennen danach wieder einen bekommen. Praktisch beim Einrichten, wenn eine benannte Kreatur davongelaufen ist. Kreaturen, die einem anderen Spieler gehoeren, bleiben unberuehrt.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 10, HideDefaultButton = true, CustomDrawer = DrawResetButton } })); Channel = cfg.Bind<string>("2 - Twitch", "Channel", "", new ConfigDescription("Which Twitch channel to read, without the leading hash. Only used while you are NOT signed in above - once you sign in, your own channel is filled in here and used instead. Empty and signed out means display-only: no connection is opened and you just see the names other players hand out. / Welcher Twitch-Kanal mitgelesen wird, ohne Raute. Wird nur genutzt, solange du oben NICHT angemeldet bist - nach dem Anmelden wird hier dein eigener Kanal eingetragen und verwendet. Leer und abgemeldet heisst nur anzeigen: es wird keine Verbindung aufgebaut und du siehst nur die Namen, die andere Spieler vergeben.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 90 } })); Language = cfg.Bind<ModLanguage>("2 - Twitch", "Language", ModLanguage.Game, new ConfigDescription("Language of everything the mod says - the feed and its answers in chat. Game follows Valheim: German gets German, everything else English. Viewers are understood in both languages either way. / Sprache von allem, was die Mod sagt - Feed und Antworten im Chat. Game richtet sich nach Valheim: Deutsch bekommt Deutsch, alles andere Englisch. Zuschauer werden unabhaengig davon in beiden Sprachen verstanden.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 85 } })); AnnounceOnConnect = cfg.Bind<bool>("2 - Twitch", "AnnounceOnConnect", true, new ConfigDescription("Say hello in your chat once after connecting, so your viewers know the mod is running and how to take part. Needs you to be signed in. / Nach dem Verbinden einmal in deinem Chat Bescheid sagen, damit deine Zuschauer wissen, dass die Mod laeuft und wie sie mitmachen. Setzt voraus, dass du angemeldet bist.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 80, IsAdvanced = true } })); AnnounceDeaths = cfg.Bind<bool>("2 - Twitch", "AnnounceDeaths", true, new ConfigDescription("Tell a viewer in chat when their creature dies, so they notice without watching the overlay. Only for your own channel, and only while you are signed in. / Einem Zuschauer im Chat Bescheid sagen, wenn seine Kreatur stirbt - damit er es mitbekommt, ohne auf das Overlay zu schauen. Nur fuer deinen eigenen Kanal, und nur solange du angemeldet bist.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 75 } })); AnnounceWaiting = cfg.Bind<bool>("2 - Twitch", "AnnounceWaiting", true, new ConfigDescription("Tell a viewer in chat when no creature is free for them, and again once one is. Only said to people who actually had to wait a few seconds - a viewer served right away hears nothing. / Einem Zuschauer im Chat sagen, wenn gerade keine Kreatur frei ist - und noch einmal, sobald eine da ist. Nur an die, die wirklich ein paar Sekunden warten mussten; wer sofort eine bekommt, hoert nichts.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 74 } })); CommandCooldownSeconds = cfg.Bind<int>("2 - Twitch", "CommandCooldownSeconds", 30, new ConfigDescription("How long a viewer waits before the same command answers them again. A global budget limits the total either way, because Twitch does. / Wie lange ein Zuschauer warten muss, bis derselbe Befehl ihm wieder antwortet. Ein globales Budget begrenzt die Gesamtmenge ohnehin, weil Twitch das tut.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 600), new object[1] { new ConfigurationManagerAttributes { Order = 60, IsAdvanced = true } })); Trigger = cfg.Bind<TriggerMode>("3 - Trigger", "TriggerMode", TriggerMode.FirstMessage, new ConfigDescription("AnyMessage: every message, rate limited by CooldownSeconds. FirstMessage: only a chatter's first message per game session. Command: only messages starting with the Command below. / AnyMessage: jede Nachricht, begrenzt durch CooldownSeconds. FirstMessage: nur die erste Nachricht pro Chatter und Spielsitzung. Command: nur Nachrichten, die mit dem Befehl unten beginnen.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { IsAdvanced = true } })); Command = cfg.Bind<string>("3 - Trigger", "Command", "!viking", new ConfigDescription("Chat command for TriggerMode = Command. Case insensitive. Has nothing to do with the viewer commands like !soul, which are fixed. / Chat-Befehl fuer TriggerMode = Command. Gross- und Kleinschreibung egal. Hat nichts mit den Zuschauer-Befehlen wie !seele zu tun, die fest eingebaut sind.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { IsAdvanced = true } })); OnlySubsAndVips = cfg.Bind<bool>("3 - Trigger", "OnlySubsAndVips", false, new ConfigDescription("Only subscribers, VIPs, moderators, the broadcaster and Twitch staff get a creature. / Nur Subscriber, VIPs, Moderatoren, der Broadcaster und Twitch-Staff bekommen eine Kreatur.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { IsAdvanced = true } })); CooldownSeconds = cfg.Bind<int>("3 - Trigger", "CooldownSeconds", 60, new ConfigDescription("Per-chatter cooldown between two assignment attempts. / Wartezeit pro Chatter zwischen zwei Vergabeversuchen.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3600), new object[1] { new ConfigurationManagerAttributes { IsAdvanced = true } })); RebirthCooldownSeconds = cfg.Bind<int>("3 - Trigger", "RebirthCooldownSeconds", 60, new ConfigDescription("After a viewer's creature dies, how long before they can be given a new one. Their next message after that wins them a body again, even under FirstMessage - the rule is one creature at a time, not one per stream. / Nachdem die Kreatur eines Zuschauers gestorben ist: wie lange, bis er eine neue bekommen kann. Seine naechste Nachricht danach verschafft ihm wieder einen Koerper, auch bei FirstMessage - die Regel ist eine Kreatur gleichzeitig, nicht eine pro Stream.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3600), new object[1] { new ConfigurationManagerAttributes { IsAdvanced = true } })); IgnoredUsers = cfg.Bind<string>("3 - Trigger", "IgnoredUsers", "nightbot,streamelements,streamlabs,moobot,fossabot", new ConfigDescription("Comma separated logins that are ignored completely. / Kommagetrennte Loginnamen, die komplett ignoriert werden.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { IsAdvanced = true } })); Radius = cfg.Bind<float>("4 - Targets", "Radius", 30f, new ConfigDescription("Search radius around the local player, in meters. / Suchradius um den lokalen Spieler, in Metern.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 200f), Array.Empty<object>())); WanderRadius = cfg.Bind<float>("4 - Targets", "WanderRadius", 100f, new ConfigDescription("Distance at which a soul leaves its creature behind and moves into one near you, so viewers stay where the stream is. 0 turns wandering off. Keep it well above Radius - with both the same, souls would flip back and forth at the edge. / Entfernung, ab der eine Seele ihre Kreatur zuruecklaesst und in eine in deiner Naehe zieht, damit Zuschauer dort sind, wo der Stream ist. 0 schaltet das Wandern ab. Deutlich groesser als Radius lassen - bei gleichen Werten wuerden Seelen am Rand hin und her springen.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 400f), Array.Empty<object>())); SameSpeciesMinutes = cfg.Bind<int>("4 - Targets", "SameSpeciesMinutes", 5, new ConfigDescription("How long a wandering soul holds out for its own kind of creature before it takes any. 0 means it takes the first one available. / Wie lange eine wandernde Seele auf ihre eigene Kreaturenart wartet, bevor sie jede nimmt. 0 bedeutet, sie nimmt die erstbeste.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 60), Array.Empty<object>())); IncludeEnemies = cfg.Bind<bool>("4 - Targets", "IncludeEnemies", true, "Name wild and hostile creatures. / Wilde und feindliche Kreaturen benennen."); IncludeTamed = cfg.Bind<bool>("4 - Targets", "IncludeTamed", true, "Name tamed animals. Their player-given name is kept and the viewer name is appended. / Gezaehmte Tiere benennen. Ihr selbst vergebener Name bleibt, der Zuschauername kommt dazu."); IncludeBosses = cfg.Bind<bool>("4 - Targets", "IncludeBosses", false, "Name bosses. / Bosse benennen."); PrefabBlacklist = cfg.Bind<string>("4 - Targets", "PrefabBlacklist", "", "Creatures that never get a name, by prefab name, comma separated - for example Crow,Seagull,Fish1. The console command 'ae nearby' lists the names around you. / Kreaturen, die nie einen Namen bekommen, als Prefab-Namen, kommagetrennt - z. B. Crow,Seagull,Fish1. Der Konsolenbefehl 'ae nearby' listet die Namen in deiner Naehe auf."); NameFormat = cfg.Bind<string>("5 - Display", "NameFormat", "{creature} ({viewer})", "Placeholders: {creature} = the normal name, {viewer} = the Twitch name. / Platzhalter: {creature} = der normale Name, {viewer} = der Twitch-Name."); ViewerColor = cfg.Bind<string>("5 - Display", "ViewerColor", "#FFCB52", "Hex colour for the viewer name. The default is a warm yellow, which stays readable against grass and stone and sits next to the orange the game uses elsewhere. Twitch purple (#9146FF) is on brand but hard to read on Valheim's backgrounds. Empty means no colouring. Used for every channel ColorPerChannel does not mention. / Hex-Farbe fuer den Zuschauernamen. Standard ist ein warmes Gelb - lesbar auf Gras und Stein und passend zum Orange, das das Spiel ohnehin verwendet. Twitch-Lila (#9146FF) passt zur Marke, ist auf Valheims Hintergruenden aber schlecht zu lesen. Leer heisst keine Faerbung. Gilt fuer jeden Kanal, der in ColorPerChannel nicht vorkommt."); ColorPerChannel = cfg.Bind<string>("5 - Display", "ColorPerChannel", "", "Per-channel colours, so you can tell at a glance which stream a name came from. Format: channel:#colour, comma separated, e.g. meinkanal:#9146FF,frauskanal:#FF69B4. / Farben pro Kanal, damit man auf einen Blick sieht, aus welchem Stream ein Name stammt. Format: kanal:#farbe, kommagetrennt, z. B. meinkanal:#9146FF,frauskanal:#FF69B4."); SoundOnAssign = cfg.Bind<bool>("5 - Display", "SoundOnAssign", true, "Let a creature announce itself when a soul moves in, using its own alerted sound - the growl or snort it makes when it notices you. Played at the creature, so it also tells you where to look. Every player with this mod who has that creature loaded hears it, which means everyone close enough to see what happened. / Eine Kreatur sich melden lassen, wenn eine Seele einzieht - mit ihrem eigenen Alarmlaut, also dem, den sie macht, wenn sie dich bemerkt. Erklingt bei der Kreatur, verraet also auch die Richtung. Jeder Spieler mit dieser Mod, der die Kreatur geladen hat, hoert ihn - also alle, die nah genug sind, um zuzusehen."); SoundMinGapSeconds = cfg.Bind<float>("5 - Display", "SoundMinGapSeconds", 1.5f, new ConfigDescription("Shortest gap between two assignment sounds on this machine. Events arriving inside the gap are skipped, not queued up - a burst of souls should not turn into a burst of growls seconds later. / Mindestabstand zwischen zwei Uebernahme-Geraeuschen auf diesem Rechner. Ereignisse innerhalb des Abstands werden uebersprungen, nicht nachgeholt - aus mehreren Seelen auf einmal soll nicht Sekunden spaeter ein Knurr-Gewitter werden.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), new object[1] { new ConfigurationManagerAttributes { IsAdvanced = true } })); CallOutMinutes = cfg.Bind<int>("5 - Display", "CallOutMinutes", 2, new ConfigDescription("How often one of your viewers calls out, using the same voice a creature uses when it notices you. It comes from the creature, so it tells you which way to look - handy when somebody has been standing behind a rock for ten minutes. 0 turns it off, and nothing is heard while no viewer is nearby. / Wie oft sich einer deiner Zuschauer meldet, mit demselben Laut, den eine Kreatur macht, wenn sie dich bemerkt. Er kommt aus der Kreatur, verraet also die Richtung - praktisch, wenn jemand seit zehn Minuten hinter einem Felsen steht. 0 schaltet es ab, und solange kein Zuschauer in der Naehe ist, bleibt es still.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 60), Array.Empty<object>())); MapPins = cfg.Bind<bool>("5 - Display", "MapPins", true, new ConfigDescription("Mark viewer creatures on the map, with the name at the pin. The pin follows the creature, and nothing is written to your save - the pins are gone when the creature dies, wanders off or unloads. / Zuschauer-Kreaturen auf der Karte markieren, mit dem Namen am Marker. Der Marker folgt der Kreatur, und in deinen Spielstand wird nichts geschrieben - die Marker verschwinden, wenn die Kreatur stirbt, davonzieht oder entladen wird.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 30 } })); MapPinLimit = cfg.Bind<int>("5 - Display", "MapPinLimit", 20, new ConfigDescription("At most this many pins at once. A busy stream would otherwise bury the map under its own audience. / Hoechstens so viele Marker gleichzeitig. Ein voller Stream wuerde die Karte sonst unter seinem eigenen Publikum begraben.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 200), new object[1] { new ConfigurationManagerAttributes { IsAdvanced = true } })); ReminderMinutes = cfg.Bind<int>("2 - Twitch", "ReminderMinutes", 15, new ConfigDescription("How often to remind chat that the mod is running and which commands exist. Most viewers arrive in the middle of a stream and never saw the message from when it connected. 0 turns it off. / Wie oft der Chat daran erinnert wird, dass die Mod laeuft und welche Befehle es gibt. Die meisten Zuschauer kommen mitten im Stream dazu und haben die Meldung von der Verbindung nie gesehen. 0 schaltet es ab.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 240), Array.Empty<object>())); BindFeed(cfg); BindPoints(cfg); BindShop(cfg); BindRaven(cfg); DropStaleEntries(cfg); RebuildCaches(); IgnoredUsers.SettingChanged += delegate { RebuildCaches(); }; PrefabBlacklist.SettingChanged += delegate { RebuildCaches(); }; ViewerColor.SettingChanged += delegate { RebuildCaches(); }; ColorPerChannel.SettingChanged += delegate { RebuildCaches(); }; NameFormat.SettingChanged += delegate { RaiseDisplayChanged(); }; Language.SettingChanged += delegate { Texts.Setting = Language.Value; RaiseDisplayChanged(); }; Enabled.SettingChanged += delegate { RaiseDisplayChanged(); }; } private static void BindPoints(ConfigFile cfg) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Expected O, but got Unknown //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Expected O, but got Unknown //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Expected O, but got Unknown //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Expected O, but got Unknown //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Expected O, but got Unknown PointsEnabled = cfg.Bind<bool>("7 - Points", "Enabled", true, "Let viewers earn Valheim Points for watching and writing. Needs a Twitch sign-in, because the mod has to be able to answer in chat. / Zuschauer Valheim-Punkte fuer Zuschauen und Schreiben verdienen lassen. Braucht eine Twitch-Anmeldung, weil die Mod im Chat antworten muss."); OnlyWhenLive = cfg.Bind<bool>("7 - Points", "OnlyWhenLive", true, "Only earn points while the channel is actually live. Off means points also accumulate during testing, which is handy but rewards nobody for being an audience. / Punkte nur verdienen, solange der Kanal wirklich live ist. Aus heisst, dass auch beim Testen Punkte anfallen - praktisch, belohnt aber niemanden fuers Zuschauen."); PresencePoints = cfg.Bind<int>("7 - Points", "PresencePoints", 10, new ConfigDescription("Points for being present, paid once per PresenceIntervalMinutes. This is the main way to earn: it rewards watching rather than talking. / Punkte fuers Dabeisein, einmal pro PresenceIntervalMinutes. Das ist der Hauptweg: belohnt wird Zuschauen, nicht Reden.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>())); PresenceIntervalMinutes = cfg.Bind<int>("7 - Points", "PresenceIntervalMinutes", 5, new ConfigDescription("How often presence is paid. The first sighting pays nothing - it starts the clock, so nobody earns by appearing for a second. / Wie oft Anwesenheit bezahlt wird. Die erste Sichtung zahlt nichts, sie startet nur die Uhr - damit niemand durch kurzes Auftauchen verdient.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 120), Array.Empty<object>())); ChatBonusPoints = cfg.Bind<int>("7 - Points", "ChatBonusPoints", 5, new ConfigDescription("Extra points for writing something. Mod commands do not count, otherwise asking for your balance would be the cheapest way to raise it. / Zusatzpunkte fuers Schreiben. Mod-Befehle zaehlen nicht - sonst waere die Frage nach dem Punktestand der billigste Weg, ihn zu erhoehen.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>())); ChatBonusCooldownMinutes = cfg.Bind<int>("7 - Points", "ChatBonusCooldownMinutes", 10, new ConfigDescription("Shortest gap between two chat bonuses for the same viewer, so a wall of messages is worth no more than one. / Mindestabstand zwischen zwei Chat-Boni desselben Zuschauers, damit eine Textwand nicht mehr wert ist als eine Nachricht.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 240), Array.Empty<object>())); BroadcasterEarns = cfg.Bind<bool>("7 - Points", "BroadcasterEarns", false, "Let the channel owner earn points too. Off by default - there is nobody to cheat, but a streamer at the top of their own list is noise. / Den Kanalinhaber ebenfalls Punkte verdienen lassen. Standardmaessig aus - es gibt niemanden zu betruegen, aber ein Streamer an der Spitze seiner eigenen Liste ist nur Rauschen."); PresenceWindowMinutes = cfg.Bind<int>("7 - Points", "PresenceWindowMinutes", 15, new ConfigDescription("Fallback definition of 'present' when the mod cannot see lurkers: wrote within this many minutes. Used only while the Twitch sign-in lacks the lurker permission - reconnect once under 2 - Twitch to grant it. / Ersatzdefinition von 'anwesend', wenn die Mod keine stillen Zuschauer sehen kann: hat innerhalb dieser Minuten geschrieben. Gilt nur, solange der Twitch-Anmeldung die Berechtigung dafuer fehlt - dafuer einmal unter 2 - Twitch neu verbinden.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 240), new object[1] { new ConfigurationManagerAttributes { IsAdvanced = true } })); ForgetAfterDays = cfg.Bind<int>("7 - Points", "ForgetAfterDays", 30, new ConfigDescription("Forget viewers who have no points and have not been seen for this many days. Anyone holding points is kept forever - those were earned. 0 forgets nobody. / Zuschauer vergessen, die keine Punkte haben und so lange nicht gesehen wurden. Wer Punkte hat, bleibt fuer immer - die sind verdient. 0 vergisst niemanden.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3650), new object[1] { new ConfigurationManagerAttributes { IsAdvanced = true } })); } private static void BindShop(ConfigFile cfg) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Expected O, but got Unknown //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Expected O, but got Unknown //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Expected O, but got Unknown //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Expected O, but got Unknown //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Expected O, but got Unknown ShopEnabled = cfg.Bind<bool>("8 - Shop", "Enabled", true, "Let viewers spend their points on things that happen in your game. Needs Points to be on and a Twitch sign-in, because every purchase is answered in chat. / Zuschauer ihre Punkte fuer Dinge ausgeben lassen, die in deinem Spiel passieren. Braucht aktive Punkte und eine Twitch-Anmeldung, weil jeder Kauf im Chat beantwortet wird."); ViewerCooldownSeconds = cfg.Bind<int>("8 - Shop", "ViewerCooldownSeconds", 30, new ConfigDescription("How long one viewer must wait between purchases, so a single person cannot drive the whole stream. / Wie lange ein Zuschauer zwischen zwei Kaeufen warten muss, damit nicht eine Person den ganzen Stream steuert.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3600), Array.Empty<object>())); ActionCooldownSeconds = cfg.Bind<int>("8 - Shop", "ActionCooldownSeconds", 60, new ConfigDescription("How long before the same action can be bought again by anyone. / Wie lange es dauert, bis dieselbe Aktion wieder gekauft werden kann - von irgendwem.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3600), Array.Empty<object>())); MischiefCooldownSeconds = cfg.Bind<int>("8 - Shop", "MischiefCooldownSeconds", 120, new ConfigDescription("Extra shared cooldown across all of Loki's Mischief. It is the cheapest category and would otherwise crowd everything else out. / Zusaetzliche gemeinsame Wartezeit fuer ganz Lokis Schabernack. Das ist die billigste Kategorie und wuerde sonst alles andere verdraengen.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3600), Array.Empty<object>())); MaxHostileSpawns = cfg.Bind<int>("8 - Shop", "MaxHostileSpawns", 8, new ConfigDescription("How many bought enemies may be alive at once. / Wie viele gekaufte Gegner gleichzeitig leben duerfen.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 50), Array.Empty<object>())); MaxHarmlessSpawns = cfg.Bind<int>("8 - Shop", "MaxHarmlessSpawns", 20, new ConfigDescription("How many bought harmless creatures may be alive at once. Counted separately from the hostile ones on purpose: a flock of chickens must not block a boar. / Wie viele gekaufte harmlose Kreaturen gleichzeitig leben duerfen. Absichtlich getrennt gezaehlt: ein Huehnerschwarm darf kein Wildschwein blockieren.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>())); SpawnLifetimeMinutes = cfg.Bind<int>("8 - Shop", "SpawnLifetimeMinutes", 10, new ConfigDescription("How long a bought creature stays before it is removed again. Creatures still fighting are left alone, and tamed ones are kept for good - if you tamed it, you earned it. 0 never removes anything. / Wie lange eine gekaufte Kreatur bleibt, bevor sie wieder verschwindet. Wer gerade kaempft, wird in Ruhe gelassen, und Gezaehmte bleiben fuer immer - wer zaehmt, hat sie sich verdient. 0 entfernt nie etwas.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 240), Array.Empty<object>())); PauseWrathNearBoss = cfg.Bind<bool>("8 - Shop", "PauseWrathNearBoss", true, "Hold back Odin's Wrath while a boss is alive near you. A boss fight is hard enough. / Odins Zorn zurueckhalten, solange ein Boss in deiner Naehe lebt. Ein Bosskampf ist schwer genug."); WrathMinHealthPercent = cfg.Bind<int>("8 - Shop", "WrathMinHealthPercent", 35, new ConfigDescription("Below this much health, Odin's Wrath is held back - as it is while swimming or in the air. Chat buying three boars at 20 percent health is not a joke, it is a tombstone in the mountains. 0 switches the guard off. / Unter so viel Leben wird Odins Zorn zurueckgehalten - ebenso beim Schwimmen und in der Luft. Drei Wildschweine bei 20 Prozent Leben sind kein Witz, sondern ein Grabstein im Gebirge. 0 schaltet die Sperre ab.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>())); FeedShowShopActions = cfg.Bind<bool>("8 - Shop", "ShowInFeed", true, "Report purchases in the on-screen feed, the same way names and deaths are reported. / Kaeufe im Feed melden, genau wie Namen und Todesfaelle."); BindWorldEvents(cfg); BindShopActions(cfg); } private static void BindRaven(ConfigFile cfg) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected O, but got Unknown //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Expected O, but got Unknown //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Expected O, but got Unknown //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Expected O, but got Unknown //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Expected O, but got Unknown //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Expected O, but got Unknown RavenEnabled = cfg.Bind<bool>("12 - Hugin", "Enabled", true, new ConfigDescription("Let Hugin fly in and announce what happens on Twitch. Works even with the game's tutorials switched off. / Hugin heranfliegen lassen und melden, was auf Twitch passiert. Funktioniert auch bei abgeschalteten Tutorials.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 100 } })); RavenOnSubscription = cfg.Bind<bool>("12 - Hugin", "OnSubscription", true, new ConfigDescription("Announce subscriptions and renewals. / Abos und Verlaengerungen melden.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 90 } })); RavenOnGift = cfg.Bind<bool>("12 - Hugin", "OnGift", true, new ConfigDescription("Announce gifted subscriptions. A batch is announced once, with its total, rather than once per gift. / Verschenkte Abos melden. Ein Schwung wird einmal mit seiner Gesamtzahl gemeldet und nicht einmal pro Geschenk.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 80 } })); RavenOnRaid = cfg.Bind<bool>("12 - Hugin", "OnRaid", true, new ConfigDescription("Announce incoming raids. / Eingehende Raids melden.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 70 } })); RavenOnCheer = cfg.Bind<bool>("12 - Hugin", "OnCheer", true, new ConfigDescription("Announce bits. / Bits melden.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 60 } })); RavenMinimumBits = cfg.Bind<int>("12 - Hugin", "MinimumBits", 100, new ConfigDescription("Bits below this go unannounced. One bit is a thank-you, a thousand is an event, and where the line falls is yours. / Weniger Bits als das werden nicht gemeldet. Ein Bit ist ein Dankeschoen, tausend sind ein Ereignis - wo die Grenze liegt, entscheidest du.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100000), new object[1] { new ConfigurationManagerAttributes { Order = 50 } })); RavenGapSeconds = cfg.Bind<int>("12 - Hugin", "GapSeconds", 15, new ConfigDescription("Shortest time between two announcements. What arrives inside it is dropped, not queued: a raven still working through a gift bomb from five minutes ago is worse than having missed it. / Kuerzester Abstand zwischen zwei Meldungen. Was dazwischen ankommt, faellt weg statt sich anzustauen - ein Rabe, der noch den Abo-Regen von vor fuenf Minuten abarbeitet, ist schlimmer als eine verpasste Meldung.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 300), new object[1] { new ConfigurationManagerAttributes { Order = 40, IsAdvanced = true } })); } private static void BindWorldEvents(ConfigFile cfg) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected O, but got Unknown AllowOtherChannels = cfg.Bind<bool>("8 - Shop", "AllowOtherChannels", true, "Only matters when you host and somebody else in your world also streams: whether their viewers may start world events here. A raid hits everyone, including you. / Zaehlt nur, wenn du hostest und jemand anderes in deiner Welt ebenfalls streamt: ob dessen Zuschauer hier Welt-Ereignisse ausloesen duerfen. Ein Ueberfall trifft alle, auch dich."); WorldEventCooldownSeconds = cfg.Bind<int>("8 - Shop", "WorldEventCooldownSeconds", 600, new ConfigDescription("Shortest gap between two world events, counted by the host for the whole world rather than per channel - two streamers must not be able to stack raids. / Mindestabstand zwischen zwei Welt-Ereignissen. Der Host zaehlt ihn fuer die ganze Welt statt pro Kanal - zwei Streamer sollen Ueberfaelle nicht stapeln koennen.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 7200), Array.Empty<object>())); RaidDifficultySetting = cfg.Bind<RaidDifficulty>("8 - Shop", "RaidDifficulty", RaidDifficulty.Gentle, "Which raid a purchase starts. Gentle picks one boss tier below what your world has beaten, Matched picks at your level, Anything picks freely from everything unlocked. The list itself is read from the game, so a Valheim update brings new raids along by itself. / Welchen Ueberfall ein Kauf startet. Gentle waehlt eine Boss-Stufe unter dem Stand deiner Welt, Matched auf deiner Hoehe, Anything frei aus allem Freigeschalteten. Die Liste selbst liest die Mod aus dem Spiel, ein Valheim-Update bringt neue Ueberfaelle also von allein mit."); RaidSeconds = cfg.Bind<int>("8 - Shop", "RaidSeconds", 60, new ConfigDescription("End a bought raid after this long. What a raid really costs you is not the single creature but that reinforcements keep arriving for two minutes, so this is the main difficulty dial. What has already spawned stays. 0 lets it run its full length. / Einen gekauften Ueberfall nach dieser Zeit beenden. Hart macht einen Ueberfall nicht der einzelne Gegner, sondern dass zwei Minuten lang nachgeschoben wird - das ist also der eigentliche Schwierigkeitsregler. Was schon da ist, bleibt. 0 laesst ihn vollstaendig durchlaufen.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 600), Array.Empty<object>())); } private static void BindShopActions(ConfigFile cfg) { //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Expected O, but got Unknown //IL_0255: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Expected O, but got Unknown ShopEnabledEntries.Clear(); ShopPriceEntries.Clear(); ShopAmountEntries.Clear(); foreach (ShopCategory value in Enum.GetValues(typeof(ShopCategory))) { ShopEnabledEntries[CategoryKey(value)] = cfg.Bind<bool>(SectionFor(value), "Enabled", true, "Offer this whole category. Off hides every action in it from the shop listing. / Diese ganze Kategorie anbieten. Aus versteckt jede Aktion darin auch aus der Liste."); } foreach (ActionDefinition action in ShopCatalog.Actions) { string text = SectionFor(action.Category); string text2 = string.Join(" / ", action.Commands); string text3 = ((action.UnlockedAt == BossTier.None) ? " Available from the start. / Von Anfang an verfuegbar." : (" Unlocked once the world has beaten " + action.UnlockedAt.ToString() + ". / Freigeschaltet, sobald die Welt " + action.UnlockedAt.ToString() + " besiegt hat.")); ShopEnabledEntries[action.Id] = cfg.Bind<bool>(text, action.Id + ".Enabled", true, "Offer " + text2 + " in the shop. / " + text2 + " im Laden anbieten." + text3); ShopPriceEntries[action.Id] = cfg.Bind<int>(text, action.Id + ".Price", action.DefaultPrice, new ConfigDescription("What " + text2 + " costs in Valheim Points. / Was " + text2 + " in Valheim-Punkten kostet.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100000), Array.Empty<object>())); if (action.HasAmount) { ShopAmountEntries[action.Id] = cfg.Bind<int>(text, action.Id + ".Amount", action.DefaultAmount, new ConfigDescription("How many " + text2 + " delivers. / Wie viele " + text2 + " liefert.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>())); } } } private static string SectionFor(ShopCategory category) { return category switch { ShopCategory.Wrath => "10 - Odins Wrath", ShopCategory.Mischief => "11 - Lokis Mischief", _ => "9 - Odins Blessing", }; } private static string CategoryKey(ShopCategory category) { return "CATEGORY:" + category; } public static bool ShopCategoryEnabled(ShopCategory category) { if (ShopEnabledEntries.TryGetValue(CategoryKey(category), out var value)) { return value.Value; } return true; } public static bool ShopActionEnabled(ActionDefinition action) { if (action == null) { return false; } if (ShopEnabledEntries.TryGetValue(action.Id, out var value)) { return value.Value; } return true; } public static int ShopPrice(ActionDefinition action) { if (action == null) { return 0; } if (!ShopPriceEntries.TryGetValue(action.Id, out var value)) { return action.DefaultPrice; } return value.Value; } public static int ShopAmount(ActionDefinition action) { if (action == null) { return 0; } if (!ShopAmountEntries.TryGetValue(action.Id, out var value)) { return action.DefaultAmount; } return value.Value; } private static void BindFeed(ConfigFile cfg) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Expected O, but got Unknown //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Expected O, but got Unknown //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Expected O, but got Unknown //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Expected O, but got Unknown //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Expected O, but got Unknown //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Expected O, but got Unknown //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Expected O, but got Unknown //IL_02a7: Unknown result type (might be due to invalid IL or missing references) //IL_02b1: Expected O, but got Unknown //IL_0318: Unknown result type (might be due to invalid IL or missing references) //IL_0322: Expected O, but got Unknown //IL_0363: Unknown result type (might be due to invalid IL or missing references) //IL_036d: Expected O, but got Unknown //IL_03a7: Unknown result type (might be due to invalid IL or missing references) //IL_03b1: Expected O, but got Unknown //IL_03e7: Unknown result type (might be due to invalid IL or missing references) //IL_03f1: Expected O, but got Unknown //IL_0423: Unknown result type (might be due to invalid IL or missing references) //IL_042d: Expected O, but got Unknown //IL_046b: Unknown result type (might be due to invalid IL or missing references) //IL_0475: Expected O, but got Unknown //IL_04b7: Unknown result type (might be due to invalid IL or missing references) //IL_04c1: Expected O, but got Unknown //IL_04fa: Unknown result type (might be due to invalid IL or missing references) //IL_0504: Expected O, but got Unknown //IL_0548: Unknown result type (might be due to invalid IL or missing references) //IL_0552: Expected O, but got Unknown //IL_0596: Unknown result type (might be due to invalid IL or missing references) //IL_05a0: Expected O, but got Unknown FeedPreview = cfg.Bind<bool>("6 - Feed", "Preview", false, new ConfigDescription("Shows example lines so you can adjust position and look. They do not fade out and are never sent to other players. Resets to off when you start the game or enter a world. / Zeigt Beispielzeilen an, damit du Position und Aussehen einstellen kannst. Sie blenden nicht aus und werden nie an andere Spieler gesendet. Wird beim Spielstart und beim Betreten einer Welt automatisch abgeschaltet.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 100 } })); FeedPreviewAnimation = cfg.Bind<bool>("6 - Feed", "PreviewAnimation", false, new ConfigDescription("Plays three test events one after another with their normal lifetime, so you can see the fade out and the lines sliding up. Switches itself off again. / Spielt drei Testereignisse nacheinander mit normaler Lebensdauer ab, damit du das Ausblenden und das Nachrutschen siehst. Schaltet sich danach von selbst wieder aus.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 99, HideDefaultButton = true, CustomDrawer = DrawPreviewAnimationButton } })); FeedShowSoulCount = cfg.Bind<bool>("6 - Feed", "ShowSoulCount", true, new ConfigDescription("Shows a line with how many souls are in creatures and how many are waiting for one. Sits at the outer edge of the feed and stays put while the event lines come and go. / Zeigt eine Zeile mit der Anzahl der Seelen in Kreaturen und der Wartenden. Sitzt an der Aussenkante des Feeds und bleibt stehen, waehrend die Ereigniszeilen kommen und gehen.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 88 } })); FeedEnabled = cfg.Bind<bool>("6 - Feed", "Enabled", true, new ConfigDescription("Shows the event feed on screen. / Zeigt den Ereignis-Feed auf dem Bildschirm an.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 90 } })); FeedShowNameAssigned = cfg.Bind<bool>("6 - Feed", "ShowNameAssigned", true, new ConfigDescription("Add a line when a chatter gets a creature. / Zeile anzeigen, wenn ein Chatter eine Kreatur bekommt.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 80 } })); FeedShowCreatureKilled = cfg.Bind<bool>("6 - Feed", "ShowCreatureKilled", true, new ConfigDescription("Add a line when a named creature dies. / Zeile anzeigen, wenn eine benannte Kreatur stirbt.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 79 } })); FeedScopeSetting = cfg.Bind<FeedScope>("6 - Feed", "Scope", FeedScope.All, new ConfigDescription("All: show events from every streamer in the group. OwnOnly: show only events from your own channel - if you have no channel set, that means none at all. / All: Ereignisse aller Streamer in der Gruppe anzeigen. OwnOnly: nur Ereignisse aus dem eigenen Kanal - ohne eingetragenen Kanal also gar keine.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 85, IsAdvanced = true } })); FeedAnchorPosition = cfg.Bind<FeedAnchor>("6 - Feed", "Anchor", FeedAnchor.TopRight, new ConfigDescription("Screen corner the feed is attached to. / Bildschirmecke, an der der Feed haengt.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 60 } })); _pendingPixelsX = TakePixels(cfg, "6 - Feed", "OffsetX"); _pendingPixelsY = TakePixels(cfg, "6 - Feed", "OffsetY"); FeedPositionX = cfg.Bind<float>("6 - Feed", "PositionX", 4f, new ConfigDescription("Distance from that corner, sideways, as a percentage of the screen. 0 sits flush against the edge. / Abstand von dieser Ecke zur Seite, in Prozent der Bildschirmbreite. 0 heisst buendig am Rand.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), new object[1] { new ConfigurationManagerAttributes { Order = 59, CustomDrawer = DrawPercent } })); FeedPositionY = cfg.Bind<float>("6 - Feed", "PositionY", 60f, new ConfigDescription("Distance from that corner, up or down, as a percentage of the screen height. / Abstand von dieser Ecke nach oben oder unten, in Prozent der Bildschirmhoehe.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), new object[1] { new ConfigurationManagerAttributes { Order = 58, CustomDrawer = DrawPercent } })); FeedWidth = cfg.Bind<int>("6 - Feed", "Width", 520, new ConfigDescription("Width of a line before it wraps, in pixels. / Breite einer Zeile bis zum Umbruch, in Pixeln.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(80, 1600), new object[1] { new ConfigurationManagerAttributes { Order = 57 } })); FeedFontSize = cfg.Bind<int>("6 - Feed", "FontSize", 22, new ConfigDescription("Font size of the feed. / Schriftgroesse des Feeds.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(8, 72), new object[1] { new ConfigurationManagerAttributes { Order = 50 } })); FeedTextColor = cfg.Bind<string>("6 - Feed", "TextColor", "#FFFFFF", new ConfigDescription("Colour of the normal text. The viewer name keeps its channel colour. / Farbe des normalen Texts. Der Zuschauername behaelt seine Kanalfarbe.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 49 } })); FeedShadow = cfg.Bind<bool>("6 - Feed", "Shadow", true, new ConfigDescription("Draws a dark shadow behind the text so it stays readable on bright scenery. / Zeichnet einen dunklen Schatten hinter den Text, damit er auf hellem Hintergrund lesbar bleibt.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 48 } })); FeedUseGameFont = cfg.Bind<bool>("6 - Feed", "UseGameFont", true, new ConfigDescription("Try to draw the feed with one of the game's own fonts instead of the plain built-in one. Falls back silently if none is usable - Valheim's UI is TextMeshPro, which does not always expose a font this can use. Run the console command 'ae fonts' to see what was found. / Versucht, den Feed mit einer Schrift des Spiels zu zeichnen statt mit der schlichten Standardschrift. Faellt stillschweigend zurueck, wenn keine brauchbar ist - Valheims UI ist TextMeshPro und gibt nicht immer eine passende Schrift her. Der Konsolenbefehl 'ae fonts' zeigt, was gefunden wurde.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 47, IsAdvanced = true } })); FeedGameFontName = cfg.Bind<string>("6 - Feed", "GameFontName", "", new ConfigDescription("Name of the font to use, from 'ae fonts'. Empty picks automatically. / Name der gewuenschten Schrift, siehe 'ae fonts'. Leer waehlt automatisch.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 46, IsAdvanced = true } })); FeedMaxEntries = cfg.Bind<int>("6 - Feed", "MaxEntries", 5, new ConfigDescription("How many lines are shown at once. / Wie viele Zeilen gleichzeitig angezeigt werden.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), new object[1] { new ConfigurationManagerAttributes { Order = 40 } })); FeedLifetimeSeconds = cfg.Bind<float>("6 - Feed", "LifetimeSeconds", 20f, new ConfigDescription("How long a line stays on screen, including the fade. / Wie lange eine Zeile stehen bleibt, samt Ausblenden.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 120f), new object[1] { new ConfigurationManagerAttributes { Order = 39 } })); FeedFadeSeconds = cfg.Bind<float>("6 - Feed", "FadeSeconds", 1.5f, new ConfigDescription("Length of the fade out at the end of the lifetime. 0 makes lines vanish instantly. / Dauer des Ausblendens am Ende der Lebensdauer. 0 laesst Zeilen sofort verschwinden.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), new object[1] { new ConfigurationManagerAttributes { Order = 38 } })); FeedTextColor.SettingChanged += delegate { RaiseDisplayChanged(); }; FeedFontSize.SettingChanged += delegate { RaiseDisplayChanged(); }; FeedAnchorPosition.SettingChanged += delegate { RaiseDisplayChanged(); }; FeedWidth.SettingChanged += delegate { RaiseDisplayChanged(); }; FeedPreview.SettingChanged += delegate { RaiseDisplayChanged(); }; FeedUseGameFont.SettingChanged += delegate { RaiseDisplayChanged(); }; FeedGameFontName.SettingChanged += delegate { RaiseDisplayChanged(); }; FeedPreviewAnimation.SettingChanged += delegate { if (FeedPreviewAnimation.Value) { RaisePreviewAnimationRequested(); FeedPreviewAnimation.Value = false; } }; ResetCreatures.SettingChanged += delegate { if (ResetCreatures.Value) { AllfathersEyePlugin instance = AllfathersEyePlugin.Instance; if ((Object)(object)instance != (Object)null) { instance.ResetCreatures(out var _); } ResetCreatures.Value = false; } }; } private static void DrawTwitchAccount(ConfigEntryBase entry) { TwitchAuth twitchAuth = ((AuthAccessor != null) ? AuthAccessor() : null); if (twitchAuth == null) { GUILayout.Label("Starting... / Startet...", Array.Empty<GUILayoutOption>()); return; } AuthState state = twitchAuth.State; string statusDetail = twitchAuth.StatusDetail; GUILayout.BeginVertical(Array.Empty<GUILayoutOption>()); switch (state) { case AuthState.Connected: GUILayout.Label((twitchAuth.Login.Length > 0) ? ("Connected as " + twitchAuth.Login + " / Verbunden als " + twitchAuth.Login) : "Connected / Verbunden", Array.Empty<GUILayoutOption>()); GUILayout.Label((twitchAuth.Login.Length > 0) ? ("Reading #" + twitchAuth.Login + ", and allowed to write there. / Liest #" + twitchAuth.Login + " mit und darf dort schreiben.") : "Reading chat, and allowed to write. / Liest den Chat mit und darf schreiben.", Array.Empty<GUILayoutOption>()); if (!twitchAuth.HasScope("moderator:read:chatters")) { GUILayout.Label("This sign-in is older than the viewer-points feature, so silent viewers cannot be seen and earn nothing. Disconnect and connect again to fix it. / Diese Anmeldung ist aelter als die Zuschauerpunkte - stille Zuschauer koennen nicht erkannt werden und verdienen nichts. Einmal trennen und neu verbinden behebt das.", Array.Empty<GUILayoutOption>()); } if (GUILayout.Button("Disconnect / Trennen", Array.Empty<GUILayoutOption>())) { twitchAuth.Disconnect(); } break; case AuthState.WaitingForUser: GUILayout.Label("Waiting for confirmation... / Warte auf Bestaetigung...", Array.Empty<GUILayoutOption>()); GUILayout.Label("1) Open / oeffne: " + twitchAuth.VerificationUri, Array.Empty<GUILayoutOption>()); GUILayout.Label("2) Enter this code / diesen Code eingeben:", Array.Empty<GUILayoutOption>()); GUILayout.TextField(twitchAuth.UserCode, Array.Empty<GUILayoutOption>()); GUILayout.Label("Do not show this code or that page on stream - the address bar contains the code too! / Diesen Code und diese Seite nicht im Stream zeigen - die Adresszeile enthaelt den Code ebenfalls!", Array.Empty<GUILayoutOption>()); if (GUILayout.Button("Open page again / Seite erneut oeffnen", Array.Empty<GUILayoutOption>())) { Application.OpenURL(twitchAuth.VerificationUri); } if (GUILayout.Button("Cancel / Abbrechen", Array.Empty<GUILayoutOption>())) { twitchAuth.CancelPending(); } break; case AuthState.Requesting: GUILayout.Label((statusDetail.Length > 0) ? statusDetail : "Contacting Twitch... / Kontaktiere Twitch...", Array.Empty<GUILayoutOption>()); break; default: if (statusDetail.Length > 0) { GUILayout.Label(statusDetail, Array.Empty<GUILayoutOption>()); } else { GUILayout.Label("Not connected / Nicht verbunden", Array.Empty<GUILayoutOption>()); } GUILayout.Label("Names are still handed out - the Channel below is read anonymously. Signing in only adds writing in your own chat. / Namen werden trotzdem vergeben - der Kanal unten wird anonym mitgelesen. Das Anmelden erlaubt zusaetzlich das Schreiben im eigenen Chat.", Array.Empty<GUILayoutOption>()); if (GUILayout.Button("Connect Twitch / Mit Twitch verbinden", Array.Empty<GUILayoutOption>())) { twitchAuth.BeginConnect(); } break; } GUILayout.EndVertical(); } private static void DrawOverlayStatus(ConfigEntryBase entry) { OverlaySync overlaySync = ((OverlayAccessor != null) ? OverlayAccessor() : null); TwitchAuth twitchAuth = ((AuthAccessor != null) ? AuthAccessor() : null); GUILayout.BeginVertical(Array.Empty<GUILayoutOption>()); if (!PointsEnabled.Value || !ShopEnabled.Value) { GUILayout.Label("Off: there is no shop to show. Switch Points and Shop on. / Aus: es gibt keinen Shop zu zeigen. Punkte und Shop einschalten.", Array.Empty<GUILayoutOption>()); } else if (twitchAuth == null || twitchAuth.UserId.Length == 0) { GUILayout.Label("Off: sign in above first - the overlay belongs to your own channel. / Aus: erst oben anmelden - das Overlay gehoert zu deinem eigenen Kanal.", Array.Empty<GUILayoutOption>()); } else if (overlaySync == null || !overlaySync.IsConnected) { GUILayout.Label("Cannot be reached. Without the extension on your channel this stays so, and that is fine - nothing else depends on it. / Nicht erreichbar. Ohne die Erweiterung auf deinem Kanal bleibt das so, und das ist in Ordnung - nichts anderes haengt daran.", Array.Empty<GUILayoutOption>()); } else if (overlaySync.IsWatched) { GUILayout.Label("Active: somebody has the shop open on your stream. / Aktiv: jemand hat den Shop in deinem Stream offen.", Array.Empty<GUILayoutOption>()); } else { GUILayout.Label("Ready: nobody is looking at the shop, so nothing is being sent. / Bereit: niemand schaut auf den Shop, also wird nichts gesendet.", Array.Empty<GUILayoutOption>()); } if (GUILayout.Button("How it works / Wie es funktioniert", Array.Empty<GUILayoutOption>())) { Application.OpenURL("https://allfatherseye.sohneg.ch"); } GUILayout.EndVertical(); } public static void DropStaleEntries(ConfigFile cfg) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown bool flag = false; for (int i = 0; i < StaleEntries.Length; i += 2) { ConfigDefinition val = new ConfigDefinition(StaleEntries[i], StaleEntries[i + 1]); cfg.Bind<string>(val, "", (ConfigDescription)null); flag |= cfg.Remove(val); } if (flag) { cfg.Save(); } } private static int TakePixels(ConfigFile cfg, string section, string key) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected O, but got Unknown ConfigDefinition val = new ConfigDefinition(section, key); ConfigEntry<int> val2 = cfg.Bind<int>(val, -1, (ConfigDescription)null); int value = val2.Value; cfg.Remove(val); return value; } public static void MigratePendingPositions() { if (_pendingPixelsX < 0 && _pendingPixelsY < 0) { return; } int width = Screen.width; int height = Screen.height; if (width >= 640 && height >= 480) { if (_pendingPixelsX >= 0) { FeedPositionX.Value = FeedLayout.PercentOf(_pendingPixelsX, width); } if (_pendingPixelsY >= 0) { FeedPositionY.Value = FeedLayout.PercentOf(_pendingPixelsY, height); } _pendingPixelsX = -1; _pendingPixelsY = -1; } } private static void DrawPercent(ConfigEntryBase entry) { if (entry is ConfigEntry<float> val) { float num = FeedLayout.Round(GUILayout.HorizontalSlider(val.Value, 0f, 100f, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) })); GUILayout.Label(num.ToString("0.#", CultureInfo.InvariantCulture) + " %", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(52f) }); if (num != val.Value) { val.Value = num; } } } private static void DrawResetButton(ConfigEntryBase entry) { GUILayout.BeginVertical(Array.Empty<GUILayoutOption>()); if (GUILayout.Button("Reset all creatures / Alle Kreaturen zuruecksetzen", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) })) { AllfathersEyePlugin instance = AllfathersEyePlugin.Instance; if ((Object)(object)instance == (Object)null) { _lastResetResult = "Not ready. / Noch nicht bereit."; } else { int refused; int num = instance.ResetCreatures(out refused); _lastResetResult = num + " name(s) cleared" + ((refused > 0) ? (", " + refused + " belong to another player") : "") + " / " + num + " Name(n) entfernt" + ((refused > 0) ? (", " + refused + " gehoeren einem anderen Spieler") : ""); } } if (_lastResetResult.Length > 0) { GUILayout.Label(_lastResetResult, Array.Empty<GUILayoutOption>()); } GUILayout.EndVertical(); } private static void DrawPreviewAnimationButton(ConfigEntryBase entry) { if (GUILayout.Button("Play test sequence / Testsequenz abspielen", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) })) { RaisePreviewAnimationRequested(); } } private static void RaisePreviewAnimationRequested() { ModConfig.PreviewAnimationRequested?.Invoke(); } private static void RebuildCaches() { IgnoredUserSet = SplitToSet(IgnoredUsers.Value); PrefabBlacklistSet = SplitToSet(PrefabBlacklist.Value); SafeViewerColor = ColorSpec.Sanitize(ViewerColor.Value); ChannelColors = ColorSpec.ParseMap(ColorPerChannel.Value); RaiseDisplayChanged(); } public static string ColorForChannel(string channel) { if (!string.IsNullOrEmpty(channel) && ChannelColors.TryGetValue(channel, out var value)) { return value; } return SafeViewerColor; } private static void RaiseDisplayChanged() { ModConfig.DisplayChanged?.Invoke(); } private static HashSet<string> SplitToSet(string csv) { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrEmpty(csv)) { return hashSet; } string[] array = csv.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { hashSet.Add(text); } } return hashSet; } } [BepInPlugin("sohneg.AllfathersEye", "Allfather's Eye", "0.23.7")] [BepInProcess("valheim.exe")] public class AllfathersEyePlugin : BaseUnityPlugin { public const string ModGuid = "sohneg.AllfathersEye"; public const string ModName = "Allfather's Eye"; public const string ModVersion = "0.23.7"; public const string TwitchClientId = "tcwqrjygut9d0qszcqytwq8kdakfze"; private const TlsValidationMode TlsPolicy = TlsValidationMode.PinnedRoots; private const float PruneIntervalSeconds = 30f; private static readonly TimeSpan AnnounceCooldown = TimeSpan.FromMinutes(10.0); private readonly List<IChatHandler> _chatHandlers = new List<IChatHandler>(); private Harmony _harmony; private NameAssigner _assigner; private TwitchChatClient _client; private EventFeed _feed; private TwitchAuth _auth; private FeedNetwork _network; private CommandResponder _commands; private PointService _points; private ShopService _shop; private OverlaySync _overlay; private readonly ViewerDirectory _viewers = new ViewerDirectory(); private string _lastCatalog = ""; private const float CatalogCheckSeconds = 10f; private float _nextCatalogCheckAt; private bool _connectionSettingsDirty; private bool _wasInWorld; private float _nextPruneAt; private DateTime _lastAnnounceUtc = DateTime.MinValue; private DateTime _nextReminderUtc = DateTime.MinValue; private readonly CallOut _callOut = new CallOut(); private readonly List<Character> _callOutCandidates = new List<Character>(64); public static AllfathersEyePlugin Instance { get; private set; } public static ManualLogSource Log { get; private set; } public NameAssigner Assigner => _assigner; public TwitchChatClient Client => _client; public EventFeed Feed => _feed; public PointService Points => _points; public ShopService Shop => _shop; public FeedNetwork Network => _network; public TwitchAuth Auth => _auth; public int ResetCreatures(out int refused) { int result = NameStore.ClearAll(out refused); if (_assigner != null) { _assigner.ClearQueue(); _assigner.ResetChatterState(); } HoverNamePatch.InvalidateCache(); ((BaseUnityPlugin)this).Logger.