Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Gjallarhorn v0.1.0
plugins/Gjallarhorn.dll
Decompiled 3 hours agousing 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<MethodBase> 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<HarmonyMethod> 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<ZDOID, DateTime> Announced = new Dictionary<ZDOID, DateTime>(); private static readonly Dictionary<ZDOID, Pending> Waiting = new Dictionary<ZDOID, Pending>(); private static readonly Dictionary<string, DateTime> RecentBosses = new Dictionary<string, DateTime>(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<DeathReport> list = null; DateTime utcNow = DateTime.UtcNow; lock (Gate) { if (Waiting.Count > 0) { List<ZDOID> list2 = null; foreach (KeyValuePair<ZDOID, Pending> item in Waiting) { if (!(item.Value.GuessDueAt > utcNow)) { (list2 ?? (list2 = new List<ZDOID>())).Add(item.Key); (list ?? (list = new List<DeathReport>())).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<ZDOID> list = null; foreach (KeyValuePair<ZDOID, DateTime> item in Announced) { if (now - item.Value > AnnouncedRetention) { (list ?? (list = new List<ZDOID>())).Add(item.Key); } } if (list != null) { foreach (ZDOID item2 in list) { Announced.Remove(item2); } } } if (RecentBosses.Count <= 0) { return; } List<string> list2 = null; foreach (KeyValuePair<string, DateTime> recentBoss in RecentBosses) { if (now - recentBoss.Value > BossDedupeWindow) { (list2 ?? (list2 = new List<string>())).Add(recentBoss.Key); } } if (list2 == null) { return; } foreach (string item3 in list2) { RecentBosses.Remove(item3); } } } public static class DeathWatcher { private static readonly Dictionary<ZDOID, bool> LastSeenDead = new Dictionary<ZDOID, bool>(); private static readonly List<ZDOID> Seen = new List<ZDOID>(); private static readonly List<ZDOID> Gone = new List<ZDOID>(); private static readonly List<ZNetPeer> EmptyPeers = new List<ZNetPeer>(); 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<Candidate> 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<Candidate> 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<Candidate> list = new List<Candidate>(); 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<ZDO> list2 = new List<ZDO>(); ZDOMan.instance.FindSectorObjects(zone, new SimulationDistance(1, 0, false), list2, (List<ZDO>)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<Character>(); if (!((Object)(object)component == (Object)null) && !component.IsPlayer() && !((Object)(object)prefab.GetComponent<BaseAI>() == (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<string, string> Creatures = new Dictionary<string, string>(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<string, string> BossKeys = new Dictionary<string, string>(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<string, string> Biomes = new Dictionary<string, string>(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<bool> AnnounceDeaths; public static ConfigEntry<bool> AnnounceBossDefeats; public static ConfigEntry<bool> UseTopLeftFeed; public static ConfigEntry<bool> UseChatLine; public static ConfigEntry<bool> BossUsesCenterBanner; public static ConfigEntry<string> ChatSenderName; public static ConfigEntry<bool> IncludeBiome; public static ConfigEntry<bool> IncludeStarRank; public static ConfigEntry<bool> LogToConsole; public static ConfigEntry<float> PollInterval; public static ConfigEntry<float> ExactReportGrace; public static ConfigEntry<float> KillerSearchRadius; public static ConfigEntry<float> SwarmRadius; public static ConfigEntry<int> SwarmThreshold; public static ConfigEntry<string> MonsterMessages; public static ConfigEntry<string> BossKilledPlayerMessages; public static ConfigEntry<string> PvpMessages; public static ConfigEntry<string> OverwhelmedMessages; public static ConfigEntry<string> FallMessages; public static ConfigEntry<string> DrowningMessages; public static ConfigEntry<string> FireMessages; public static ConfigEntry<string> FreezingMessages; public static ConfigEntry<string> PoisonMessages; public static ConfigEntry<string> TreeMessages; public static ConfigEntry<string> EnvironmentMessages; public static ConfigEntry<string> SelfMessages; public static ConfigEntry<string> GenericMessages; public static ConfigEntry<string> BossDefeatMessages; public static ConfigEntry<bool> 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<bool>("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<bool>("1 - Channels", "AnnounceDeaths", true, new ConfigDescription("Announce player deaths server-wide.", (AcceptableValueBase)null, new object[1] { val })); AnnounceBossDefeats = config.Bind<bool>("1 - Channels", "AnnounceBossDefeats", true, new ConfigDescription("Announce when a boss is defeated.", (AcceptableValueBase)null, new object[1] { val })); UseTopLeftFeed = config.Bind<bool>("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<bool>("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<bool>("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<string>("1 - Channels", "ChatSenderName", "Gjallarhorn", new ConfigDescription("Name shown as the speaker on chat lines.", (AcceptableValueBase)null, new object[1] { val })); IncludeBiome = config.Bind<bool>("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<bool>("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<bool>("2 - Content", "LogToConsole", true, new ConfigDescription("Write every announcement to the server console log.", (AcceptableValueBase)null, new object[1] { val })); PollInterval = config.Bind<float>("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<float>(0.25f, 10f), new object[1] { val })); ExactReportGrace = config.Bind<float>("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<float>(0f, 10f), new object[1] { val })); KillerSearchRadius = config.Bind<float>("3 - Detection", "KillerSearchRadius", 20f, new ConfigDescription("Radius searched for a likely killer when guessing. Guess only.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 64f), new object[1] { val })); SwarmRadius = config.Bind<float>("3 - Detection", "SwarmRadius", 6f, new ConfigDescription("Radius counted for the 'overwhelmed by a horde' message. Guess only.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 32f), new object[1] { val })); SwarmThreshold = config.Bind<int>("3 - Detection", "SwarmThreshold", 4, new ConfigDescription("Hostiles within SwarmRadius needed before a death reads as a horde death.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(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<string> 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<string>("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<ChronicleEntry> Entries = new List<ChronicleEntry>(); 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<ChronicleEntry> Recent(int count = 25) { lock (Gate) { int num = Math.Min(count, Entries.Count); List<ChronicleEntry> list = new List<ChronicleEntry>(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) { } } }