using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Steamworks; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyCompany("RustyLaserGaming")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Fantasy rune Viking character customization for Valheim.")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0")] [assembly: AssemblyProduct("RustyHeathensRuneborn")] [assembly: AssemblyTitle("RustyHeathensRuneborn")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("0.1.0.0")] namespace RustyHeathensRuneborn; [HarmonyPatch(typeof(Character), "OnDeath")] public static class RunebornBossKillMessagesPatch { private const string RpcReportBossKillName = "RustyHeathensMessenger_ReportBossKill"; private static readonly HashSet _announcedBosses = new HashSet(); private static ZRoutedRpc _registeredRpcInstance; private static string _lastBossKey = string.Empty; private static float _lastBossMessageTime; public static void EnsureRegistered() { if (ZRoutedRpc.instance != null && _registeredRpcInstance != ZRoutedRpc.instance) { _registeredRpcInstance = ZRoutedRpc.instance; ZRoutedRpc.instance.Register("RustyHeathensMessenger_ReportBossKill", (Action)RPC_ReportBossKill); } } private static void Postfix(Character __instance) { if ((Object)(object)__instance == (Object)null) { return; } EnsureRegistered(); string characterSearchText = GetCharacterSearchText(__instance); string bossDisplayName = GetBossDisplayName(characterSearchText); if (string.IsNullOrEmpty(bossDisplayName)) { return; } int num = (((Object)(object)((Component)__instance).gameObject != (Object)null) ? ((Object)((Component)__instance).gameObject).GetInstanceID() : 0); if (num != 0) { if (_announcedBosses.Contains(num)) { return; } _announcedBosses.Add(num); } if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { AnnounceBossKillFromServer(bossDisplayName); } else if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(GetServerPeerId(), "RustyHeathensMessenger_ReportBossKill", new object[1] { bossDisplayName }); } } private static void RPC_ReportBossKill(long sender, string bossDisplayName) { if (!((Object)(object)ZNet.instance != (Object)null) || ZNet.instance.IsServer()) { AnnounceBossKillFromServer(bossDisplayName); } } private static void AnnounceBossKillFromServer(string bossDisplayName) { if (!string.IsNullOrWhiteSpace(bossDisplayName) && RunebornServerConfigSync.BossKillMessagesEnabled()) { string text = bossDisplayName.ToLowerInvariant(); if (!(_lastBossKey == text) || !(Time.time - _lastBossMessageTime < 8f)) { _lastBossKey = text; _lastBossMessageTime = Time.time; RunebornMessageBroadcast.BroadcastCenter("The Rusty Heathens defeated " + bossDisplayName + "!\nValhalla heard the victory roar."); } } } private static string GetCharacterSearchText(Character character) { List list = new List(); if ((Object)(object)character == (Object)null) { return string.Empty; } if ((Object)(object)((Component)character).gameObject != (Object)null) { list.Add(((Object)((Component)character).gameObject).name); } if (!string.IsNullOrWhiteSpace(((Object)character).name)) { list.Add(((Object)character).name); } try { FieldInfo fieldInfo = AccessTools.Field(typeof(Character), "m_name"); if (fieldInfo != null) { object value = fieldInfo.GetValue(character); if (value != null) { list.Add(value.ToString()); } } } catch { } return string.Join(" ", list.ToArray()); } private static string GetBossDisplayName(string rawName) { if (string.IsNullOrEmpty(rawName)) { return string.Empty; } string text = rawName.ToLowerInvariant(); if (text.Contains("eikthyr")) { return "Eikthyr"; } if (text.Contains("gd_king") || text.Contains("theelder") || text.Contains("elder")) { return "The Elder"; } if (text.Contains("bonemass")) { return "Bonemass"; } if (text.Contains("dragon") || text.Contains("moder")) { return "Moder"; } if (text.Contains("goblinking") || text.Contains("yagluth")) { return "Yagluth"; } if (text.Contains("queen") || text.Contains("seekerqueen")) { return "The Queen"; } if (text.Contains("fader")) { return "Fader"; } return string.Empty; } private static long GetServerPeerId() { try { if ((Object)(object)ZNet.instance == (Object)null) { return ZRoutedRpc.Everybody; } MethodInfo method = typeof(ZNet).GetMethod("GetServerPeer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { return ZRoutedRpc.Everybody; } object obj = method.Invoke(ZNet.instance, null); if (obj == null) { return ZRoutedRpc.Everybody; } FieldInfo field = obj.GetType().GetField("m_uid", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field == null) { return ZRoutedRpc.Everybody; } if (field.GetValue(obj) is long result) { return result; } } catch { } return ZRoutedRpc.Everybody; } } [HarmonyPatch(typeof(Chat), "SendText")] public static class RunebornChatCommands { private static int _funnyPickCounter = -1; private const string RpcProtectedCommandRequestName = "RustyHeathensMessenger_ProtectedCommandRequest"; private static ZRoutedRpc _registeredRpcInstance; public static void EnsureRegistered() { if (ZRoutedRpc.instance != null && _registeredRpcInstance != ZRoutedRpc.instance) { _registeredRpcInstance = ZRoutedRpc.instance; ZRoutedRpc.instance.Register("RustyHeathensMessenger_ProtectedCommandRequest", (Action)RPC_ProtectedCommandRequest); } } private static void RequestProtectedCommandFromServer(string command) { EnsureRegistered(); string localSteamIdForCommandRequest = GetLocalSteamIdForCommandRequest(); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { if (!ServerSteamIdIsAllowed(localSteamIdForCommandRequest)) { RunebornMessageBroadcast.BroadcastCenter("COMMAND LOCKED\n\nOnly approved server admins can use this command."); } else if (IsRhmdiagCommand(command)) { RunebornMessageBroadcast.BroadcastCenter(BuildServerDiagnosticsMessage()); } else { HandleCommand(command); } } else if (ZRoutedRpc.instance == null) { RunebornMessageBroadcast.ShowLocalCenter("COMMAND WAITING\n\nServer permissions are not ready yet."); } else { long serverPeerIdForProtectedCommands = GetServerPeerIdForProtectedCommands(); ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerIdForProtectedCommands, "RustyHeathensMessenger_ProtectedCommandRequest", new object[2] { command, localSteamIdForCommandRequest }); } } private static void RPC_ProtectedCommandRequest(long sender, string command, string steamId) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || string.IsNullOrWhiteSpace(command)) { return; } command = command.Trim().ToLowerInvariant(); if (IsRhmCommand(command) && !IsAlwaysAllowedCommand(command)) { if (!ServerSteamIdIsAllowed(steamId)) { RunebornMessageBroadcast.BroadcastCenter("COMMAND LOCKED\n\nOnly approved server admins can use this command."); } else if (IsRhmdiagCommand(command)) { RunebornMessageBroadcast.BroadcastCenter(BuildServerDiagnosticsMessage()); } else { HandleCommand(command); } } } private static string GetLocalSteamIdForCommandRequest() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) try { return SteamUser.GetSteamID().m_SteamID.ToString(); } catch { } if ((Object)(object)Player.m_localPlayer != (Object)null) { return Player.m_localPlayer.GetPlayerID().ToString(); } return string.Empty; } private static long GetServerPeerIdForProtectedCommands() { try { if ((Object)(object)ZNet.instance != (Object)null) { MethodInfo method = typeof(ZNet).GetMethod("GetServerPeer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method != null) { object obj = method.Invoke(ZNet.instance, null); if (obj != null) { FieldInfo field = obj.GetType().GetField("m_uid", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.GetValue(obj) is long num && num != 0) { return num; } } } } } catch { } return ZRoutedRpc.Everybody; } private static bool ServerSteamIdIsAllowed(string steamId) { if (string.IsNullOrWhiteSpace(steamId)) { return false; } string a = ((RunebornPlugin.CommandPermissionModeEntry != null) ? RunebornPlugin.CommandPermissionModeEntry.Value : "SteamIds"); if (string.Equals(a, "Everyone", StringComparison.OrdinalIgnoreCase)) { return true; } string text = ((RunebornPlugin.CommandAllowedSteamIdsEntry != null) ? RunebornPlugin.CommandAllowedSteamIdsEntry.Value : string.Empty); if (string.IsNullOrWhiteSpace(text)) { return false; } string[] array = text.Split(new char[4] { ',', ';', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); string[] array2 = array; foreach (string text2 in array2) { string a2 = text2.Trim(); if (string.Equals(a2, steamId.Trim(), StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static bool IsRhmdiagCommand(string command) { return string.Equals(command, "!rhmdiag", StringComparison.OrdinalIgnoreCase) || string.Equals(command, "/rhm rhmdiag", StringComparison.OrdinalIgnoreCase); } private static string BuildServerDiagnosticsMessage() { bool flag = RunebornPlugin.ChatCommandsEnabledEntry == null || RunebornPlugin.ChatCommandsEnabledEntry.Value; bool flag2 = RunebornPlugin.DeathLedgerEnabledEntry == null || RunebornPlugin.DeathLedgerEnabledEntry.Value; bool flag3 = RunebornPlugin.PlayerJoinMessagesEnabledEntry == null || RunebornPlugin.PlayerJoinMessagesEnabledEntry.Value; bool flag4 = RunebornPlugin.StreamModeEnabledEntry == null || RunebornPlugin.StreamModeEnabledEntry.Value; string text = ((RunebornPlugin.CommandPermissionModeEntry != null) ? RunebornPlugin.CommandPermissionModeEntry.Value : "Unknown"); return "RUSTY HEATHENS DIAGNOSTICS\n\nVersion: 0.18.6\nCommands: " + (flag ? "Enabled" : "Disabled") + "\nDeath Ledger: " + (flag2 ? "Enabled" : "Disabled") + "\nJoin Messages: " + (flag3 ? "Enabled" : "Disabled") + "\nStream Mode: " + (flag4 ? "Enabled" : "Disabled") + "\nPermissions: Server checked\nMode: " + text; } private static bool Prefix(Type type, string text) { if (string.IsNullOrWhiteSpace(text)) { return true; } string command = text.Trim().ToLowerInvariant(); if (!IsRhmCommand(command)) { return true; } if (!RunebornServerConfigSync.ChatCommandsEnabled()) { return true; } if (!IsAlwaysAllowedCommand(command)) { RequestProtectedCommandFromServer(command); return false; } HandleCommand(command); return false; } private static bool IsRhmCommand(string command) { int result; switch (command) { default: result = ((command == "!raid") ? 1 : 0); break; case "/rhm": case "/rhm help": case "!help": case "/rhm testbroadcast": case "!testbroadcast": case "/rhm whoami": case "!whoami": case "/rhm rhmdiag": case "!rhmdiag": case "/rhm deaths": case "!deaths": case "/rhm mydeaths": case "!mydeaths": case "/rhm deathclear confirm": case "!deathclear confirm": case "/rhm streammode": case "/rhm streammode on": case "/rhm streammode off": case "!streammode": case "!streammode on": case "!streammode off": case "/rhm stream": case "!stream": case "/rhm boss": case "!boss": case "/rhm base": case "!base": case "/rhm discord": case "!discord": case "/rhm ending": case "/rhm end": case "/rhm stream ending": case "!ending": case "!end": case "!streamending": case "/rhm body": case "!body": case "!version": case "/rhm version": case "/rhm raid": result = 1; break; } return (byte)result != 0; } private static bool IsAlwaysAllowedCommand(string command) { int result; switch (command) { default: result = ((command == "!whoami") ? 1 : 0); break; case "!help": case "/rhm": case "/rhm help": case "/rhm deaths": case "!deaths": case "/rhm mydeaths": case "!mydeaths": case "!version": case "/rhm version": case "/rhm whoami": result = 1; break; } return (byte)result != 0; } private static bool PlayerCanUseCommands() { string a = ((RunebornPlugin.CommandPermissionModeEntry != null) ? RunebornPlugin.CommandPermissionModeEntry.Value : "Everyone"); if (string.Equals(a, "Everyone", StringComparison.OrdinalIgnoreCase)) { return true; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return false; } if (string.Equals(a, "HostOnly", StringComparison.OrdinalIgnoreCase)) { return (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer(); } if (string.Equals(a, "NamedPlayers", StringComparison.OrdinalIgnoreCase)) { return PlayerNameIsAllowed(localPlayer); } if (string.Equals(a, "SteamIds", StringComparison.OrdinalIgnoreCase)) { return PlayerSteamIdIsAllowed(localPlayer); } if (string.Equals(a, "NamedPlayersOrSteamIds", StringComparison.OrdinalIgnoreCase)) { return PlayerNameIsAllowed(localPlayer) || PlayerSteamIdIsAllowed(localPlayer); } if (string.Equals(a, "ServerAdminList", StringComparison.OrdinalIgnoreCase)) { return PlayerIsServerAdmin(localPlayer) || PlayerSteamIdIsAllowed(localPlayer); } return true; } private static bool PlayerIsServerAdmin(Player player) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null) { return false; } if (ZNet.instance.IsServer()) { return true; } List list = new List(); if ((Object)(object)player != (Object)null) { list.Add(player.GetPlayerID().ToString()); } try { list.Add(ZNet.GetUID().ToString()); } catch { } try { list.Add(SteamUser.GetSteamID().m_SteamID.ToString()); } catch { } foreach (string item in list) { if (string.IsNullOrWhiteSpace(item) || !ZNetIsAdmin(item.Trim())) { continue; } return true; } return false; } private static bool ZNetIsAdmin(string id) { try { MethodInfo[] methods = typeof(ZNet).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); MethodInfo[] array = methods; foreach (MethodInfo methodInfo in array) { if (methodInfo.Name != "IsAdmin") { continue; } ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length != 1) { continue; } object obj = null; if (parameters[0].ParameterType == typeof(string)) { obj = methodInfo.Invoke(ZNet.instance, new object[1] { id }); } else if (parameters[0].ParameterType == typeof(long)) { if (!long.TryParse(id, out var result)) { continue; } obj = methodInfo.Invoke(ZNet.instance, new object[1] { result }); } if (!(obj is int num) || num == 0) { continue; } return true; } } catch { } return false; } private static bool PlayerNameIsAllowed(Player player) { string text = ((RunebornPlugin.CommandAllowedPlayerNamesEntry != null) ? RunebornPlugin.CommandAllowedPlayerNamesEntry.Value : string.Empty); string playerName = player.GetPlayerName(); string[] array = text.Split(new char[1] { ',' }); string[] array2 = array; foreach (string text2 in array2) { if (string.Equals(text2.Trim(), playerName, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static bool PlayerSteamIdIsAllowed(Player player) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) string text = ((RunebornPlugin.CommandAllowedSteamIdsEntry != null) ? RunebornPlugin.CommandAllowedSteamIdsEntry.Value : string.Empty); if (string.IsNullOrWhiteSpace(text)) { return false; } List list = new List(); try { list.Add(SteamUser.GetSteamID().m_SteamID.ToString()); } catch { } try { list.Add(ZNet.GetUID().ToString()); } catch { } if ((Object)(object)player != (Object)null) { list.Add(player.GetPlayerID().ToString()); } string[] array = text.Split(new char[4] { ',', ';', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); string[] array2 = array; foreach (string text2 in array2) { string text3 = text2.Trim(); if (string.IsNullOrWhiteSpace(text3)) { continue; } foreach (string item in list) { string b = item.Trim(); if (string.Equals(text3, b, StringComparison.OrdinalIgnoreCase)) { return true; } } } return false; } private static string BuildVersionMessage() { return "RUSTY HEATHENS MESSENGER\n\nVersion: 0.18.6\nPermissions: Server-side\nMode: Messaging only\n\n"; } private static void HandleCommand(string command) { if (command == "!version" || command == "/rhm version") { RunebornMessageBroadcast.ShowLocalCenter(BuildVersionMessage()); return; } if (command == "!help" || command == "/rhm" || command == "/rhm help") { RunebornMessageBroadcast.ShowLocalCenter("RUSTY HEATHENS MESSENGER\n\nOpen Commands:\n!help - Show this help\n!version - Show mod version\n!whoami - Show your player info\n!deaths - Show death ledger\n!mydeaths - Show your death count\n\nProtected Commands:\n!stream - Stream starting soon\n!boss - Boss fight starting soon\n!base - Return to base\n!discord - Discord reminder\n!ending - Stream ending\n!body - Heathen down\n!raid - Incoming raid\n!testbroadcast - Test server broadcast\n!rhmdiag - Server diagnostics\n!deathclear confirm - Clear death ledger"); return; } if (command == "/rhm testbroadcast" || command == "!testbroadcast") { ShowCenter("BROADCAST TEST\n\nIf you can see this, Rusty Heathens Messenger broadcasts are working."); return; } if (command == "/rhm whoami" || command == "!whoami") { RunebornMessageBroadcast.ShowLocalCenter(BuildWhoAmIMessage()); return; } if (command == "/rhm rhmdiag" || command == "!rhmdiag") { RunebornMessageBroadcast.ShowLocalCenter(BuildDiagnosticsMessage()); return; } if (command == "/rhm deaths" || command == "!deaths") { RunebornDeathLedger.RequestLedgerBroadcast(); return; } if (command == "/rhm mydeaths" || command == "!mydeaths") { RunebornDeathLedger.RequestMyDeaths(Player.m_localPlayer); return; } if (command == "/rhm deathclear confirm" || command == "!deathclear confirm") { RunebornDeathLedger.ClearLedgerBroadcast(); return; } switch (command) { default: if (!(command == "!streammode off")) { if (command == "/rhm stream" || command == "!stream") { ShowConfiguredMessage(RunebornPlugin.StreamTitleEntry, RunebornPlugin.StreamBodyEntry, RunebornPlugin.StreamStyleEntry, "STREAM STARTING SOON", "The Rusty Heathens are gathering.\nGrab your gear, check your food, and prepare for chaos."); break; } if (command == "/rhm boss" || command == "!boss") { ShowConfiguredMessage(RunebornPlugin.BossStartTitleEntry, RunebornPlugin.BossStartBodyEntry, RunebornPlugin.BossStartStyleEntry, "BOSS FIGHT STARTING SOON", "Return to base, repair your gear, grab food, and wait for the group."); break; } if (command == "/rhm base" || command == "!base") { ShowConfiguredMessage(RunebornPlugin.BaseTitleEntry, RunebornPlugin.BaseBodyEntry, RunebornPlugin.BaseStyleEntry, "RETURN TO BASE", "Everyone head back to base.\nRegroup before the chaos gets worse."); break; } if (command == "/rhm discord" || command == "!discord") { ShowConfiguredMessage(RunebornPlugin.DiscordTitleEntry, RunebornPlugin.DiscordBodyEntry, RunebornPlugin.DiscordStyleEntry, "DISCORD REMINDER", "Use Discord for rules, event plans, updates, and stream info.\nMessage RustyLaser if you need help."); break; } switch (command) { default: if (!(command == "!streamending")) { if (command == "/rhm body" || command == "!body") { ShowConfiguredMessage(RunebornPlugin.BodyTitleEntry, RunebornPlugin.BodyBodyEntry, RunebornPlugin.BodyStyleEntry, "HEATHEN DOWN", "Head back to base and regroup.\nA Heathen needs help recovering their kit."); } else if (command == "/rhm raid" || command == "!raid") { ShowConfiguredMessage(RunebornPlugin.RaidCommandTitleEntry, RunebornPlugin.RaidCommandBodyEntry, RunebornPlugin.RaidCommandStyleEntry, "INCOMING RAID", "Head back to base.\nHelp keep the Rusty Heathens safe."); } break; } goto case "/rhm ending"; case "/rhm ending": case "/rhm end": case "/rhm stream ending": case "!ending": case "!end": ShowConfiguredMessage(RunebornPlugin.EndingTitleEntry, RunebornPlugin.EndingBodyEntry, RunebornPlugin.EndingStyleEntry, "STREAM IS ENDING", "The stream is ending.\nIf you want to say bye to chat, return to the wall."); break; } break; } goto case "/rhm streammode"; case "/rhm streammode": case "/rhm streammode on": case "/rhm streammode off": case "!streammode": case "!streammode on": HandleStreamModeCommand(command); break; } } private static string BuildWhoAmIMessage() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; string text = "Unknown"; string text2 = "Unavailable"; if ((Object)(object)localPlayer != (Object)null) { text = localPlayer.GetPlayerName(); } try { text2 = SteamUser.GetSteamID().m_SteamID.ToString(); } catch { } return "PLAYER INFO\n\nName: " + text + "\nSteam ID: " + text2 + "\nVersion: 0.18.6\nPermissions: Server checked\nHelp: !help"; } private static string BuildDiagnosticsMessage() { return BuildServerDiagnosticsMessage(); } private static void HandleStreamModeCommand(string command) { if (RunebornPlugin.StreamModeEnabledEntry != null && !RunebornPlugin.StreamModeEnabledEntry.Value) { ShowCenter("STREAM MODE DISABLED\n\nStream Mode commands are disabled in the config."); } else if (command == "/rhm streammode on" || command == "!streammode on") { RunebornPlugin.StreamModeActive = true; string configText = GetConfigText(RunebornPlugin.StreamModeOnTitleEntry, "STREAM MODE ENABLED"); string configText2 = GetConfigText(RunebornPlugin.StreamModeOnBodyEntry, "Chat is watching.\nKeep it fun, keep it moving, and prepare for Rusty Heathens chaos."); ShowCenter(configText + "\n\n" + configText2); } else if (command == "/rhm streammode off" || command == "!streammode off") { RunebornPlugin.StreamModeActive = false; string configText3 = GetConfigText(RunebornPlugin.StreamModeOffTitleEntry, "STREAM MODE DISABLED"); string configText4 = GetConfigText(RunebornPlugin.StreamModeOffBodyEntry, "Chat has been released.\nYou may now return to unsupervised bad decisions."); ShowCenter(configText3 + "\n\n" + configText4); } else { string configText5 = GetConfigText(RunebornPlugin.StreamModeStatusTitleEntry, "STREAM MODE STATUS"); string text = (RunebornPlugin.StreamModeActive ? GetConfigText(RunebornPlugin.StreamModeStatusOnBodyEntry, "Stream Mode is ON.\nChat is watching the chaos unfold.") : GetConfigText(RunebornPlugin.StreamModeStatusOffBodyEntry, "Stream Mode is OFF.\nThe Heathens are currently causing chaos off-camera.")); ShowCenter(configText5 + "\n\n" + text); } } private static void ShowConfiguredMessage(ConfigEntry titleEntry, ConfigEntry bodyEntry, ConfigEntry styleEntry, string defaultTitle, string defaultBody) { string configText = GetConfigText(titleEntry, defaultTitle); string configText2 = GetConfigText(bodyEntry, defaultBody); string configText3 = GetConfigText(styleEntry, "Normal"); string text = ApplyMessageStyle(configText, configText2, configText3); if (RunebornPlugin.StreamModeActive) { string configText4 = GetConfigText(RunebornPlugin.StreamModeFooterEntry, "LIVE STREAM MODE: Chat is watching."); if (!string.IsNullOrWhiteSpace(configText4)) { text = text + "\n\n" + configText4; } } ShowCenter(text); } private static string ApplyMessageStyle(string title, string body, string style) { style = (string.IsNullOrWhiteSpace(style) ? "Normal" : style.Trim()); if (string.Equals(style, "Epic", StringComparison.OrdinalIgnoreCase)) { return "=== " + title.ToUpperInvariant() + " ===\n\n" + body; } if (string.Equals(style, "Funny", StringComparison.OrdinalIgnoreCase)) { return GetFunnyFooter(title) + "\n\n" + title.ToUpperInvariant() + "\n\n" + body; } if (string.Equals(style, "Emergency", StringComparison.OrdinalIgnoreCase)) { return "!!! " + title.ToUpperInvariant() + " !!!\n\n" + body; } return title + "\n\n" + body; } private static string GetFunnyHeader(string title) { string text = title.ToLowerInvariant(); if (text.Contains("stream starting")) { return "CHAT'S WATCHING: " + title.ToUpperInvariant(); } if (text.Contains("boss")) { return "BAD IDEA LOADING: " + title.ToUpperInvariant(); } if (text.Contains("return to base")) { return "PANIC WALK ENGAGED: " + title.ToUpperInvariant(); } if (text.Contains("discord")) { return "READ THE RUNES: " + title.ToUpperInvariant(); } if (text.Contains("ending")) { return "LAST CALL HEATHENS: " + title.ToUpperInvariant(); } if (text.Contains("heathen down")) { return "CORPSE RUN O'CLOCK: " + title.ToUpperInvariant(); } if (text.Contains("raid")) { return "PANIC PROFESSIONALLY: " + title.ToUpperInvariant(); } return "RUSTY HEATHENS PSA: " + title.ToUpperInvariant(); } private static string GetFunnyFooter(string title) { string text = title.ToLowerInvariant(); if (text.Contains("stream starting")) { return Pick(new string[5] { "Try to look busy. Chat is watching.", "The mead is warm, the gear is broken, and somehow we are live.", "Everybody act professional. Or at least Viking-adjacent.", "Rusty hit the button. There is no going back now.", "Panda the Chaos Bringer has entered the chat. Prepare accordingly." }); } if (text.Contains("boss")) { return Pick(new string[5] { "Bring food, bring courage, and maybe bring a backup plan.", "If you forgot rested buff, Odin already judged you.", "This is the part where confidence becomes a corpse run.", "Check your gear now. Regret waits at the boss altar.", "Panda called it a plan. The rest of us call it evidence." }); } if (text.Contains("return to base")) { return Pick(new string[5] { "If you are lost, follow the screaming.", "Return to base before the base becomes a memory.", "The longship of poor decisions is heading home.", "Regroup now. Wander dramatically later.", "Someone check on Seppy before he finds another creative way to die." }); } if (text.Contains("discord")) { return Pick(new string[5] { "Yes, this is your reminder before someone says they did not know.", "The runes were posted in Discord. Probably.", "Check Discord before asking Rusty for the third time.", "Important things happen there. Sometimes even plans.", "Discord has the rules. Panda has the chaos. Try not to confuse them." }); } if (text.Contains("ending")) { return Pick(new string[5] { "Last chance to wave before Rusty hits the button.", "Say bye to chat before Valhalla claims the outro.", "Return to the wall. The ceremonial goodbye awkwardness begins.", "The stream is ending, but the chaos will remember us.", "One final wave before Panda starts another problem." }); } if (text.Contains("heathen down")) { return Pick(new string[5] { "A kit has fallen. Again. Nobody act surprised.", "Someone has donated gear to the wilderness.", "Corpse run time. Try not to create a second one.", "A Heathen is down. Somebody get Witchy before this becomes a ritual.", "Witchy saw the body and said, 'That looks like a learning experience.'" }); } if (text.Contains("raid")) { return Pick(new string[5] { "Heathens to the walls. Panic professionally.", "The base is under attack. Try not to decorate the floor with your corpse.", "This is not a drill. Unless it is a troll. Then it is worse.", "Protect the base, the mead, and whatever Seppy is standing near.", "Panda the Chaos Bringer says this is fine. It is not fine." }); } return Pick(new string[5] { "This is probably fine.", "Odin has questions.", "The Rusty Heathens have made another decision.", "Somebody write this down as a lesson.", "Chaos has entered the server." }); } private static string Pick(string[] lines) { if (lines == null || lines.Length == 0) { return "This is probably fine."; } if (lines.Length == 1) { return lines[0]; } _funnyPickCounter++; if (_funnyPickCounter >= lines.Length) { _funnyPickCounter = 0; } return lines[_funnyPickCounter]; } private static string GetConfigText(ConfigEntry entry, string fallback) { if (entry == null) { return fallback; } if (string.IsNullOrWhiteSpace(entry.Value)) { return fallback; } return entry.Value.Replace("\\n", "\n"); } private static void ShowCenter(string message) { RunebornMessageBroadcast.BroadcastCenter(message); } } [HarmonyPatch(typeof(Player), "Awake")] public static class RunebornDeathCounterPlayerAwakePatch { private static void Postfix(Player __instance) { if (!((Object)(object)__instance == (Object)null) && (Object)(object)((Component)__instance).GetComponent() == (Object)null) { ((Component)__instance).gameObject.AddComponent(); } } } public class RunebornDeathCounter : MonoBehaviour { private const string DeathCountKey = "RustyHeathensMessenger.DeathCount"; private Player _player; private bool _wasDead; private bool _deathAlreadyCounted; private bool _hasBeenAlive; private static DateTime _lastLocalDeathReportTime = DateTime.MinValue; private const double LocalDeathDuplicateWindowSeconds = 10.0; private static int _lastFunnyDeathMessageIndex = -1; private void Awake() { _player = ((Component)this).GetComponent(); } private void Update() { if ((Object)(object)_player == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)_player != (Object)(object)Player.m_localPlayer) { return; } bool flag = ((Character)_player).IsDead(); if (!flag) { _hasBeenAlive = true; _deathAlreadyCounted = false; _wasDead = false; return; } if (!_hasBeenAlive) { _wasDead = flag; return; } if (flag && !_wasDead && !_deathAlreadyCounted && CanReportLocalDeath()) { _deathAlreadyCounted = true; RegisterDeath(_player); } _wasDead = flag; } private static bool CanReportLocalDeath() { DateTime utcNow = DateTime.UtcNow; double totalSeconds = (utcNow - _lastLocalDeathReportTime).TotalSeconds; if (totalSeconds < 10.0) { return false; } _lastLocalDeathReportTime = utcNow; return true; } public static void ClearLocalDeathCount() { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && localPlayer.m_customData != null && localPlayer.m_customData.ContainsKey("RustyHeathensMessenger.DeathCount")) { localPlayer.m_customData.Remove("RustyHeathensMessenger.DeathCount"); } } private static void RegisterDeath(Player player) { if ((Object)(object)player == (Object)null || player.m_customData == null) { return; } int deathCount = GetDeathCount(player) + 1; player.m_customData["RustyHeathensMessenger.DeathCount"] = deathCount.ToString(); RunebornPlayerJoinMessages.SuppressNextJoinMessageForDeath(player); RunebornDeathLedger.ReportLocalDeath(player); if (RunebornServerConfigSync.DeathMessagesEnabled()) { string funnyDeathMessage = GetFunnyDeathMessage(deathCount); string a = RunebornServerConfigSync.DeathMessageVisibility(); if (!string.Equals(a, "Public", StringComparison.OrdinalIgnoreCase)) { ((Character)player).Message((MessageType)2, "Death #" + deathCount + "\n" + funnyDeathMessage, 0, (Sprite)null); } } } private static int GetDeathCount(Player player) { if (player.m_customData == null) { return 0; } if (!player.m_customData.ContainsKey("RustyHeathensMessenger.DeathCount")) { return 0; } if (!int.TryParse(player.m_customData["RustyHeathensMessenger.DeathCount"], out var result)) { return 0; } return result; } private static string GetFunnyDeathMessage(int deathCount) { switch (deathCount) { case 1: return "First blood. Odin noticed."; case 2: return "The corpse run begins."; case 3: return "Three deaths in. Confidence remains dangerously high."; case 4: return "Four deaths. The gods are starting to recognize you."; case 5: return "Five deaths. That is a strategy now."; case 7: return "Seven deaths. Lucky number. Unlucky heathen."; case 10: return "Ten deaths. Valhalla has a reserved seat."; case 13: return "Thirteen deaths. Even the greydwarfs are concerned."; case 15: return "Fifteen deaths. The corpse run has become cardio."; case 20: return "Twenty deaths. Your tombstone needs its own map marker."; case 25: return "Twenty-five deaths. The ground knows your name."; case 30: return "Thirty deaths. At this point, death is part of the build plan."; case 40: return "Forty deaths. The saga is mostly screaming now."; case 50: return "Fifty deaths. The saga is mostly tombstones."; case 69: return "Sixty-nine deaths. Nice. Terrible, but nice."; case 75: return "Seventy-five deaths. Valheim has issued a warning label."; case 100: return "One hundred deaths. Seppy would be proud."; default: { string[] array = new string[40] { "Another glorious disaster.", "The Rusty Heathens felt that one.", "Your corpse is now part of the landscape.", "That was less of a fight and more of a donation.", "Valheim remains undefeated.", "The gods laughed. Respectfully.", "That death had confidence behind it.", "Bold choice. Bad outcome.", "You have once again become a cautionary tale.", "The mead did not save you.", "The food timer was probably involved.", "A tactical retreat would have been nice about ten seconds ago.", "Your tombstone has entered the chat.", "Somewhere in Valhalla, paperwork was filed.", "The plan was brave. The result was not.", "That was not a death. That was a learning experience.", "The Rusty Heathens will pretend they did not see that.", "You died doing what you loved: making questionable choices.", "A new tombstone has joined the server.", "The gods have reviewed the footage and declined to comment.", "You brought courage. Valheim brought consequences.", "That one looked personal.", "The death counter appreciates your commitment.", "Your survival plan has been rejected.", "That was a full-send into bad news.", "The saga continues, mostly because you keep respawning.", "Someone mark the map. Again.", "The health bar disappeared faster than the plan.", "A fine example of what not to do.", "The ancestors are clapping slowly.", "The body recovery team has been notified.", "Your gear is now having an adventure without you.", "That was a bold attempt at becoming a legend.", "You are not dead. You are temporarily loot-shaped.", "The server felt that one.", "The gods admire your dedication to testing gravity.", "Your confidence has been returned to spawn.", "That death was sponsored by poor timing.", "You have unlocked: another corpse run.", "The bad idea has been completed successfully." }; int num = Random.Range(0, array.Length); if (array.Length > 1 && num == _lastFunnyDeathMessageIndex) { num = (num + 1) % array.Length; } _lastFunnyDeathMessageIndex = num; return array[num]; } } } } public static class RunebornDeathLedger { private class DeathLedgerEntry { public string SteamId; public string DisplayName; public int Deaths; } private const string RpcReportDeathName = "RustyHeathensMessenger_ReportDeath"; private const string RpcRequestLedgerName = "RustyHeathensMessenger_RequestDeathLedger"; private const string RpcRequestMyDeathsName = "RustyHeathensMessenger_RequestMyDeaths"; private const string RpcShowMyDeathsName = "RustyHeathensMessenger_ShowMyDeaths"; private const string RpcClearLedgerName = "RustyHeathensMessenger_ClearDeathLedger"; private const string RpcClearLocalDeathCountName = "RustyHeathensMessenger_ClearLocalDeathCount"; private static readonly Dictionary Ledger = new Dictionary(); private static readonly Dictionary RecentDeathReports = new Dictionary(); private const double DuplicateDeathWindowSeconds = 5.0; private static DateTime _lastLedgerBroadcastRequestTime = DateTime.MinValue; private const double LedgerBroadcastDuplicateWindowSeconds = 2.0; private static ZRoutedRpc _registeredRpcInstance; private static bool _loaded; private static int _lastPublicDeathMessageIndex = -1; public static void EnsureRegistered() { if (ZRoutedRpc.instance != null && _registeredRpcInstance != ZRoutedRpc.instance) { _registeredRpcInstance = ZRoutedRpc.instance; ZRoutedRpc.instance.Register("RustyHeathensMessenger_ReportDeath", (Action)RPC_ReportDeath); ZRoutedRpc.instance.Register("RustyHeathensMessenger_RequestDeathLedger", (Action)RPC_RequestLedger); ZRoutedRpc.instance.Register("RustyHeathensMessenger_RequestMyDeaths", (Action)RPC_RequestMyDeaths); ZRoutedRpc.instance.Register("RustyHeathensMessenger_ShowMyDeaths", (Action)RPC_ShowMyDeaths); ZRoutedRpc.instance.Register("RustyHeathensMessenger_ClearDeathLedger", (Action)RPC_ClearLedger); ZRoutedRpc.instance.Register("RustyHeathensMessenger_ClearLocalDeathCount", (Action)RPC_ClearLocalDeathCount); } } private static void RPC_ClearLocalDeathCount(long sender, string request) { if (string.Equals(request, "confirm", StringComparison.OrdinalIgnoreCase)) { RunebornDeathCounter.ClearLocalDeathCount(); } } public static void ReportLocalDeath(Player player) { if (IsEnabled() && !((Object)(object)player == (Object)null)) { EnsureRegistered(); string text = GetLocalSteamId(); if (string.IsNullOrWhiteSpace(text)) { text = player.GetPlayerID().ToString(); } string playerName = player.GetPlayerName(); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { AddDeath(text, playerName); return; } if (ZRoutedRpc.instance == null) { AddDeath(text, playerName); return; } ZRoutedRpc.instance.InvokeRoutedRPC(GetServerPeerId(), "RustyHeathensMessenger_ReportDeath", new object[2] { text, playerName }); } } public static void RequestLedgerBroadcast() { if (!IsEnabled()) { RunebornMessageBroadcast.ShowLocalCenter("DEATH LEDGER DISABLED\n\nDeath ledger tracking is disabled in the config."); return; } EnsureRegistered(); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { BroadcastLedger(); return; } if (ZRoutedRpc.instance == null) { RunebornMessageBroadcast.ShowLocalCenter(BuildLedgerMessage()); return; } long serverPeerId = GetServerPeerId(); ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerId, "RustyHeathensMessenger_RequestDeathLedger", new object[1] { "request" }); if (serverPeerId != ZRoutedRpc.Everybody) { ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "RustyHeathensMessenger_RequestDeathLedger", new object[1] { "request" }); } } public static void RequestMyDeaths(Player player) { if (!IsEnabled()) { RunebornMessageBroadcast.ShowLocalCenter("DEATH LEDGER DISABLED\n\nDeath ledger tracking is disabled in the config."); } else if (!((Object)(object)player == (Object)null)) { EnsureRegistered(); string text = GetLocalSteamId(); if (string.IsNullOrWhiteSpace(text)) { text = player.GetPlayerID().ToString(); } string playerName = player.GetPlayerName(); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { RunebornMessageBroadcast.ShowLocalCenter(BuildMyDeathsMessage(text, playerName)); return; } if (ZRoutedRpc.instance == null) { RunebornMessageBroadcast.ShowLocalCenter(BuildMyDeathsMessage(text, playerName)); return; } ZRoutedRpc.instance.InvokeRoutedRPC(GetServerPeerId(), "RustyHeathensMessenger_RequestMyDeaths", new object[2] { text, playerName }); } } public static void ClearLedgerBroadcast() { if (!IsEnabled()) { RunebornMessageBroadcast.ShowLocalCenter("DEATH LEDGER DISABLED\n\nDeath ledger tracking is disabled in the config."); return; } EnsureRegistered(); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { ClearLedgerNow(); return; } if (ZRoutedRpc.instance == null) { ClearLedgerNow(); return; } ZRoutedRpc.instance.InvokeRoutedRPC(GetServerPeerId(), "RustyHeathensMessenger_ClearDeathLedger", new object[1] { "confirm" }); } private static void RPC_RequestMyDeaths(long sender, string steamId, string playerName) { if (IsEnabled() && (!((Object)(object)ZNet.instance != (Object)null) || ZNet.instance.IsServer())) { string text = BuildMyDeathsMessage(steamId, playerName); if (ZRoutedRpc.instance == null) { RunebornMessageBroadcast.ShowLocalCenter(text); return; } ZRoutedRpc.instance.InvokeRoutedRPC(sender, "RustyHeathensMessenger_ShowMyDeaths", new object[1] { text }); } } private static void RPC_ShowMyDeaths(long sender, string message) { RunebornMessageBroadcast.ShowLocalCenter(message); } private static void RPC_ClearLedger(long sender, string request) { if (IsEnabled() && (!((Object)(object)ZNet.instance != (Object)null) || ZNet.instance.IsServer()) && string.Equals(request, "confirm", StringComparison.OrdinalIgnoreCase)) { ClearLedgerNow(); } } private static void ClearLedgerNow() { LoadLedger(); Ledger.Clear(); SaveLedger(); RunebornDeathCounter.ClearLocalDeathCount(); if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "RustyHeathensMessenger_ClearLocalDeathCount", new object[1] { "confirm" }); } RunebornMessageBroadcast.BroadcastCenter("DEATH LEDGER CLEARED\n\nAll death counts have been reset."); } private static string BuildMyDeathsMessage(string steamId, string playerName) { LoadLedger(); DeathLedgerEntry value = null; if (!string.IsNullOrWhiteSpace(steamId)) { Ledger.TryGetValue(steamId.Trim(), out value); } if (value == null && !string.IsNullOrWhiteSpace(playerName)) { foreach (DeathLedgerEntry value2 in Ledger.Values) { if (string.Equals(value2.DisplayName, playerName, StringComparison.OrdinalIgnoreCase)) { value = value2; break; } } } string text = ((!string.IsNullOrWhiteSpace(playerName)) ? playerName : "You"); int num = 0; if (value != null) { text = value.DisplayName; num = value.Deaths; } return "MY DEATHS\n\n" + text + " - " + num + ((num == 1) ? " death" : " deaths"); } private static void RPC_ReportDeath(long sender, string steamId, string playerName) { if (IsEnabled() && (!((Object)(object)ZNet.instance != (Object)null) || ZNet.instance.IsServer())) { AddDeath(steamId, playerName); } } private static void RPC_RequestLedger(long sender, string request) { if (IsEnabled() && (!((Object)(object)ZNet.instance != (Object)null) || ZNet.instance.IsServer())) { DateTime utcNow = DateTime.UtcNow; double totalSeconds = (utcNow - _lastLedgerBroadcastRequestTime).TotalSeconds; if (!(totalSeconds < 2.0)) { _lastLedgerBroadcastRequestTime = utcNow; BroadcastLedger(); } } } private static bool PublicDeathMessagesEnabled() { if (!RunebornServerConfigSync.DeathMessagesEnabled()) { return false; } string a = RunebornServerConfigSync.DeathMessageVisibility(); return string.Equals(a, "Public", StringComparison.OrdinalIgnoreCase); } private static void TryBroadcastPublicDeathMessage(string playerName, int deathCount) { if (PublicDeathMessagesEnabled()) { string playerName2 = (string.IsNullOrWhiteSpace(playerName) ? "A heathen" : playerName.Trim()); RunebornMessageBroadcast.BroadcastCenter(PickPublicDeathMessage(playerName2, deathCount)); } } private static string PickPublicDeathMessage(string playerName, int deathCount) { string text = deathCount.ToString(); string[] array = new string[7] { "HEATHEN DOWN\n\n" + playerName + " has fallen. The ledger demands another tally. Death #" + text + ".", "THE GODS ARE LAUGHING\n\n" + playerName + " has died again. Death #" + text + " enters the saga.", "ANOTHER ONE FOR THE LEDGER\n\n" + playerName + " met Valheim the hard way. Death #" + text + ".", "VALHALLA CALLED\n\n" + playerName + " answered immediately. Death #" + text + ".", "HEATHEN STATUS: HORIZONTAL\n\n" + playerName + " has been added to the death ledger. Death #" + text + ".", "SAGA INTERRUPTION\n\n" + playerName + " has temporarily stopped living. Death #" + text + ".", "THE DEATH COUNTER GROWS\n\n" + playerName + " just fed the ledger. Death #" + text + "." }; int num = Random.Range(0, array.Length); if (array.Length > 1 && num == _lastPublicDeathMessageIndex) { num = (num + 1) % array.Length; } _lastPublicDeathMessageIndex = num; return array[num]; } private static void AddDeath(string steamId, string playerName) { if (string.IsNullOrWhiteSpace(steamId)) { return; } string text = steamId.Trim(); if (!IsDuplicateDeathReport(text)) { LoadLedger(); if (!Ledger.TryGetValue(text, out var value)) { value = new DeathLedgerEntry { SteamId = text, DisplayName = CleanField(playerName), Deaths = 0 }; Ledger[text] = value; } if (!string.IsNullOrWhiteSpace(playerName)) { value.DisplayName = CleanField(playerName); } value.Deaths++; SaveLedger(); TryBroadcastPublicDeathMessage(playerName, value.Deaths); } } private static bool IsDuplicateDeathReport(string key) { DateTime utcNow = DateTime.UtcNow; if (RecentDeathReports.TryGetValue(key, out var value)) { double totalSeconds = (utcNow - value).TotalSeconds; if (totalSeconds < 5.0) { return true; } } RecentDeathReports[key] = utcNow; return false; } private static void BroadcastLedger() { string message = BuildLedgerMessage(); RunebornMessageBroadcast.BroadcastCenter(message); } private static string BuildLedgerMessage() { LoadLedger(); string configText = GetConfigText(RunebornPlugin.DeathLedgerTitleEntry, "DEATH LEDGER"); if (Ledger.Count == 0) { string configText2 = GetConfigText(RunebornPlugin.DeathLedgerNoDeathsMessageEntry, "No deaths recorded yet."); return configText + "\n\n" + configText2; } int num = 10; if (RunebornPlugin.DeathLedgerTopCountEntry != null) { num = Math.Max(1, RunebornPlugin.DeathLedgerTopCountEntry.Value); } List list = new List(Ledger.Values); list.Sort((DeathLedgerEntry a, DeathLedgerEntry b) => b.Deaths.CompareTo(a.Deaths)); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(configText); stringBuilder.AppendLine(); int num2 = 0; for (int num3 = 0; num3 < list.Count; num3++) { if (num2 >= num) { break; } DeathLedgerEntry deathLedgerEntry = list[num3]; num2++; stringBuilder.AppendLine(num2 + ". " + deathLedgerEntry.DisplayName + " - " + deathLedgerEntry.Deaths + ((deathLedgerEntry.Deaths == 1) ? " death" : " deaths")); } return stringBuilder.ToString().TrimEnd(Array.Empty()); } private static void LoadLedger() { if (_loaded) { return; } _loaded = true; Ledger.Clear(); string ledgerPath = GetLedgerPath(); if (!File.Exists(ledgerPath)) { return; } string[] array = File.ReadAllLines(ledgerPath); string[] array2 = array; foreach (string text in array2) { if (string.IsNullOrWhiteSpace(text)) { continue; } string[] array3 = text.Split(new char[1] { '|' }); if (array3.Length >= 3) { string text2 = array3[0].Trim(); string displayName = array3[1].Trim(); if (int.TryParse(array3[2].Trim(), out var result) && !string.IsNullOrWhiteSpace(text2)) { Ledger[text2] = new DeathLedgerEntry { SteamId = text2, DisplayName = displayName, Deaths = result }; } } } } private static void SaveLedger() { string ledgerPath = GetLedgerPath(); string directoryName = Path.GetDirectoryName(ledgerPath); if (!Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } List list = new List(); foreach (DeathLedgerEntry value in Ledger.Values) { list.Add(CleanField(value.SteamId) + "|" + CleanField(value.DisplayName) + "|" + value.Deaths); } File.WriteAllLines(ledgerPath, list.ToArray()); } private static string GetLedgerPath() { string path = "RustyHeathensMessenger_DeathLedger.txt"; if (RunebornPlugin.DeathLedgerFileNameEntry != null && !string.IsNullOrWhiteSpace(RunebornPlugin.DeathLedgerFileNameEntry.Value)) { path = RunebornPlugin.DeathLedgerFileNameEntry.Value; } path = Path.GetFileName(path); return Path.Combine(Paths.ConfigPath, path); } private static long GetServerPeerId() { try { if ((Object)(object)ZNet.instance == (Object)null) { return ZRoutedRpc.Everybody; } MethodInfo method = typeof(ZNet).GetMethod("GetServerPeer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { return ZRoutedRpc.Everybody; } object obj = method.Invoke(ZNet.instance, null); if (obj == null) { return ZRoutedRpc.Everybody; } FieldInfo field = obj.GetType().GetField("m_uid", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field == null) { return ZRoutedRpc.Everybody; } if (field.GetValue(obj) is long result) { return result; } } catch { } return ZRoutedRpc.Everybody; } private static string GetLocalSteamId() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) try { return SteamUser.GetSteamID().m_SteamID.ToString(); } catch { return string.Empty; } } private static bool IsEnabled() { return RunebornServerConfigSync.DeathLedgerEnabled(); } private static string GetConfigText(ConfigEntry entry, string fallback) { if (entry == null) { return fallback; } if (string.IsNullOrWhiteSpace(entry.Value)) { return fallback; } return entry.Value.Replace("\\n", "\n"); } private static string CleanField(string value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } return value.Replace("|", "/").Trim(); } } public static class RunebornMessageBroadcast { private const string RpcName = "RustyHeathensMessenger_ShowCenterMessage"; private static ZRoutedRpc _registeredRpcInstance; public static void EnsureRegistered() { if (ZRoutedRpc.instance != null && _registeredRpcInstance != ZRoutedRpc.instance) { _registeredRpcInstance = ZRoutedRpc.instance; ZRoutedRpc.instance.Register("RustyHeathensMessenger_ShowCenterMessage", (Action)RPC_ShowCenterMessage); } } public static void BroadcastCenter(string message) { EnsureRegistered(); if (ZRoutedRpc.instance == null) { ShowLocalCenter(message); return; } ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "RustyHeathensMessenger_ShowCenterMessage", new object[1] { message }); } public static void ShowLocalCenter(string message) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { ((Character)localPlayer).Message((MessageType)2, message, 0, (Sprite)null); } } private static void RPC_ShowCenterMessage(long sender, string message) { if (!string.IsNullOrWhiteSpace(message)) { if (message.StartsWith("DEATH LEDGER CLEARED", StringComparison.OrdinalIgnoreCase)) { RunebornDeathCounter.ClearLocalDeathCount(); } ShowLocalCenter(message); } } } [HarmonyPatch(typeof(Player), "Awake")] public static class RunebornPlayerJoinMessagesPlayerAwakePatch { private static void Postfix(Player __instance) { if (!((Object)(object)__instance == (Object)null) && (Object)(object)((Component)__instance).GetComponent() == (Object)null) { ((Component)__instance).gameObject.AddComponent(); } } } public class RunebornPlayerJoinMessages : MonoBehaviour { private const string RpcReportJoinName = "RustyHeathensMessenger_ReportPlayerJoin"; private static readonly Dictionary RecentJoinReports = new Dictionary(); private static readonly HashSet SuppressNextJoinReportAfterDeath = new HashSet(); private const double DuplicateJoinWindowSeconds = 30.0; private static ZRoutedRpc _registeredRpcInstance; private static int _lastGeneralJoinMessageIndex = -1; private Player _player; private bool _reported; private float _reportAt; public static void EnsureRegistered() { if (ZRoutedRpc.instance != null && _registeredRpcInstance != ZRoutedRpc.instance) { _registeredRpcInstance = ZRoutedRpc.instance; ZRoutedRpc.instance.Register("RustyHeathensMessenger_ReportPlayerJoin", (Action)RPC_ReportPlayerJoin); } } public static void SuppressNextJoinMessageForDeath(Player player) { if (!((Object)(object)player == (Object)null)) { string text = GetLocalSteamId(); if (string.IsNullOrWhiteSpace(text)) { text = player.GetPlayerID().ToString(); } if (!string.IsNullOrWhiteSpace(text)) { SuppressNextJoinReportAfterDeath.Add(text.Trim()); } } } private void Awake() { _player = ((Component)this).GetComponent(); float num = 3f; if (RunebornPlugin.PlayerJoinMessageDelaySecondsEntry != null) { num = Mathf.Max(0f, RunebornPlugin.PlayerJoinMessageDelaySecondsEntry.Value); } _reportAt = Time.time + num; } private void Update() { if (!_reported && RunebornServerConfigSync.PlayerJoinMessagesEnabled() && !((Object)(object)_player == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null) && !((Object)(object)_player != (Object)(object)Player.m_localPlayer) && !(Time.time < _reportAt)) { _reported = true; ReportLocalJoin(_player); } } private static void ReportLocalJoin(Player player) { if ((Object)(object)player == (Object)null) { return; } EnsureRegistered(); string text = GetLocalSteamId(); if (string.IsNullOrWhiteSpace(text)) { text = player.GetPlayerID().ToString(); } string playerName = player.GetPlayerName(); if (!ShouldSuppressBecauseOfRecentDeath(text)) { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { TryBroadcastJoinMessageFromServer(text, playerName); return; } if (ZRoutedRpc.instance == null) { TryBroadcastJoinMessageFromServer(text, playerName); return; } ZRoutedRpc.instance.InvokeRoutedRPC(GetServerPeerId(), "RustyHeathensMessenger_ReportPlayerJoin", new object[2] { text, playerName }); } } private static void RPC_ReportPlayerJoin(long sender, string steamId, string playerName) { if (RunebornServerConfigSync.PlayerJoinMessagesEnabled() && (!((Object)(object)ZNet.instance != (Object)null) || ZNet.instance.IsServer())) { TryBroadcastJoinMessageFromServer(steamId, playerName); } } private static void BroadcastGeneralJoinMessage(string steamId, string playerName) { string text = ((!string.IsNullOrWhiteSpace(steamId)) ? steamId.Trim() : ((!string.IsNullOrWhiteSpace(playerName)) ? playerName.Trim() : "unknown")); if (string.IsNullOrWhiteSpace(text)) { text = "unknown"; } string key = "general|" + text; if (!IsDuplicateJoinReport(key)) { string playerName2 = (string.IsNullOrWhiteSpace(playerName) ? "A new heathen" : playerName.Trim()); RunebornMessageBroadcast.BroadcastCenter(PickGeneralJoinMessage(playerName2)); } } private static string PickGeneralJoinMessage(string playerName) { string[] array = new string[7] { "A NEW HEATHEN HAS ARRIVED\n\n" + playerName + " wandered into the saga. Hide the good mead.", "HEATHEN DETECTED\n\n" + playerName + " has joined. Confidence is high. Survival is unlikely.", "FRESH MEAT HAS ENTERED\n\n" + playerName + " has arrived. The gods have sent another volunteer.", "ANOTHER ONE JOINS THE CHAOS\n\n" + playerName + " is here. They read the server rules, right?", "A NEW SOUL ENTERS VALHEIM\n\n" + playerName + " has joined. Blame them for the next raid.", "THE SAGA GAINS ANOTHER\n\n" + playerName + " has arrived. May their axe be sharp and their death count stay low.", "NEW HEATHEN ONLINE\n\n" + playerName + " has joined. Somebody show them the emergency portal." }; int num = Random.Range(0, array.Length); if (array.Length > 1 && num == _lastGeneralJoinMessageIndex) { num = (num + 1) % array.Length; } _lastGeneralJoinMessageIndex = num; return array[num]; } private static void TryBroadcastJoinMessageFromServer(string steamId, string playerName) { string text = ((RunebornPlugin.PlayerJoinMessagesEntry != null) ? RunebornPlugin.PlayerJoinMessagesEntry.Value : string.Empty); if (string.IsNullOrWhiteSpace(text)) { BroadcastGeneralJoinMessage(steamId, playerName); return; } string[] array = text.Split(new char[1] { ';' }); string[] array2 = array; foreach (string text2 in array2) { if (string.IsNullOrWhiteSpace(text2)) { continue; } string[] array3 = text2.Split(new char[1] { '|' }); if (array3.Length < 4) { continue; } string text3 = array3[0].Trim(); string text4 = array3[1].Trim(); string text5 = array3[2].Trim(); string text6 = array3[3].Trim(); bool flag = !string.IsNullOrWhiteSpace(text3) && !string.IsNullOrWhiteSpace(steamId) && string.Equals(text3, steamId, StringComparison.OrdinalIgnoreCase); bool flag2 = !string.IsNullOrWhiteSpace(text4) && !string.IsNullOrWhiteSpace(playerName) && string.Equals(text4, playerName, StringComparison.OrdinalIgnoreCase); if (!flag && !flag2) { continue; } string key = ((!string.IsNullOrWhiteSpace(text3)) ? ("steam:" + text3) : ("name:" + text4.ToLowerInvariant())); if (!IsDuplicateJoinReport(key)) { if (string.IsNullOrWhiteSpace(text5)) { text5 = text4 + " HAS ARRIVED"; } text6 = text6.Replace("\\n", "\n"); RunebornMessageBroadcast.BroadcastCenter(text5 + "\n\n" + text6); } return; } BroadcastGeneralJoinMessage(steamId, playerName); } private static bool IsDuplicateJoinReport(string key) { DateTime utcNow = DateTime.UtcNow; if (RecentJoinReports.TryGetValue(key, out var value)) { double totalSeconds = (utcNow - value).TotalSeconds; if (totalSeconds < 30.0) { return true; } } RecentJoinReports[key] = utcNow; return false; } private static bool ShouldSuppressBecauseOfRecentDeath(string steamId) { if (string.IsNullOrWhiteSpace(steamId)) { return false; } string item = steamId.Trim(); if (!SuppressNextJoinReportAfterDeath.Contains(item)) { return false; } SuppressNextJoinReportAfterDeath.Remove(item); return true; } private static long GetServerPeerId() { try { if ((Object)(object)ZNet.instance == (Object)null) { return ZRoutedRpc.Everybody; } MethodInfo method = typeof(ZNet).GetMethod("GetServerPeer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { return ZRoutedRpc.Everybody; } object obj = method.Invoke(ZNet.instance, null); if (obj == null) { return ZRoutedRpc.Everybody; } FieldInfo field = obj.GetType().GetField("m_uid", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field == null) { return ZRoutedRpc.Everybody; } if (field.GetValue(obj) is long result) { return result; } } catch { } return ZRoutedRpc.Everybody; } private static string GetLocalSteamId() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) try { return SteamUser.GetSteamID().m_SteamID.ToString(); } catch { return string.Empty; } } } [BepInPlugin("com.rustylasergaming.heathensmessenger", "Rusty Heathens Messenger", "0.18.6")] public sealed class RunebornPlugin : BaseUnityPlugin { public const string PluginGuid = "com.rustylasergaming.heathensmessenger"; public const string PluginName = "Rusty Heathens Messenger"; public const string PluginVersion = "0.18.6"; internal static ConfigEntry ServerReminderEnabledEntry; internal static ConfigEntry RaidWarningsEnabledEntry; internal static ConfigEntry WelcomeMessageEnabledEntry; internal static ConfigEntry BossKillMessagesEnabledEntry; internal static ConfigEntry DeathMessagesEnabledEntry; internal static ConfigEntry DeathMessageVisibilityEntry; internal static ConfigEntry ChatCommandsEnabledEntry; internal static ConfigEntry CommandPermissionModeEntry; internal static ConfigEntry CommandAllowedPlayerNamesEntry; internal static ConfigEntry CommandAllowedSteamIdsEntry; internal static ConfigEntry WelcomeTitleEntry; internal static ConfigEntry WelcomeBodyEntry; internal static ConfigEntry ServerReminderTitleEntry; internal static ConfigEntry ServerReminderBodyEntry; internal static ConfigEntry StreamTitleEntry; internal static ConfigEntry StreamBodyEntry; internal static ConfigEntry BossStartTitleEntry; internal static ConfigEntry BossStartBodyEntry; internal static ConfigEntry BaseTitleEntry; internal static ConfigEntry BaseBodyEntry; internal static ConfigEntry DiscordTitleEntry; internal static ConfigEntry DiscordBodyEntry; internal static ConfigEntry EndingTitleEntry; internal static ConfigEntry EndingBodyEntry; internal static ConfigEntry BodyTitleEntry; internal static ConfigEntry BodyBodyEntry; internal static ConfigEntry RaidCommandTitleEntry; internal static ConfigEntry RaidCommandBodyEntry; internal static ConfigEntry StreamStyleEntry; internal static ConfigEntry BossStartStyleEntry; internal static ConfigEntry BaseStyleEntry; internal static ConfigEntry DiscordStyleEntry; internal static ConfigEntry EndingStyleEntry; internal static ConfigEntry BodyStyleEntry; internal static ConfigEntry RaidCommandStyleEntry; internal static ConfigEntry StreamModeEnabledEntry; internal static ConfigEntry StreamModeStartsEnabledEntry; internal static ConfigEntry StreamModeOnTitleEntry; internal static ConfigEntry StreamModeOnBodyEntry; internal static ConfigEntry StreamModeOffTitleEntry; internal static ConfigEntry StreamModeOffBodyEntry; internal static ConfigEntry StreamModeStatusTitleEntry; internal static ConfigEntry StreamModeStatusOnBodyEntry; internal static ConfigEntry StreamModeStatusOffBodyEntry; internal static ConfigEntry StreamModeFooterEntry; internal static ConfigEntry PlayerJoinMessagesEnabledEntry; internal static ConfigEntry PlayerJoinMessageDelaySecondsEntry; internal static ConfigEntry PlayerJoinMessagesEntry; internal static ConfigEntry DeathLedgerEnabledEntry; internal static ConfigEntry DeathLedgerTopCountEntry; internal static ConfigEntry DeathLedgerTitleEntry; internal static ConfigEntry DeathLedgerNoDeathsMessageEntry; internal static ConfigEntry DeathLedgerFileNameEntry; internal static bool StreamModeActive; private Harmony _harmony; internal static RunebornPlugin Instance { get; private set; } private void Awake() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown Instance = this; BindConfig(); _harmony = new Harmony("com.rustylasergaming.heathensmessenger"); _harmony.PatchAll(Assembly.GetExecutingAssembly()); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Rusty Heathens Messenger 0.18.6 loaded."); } private void Update() { RunebornMessageBroadcast.EnsureRegistered(); RunebornDeathLedger.EnsureRegistered(); RunebornPlayerJoinMessages.EnsureRegistered(); RunebornChatCommands.EnsureRegistered(); RunebornBossKillMessagesPatch.EnsureRegistered(); RunebornServerConfigSync.Update(); } private void BindConfig() { WelcomeMessageEnabledEntry = ((BaseUnityPlugin)this).Config.Bind("01 - Features", "EnableWelcomeMessage", true, "Show the welcome message when joining a world."); ServerReminderEnabledEntry = ((BaseUnityPlugin)this).Config.Bind("01 - Features", "EnableServerReminder", true, "Show the server reminder message after joining."); RaidWarningsEnabledEntry = ((BaseUnityPlugin)this).Config.Bind("01 - Features", "EnableRaidWarnings", true, "Show raid warning messages."); BossKillMessagesEnabledEntry = ((BaseUnityPlugin)this).Config.Bind("01 - Features", "EnableBossKillMessages", true, "Show boss kill messages."); DeathMessageVisibilityEntry = ((BaseUnityPlugin)this).Config.Bind("01 - Features", "DeathMessageVisibility", "Private", "Controls who sees death messages. Options: Private, Public."); DeathMessagesEnabledEntry = ((BaseUnityPlugin)this).Config.Bind("01 - Features", "EnableDeathMessages", true, "Show death counter and funny death messages."); ChatCommandsEnabledEntry = ((BaseUnityPlugin)this).Config.Bind("01 - Features", "EnableChatCommands", true, "Enable Rusty Heathens Messenger chat commands."); CommandPermissionModeEntry = ((BaseUnityPlugin)this).Config.Bind("02 - Command Permissions", "CommandPermissionMode", "SteamIds", "Controls who can use protected commands. For dedicated servers such as DatHost, SteamIds is recommended. Protected commands are checked by the server config."); CommandAllowedSteamIdsEntry = ((BaseUnityPlugin)this).Config.Bind("02 - Command Permissions", "CommandAllowedSteamIds", "", "Comma-separated Steam64 IDs allowed to use protected commands when CommandPermissionMode is SteamIds or NamedPlayersOrSteamIds."); CommandAllowedPlayerNamesEntry = ((BaseUnityPlugin)this).Config.Bind("02 - Command Permissions", "CommandAllowedPlayerNames", "", "Comma-separated player names allowed to use protected commands when CommandPermissionMode is NamedPlayers or NamedPlayersOrSteamIds. SteamIds is recommended for dedicated servers."); WelcomeTitleEntry = ((BaseUnityPlugin)this).Config.Bind("03 - Welcome Message", "WelcomeTitle", "Welcome to the Rusty Heathens", "Title shown in the welcome message."); WelcomeBodyEntry = ((BaseUnityPlugin)this).Config.Bind("03 - Welcome Message", "WelcomeBody", "Survive the chaos. Earn Valhalla.", "Body shown in the welcome message."); ServerReminderTitleEntry = ((BaseUnityPlugin)this).Config.Bind("04 - Server Reminder", "ServerReminderTitle", "RUSTY HEATHENS SERVER", "Title shown in the server reminder."); ServerReminderBodyEntry = ((BaseUnityPlugin)this).Config.Bind("04 - Server Reminder", "ServerReminderBody", "Respect the crew.\nNo stealing.\nNo boss fights without the group.\nHelp with body recovery when you can.\nJoin Discord for rules, events, and stream info.", "Body shown in the server reminder."); StreamTitleEntry = ((BaseUnityPlugin)this).Config.Bind("05 - Chat Commands", "StreamTitle", "STREAM STARTING SOON", "Title for stream starting command."); StreamBodyEntry = ((BaseUnityPlugin)this).Config.Bind("05 - Chat Commands", "StreamBody", "The Rusty Heathens are gathering.\nGrab your gear, check your food, and prepare for chaos.", "Body for stream starting command."); BossStartTitleEntry = ((BaseUnityPlugin)this).Config.Bind("05 - Chat Commands", "BossStartTitle", "BOSS FIGHT STARTING SOON", "Title for boss fight starting command."); BossStartBodyEntry = ((BaseUnityPlugin)this).Config.Bind("05 - Chat Commands", "BossStartBody", "Return to base, repair your gear, grab food, and wait for the group.", "Body for boss fight starting command."); BaseTitleEntry = ((BaseUnityPlugin)this).Config.Bind("05 - Chat Commands", "BaseTitle", "RETURN TO BASE", "Title for return to base command."); BaseBodyEntry = ((BaseUnityPlugin)this).Config.Bind("05 - Chat Commands", "BaseBody", "Everyone head back to base.\nRegroup before the chaos gets worse.", "Body for return to base command."); DiscordTitleEntry = ((BaseUnityPlugin)this).Config.Bind("05 - Chat Commands", "DiscordTitle", "DISCORD REMINDER", "Title for Discord reminder command."); DiscordBodyEntry = ((BaseUnityPlugin)this).Config.Bind("05 - Chat Commands", "DiscordBody", "Use Discord for rules, event plans, updates, and stream info.\nMessage RustyLaser if you need help.", "Body for Discord reminder command."); EndingTitleEntry = ((BaseUnityPlugin)this).Config.Bind("05 - Chat Commands", "EndingTitle", "STREAM IS ENDING", "Title for stream ending command."); EndingBodyEntry = ((BaseUnityPlugin)this).Config.Bind("05 - Chat Commands", "EndingBody", "The stream is ending.\nIf you want to say bye to chat, return to the wall.", "Body for stream ending command."); BodyTitleEntry = ((BaseUnityPlugin)this).Config.Bind("05 - Chat Commands", "BodyTitle", "HEATHEN DOWN", "Title for body recovery command."); BodyBodyEntry = ((BaseUnityPlugin)this).Config.Bind("05 - Chat Commands", "BodyBody", "Head back to base and regroup.\nA Heathen needs help recovering their kit.", "Body for body recovery command."); RaidCommandTitleEntry = ((BaseUnityPlugin)this).Config.Bind("05 - Chat Commands", "RaidTitle", "INCOMING RAID", "Title for incoming raid command."); RaidCommandBodyEntry = ((BaseUnityPlugin)this).Config.Bind("05 - Chat Commands", "RaidBody", "Head back to base.\nHelp keep the Rusty Heathens safe.", "Body for incoming raid command."); StreamStyleEntry = ((BaseUnityPlugin)this).Config.Bind("06 - Message Styles", "StreamStyle", "Normal", "Style for stream starting command. Options: Normal, Epic, Funny, Emergency."); BossStartStyleEntry = ((BaseUnityPlugin)this).Config.Bind("06 - Message Styles", "BossStartStyle", "Epic", "Style for boss fight starting command. Options: Normal, Epic, Funny, Emergency."); BaseStyleEntry = ((BaseUnityPlugin)this).Config.Bind("06 - Message Styles", "BaseStyle", "Normal", "Style for return to base command. Options: Normal, Epic, Funny, Emergency."); DiscordStyleEntry = ((BaseUnityPlugin)this).Config.Bind("06 - Message Styles", "DiscordStyle", "Normal", "Style for Discord reminder command. Options: Normal, Epic, Funny, Emergency."); EndingStyleEntry = ((BaseUnityPlugin)this).Config.Bind("06 - Message Styles", "EndingStyle", "Normal", "Style for stream ending command. Options: Normal, Epic, Funny, Emergency."); BodyStyleEntry = ((BaseUnityPlugin)this).Config.Bind("06 - Message Styles", "BodyStyle", "Epic", "Style for body recovery command. Options: Normal, Epic, Funny, Emergency."); RaidCommandStyleEntry = ((BaseUnityPlugin)this).Config.Bind("06 - Message Styles", "RaidStyle", "Emergency", "Style for incoming raid command. Options: Normal, Epic, Funny, Emergency."); StreamModeEnabledEntry = ((BaseUnityPlugin)this).Config.Bind("07 - Stream Mode", "EnableStreamMode", true, "Enable or disable Stream Mode commands and stream mode footer messages."); StreamModeStartsEnabledEntry = ((BaseUnityPlugin)this).Config.Bind("07 - Stream Mode", "StreamModeStartsEnabled", false, "If true, Stream Mode starts enabled when the world loads."); StreamModeOnTitleEntry = ((BaseUnityPlugin)this).Config.Bind("07 - Stream Mode", "StreamModeOnTitle", "STREAM MODE ENABLED", "Title shown when Stream Mode is turned on."); StreamModeOnBodyEntry = ((BaseUnityPlugin)this).Config.Bind("07 - Stream Mode", "StreamModeOnBody", "Chat is watching.\nKeep it fun, keep it moving, and prepare for Rusty Heathens chaos.", "Body shown when Stream Mode is turned on."); StreamModeOffTitleEntry = ((BaseUnityPlugin)this).Config.Bind("07 - Stream Mode", "StreamModeOffTitle", "STREAM MODE DISABLED", "Title shown when Stream Mode is turned off."); StreamModeOffBodyEntry = ((BaseUnityPlugin)this).Config.Bind("07 - Stream Mode", "StreamModeOffBody", "Chat has been released.\nYou may now return to unsupervised bad decisions.", "Body shown when Stream Mode is turned off."); StreamModeStatusTitleEntry = ((BaseUnityPlugin)this).Config.Bind("07 - Stream Mode", "StreamModeStatusTitle", "STREAM MODE STATUS", "Title shown when checking Stream Mode status."); StreamModeStatusOnBodyEntry = ((BaseUnityPlugin)this).Config.Bind("07 - Stream Mode", "StreamModeStatusOnBody", "Stream Mode is ON.\nChat is watching the chaos unfold.", "Body shown when Stream Mode is currently on."); StreamModeStatusOffBodyEntry = ((BaseUnityPlugin)this).Config.Bind("07 - Stream Mode", "StreamModeStatusOffBody", "Stream Mode is OFF.\nThe Heathens are currently causing chaos off-camera.", "Body shown when Stream Mode is currently off."); StreamModeFooterEntry = ((BaseUnityPlugin)this).Config.Bind("07 - Stream Mode", "StreamModeFooter", "LIVE STREAM MODE: Chat is watching.", "Footer added to broadcast command messages while Stream Mode is on."); PlayerJoinMessagesEnabledEntry = ((BaseUnityPlugin)this).Config.Bind("08 - Player Join Messages", "EnablePlayerJoinMessages", true, "Enable or disable player-specific join messages."); PlayerJoinMessageDelaySecondsEntry = ((BaseUnityPlugin)this).Config.Bind("08 - Player Join Messages", "PlayerJoinMessageDelaySeconds", 3f, "Delay in seconds before showing a player join message after the player loads in."); string text = "|RustyLaser|RUSTYLASER HAS ARRIVED|The longship has a captain.\\nChaos now has supervision.;|Panda|PANDA HAS ARRIVED|The Chaos Bringer is here.\\nHide the important stuff.;|Witchy|WITCHY HAS ARRIVED|The ritual can begin.\\nSomebody check the cauldron.;|Seppy|SEPPY HAS ARRIVED|The tiny tank has entered the saga.\\nSomebody keep an eye on him.;|Phuture|PHUTURE HAS ARRIVED|First to retreat, last to fall.\\nThe storm has entered the saga.;|Pinklady|PINKLADY HAS ARRIVED|The builder is here.\\nMaybe the base will survive after all.;|Mama Laser|MAMA LASER HAS ARRIVED|The oracle is here.\\nTrolls have been warned.;|Saelion|SAELION HAS ARRIVED|The blade joins the saga.\\nSomething is about to become a problem."; PlayerJoinMessagesEntry = ((BaseUnityPlugin)this).Config.Bind("08 - Player Join Messages", "PlayerJoinMessages", text, "Player join messages. Format: SteamID|DisplayName|Title|Message. Separate multiple players with semicolons. SteamID can be blank to match by player name."); DeathLedgerEnabledEntry = ((BaseUnityPlugin)this).Config.Bind("09 - Death Ledger", "EnableDeathLedger", true, "Enable or disable server-wide death ledger tracking."); DeathLedgerTopCountEntry = ((BaseUnityPlugin)this).Config.Bind("09 - Death Ledger", "DeathLedgerTopCount", 10, "How many players to show when using !deaths or /rhm deaths."); DeathLedgerTitleEntry = ((BaseUnityPlugin)this).Config.Bind("09 - Death Ledger", "DeathLedgerTitle", "DEATH LEDGER", "Title shown for the death ledger message."); DeathLedgerNoDeathsMessageEntry = ((BaseUnityPlugin)this).Config.Bind("09 - Death Ledger", "DeathLedgerNoDeathsMessage", "No deaths recorded yet.", "Message shown when the death ledger is empty."); DeathLedgerFileNameEntry = ((BaseUnityPlugin)this).Config.Bind("09 - Death Ledger", "DeathLedgerFileName", "RustyHeathensMessenger_DeathLedger.txt", "File name used to save server-wide death counts."); StreamModeActive = StreamModeEnabledEntry.Value && StreamModeStartsEnabledEntry.Value; ApplyMessagingSettingsToRuntime(); } internal static void ApplyMessagingSettingsToRuntime() { if (ServerReminderEnabledEntry != null) { RunebornServerReminder.ServerReminderEnabled = ServerReminderEnabledEntry.Value; } if (RaidWarningsEnabledEntry != null) { RunebornRaidWarnings.RaidWarningsEnabled = RaidWarningsEnabledEntry.Value; } } } public sealed class RunebornRaidWarnings : MonoBehaviour { private Player _player; private string _activeRaid = string.Empty; private float _nextCheck; private float _nextRepeatMessage; public static bool RaidWarningsEnabled = true; private void Awake() { _player = ((Component)this).GetComponent(); } private void Update() { if (!((Object)(object)_player == (Object)null) && !((Object)(object)Player.m_localPlayer != (Object)(object)_player) && RunebornServerConfigSync.HasServerConfig() && RunebornServerConfigSync.RaidWarningsEnabled() && !(Time.time < _nextCheck)) { _nextCheck = Time.time + 1f; string currentRaidDisplayName = GetCurrentRaidDisplayName(); if (!string.IsNullOrEmpty(currentRaidDisplayName) && string.IsNullOrEmpty(_activeRaid)) { _activeRaid = currentRaidDisplayName; _nextRepeatMessage = Time.time + 45f; ShowCenter("RAID WARNING\n" + currentRaidDisplayName + "\nThe Rusty Heathens are under attack!"); } else if (!string.IsNullOrEmpty(currentRaidDisplayName) && currentRaidDisplayName == _activeRaid && Time.time >= _nextRepeatMessage) { _nextRepeatMessage = Time.time + 45f; ShowCenter("RAID STILL ACTIVE\n" + currentRaidDisplayName + "\nHold the line, Heathens!"); } else if (string.IsNullOrEmpty(currentRaidDisplayName) && !string.IsNullOrEmpty(_activeRaid)) { ShowCenter("RAID CLEARED\nThe Heathens still stand."); _activeRaid = string.Empty; _nextRepeatMessage = 0f; } } } public static string GetCurrentRaidDisplayName() { object currentRandomEventObject = GetCurrentRandomEventObject(); if (currentRandomEventObject == null) { return string.Empty; } string text = GetStringField(currentRandomEventObject, "m_name"); if (string.IsNullOrEmpty(text)) { text = currentRandomEventObject.ToString(); } return MakeRaidNameFriendly(text); } private static object GetCurrentRandomEventObject() { try { Type type = Type.GetType("RandEventSystem, Assembly-CSharp"); if (type == null) { return null; } object obj = GetStaticField(type, "instance") ?? GetStaticField(type, "m_instance"); if (obj == null) { Object val = Object.FindFirstObjectByType(type); if (val != (Object)null) { obj = val; } } if (obj == null) { return null; } object result; if ((result = GetInstanceField(obj, "m_randomEvent")) == null) { result = GetInstanceField(obj, "m_currentRandomEvent") ?? GetInstanceField(obj, "m_event"); } return result; } catch { return null; } } private static object GetStaticField(Type type, string fieldName) { FieldInfo field = type.GetField(fieldName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (!(field != null)) { return null; } return field.GetValue(null); } private static object GetInstanceField(object target, string fieldName) { if (target == null) { return null; } FieldInfo field = target.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (!(field != null)) { return null; } return field.GetValue(target); } private static string GetStringField(object target, string fieldName) { object instanceField = GetInstanceField(target, fieldName); if (instanceField == null) { return string.Empty; } return instanceField.ToString(); } private static string MakeRaidNameFriendly(string rawName) { if (string.IsNullOrEmpty(rawName)) { return "Unknown Raid"; } string text = rawName.ToLowerInvariant(); if (text.Contains("army_eikthyr")) { return "Eikthyr's Herd"; } if (text.Contains("army_theelder") || text.Contains("foresttrolls")) { return "The Forest Moves"; } if (text.Contains("army_bonemass") || text.Contains("skeletons")) { return "Bonemass' Dead"; } if (text.Contains("army_moder") || text.Contains("wolves")) { return "The Mountain Hunt"; } if (text.Contains("army_goblin") || text.Contains("goblin")) { return "The Plains Warband"; } if (text.Contains("army_gjall") || text.Contains("gjall")) { return "Mistlands Terror"; } if (text.Contains("army_charred") || text.Contains("charred")) { return "Ashlands Siege"; } return CleanName(rawName); } private static string CleanName(string rawName) { string text = rawName.Replace("$event_", string.Empty).Replace("event_", string.Empty).Replace("army_", string.Empty) .Replace("_", " ") .Trim(); if (string.IsNullOrEmpty(text)) { return "Unknown Raid"; } StringBuilder stringBuilder = new StringBuilder(text.Length); bool flag = true; string text2 = text; foreach (char c in text2) { if (char.IsWhiteSpace(c)) { stringBuilder.Append(c); flag = true; } else if (flag) { stringBuilder.Append(char.ToUpperInvariant(c)); flag = false; } else { stringBuilder.Append(c); } } return stringBuilder.ToString(); } private void ShowCenter(string text) { if ((Object)(object)_player == (Object)null) { return; } try { ((Character)Player.m_localPlayer).Message((MessageType)2, text, 0, (Sprite)null); } catch { } } } [HarmonyPatch(typeof(Player), "Awake")] public static class RunebornRaidWarningsPlayerAwakePatch { private static void Postfix(Player __instance) { if (!((Object)(object)__instance == (Object)null) && (Object)(object)((Component)__instance).GetComponent() == (Object)null) { ((Component)__instance).gameObject.AddComponent(); } } } public static class RunebornServerConfigSync { private const string RpcRequestConfigName = "RustyHeathensMessenger_RequestServerConfig"; private const string RpcReceiveConfigName = "RustyHeathensMessenger_ReceiveServerConfig"; private static ZRoutedRpc _registeredRpcInstance; private static float _nextRequestTime; private static bool _hasServerConfig; private static bool _welcomeMessageEnabled = true; private static bool _serverReminderEnabled = true; private static bool _raidWarningsEnabled = true; private static bool _bossKillMessagesEnabled = true; private static bool _deathMessagesEnabled = true; private static bool _deathLedgerEnabled = true; private static bool _playerJoinMessagesEnabled = true; private static bool _chatCommandsEnabled = true; private static string _deathMessageVisibility = "Private"; private static string _welcomeTitle = "Welcome to the Rusty Heathens"; private static string _welcomeBody = "Survive the chaos. Earn Valhalla."; private static string _serverReminderTitle = "RUSTY HEATHENS SERVER"; private static string _serverReminderBody = "Respect the crew.\nNo stealing.\nNo boss fights without the group.\nHelp with body recovery when you can.\nJoin Discord for rules, events, and stream info."; public static void Update() { EnsureRegistered(); if (ZRoutedRpc.instance != null && !((Object)(object)ZNet.instance == (Object)null)) { if (ZNet.instance.IsServer()) { ApplyLocalServerConfig(); } else if (!_hasServerConfig && !(Time.time < _nextRequestTime)) { _nextRequestTime = Time.time + 5f; RequestServerConfig(); } } } public static void EnsureRegistered() { if (ZRoutedRpc.instance != null && _registeredRpcInstance != ZRoutedRpc.instance) { _registeredRpcInstance = ZRoutedRpc.instance; ZRoutedRpc.instance.Register("RustyHeathensMessenger_RequestServerConfig", (Action)RPC_RequestServerConfig); ZRoutedRpc.instance.Register("RustyHeathensMessenger_ReceiveServerConfig", (Action)RPC_ReceiveServerConfig); } } private static void RequestServerConfig() { ZRoutedRpc.instance.InvokeRoutedRPC(GetServerPeerId(), "RustyHeathensMessenger_RequestServerConfig", new object[1] { "request" }); } private static void RPC_RequestServerConfig(long sender, string request) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { BroadcastServerConfig(); } } private static void BroadcastServerConfig() { if (ZRoutedRpc.instance != null) { ApplyLocalServerConfig(); ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "RustyHeathensMessenger_ReceiveServerConfig", new object[1] { BuildPayload() }); } } private static void RPC_ReceiveServerConfig(long sender, string payload) { ApplyPayload(payload); } private static void ApplyLocalServerConfig() { _welcomeMessageEnabled = RunebornPlugin.WelcomeMessageEnabledEntry == null || RunebornPlugin.WelcomeMessageEnabledEntry.Value; _serverReminderEnabled = RunebornPlugin.ServerReminderEnabledEntry == null || RunebornPlugin.ServerReminderEnabledEntry.Value; _raidWarningsEnabled = RunebornPlugin.RaidWarningsEnabledEntry == null || RunebornPlugin.RaidWarningsEnabledEntry.Value; _bossKillMessagesEnabled = RunebornPlugin.BossKillMessagesEnabledEntry == null || RunebornPlugin.BossKillMessagesEnabledEntry.Value; _deathMessagesEnabled = RunebornPlugin.DeathMessagesEnabledEntry == null || RunebornPlugin.DeathMessagesEnabledEntry.Value; _deathLedgerEnabled = RunebornPlugin.DeathLedgerEnabledEntry == null || RunebornPlugin.DeathLedgerEnabledEntry.Value; _playerJoinMessagesEnabled = RunebornPlugin.PlayerJoinMessagesEnabledEntry == null || RunebornPlugin.PlayerJoinMessagesEnabledEntry.Value; _chatCommandsEnabled = RunebornPlugin.ChatCommandsEnabledEntry == null || RunebornPlugin.ChatCommandsEnabledEntry.Value; _deathMessageVisibility = ((RunebornPlugin.DeathMessageVisibilityEntry != null) ? RunebornPlugin.DeathMessageVisibilityEntry.Value : "Private"); _welcomeTitle = ((RunebornPlugin.WelcomeTitleEntry != null) ? RunebornPlugin.WelcomeTitleEntry.Value.Replace("\\n", "\n") : "Welcome to the Rusty Heathens"); _welcomeBody = ((RunebornPlugin.WelcomeBodyEntry != null) ? RunebornPlugin.WelcomeBodyEntry.Value.Replace("\\n", "\n") : "Survive the chaos. Earn Valhalla."); _serverReminderTitle = ((RunebornPlugin.ServerReminderTitleEntry != null) ? RunebornPlugin.ServerReminderTitleEntry.Value.Replace("\\n", "\n") : "RUSTY HEATHENS SERVER"); _serverReminderBody = ((RunebornPlugin.ServerReminderBodyEntry != null) ? RunebornPlugin.ServerReminderBodyEntry.Value.Replace("\\n", "\n") : "Respect the crew.\nNo stealing.\nNo boss fights without the group.\nHelp with body recovery when you can.\nJoin Discord for rules, events, and stream info."); _hasServerConfig = true; } private static string BuildPayload() { return BoolToText(_welcomeMessageEnabled) + "|" + BoolToText(_serverReminderEnabled) + "|" + BoolToText(_raidWarningsEnabled) + "|" + BoolToText(_bossKillMessagesEnabled) + "|" + BoolToText(_deathMessagesEnabled) + "|" + BoolToText(_chatCommandsEnabled) + "|" + (_deathMessageVisibility ?? "Private") + "|" + BoolToText(_deathLedgerEnabled) + "|" + BoolToText(_playerJoinMessagesEnabled) + "|" + EncodeText(_welcomeTitle) + "|" + EncodeText(_welcomeBody) + "|" + EncodeText(_serverReminderTitle) + "|" + EncodeText(_serverReminderBody); } private static void ApplyPayload(string payload) { if (string.IsNullOrWhiteSpace(payload)) { return; } string[] array = payload.Split(new char[1] { '|' }); if (array.Length >= 7) { _welcomeMessageEnabled = TextToBool(array[0], fallback: true); _serverReminderEnabled = TextToBool(array[1], fallback: true); _raidWarningsEnabled = TextToBool(array[2], fallback: true); _bossKillMessagesEnabled = TextToBool(array[3], fallback: true); _deathMessagesEnabled = TextToBool(array[4], fallback: true); _chatCommandsEnabled = TextToBool(array[5], fallback: true); _deathMessageVisibility = (string.IsNullOrWhiteSpace(array[6]) ? "Private" : array[6].Trim()); if (array.Length >= 8) { _deathLedgerEnabled = TextToBool(array[7], fallback: true); } if (array.Length >= 9) { _playerJoinMessagesEnabled = TextToBool(array[8], fallback: true); } if (array.Length >= 13) { _welcomeTitle = DecodeText(array[9], "Welcome to the Rusty Heathens"); _welcomeBody = DecodeText(array[10], "Survive the chaos. Earn Valhalla."); _serverReminderTitle = DecodeText(array[11], "RUSTY HEATHENS SERVER"); _serverReminderBody = DecodeText(array[12], "Respect the crew.\nNo stealing.\nNo boss fights without the group.\nHelp with body recovery when you can.\nJoin Discord for rules, events, and stream info."); } _hasServerConfig = true; } } public static bool HasServerConfig() { return _hasServerConfig; } public static string WelcomeTitle() { return _hasServerConfig ? _welcomeTitle : ((RunebornPlugin.WelcomeTitleEntry != null) ? RunebornPlugin.WelcomeTitleEntry.Value.Replace("\\n", "\n") : "Welcome to the Rusty Heathens"); } public static string WelcomeBody() { return _hasServerConfig ? _welcomeBody : ((RunebornPlugin.WelcomeBodyEntry != null) ? RunebornPlugin.WelcomeBodyEntry.Value.Replace("\\n", "\n") : "Survive the chaos. Earn Valhalla."); } public static string ServerReminderTitle() { return _hasServerConfig ? _serverReminderTitle : ((RunebornPlugin.ServerReminderTitleEntry != null) ? RunebornPlugin.ServerReminderTitleEntry.Value.Replace("\\n", "\n") : "RUSTY HEATHENS SERVER"); } public static string ServerReminderBody() { return _hasServerConfig ? _serverReminderBody : ((RunebornPlugin.ServerReminderBodyEntry != null) ? RunebornPlugin.ServerReminderBodyEntry.Value.Replace("\\n", "\n") : "Respect the crew.\nNo stealing.\nNo boss fights without the group.\nHelp with body recovery when you can.\nJoin Discord for rules, events, and stream info."); } public static bool PlayerJoinMessagesEnabled() { return _hasServerConfig ? _playerJoinMessagesEnabled : (RunebornPlugin.PlayerJoinMessagesEnabledEntry == null || RunebornPlugin.PlayerJoinMessagesEnabledEntry.Value); } public static bool DeathLedgerEnabled() { return _hasServerConfig ? _deathLedgerEnabled : (RunebornPlugin.DeathLedgerEnabledEntry == null || RunebornPlugin.DeathLedgerEnabledEntry.Value); } public static bool WelcomeMessageEnabled() { return _hasServerConfig ? _welcomeMessageEnabled : (RunebornPlugin.WelcomeMessageEnabledEntry == null || RunebornPlugin.WelcomeMessageEnabledEntry.Value); } public static bool ServerReminderEnabled() { return _hasServerConfig ? _serverReminderEnabled : (RunebornPlugin.ServerReminderEnabledEntry == null || RunebornPlugin.ServerReminderEnabledEntry.Value); } public static bool RaidWarningsEnabled() { return _hasServerConfig ? _raidWarningsEnabled : (RunebornPlugin.RaidWarningsEnabledEntry == null || RunebornPlugin.RaidWarningsEnabledEntry.Value); } public static bool BossKillMessagesEnabled() { return _hasServerConfig ? _bossKillMessagesEnabled : (RunebornPlugin.BossKillMessagesEnabledEntry == null || RunebornPlugin.BossKillMessagesEnabledEntry.Value); } public static bool DeathMessagesEnabled() { return _hasServerConfig ? _deathMessagesEnabled : (RunebornPlugin.DeathMessagesEnabledEntry == null || RunebornPlugin.DeathMessagesEnabledEntry.Value); } public static bool ChatCommandsEnabled() { return _hasServerConfig ? _chatCommandsEnabled : (RunebornPlugin.ChatCommandsEnabledEntry == null || RunebornPlugin.ChatCommandsEnabledEntry.Value); } public static string DeathMessageVisibility() { return _hasServerConfig ? _deathMessageVisibility : ((RunebornPlugin.DeathMessageVisibilityEntry != null) ? RunebornPlugin.DeathMessageVisibilityEntry.Value : "Private"); } private static string EncodeText(string value) { if (value == null) { value = string.Empty; } byte[] bytes = Encoding.UTF8.GetBytes(value); return Convert.ToBase64String(bytes); } private static string DecodeText(string value, string fallback) { if (string.IsNullOrWhiteSpace(value)) { return fallback; } try { byte[] bytes = Convert.FromBase64String(value); return Encoding.UTF8.GetString(bytes); } catch { return fallback; } } private static string BoolToText(bool value) { return value ? "1" : "0"; } private static bool TextToBool(string value, bool fallback) { if (string.IsNullOrWhiteSpace(value)) { return fallback; } value = value.Trim(); if (value == "1") { return true; } if (value == "0") { return false; } if (bool.TryParse(value, out var result)) { return result; } return fallback; } private static long GetServerPeerId() { try { if ((Object)(object)ZNet.instance != (Object)null) { MethodInfo method = typeof(ZNet).GetMethod("GetServerPeer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method != null) { object obj = method.Invoke(ZNet.instance, null); if (obj != null) { FieldInfo field = obj.GetType().GetField("m_uid", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.GetValue(obj) is long num && num != 0) { return num; } } } } } catch { } return ZRoutedRpc.Everybody; } } public sealed class RunebornServerReminder : MonoBehaviour { private static bool _serverReminderShownThisGameSession; private Player _player; private int _messagesShown; private float _nextMessageAt; public static bool ServerReminderEnabled = true; private const int ReminderRepeatCount = 1; private const float ReminderFirstDelay = 16f; private const float ReminderRepeatDelay = 6f; private const string ReminderText = "RUSTY HEATHENS SERVER\n\nRespect the crew.\nNo stealing.\nNo boss fights without the group.\nHelp with body recovery when you can.\nJoin Discord for rules, events, and updates.\n\nSkal, Heathens!"; private void Awake() { _player = ((Component)this).GetComponent(); _nextMessageAt = Time.time + 16f; if (_serverReminderShownThisGameSession) { _messagesShown = 1; } } private void Update() { if (_messagesShown >= 1 || !RunebornServerConfigSync.HasServerConfig()) { return; } if (!RunebornServerConfigSync.ServerReminderEnabled()) { _messagesShown = 1; _serverReminderShownThisGameSession = true; } else if (!((Object)(object)_player == (Object)null) && !((Object)(object)Player.m_localPlayer != (Object)(object)_player) && !(Time.time < _nextMessageAt)) { try { string text = RunebornServerConfigSync.ServerReminderTitle(); string text2 = RunebornServerConfigSync.ServerReminderBody(); ((Character)_player).Message((MessageType)2, text + "\n\n" + text2, 0, (Sprite)null); } catch { } _messagesShown++; _serverReminderShownThisGameSession = true; _nextMessageAt = Time.time + 6f; } } } [HarmonyPatch(typeof(Player), "Awake")] public static class RunebornServerReminderPlayerAwakePatch { private static void Postfix(Player __instance) { if (!((Object)(object)__instance == (Object)null) && (Object)(object)((Component)__instance).GetComponent() == (Object)null) { ((Component)__instance).gameObject.AddComponent(); } } } public sealed class RunebornWelcomeMessageOnce : MonoBehaviour { private static bool _welcomeShownThisGameSession; private Player _player; private bool _shown; private float _showAt; private const string WelcomeText = "Welcome to the Rusty Heathens\nSurvive the chaos. Earn Valhalla."; private void Awake() { _player = ((Component)this).GetComponent(); _showAt = Time.time + 5f; if (_welcomeShownThisGameSession) { _shown = true; } } private void Update() { if (_shown || (Object)(object)_player == (Object)null || (Object)(object)Player.m_localPlayer != (Object)(object)_player || Time.time < _showAt || !RunebornServerConfigSync.HasServerConfig()) { return; } if (!RunebornServerConfigSync.WelcomeMessageEnabled()) { _shown = true; _welcomeShownThisGameSession = true; return; } _shown = true; _welcomeShownThisGameSession = true; try { string text = RunebornServerConfigSync.WelcomeTitle(); string text2 = RunebornServerConfigSync.WelcomeBody(); ((Character)_player).Message((MessageType)2, text + "\n" + text2, 0, (Sprite)null); } catch { } } } [HarmonyPatch(typeof(Player), "Awake")] public static class RunebornWelcomeMessageOncePlayerAwakePatch { private static void Postfix(Player __instance) { if (!((Object)(object)__instance == (Object)null) && (Object)(object)((Component)__instance).GetComponent() == (Object)null) { ((Component)__instance).gameObject.AddComponent(); } } }