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 Ragnavik Progress v1.1.0
plugins/RagnavikProgress/RagnavikProgress.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("0.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 RagnavikProgress { [BepInPlugin("lostkode.ragnavik.progress", "Ragnavik Progress", "1.1.0")] public sealed class RagnavikProgressPlugin : BaseUnityPlugin { [Serializable] private sealed class ProgressReport { public string server = ""; public string instance = ""; public string[] bosses = Array.Empty<string>(); public PlayerProgress[] players = Array.Empty<PlayerProgress>(); public BossKill[] bossKills = Array.Empty<BossKill>(); public int milestoneStep = 10; } [Serializable] private sealed class PlayerProgress { public string id = ""; public string name = ""; public int level; } [Serializable] private sealed class BossKill { public string id = ""; public string key = ""; public string boss = ""; public string killer = ""; public string[] participants = Array.Empty<string>(); } private static RagnavikProgressPlugin? _instance; private readonly FieldInfo? _globalKeysField = AccessTools.Field(typeof(ZoneSystem), "m_globalKeys"); private ConfigEntry<bool>? _enabled; private ConfigEntry<string>? _endpoint; private ConfigEntry<string>? _tokenFile; private ConfigEntry<string>? _tokenHeader; private ConfigEntry<string>? _serverName; private ConfigEntry<int>? _checkSeconds; private ConfigEntry<int>? _heartbeatSeconds; private ConfigEntry<int>? _milestoneStep; private ConfigEntry<float>? _participantRadius; private ConfigEntry<bool>? _reportBosses; private ConfigEntry<bool>? _reportLevels; private float _nextCheck; private float _nextHeartbeat; private string _lastSignature = ""; private string _reportSignature = ""; private Task<bool>? _reportTask; private readonly List<BossKill> _pendingBossKills = new List<BossKill>(); private readonly HashSet<string> _seenBossDeaths = new HashSet<string>(StringComparer.Ordinal); private string[] _reportedBossKillIds = Array.Empty<string>(); private void Awake() { //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Expected O, but got Unknown //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Expected O, but got Unknown //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Expected O, but got Unknown //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Expected O, but got Unknown _instance = this; _enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", false, "Enable reporting after a private server endpoint is configured."); _endpoint = ((BaseUnityPlugin)this).Config.Bind<string>("Connection", "Endpoint", "", "Private HTTP endpoint that accepts progress reports."); _tokenFile = ((BaseUnityPlugin)this).Config.Bind<string>("Connection", "TokenFile", "", "Server-only path containing the authentication token."); _tokenHeader = ((BaseUnityPlugin)this).Config.Bind<string>("Connection", "TokenHeader", "X-Progress-Token", "HTTP header used to send the token."); _serverName = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "ServerName", "Valheim", "Server name included in progress reports."); _milestoneStep = ((BaseUnityPlugin)this).Config.Bind<int>("Milestones", "EpicMMOLevelStep", 10, new ConfigDescription("Report EpicMMO player levels in this interval.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>())); _reportBosses = ((BaseUnityPlugin)this).Config.Bind<bool>("Milestones", "ReportBossDefeats", true, "Report boss defeats, killing player, and nearby participants."); _reportLevels = ((BaseUnityPlugin)this).Config.Bind<bool>("Milestones", "ReportEpicMMOLevels", true, "Report connected characters reaching EpicMMO level milestones."); _participantRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Milestones", "BossParticipantRadius", 100f, new ConfigDescription("Meters from a defeated boss counted as participating players.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 500f), Array.Empty<object>())); _checkSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("Timing", "CheckSeconds", 30, new ConfigDescription("Seconds between progress checks.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 600), Array.Empty<object>())); _heartbeatSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("Timing", "HeartbeatSeconds", 3600, new ConfigDescription("Seconds between unchanged state reports.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(300, 86400), Array.Empty<object>())); Harmony.CreateAndPatchAll(typeof(RagnavikProgressPlugin), (string)null); } private void Update() { Task<bool> reportTask = _reportTask; if (reportTask != null && reportTask.IsCompleted) { try { if (_reportTask.GetAwaiter().GetResult()) { _lastSignature = _reportSignature; _nextHeartbeat = Time.realtimeSinceStartup + (float)_heartbeatSeconds.Value; _pendingBossKills.RemoveAll((BossKill kill) => _reportedBossKillIds.Contains(kill.id)); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Progress report returned an unexpected HTTP status."); _nextCheck = Time.realtimeSinceStartup + 300f; } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Progress report failed: " + ex.Message)); _nextCheck = Time.realtimeSinceStartup + 300f; } _reportTask = null; } ConfigEntry<bool>? enabled = _enabled; if (enabled == null || !enabled.Value || string.IsNullOrWhiteSpace(_endpoint?.Value) || Time.realtimeSinceStartup < _nextCheck) { return; } _nextCheck = Time.realtimeSinceStartup + (float)_checkSeconds.Value; if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || (Object)(object)ZoneSystem.instance == (Object)null) { return; } if (_globalKeysField == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"ZoneSystem.m_globalKeys is unavailable; progress cannot be reported."); } else { if (!(_globalKeysField.GetValue(ZoneSystem.instance) is HashSet<string> source)) { return; } string[] array = (_reportBosses.Value ? source.Where((string key) => key.StartsWith("defeated_", StringComparison.OrdinalIgnoreCase)).OrderBy<string, string>((string key) => key, StringComparer.OrdinalIgnoreCase).ToArray() : Array.Empty<string>()); PlayerProgress[] array2 = (_reportLevels.Value ? ReadPlayers() : Array.Empty<PlayerProgress>()); string text = string.Join("|", array) + "#" + string.Join("|", array2.Select((PlayerProgress player) => $"{player.id}:{player.level}")) + "#" + string.Join("|", _pendingBossKills.Select((BossKill kill) => kill.id)); if (_reportTask == null && (!(text == _lastSignature) || !(Time.realtimeSinceStartup < _nextHeartbeat))) { string token; try { token = (string.IsNullOrWhiteSpace(_tokenFile?.Value) ? "" : File.ReadAllText(_tokenFile.Value).Trim()); } catch (Exception ex2) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Progress token unavailable: " + ex2.Message)); _nextCheck = Time.realtimeSinceStartup + 600f; return; } string body = JsonUtility.ToJson((object)new ProgressReport { server = _serverName.Value, instance = (Environment.GetEnvironmentVariable("HOSTNAME") ?? ""), bosses = array, players = array2, bossKills = _pendingBossKills.ToArray(), milestoneStep = _milestoneStep.Value }); _reportedBossKillIds = _pendingBossKills.Select((BossKill kill) => kill.id).ToArray(); _reportSignature = text; _reportTask = Task.Run(() => SendReport(body, token)); } } } [HarmonyPatch(typeof(Character), "RPC_Damage")] [HarmonyPostfix] private static void AfterCharacterDamage(Character __instance, HitData hit) { if ((Object)(object)_instance == (Object)null) { return; } ConfigEntry<bool>? reportBosses = _instance._reportBosses; if (reportBosses == null || !reportBosses.Value || (Object)(object)__instance == (Object)null || hit == null || !__instance.IsBoss() || __instance.GetHealth() > 0f) { return; } ZNetView component = ((Component)__instance).GetComponent<ZNetView>(); string text = ((object)Unsafe.As<ZDOID, ZDOID>(ref ((component != null) ? component.GetZDO() : null)?.m_uid)/*cast due to .constrained prefix*/).ToString() ?? $"{((Object)__instance).name}:{Time.frameCount}"; if (_instance._seenBossDeaths.Add(text)) { string[] participants = (from player in Player.GetAllPlayers() where (Object)(object)player != (Object)null && !((Character)player).IsDead() && Vector3.Distance(((Component)player).transform.position, ((Component)__instance).transform.position) <= _instance._participantRadius.Value select player.GetPlayerName() into name where !string.IsNullOrWhiteSpace(name) select name).Distinct<string>(StringComparer.OrdinalIgnoreCase).OrderBy<string, string>((string name) => name, StringComparer.OrdinalIgnoreCase).ToArray(); Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); string killer = ((val != null) ? val.GetPlayerName() : "Unknown Viking"); _instance._pendingBossKills.Add(new BossKill { id = text, key = (__instance.m_defeatSetGlobalKey ?? ""), boss = __instance.GetHoverName(), killer = killer, participants = participants }); _instance._nextCheck = 0f; } } private PlayerProgress[] ReadPlayers() { //IL_0027: 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: 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_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0049: 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_0066: Unknown result type (might be due to invalid IL or missing references) if (ZDOMan.instance == null) { return Array.Empty<PlayerProgress>(); } List<PlayerProgress> list = new List<PlayerProgress>(); foreach (PlayerInfo player in ZNet.instance.GetPlayerList()) { ZDO zDO = ZDOMan.instance.GetZDO(player.m_characterID); if (zDO != null) { PlayerProgress playerProgress = new PlayerProgress(); ZDOID characterID = player.m_characterID; playerProgress.id = ((ZDOID)(ref characterID)).UserID.ToString(); playerProgress.name = player.m_name ?? "Unknown Viking"; playerProgress.level = Math.Max(1, zDO.GetInt("EpicMMOSystem_level", 1)); list.Add(playerProgress); } } return list.OrderBy<PlayerProgress, string>((PlayerProgress player) => player.id, StringComparer.Ordinal).ToArray(); } private bool SendReport(string body, string token) { byte[] bytes = Encoding.UTF8.GetBytes(body); HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(_endpoint.Value); httpWebRequest.Method = "POST"; httpWebRequest.ContentType = "application/json"; if (!string.IsNullOrWhiteSpace(token)) { httpWebRequest.Headers[_tokenHeader.Value] = token; } httpWebRequest.Timeout = 3000; httpWebRequest.ReadWriteTimeout = 3000; httpWebRequest.ContentLength = bytes.Length; using (Stream stream = httpWebRequest.GetRequestStream()) { stream.Write(bytes, 0, bytes.Length); } using HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse(); return httpWebResponse.StatusCode == HttpStatusCode.NoContent; } } }