using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using Gjallarhorn.Chronicle; using Gjallarhorn.Config; using Gjallarhorn.Logic; using Gjallarhorn.Model; using Gjallarhorn.Net; using Gjallarhorn.Server; using HarmonyLib; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using Splatform; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: IgnoresAccessChecksTo("assembly_guiutils")] [assembly: IgnoresAccessChecksTo("assembly_utils")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: IgnoresAccessChecksTo("Splatform")] [assembly: AssemblyCompany("RAGEmedia")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Server-side Viking death announcements and boss-defeat chronicles. Vanilla clients supported; installing it client-side upgrades the feed to exact killer and cause of death.")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0")] [assembly: AssemblyProduct("Gjallarhorn")] [assembly: AssemblyTitle("Gjallarhorn")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsReadOnlyAttribute : Attribute { } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Gjallarhorn { [BepInPlugin("com.ragemedia.gjallarhorn", "Gjallarhorn", "0.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class GjallarhornPlugin : BaseUnityPlugin { public const string PluginGuid = "com.ragemedia.gjallarhorn"; public const string PluginName = "Gjallarhorn"; public const string PluginVersion = "0.1.0"; internal static GjallarhornPlugin Instance; private readonly Harmony _harmony = new Harmony("com.ragemedia.gjallarhorn"); private ZNet _knownNet; private void Awake() { Instance = this; GjallarhornConfig.Bind(((BaseUnityPlugin)this).Config); GjallarhornRpc.Register(); _harmony.PatchAll(Assembly.GetExecutingAssembly()); VerifyPatches(); } private void OnDestroy() { ((MonoBehaviour)this).CancelInvoke("PollDeaths"); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } private void Update() { ZNet instance = ZNet.instance; if (instance == _knownNet) { return; } _knownNet = instance; ((MonoBehaviour)this).CancelInvoke("PollDeaths"); if (!((Object)(object)instance == (Object)null)) { if (!instance.IsServer()) { LogInfo("Running as a client. Deaths here are reported to the server with exact detail; announcements come back from the server."); return; } float num = Math.Max(0.25f, GjallarhornConfig.PollInterval.Value); ((MonoBehaviour)this).InvokeRepeating("PollDeaths", 2f, num); LogInfo($"Running as the server. Watching for deaths every {num:0.00}s."); } } private void PollDeaths() { try { DeathWatcher.Poll(); } catch (Exception arg) { LogError($"Death poll failed: {arg}"); } } private void VerifyPatches() { List list = _harmony.GetPatchedMethods().ToList(); LogInfo($"Harmony bound {list.Count} target(s):"); foreach (MethodBase item in list) { LogInfo(" bound " + item.DeclaringType?.FullName + "." + item.Name); } int num = 0; foreach (Type item2 in from t in Assembly.GetExecutingAssembly().GetTypes() where t.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).Any() select t) { List list2 = (from HarmonyPatch a in item2.GetCustomAttributes(typeof(HarmonyPatch), inherit: true) select ((HarmonyAttribute)a).info).ToList(); HarmonyMethod val; try { val = HarmonyMethod.Merge(list2); } catch (Exception ex) { LogError(" UNRESOLVED " + item2.Name + ": could not merge patch attributes: " + ex.Message); num++; continue; } if (!(val?.declaringType == null) && !string.IsNullOrEmpty(val.methodName) && AccessTools.Method(val.declaringType, val.methodName, val.argumentTypes, (Type[])null) == null) { num++; LogError(" UNRESOLVED " + item2.Name + " -> " + val.declaringType.Name + "." + val.methodName + " does not exist. This patch will never run. The game version likely changed; re-check the method against the decompiled source."); } } if (num > 0) { LogError(string.Format("{0}: {1} patch target(s) failed to resolve. ", "Gjallarhorn", num) + "This mod is loaded but not fully functional."); } else { LogInfo("Gjallarhorn v0.1.0 ready."); } } internal static void LogInfo(string msg) { ((BaseUnityPlugin)Instance).Logger.LogInfo((object)("[Gjallarhorn] " + msg)); } internal static void LogWarning(string msg) { ((BaseUnityPlugin)Instance).Logger.LogWarning((object)("[Gjallarhorn] " + msg)); } internal static void LogError(string msg) { ((BaseUnityPlugin)Instance).Logger.LogError((object)("[Gjallarhorn] " + msg)); } internal static void LogVerbose(string msg) { if (GjallarhornConfig.VerboseLogging != null && GjallarhornConfig.VerboseLogging.Value) { ((BaseUnityPlugin)Instance).Logger.LogInfo((object)("[Gjallarhorn] " + msg)); } } } } namespace Gjallarhorn.Server { public static class AnnouncementService { private sealed class Pending { public DeathReport Report; public DateTime GuessDueAt; } private static readonly object Gate = new object(); private static readonly Dictionary Announced = new Dictionary(); private static readonly Dictionary Waiting = new Dictionary(); private static readonly Dictionary RecentBosses = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly TimeSpan AnnouncedRetention = TimeSpan.FromMinutes(10.0); private static readonly TimeSpan BossDedupeWindow = TimeSpan.FromSeconds(15.0); public static void SubmitExact(DeathReport report) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) if (report == null || !GjallarhornConfig.AnnounceDeaths.Value) { return; } lock (Gate) { if (Announced.ContainsKey(report.VictimZdoId)) { GjallarhornPlugin.LogVerbose("Exact report for '" + report.VictimName + "' arrived after the guess went out. Raise ExactReportGrace if this keeps happening."); return; } Waiting.Remove(report.VictimZdoId); Announced[report.VictimZdoId] = DateTime.UtcNow; } Dispatch(report); } public static void SubmitObserved(DeathReport report) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) if (report == null || !GjallarhornConfig.AnnounceDeaths.Value) { return; } lock (Gate) { if (Announced.ContainsKey(report.VictimZdoId) || Waiting.ContainsKey(report.VictimZdoId)) { return; } Waiting[report.VictimZdoId] = new Pending { Report = report, GuessDueAt = DateTime.UtcNow.AddSeconds(Math.Max(0f, GjallarhornConfig.ExactReportGrace.Value)) }; } GjallarhornPlugin.LogVerbose("Observed death of '" + report.VictimName + "'; waiting " + $"{GjallarhornConfig.ExactReportGrace.Value:0.0}s for exact details."); } public static void Tick() { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) List list = null; DateTime utcNow = DateTime.UtcNow; lock (Gate) { if (Waiting.Count > 0) { List list2 = null; foreach (KeyValuePair item in Waiting) { if (!(item.Value.GuessDueAt > utcNow)) { (list2 ?? (list2 = new List())).Add(item.Key); (list ?? (list = new List())).Add(item.Value.Report); } } if (list2 != null) { foreach (ZDOID item2 in list2) { Waiting.Remove(item2); Announced[item2] = utcNow; } } } Prune(utcNow); } if (list == null) { return; } foreach (DeathReport item3 in list) { KillerGuesser.Fill(item3); Dispatch(item3); } } public static void BossDefeated(string bossName, string killerName, Vector3? position) { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) if (!GjallarhornConfig.AnnounceBossDefeats.Value || string.IsNullOrWhiteSpace(bossName)) { return; } lock (Gate) { DateTime utcNow = DateTime.UtcNow; if (RecentBosses.TryGetValue(bossName, out var value) && utcNow - value < BossDedupeWindow) { GjallarhornPlugin.LogVerbose("Boss '" + bossName + "' already announced; skipping."); return; } RecentBosses[bossName] = utcNow; } string biome = (position.HasValue ? ResolveBiome(position.Value) : string.Empty); string text = MessageFormatter.BossDefeat(bossName, killerName, biome); if (GjallarhornConfig.LogToConsole.Value) { GjallarhornPlugin.LogInfo("[boss] " + text); } Broadcast.Hud(text, GjallarhornConfig.BossUsesCenterBanner.Value); if (GjallarhornConfig.UseChatLine.Value) { Broadcast.Chat(text, (Vector3)(((??)position) ?? Vector3.zero)); } } private static void Dispatch(DeathReport report) { //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) string text = MessageFormatter.Death(report); if (GjallarhornConfig.LogToConsole.Value) { GjallarhornPlugin.LogInfo("[" + ((report.Source == ReportSource.Exact) ? "exact" : "guess") + "] " + text + " " + $"(cause={report.Cause}, pos={report.Position})"); } ChronicleRegistry.Record(report, text); if (GjallarhornConfig.UseTopLeftFeed.Value) { Broadcast.Hud(text, center: false); } if (GjallarhornConfig.UseChatLine.Value) { Broadcast.Chat(text, report.Position); } } public static string ResolveBiome(Vector3 position) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) try { if (WorldGenerator.instance != null) { return ((object)WorldGenerator.instance.GetBiome(position.x, position.z, 0.02f, false)/*cast due to .constrained prefix*/).ToString(); } } catch (Exception ex) { GjallarhornPlugin.LogVerbose($"Biome lookup failed at {position}: {ex.Message}"); } return string.Empty; } private static void Prune(DateTime now) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) if (Announced.Count > 0) { List list = null; foreach (KeyValuePair item in Announced) { if (now - item.Value > AnnouncedRetention) { (list ?? (list = new List())).Add(item.Key); } } if (list != null) { foreach (ZDOID item2 in list) { Announced.Remove(item2); } } } if (RecentBosses.Count <= 0) { return; } List list2 = null; foreach (KeyValuePair recentBoss in RecentBosses) { if (now - recentBoss.Value > BossDedupeWindow) { (list2 ?? (list2 = new List())).Add(recentBoss.Key); } } if (list2 == null) { return; } foreach (string item3 in list2) { RecentBosses.Remove(item3); } } } public static class DeathWatcher { private static readonly Dictionary LastSeenDead = new Dictionary(); private static readonly List Seen = new List(); private static readonly List Gone = new List(); private static readonly List EmptyPeers = new List(); public static void Poll() { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || ZDOMan.instance == null) { return; } Seen.Clear(); foreach (ZNetPeer item in ZNet.instance.GetPeers() ?? EmptyPeers) { if (item == null || !item.IsReady() || ((ZDOID)(ref item.m_characterID)).IsNone()) { continue; } ZDO zDO = ZDOMan.instance.GetZDO(item.m_characterID); if (zDO == null) { continue; } ZDOID characterID = item.m_characterID; Seen.Add(characterID); bool flag = zDO.GetBool(ZDOVars.s_dead, false); if (!LastSeenDead.TryGetValue(characterID, out var value)) { LastSeenDead[characterID] = flag; continue; } LastSeenDead[characterID] = flag; if (!value && flag) { string text = zDO.GetString(ZDOVars.s_playerName, string.Empty); if (string.IsNullOrWhiteSpace(text)) { text = item.m_playerName; } Vector3 position = zDO.GetPosition(); AnnouncementService.SubmitObserved(new DeathReport { VictimZdoId = characterID, VictimName = text, Position = position, Biome = AnnouncementService.ResolveBiome(position), Source = ReportSource.Guessed }); } } ForgetDeparted(); AnnouncementService.Tick(); } private static void ForgetDeparted() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) if (LastSeenDead.Count == 0) { return; } Gone.Clear(); foreach (ZDOID key in LastSeenDead.Keys) { if (!Seen.Contains(key)) { Gone.Add(key); } } foreach (ZDOID item in Gone) { LastSeenDead.Remove(item); } } } public static class KillerGuesser { private readonly struct Candidate { public readonly string Name; public readonly int Level; public readonly float Distance; public readonly bool IsBoss; public Candidate(string name, int level, float distance, bool isBoss) { Name = name; Level = level; Distance = distance; IsBoss = isBoss; } } public static void Fill(DeathReport report) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) List list = Scan(report.Position, GjallarhornConfig.KillerSearchRadius.Value); if (list.Count == 0) { report.Cause = DeathCause.Generic; report.KillerName = string.Empty; report.KillerLevel = 0; return; } float value = GjallarhornConfig.SwarmRadius.Value; int num = 0; foreach (Candidate item in list) { if (item.Distance <= value) { num++; } } if (num >= GjallarhornConfig.SwarmThreshold.Value) { report.Cause = DeathCause.Overwhelmed; report.KillerName = "a horde"; report.KillerLevel = 0; return; } Candidate candidate = list[0]; foreach (Candidate item2 in list) { if (item2.Distance < candidate.Distance) { candidate = item2; } } report.Cause = ((!candidate.IsBoss) ? DeathCause.Monster : DeathCause.Boss); report.KillerName = candidate.Name; report.KillerLevel = candidate.Level; } private static List Scan(Vector3 position, float radius) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) List list = new List(); if (ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null) { return list; } try { Vector2s zone = ZoneSystem.GetZone(position); List list2 = new List(); ZDOMan.instance.FindSectorObjects(zone, new SimulationDistance(1, 0, false), list2, (List)null); GjallarhornPlugin.LogVerbose($"Killer scan at {position}: {list2.Count} ZDO(s) in the surrounding zones."); float num2 = default(float); foreach (ZDO item in list2) { if (item == null) { continue; } float num = Vector3.Distance(item.GetPosition(), position); if (num > radius) { continue; } GameObject prefab = ZNetScene.instance.GetPrefab(item.GetPrefab()); if (!((Object)(object)prefab == (Object)null)) { Character component = prefab.GetComponent(); if (!((Object)(object)component == (Object)null) && !component.IsPlayer() && !((Object)(object)prefab.GetComponent() == (Object)null) && !item.GetBool(ZDOVars.s_tamed, false) && (!item.GetFloat(ZDOVars.s_health, ref num2) || !(num2 <= 0f))) { int num3 = item.GetInt(ZDOVars.s_level, 1); string text = NameTable.Creature(string.IsNullOrWhiteSpace(((Object)prefab).name) ? component.m_name : ((Object)prefab).name); GjallarhornPlugin.LogVerbose($" candidate '{text}' level={num3} dist={num:0.0} boss={component.IsBoss()}"); list.Add(new Candidate(text, num3, num, component.IsBoss())); } } } } catch (Exception arg) { GjallarhornPlugin.LogError($"Killer scan failed: {arg}"); } return list; } } } namespace Gjallarhorn.Patches { [HarmonyPatch(typeof(Character), "OnDeath")] public static class BossDeathPatch { private static void Prefix(Character __instance) { //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) try { if (!((Object)(object)__instance == (Object)null) && __instance.IsBoss() && GjallarhornConfig.AnnounceBossDefeats.Value && !((Object)(object)__instance.m_nview == (Object)null) && __instance.m_nview.IsValid() && __instance.m_nview.IsOwner()) { string bossName = NameTable.Creature(string.IsNullOrWhiteSpace(__instance.m_name) ? ((Object)((Component)__instance).gameObject).name : __instance.m_name); string killerName = string.Empty; HitData lastHit = __instance.m_lastHit; Character obj = ((lastHit != null) ? lastHit.GetAttacker() : null); Player val = (Player)(object)((obj is Player) ? obj : null); if (val != null) { killerName = val.GetPlayerName(); } else if ((Object)(object)Player.m_localPlayer != (Object)null) { killerName = Player.m_localPlayer.GetPlayerName(); } Vector3 position = ((Component)__instance).transform.position; if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { AnnouncementService.BossDefeated(bossName, killerName, position); } else { GjallarhornRpc.SendBoss(bossName, killerName, position); } } } catch (Exception arg) { GjallarhornPlugin.LogError($"Failed to report a boss defeat: {arg}"); } } } [HarmonyPatch(typeof(ZoneSystem), "RPC_SetGlobalKey")] public static class GlobalKeyPatch { private static void Prefix(ZoneSystem __instance, string name) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) try { if (GjallarhornConfig.AnnounceBossDefeats.Value && NameTable.IsBossDefeatKey(name) && !__instance.m_globalKeys.Contains(name)) { AnnouncementService.BossDefeated(NameTable.BossFromDefeatKey(name), string.Empty, Vector3.zero); } } catch (Exception arg) { GjallarhornPlugin.LogError($"Failed to handle global key '{name}': {arg}"); } } } [HarmonyPatch(typeof(Player), "OnDeath")] public static class PlayerDeathPatch { private static void Prefix(Player __instance) { try { if ((Object)(object)__instance == (Object)null || (Object)(object)((Character)__instance).m_nview == (Object)null || !((Character)__instance).m_nview.IsValid() || !((Character)__instance).m_nview.IsOwner()) { return; } DeathReport deathReport = Build(__instance); if (deathReport != null) { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { AnnouncementService.SubmitExact(deathReport); } else { GjallarhornRpc.SendDeath(deathReport); } } } catch (Exception arg) { GjallarhornPlugin.LogError($"Failed to report own death: {arg}"); } } private static DeathReport Build(Player player) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) Vector3 position = ((Component)player).transform.position; DeathReport deathReport = new DeathReport { VictimZdoId = ((Character)player).GetZDOID(), VictimName = player.GetPlayerName(), Position = position, Biome = AnnouncementService.ResolveBiome(position), Source = ReportSource.Exact }; HitData lastHit = ((Character)player).m_lastHit; if (lastHit == null) { deathReport.Cause = DeathCause.Generic; return deathReport; } Character attacker = lastHit.GetAttacker(); if ((Object)(object)attacker != (Object)null) { deathReport.KillerLevel = attacker.GetLevel(); Player val = (Player)(object)((attacker is Player) ? attacker : null); deathReport.KillerName = ((val != null) ? val.GetPlayerName() : NameTable.Creature(((Object)((Component)attacker).gameObject).name)); } deathReport.Cause = Classify(lastHit, attacker); return deathReport; } private static DeathCause Classify(HitData hit, Character attacker) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected I4, but got Unknown HitType hitType = hit.m_hitType; switch (hitType - 1) { case 1: return DeathCause.Pvp; case 0: if (!((Object)(object)attacker != (Object)null) || !attacker.IsBoss()) { return DeathCause.Monster; } return DeathCause.Boss; case 2: return DeathCause.Fall; case 3: case 7: case 20: return DeathCause.Drowning; case 4: case 19: case 21: case 22: return DeathCause.Fire; case 5: return DeathCause.Freezing; case 6: return DeathCause.Poison; case 12: return DeathCause.Tree; case 13: return DeathCause.Self; case 8: case 9: case 10: case 11: case 14: case 15: case 16: case 17: case 18: case 23: return DeathCause.Environment; default: return DeathCause.Generic; } } } } namespace Gjallarhorn.Net { public static class Broadcast { private const string ShowMessageRpc = "ShowMessage"; private const string ChatMessageRpc = "ChatMessage"; private static bool _warnedNoChatIdentity; public static void Hud(string text, bool center) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected I4, but got Unknown //IL_0041: Unknown result type (might be due to invalid IL or missing references) if (ZRoutedRpc.instance == null || string.IsNullOrWhiteSpace(text)) { return; } MessageType val = (MessageType)((!center) ? 1 : 2); try { ZRoutedRpc.instance.InvokeRoutedRPC(0L, "ShowMessage", new object[2] { (int)val, text }); GjallarhornPlugin.LogVerbose($"HUD broadcast ({val}): {text}"); } catch (Exception ex) { GjallarhornPlugin.LogWarning("HUD broadcast failed: " + ex.Message); } } public static void Chat(string text, Vector3 position) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_0062: Unknown result type (might be due to invalid IL or missing references) if (ZRoutedRpc.instance == null || string.IsNullOrWhiteSpace(text)) { return; } if (!TryGetChatIdentity(out var userId)) { if (!_warnedNoChatIdentity) { _warnedNoChatIdentity = true; GjallarhornPlugin.LogWarning("No connected player with a valid platform ID, so chat lines cannot be sent -- clients would reject them on the permission check. Falling back to the top-left feed only. This resolves once someone is connected."); } return; } UserInfo val = new UserInfo { Name = GjallarhornConfig.ChatSenderName.Value, UserId = userId }; try { ZRoutedRpc.instance.InvokeRoutedRPC(0L, "ChatMessage", new object[4] { position, 1, val, text }); GjallarhornPlugin.LogVerbose("Chat broadcast: " + text); } catch (Exception ex) { GjallarhornPlugin.LogWarning("Chat broadcast failed: " + ex.Message); } } private static bool TryGetChatIdentity(out PlatformUserID userId) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) userId = PlatformUserID.None; if ((Object)(object)ZNet.instance == (Object)null) { return false; } foreach (PlayerInfo player in ZNet.instance.GetPlayerList()) { PlatformUserID id = player.m_userInfo.m_id; if (((PlatformUserID)(ref id)).IsValid) { userId = player.m_userInfo.m_id; return true; } } return false; } } public static class GjallarhornRpc { [CompilerGenerated] private static class <>O { public static CoroutineHandler <0>__OnDeathReceived; public static CoroutineHandler <1>__NoOp; public static CoroutineHandler <2>__OnBossReceived; } private const string DeathRpcName = "GjallarhornDeath"; private const string BossRpcName = "GjallarhornBoss"; private static CustomRPC _deathRpc; private static CustomRPC _bossRpc; public static void Register() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown NetworkManager instance = NetworkManager.Instance; object obj = <>O.<0>__OnDeathReceived; if (obj == null) { CoroutineHandler val = OnDeathReceived; <>O.<0>__OnDeathReceived = val; obj = (object)val; } object obj2 = <>O.<1>__NoOp; if (obj2 == null) { CoroutineHandler val2 = NoOp; <>O.<1>__NoOp = val2; obj2 = (object)val2; } _deathRpc = instance.AddRPC("GjallarhornDeath", (CoroutineHandler)obj, (CoroutineHandler)obj2); NetworkManager instance2 = NetworkManager.Instance; object obj3 = <>O.<2>__OnBossReceived; if (obj3 == null) { CoroutineHandler val3 = OnBossReceived; <>O.<2>__OnBossReceived = val3; obj3 = (object)val3; } object obj4 = <>O.<1>__NoOp; if (obj4 == null) { CoroutineHandler val4 = NoOp; <>O.<1>__NoOp = val4; obj4 = (object)val4; } _bossRpc = instance2.AddRPC("GjallarhornBoss", (CoroutineHandler)obj3, (CoroutineHandler)obj4); GjallarhornPlugin.LogInfo("Registered RPCs 'GjallarhornDeath' and 'GjallarhornBoss'."); } public static void SendDeath(DeathReport report) { if (_deathRpc != null && ZRoutedRpc.instance != null) { _deathRpc.SendPackage(ZRoutedRpc.instance.GetServerPeerID(), report.Write()); GjallarhornPlugin.LogVerbose("Sent exact death report for '" + report.VictimName + "'."); } } public static void SendBoss(string bossName, string killerName, Vector3 position) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) if (_bossRpc != null && ZRoutedRpc.instance != null) { BossReport bossReport = new BossReport { BossName = bossName, KillerName = killerName, Position = position }; _bossRpc.SendPackage(ZRoutedRpc.instance.GetServerPeerID(), bossReport.Write()); GjallarhornPlugin.LogVerbose("Sent boss defeat report for '" + bossName + "'."); } } private static IEnumerator OnDeathReceived(long sender, ZPackage package) { if (package != null && package.Size() > 1) { DeathReport deathReport = DeathReport.Read(package); if (deathReport == null) { GjallarhornPlugin.LogWarning($"Discarded an unreadable death report from peer {sender}. That client " + "is most likely running a different version of Gjallarhorn."); } else { AnnouncementService.SubmitExact(deathReport); } } yield break; } private static IEnumerator OnBossReceived(long sender, ZPackage package) { if (package != null && package.Size() > 1) { BossReport bossReport = BossReport.Read(package); if (bossReport == null) { GjallarhornPlugin.LogWarning($"Discarded an unreadable boss report from peer {sender}."); } else { AnnouncementService.BossDefeated(bossReport.BossName, bossReport.KillerName, bossReport.Position); } } yield break; } private static IEnumerator NoOp(long sender, ZPackage package) { yield break; } } } namespace Gjallarhorn.Model { public class BossReport { public const int WireVersion = 1; public string BossName = string.Empty; public string KillerName = string.Empty; public Vector3 Position = Vector3.zero; public ZPackage Write() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(1); val.Write(BossName ?? string.Empty); val.Write(KillerName ?? string.Empty); val.Write(Position); return val; } public static BossReport Read(ZPackage pkg) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) try { if (pkg.ReadInt() != 1) { return null; } return new BossReport { BossName = pkg.ReadString(), KillerName = pkg.ReadString(), Position = pkg.ReadVector3() }; } catch { return null; } } } public enum DeathCause { Generic, Monster, Boss, Pvp, Overwhelmed, Fall, Drowning, Fire, Freezing, Poison, Tree, Environment, Self } public enum ReportSource { Guessed, Exact } public class DeathReport { public const int WireVersion = 1; public ZDOID VictimZdoId = ZDOID.None; public string VictimName = string.Empty; public string KillerName = string.Empty; public int KillerLevel; public DeathCause Cause; public Vector3 Position = Vector3.zero; public string Biome = string.Empty; public ReportSource Source; public ZPackage Write() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(1); val.Write(VictimZdoId); val.Write(VictimName ?? string.Empty); val.Write(KillerName ?? string.Empty); val.Write(KillerLevel); val.Write((int)Cause); val.Write(Position); val.Write(Biome ?? string.Empty); return val; } public static DeathReport Read(ZPackage pkg) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) try { if (pkg.ReadInt() != 1) { return null; } return new DeathReport { VictimZdoId = pkg.ReadZDOID(), VictimName = pkg.ReadString(), KillerName = pkg.ReadString(), KillerLevel = pkg.ReadInt(), Cause = (DeathCause)pkg.ReadInt(), Position = pkg.ReadVector3(), Biome = pkg.ReadString(), Source = ReportSource.Exact }; } catch { return null; } } } } namespace Gjallarhorn.Logic { public static class MessageFormatter { private static readonly Random Rng = new Random(); private static readonly char[] Separator = new char[1] { ';' }; public static string Death(DeathReport report) { string text = Pick(PoolFor(report.Cause)); string text2 = (string.IsNullOrWhiteSpace(report.KillerName) ? "an unseen hand" : report.KillerName); if (GjallarhornConfig.IncludeStarRank.Value && report.KillerLevel > 1) { text2 = NameTable.WithStars(text2, report.KillerLevel); } string newValue = (GjallarhornConfig.IncludeBiome.Value ? NameTable.Biome(report.Biome) : "the wilds"); return text.Replace("{victim}", Blank(report.VictimName, "A Viking")).Replace("{killer}", text2).Replace("{biome}", newValue); } public static string BossDefeat(string bossName, string killerName, string biome) { return Pick(GjallarhornConfig.BossDefeatMessages.Value).Replace("{boss}", Blank(bossName, "a great beast")).Replace("{killer}", Blank(killerName, "the Vikings")).Replace("{biome}", GjallarhornConfig.IncludeBiome.Value ? NameTable.Biome(biome) : "the wilds"); } private static string PoolFor(DeathCause cause) { return cause switch { DeathCause.Monster => GjallarhornConfig.MonsterMessages.Value, DeathCause.Boss => GjallarhornConfig.BossKilledPlayerMessages.Value, DeathCause.Pvp => GjallarhornConfig.PvpMessages.Value, DeathCause.Overwhelmed => GjallarhornConfig.OverwhelmedMessages.Value, DeathCause.Fall => GjallarhornConfig.FallMessages.Value, DeathCause.Drowning => GjallarhornConfig.DrowningMessages.Value, DeathCause.Fire => GjallarhornConfig.FireMessages.Value, DeathCause.Freezing => GjallarhornConfig.FreezingMessages.Value, DeathCause.Poison => GjallarhornConfig.PoisonMessages.Value, DeathCause.Tree => GjallarhornConfig.TreeMessages.Value, DeathCause.Environment => GjallarhornConfig.EnvironmentMessages.Value, DeathCause.Self => GjallarhornConfig.SelfMessages.Value, _ => GjallarhornConfig.GenericMessages.Value, }; } private static string Pick(string pool) { if (string.IsNullOrWhiteSpace(pool)) { return "{victim} has died in {biome}"; } string[] array = pool.Split(Separator, StringSplitOptions.RemoveEmptyEntries); if (array.Length == 0) { return "{victim} has died in {biome}"; } lock (Rng) { return array[Rng.Next(array.Length)].Trim(); } } private static string Blank(string value, string fallback) { if (!string.IsNullOrWhiteSpace(value)) { return value.Trim(); } return fallback; } } public static class NameTable { private static readonly Dictionary Creatures = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "Greydwarf_Elite", "Greydwarf Brute" }, { "Greydwarf_Shaman", "Greydwarf Shaman" }, { "Skeleton_NoArcher", "Skeleton" }, { "gd_king", "The Elder" }, { "Blob", "Blob" }, { "BlobElite", "Oozer" }, { "Draugr_Elite", "Draugr Elite" }, { "Draugr_Ranged", "Draugr Archer" }, { "Leech", "Leech" }, { "Surtling", "Surtling" }, { "Wraith", "Wraith" }, { "StoneGolem", "Stone Golem" }, { "Hatchling", "Drake" }, { "Ulv", "Ulv" }, { "Fenring_Cultist", "Cultist" }, { "Goblin", "Fuling" }, { "GoblinArcher", "Fuling Archer" }, { "GoblinBrute", "Fuling Berserker" }, { "GoblinShaman", "Fuling Shaman" }, { "GoblinKing", "Yagluth" }, { "Deathsquito", "Deathsquito" }, { "BlobTar", "Growth" }, { "Lox", "Lox" }, { "Seeker", "Seeker" }, { "SeekerBrute", "Seeker Soldier" }, { "SeekerBrood", "Seeker Brood" }, { "SeekerQueen", "The Queen" }, { "Dverger", "Dvergr" }, { "DvergerMage", "Dvergr Mage" }, { "DvergerMageFire", "Dvergr Rogue" }, { "DvergerMageIce", "Dvergr Mage" }, { "DvergerMageSupport", "Dvergr Mage" }, { "Gjall", "Gjall" }, { "Tick", "Tick" }, { "Charred_Melee", "Charred Warrior" }, { "Charred_Archer", "Charred Marksman" }, { "Charred_Mage", "Charred Twitcher" }, { "Charred_Twitcher", "Charred Twitcher" }, { "FallenValkyrie", "Fallen Valkyrie" }, { "Morgen", "Morgen" }, { "Volture", "Volture" }, { "Asksvin", "Asksvin" }, { "BonemawSerpent", "Bonemaw" }, { "Fader", "Fader" }, { "Eikthyr", "Eikthyr" }, { "Bonemass", "Bonemass" }, { "Dragon", "Moder" }, { "Serpent", "Sea Serpent" } }; private static readonly Dictionary BossKeys = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "defeated_eikthyr", "Eikthyr" }, { "defeated_gdking", "The Elder" }, { "defeated_bonemass", "Bonemass" }, { "defeated_dragon", "Moder" }, { "defeated_goblinking", "Yagluth" }, { "defeated_queen", "The Queen" }, { "defeated_fader", "Fader" } }; private static readonly Dictionary Biomes = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "None", "the wilds" }, { "Meadows", "the Meadows" }, { "BlackForest", "the Black Forest" }, { "Swamp", "the Swamp" }, { "Mountain", "the Mountains" }, { "Plains", "the Plains" }, { "Ocean", "the Ocean" }, { "Mistlands", "the Mistlands" }, { "AshLands", "the Ashlands" }, { "DeepNorth", "the Deep North" } }; public static bool IsBossDefeatKey(string globalKey) { if (!string.IsNullOrEmpty(globalKey)) { return globalKey.StartsWith("defeated_", StringComparison.OrdinalIgnoreCase); } return false; } public static string BossFromDefeatKey(string globalKey) { if (string.IsNullOrEmpty(globalKey)) { return "a great beast"; } if (BossKeys.TryGetValue(globalKey.Trim(), out var value)) { return value; } string text = globalKey.Trim(); int num = text.IndexOf('_'); if (num >= 0 && num + 1 < text.Length) { text = text.Substring(num + 1); } return Prettify(text); } public static string Creature(string raw) { if (string.IsNullOrWhiteSpace(raw)) { return "something unseen"; } string text = raw.Trim(); if (text.EndsWith("(Clone)", StringComparison.Ordinal)) { text = text.Substring(0, text.Length - "(Clone)".Length).Trim(); } if (text.StartsWith("$", StringComparison.Ordinal)) { text = text.Substring(1); if (text.StartsWith("enemy_", StringComparison.OrdinalIgnoreCase)) { text = text.Substring("enemy_".Length); } } if (!Creatures.TryGetValue(text, out var value)) { return Prettify(text); } return value; } public static string WithStars(string creature, int level) { if (level < 2) { return creature; } return $"{level - 1}-star {creature}"; } public static string Biome(string raw) { if (string.IsNullOrWhiteSpace(raw)) { return "the wilds"; } if (!Biomes.TryGetValue(raw.Trim(), out var value)) { return "the wilds"; } return value; } private static string Prettify(string name) { name = name.Replace('_', ' ').Trim(); if (name.Length == 0) { return "something unseen"; } StringBuilder stringBuilder = new StringBuilder(name.Length + 8); for (int i = 0; i < name.Length; i++) { char c = name[i]; if (i > 0 && char.IsUpper(c) && !char.IsUpper(name[i - 1]) && name[i - 1] != ' ') { stringBuilder.Append(' '); } stringBuilder.Append(c); } string text = stringBuilder.ToString(); return char.ToUpperInvariant(text[0]) + text.Substring(1); } } } namespace Gjallarhorn.Config { public static class GjallarhornConfig { public static ConfigEntry AnnounceDeaths; public static ConfigEntry AnnounceBossDefeats; public static ConfigEntry UseTopLeftFeed; public static ConfigEntry UseChatLine; public static ConfigEntry BossUsesCenterBanner; public static ConfigEntry ChatSenderName; public static ConfigEntry IncludeBiome; public static ConfigEntry IncludeStarRank; public static ConfigEntry LogToConsole; public static ConfigEntry PollInterval; public static ConfigEntry ExactReportGrace; public static ConfigEntry KillerSearchRadius; public static ConfigEntry SwarmRadius; public static ConfigEntry SwarmThreshold; public static ConfigEntry MonsterMessages; public static ConfigEntry BossKilledPlayerMessages; public static ConfigEntry PvpMessages; public static ConfigEntry OverwhelmedMessages; public static ConfigEntry FallMessages; public static ConfigEntry DrowningMessages; public static ConfigEntry FireMessages; public static ConfigEntry FreezingMessages; public static ConfigEntry PoisonMessages; public static ConfigEntry TreeMessages; public static ConfigEntry EnvironmentMessages; public static ConfigEntry SelfMessages; public static ConfigEntry GenericMessages; public static ConfigEntry BossDefeatMessages; public static ConfigEntry VerboseLogging; public static void Bind(ConfigFile config) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Expected O, but got Unknown //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Expected O, but got Unknown //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Expected O, but got Unknown //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Expected O, but got Unknown //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Expected O, but got Unknown //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Expected O, but got Unknown //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Expected O, but got Unknown //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Expected O, but got Unknown //IL_0257: Unknown result type (might be due to invalid IL or missing references) //IL_0261: Expected O, but got Unknown //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_029e: Expected O, but got Unknown //IL_02c6: Unknown result type (might be due to invalid IL or missing references) //IL_02d0: Expected O, but got Unknown ConfigurationManagerAttributes val = new ConfigurationManagerAttributes { IsAdminOnly = true }; VerboseLogging = config.Bind("0 - Diagnostics", "VerboseLogging", false, "Log every poll tick, proximity scan candidate, and broadcast. Very noisy; for debugging only. Local preference, not synced."); AnnounceDeaths = config.Bind("1 - Channels", "AnnounceDeaths", true, new ConfigDescription("Announce player deaths server-wide.", (AcceptableValueBase)null, new object[1] { val })); AnnounceBossDefeats = config.Bind("1 - Channels", "AnnounceBossDefeats", true, new ConfigDescription("Announce when a boss is defeated.", (AcceptableValueBase)null, new object[1] { val })); UseTopLeftFeed = config.Bind("1 - Channels", "UseTopLeftFeed", true, new ConfigDescription("Show death announcements in the top-left message feed, where pickup and skill-up messages appear.", (AcceptableValueBase)null, new object[1] { val })); UseChatLine = config.Bind("1 - Channels", "UseChatLine", true, new ConfigDescription("Also post death announcements as a chat line, so they stay in the chat history. Players standing near the death, or with the map open, will also see it as floating text at the spot -- that is vanilla chat behaviour, not something this mod can suppress.", (AcceptableValueBase)null, new object[1] { val })); BossUsesCenterBanner = config.Bind("1 - Channels", "BossUsesCenterBanner", true, new ConfigDescription("Boss defeats get the large centre-screen banner instead of the top-left feed. Player deaths never use the centre banner.", (AcceptableValueBase)null, new object[1] { val })); ChatSenderName = config.Bind("1 - Channels", "ChatSenderName", "Gjallarhorn", new ConfigDescription("Name shown as the speaker on chat lines.", (AcceptableValueBase)null, new object[1] { val })); IncludeBiome = config.Bind("2 - Content", "IncludeBiome", true, new ConfigDescription("Resolve the {biome} token. When false, {biome} renders as 'the wilds'.", (AcceptableValueBase)null, new object[1] { val })); IncludeStarRank = config.Bind("2 - Content", "IncludeStarRank", true, new ConfigDescription("Prefix starred creatures in {killer}, e.g. '2-star Greydwarf'.", (AcceptableValueBase)null, new object[1] { val })); LogToConsole = config.Bind("2 - Content", "LogToConsole", true, new ConfigDescription("Write every announcement to the server console log.", (AcceptableValueBase)null, new object[1] { val })); PollInterval = config.Bind("3 - Detection", "PollInterval", 1f, new ConfigDescription("Seconds between server-side checks of each connected player's death flag. Only used for players who do not have Gjallarhorn installed.", (AcceptableValueBase)(object)new AcceptableValueRange(0.25f, 10f), new object[1] { val })); ExactReportGrace = config.Bind("3 - Detection", "ExactReportGrace", 1.5f, new ConfigDescription("After the server notices a death, wait this long for the victim's client to send exact details before falling back to a guess. Players with Gjallarhorn installed get an accurate killer; raising this only delays the fallback.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), new object[1] { val })); KillerSearchRadius = config.Bind("3 - Detection", "KillerSearchRadius", 20f, new ConfigDescription("Radius searched for a likely killer when guessing. Guess only.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 64f), new object[1] { val })); SwarmRadius = config.Bind("3 - Detection", "SwarmRadius", 6f, new ConfigDescription("Radius counted for the 'overwhelmed by a horde' message. Guess only.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 32f), new object[1] { val })); SwarmThreshold = config.Bind("3 - Detection", "SwarmThreshold", 4, new ConfigDescription("Hostiles within SwarmRadius needed before a death reads as a horde death.", (AcceptableValueBase)(object)new AcceptableValueRange(2, 20), new object[1] { val })); MonsterMessages = Pool(config, "MonsterMessages", "{victim} was slain by {killer} in {biome};{victim} was torn apart by {killer};{killer} claimed the soul of {victim} in {biome};{victim} fell before the wrath of {killer}", "Killed by a creature. Tokens: {victim} {killer} {biome}", val); BossKilledPlayerMessages = Pool(config, "BossKilledPlayerMessages", "{victim} was annihilated by {killer};The legendary {killer} crushed {victim} into dust;{victim} dared challenge {killer}, and was destroyed", "Killed by a boss. Tokens: {victim} {killer} {biome}", val); PvpMessages = Pool(config, "PvpMessages", "{victim} was cut down by {killer};{killer} sent {victim} to the halls of Valhalla;{victim} lost a duel with {killer} in {biome}", "Killed by another player. Tokens: {victim} {killer} {biome}", val); OverwhelmedMessages = Pool(config, "OverwhelmedMessages", "{victim} was overwhelmed by a horde in {biome};{victim} made a last stand against overwhelming odds;{victim} fell fighting, surrounded, in {biome}", "Killed while surrounded. Tokens: {victim} {biome}", val); FallMessages = Pool(config, "FallMessages", "{victim} discovered that Valheim has gravity;{victim} fell to their death in {biome};The ground rose up to meet {victim}", "Fall damage. Tokens: {victim} {biome}", val); DrowningMessages = Pool(config, "DrowningMessages", "{victim} was claimed by the sea;{victim} drowned in {biome};The waves closed over {victim}", "Drowning or lost at sea. Tokens: {victim} {biome}", val); FireMessages = Pool(config, "FireMessages", "{victim} burned to ash in {biome};{victim} learned that fire is hot;The flames took {victim}", "Burning, cinders, lava, incinerator. Tokens: {victim} {biome}", val); FreezingMessages = Pool(config, "FreezingMessages", "{victim} froze to death in {biome};The cold took {victim};{victim} should have packed warmer clothes", "Freezing. Tokens: {victim} {biome}", val); PoisonMessages = Pool(config, "PoisonMessages", "{victim} succumbed to poison in {biome};Venom finished what {killer} started;{victim} died gasping, poisoned, in {biome}", "Poison. Tokens: {victim} {killer} {biome}", val); TreeMessages = Pool(config, "TreeMessages", "{victim} was crushed by a falling tree;The forest took its revenge on {victim};{victim} lost an argument with a tree in {biome}", "Killed by a felled tree. Tokens: {victim} {biome}", val); EnvironmentMessages = Pool(config, "EnvironmentMessages", "{victim} died to their own surroundings in {biome};{victim} was undone by the terrain;{biome} itself finished {victim}", "Carts, boats, structures, turrets, stalagtites, smoke, edge of world. Tokens: {victim} {biome}", val); SelfMessages = Pool(config, "SelfMessages", "{victim} died by their own hand;{victim} made a poor decision in {biome}", "Self-inflicted. Tokens: {victim} {biome}", val); GenericMessages = Pool(config, "GenericMessages", "{victim} has departed for the halls of Valhalla;The Norns have cut the thread of {victim}'s life;{victim} died in {biome}", "Fallback when the cause is unknown. Tokens: {victim} {biome}", val); BossDefeatMessages = Pool(config, "BossDefeatMessages", "{boss} has been defeated!;{killer} has slain {boss}!;The mighty {boss} falls. Gjallarhorns will sing of this day.", "Boss defeated. {killer} renders as 'the Vikings' when the killer is unknown. Tokens: {boss} {killer} {biome}", val); } private static ConfigEntry Pool(ConfigFile config, string key, string value, string help, ConfigurationManagerAttributes admin) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown return config.Bind("4 - Messages", key, value, new ConfigDescription(help + " Separate alternatives with semicolons.", (AcceptableValueBase)null, new object[1] { admin })); } } } namespace Gjallarhorn.Chronicle { public sealed class ChronicleEntry { public DateTime TimestampUtc; public string VictimName; public string KillerName; public DeathCause Cause; public string Biome; public Vector3 Position; public bool WasExact; public string Message; } public static class ChronicleRegistry { private const int MaxEntries = 200; private static readonly List Entries = new List(); private static readonly object Gate = new object(); public static void Record(DeathReport report, string message) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) ChronicleEntry item = new ChronicleEntry { TimestampUtc = DateTime.UtcNow, VictimName = report.VictimName, KillerName = report.KillerName, Cause = report.Cause, Biome = report.Biome, Position = report.Position, WasExact = (report.Source == ReportSource.Exact), Message = message }; lock (Gate) { Entries.Add(item); if (Entries.Count > 200) { Entries.RemoveRange(0, Entries.Count - 200); } } } public static List Recent(int count = 25) { lock (Gate) { int num = Math.Min(count, Entries.Count); List list = new List(num); for (int num2 = Entries.Count - 1; num2 >= Entries.Count - num; num2--) { list.Add(Entries[num2]); } return list; } } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }