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 ValheimProgress v1.0.0
plugins/ValheimProgress.dll
Decompiled 2 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using UnityEngine; [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace ValheimProgress; [BepInPlugin("nl.ricoscripts.valheimprogress", "Valheim Progress", "1.0.0")] public sealed class ValheimProgressPlugin : BaseUnityPlugin { private sealed class BiomeResult { public long Total; public long Explored; public List<BiomeItem> Items = new List<BiomeItem>(); } private sealed class BiomeItem { public string Name; public long Total; public long Explored; } [HarmonyPatch(typeof(ZNet), "LoadWorld")] private static class LoadWorldPatch { private static void Postfix(ZNet __instance) { if (__instance.IsServer()) { _worldStartedUtc = DateTime.UtcNow; Instance.LoadExploration(); Instance.WriteStatus(); } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class NewConnectionPatch { private static void Postfix(ZNetPeer peer, ZNet __instance) { if (__instance.IsServer()) { peer.m_rpc.Register<ZPackage>("ValheimProgress_MapData_v1", (Action<ZRpc, ZPackage>)ReceiveMapData); } } } [HarmonyPatch(typeof(Minimap), "SetMapData", new Type[] { typeof(byte[]) })] private static class MapLoadedPatch { private static void Postfix(Minimap __instance) { if ((Object)(object)Instance != (Object)null && (Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer()) { ((MonoBehaviour)Instance).StartCoroutine(SendFullMap(__instance)); } } } [HarmonyPatch(typeof(Minimap), "Explore", new Type[] { typeof(int), typeof(int) })] private static class ExplorePatch { private static void Postfix(Minimap __instance, int x, int y, bool __result) { if (!__result || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { return; } int value = Traverse.Create((object)__instance).Field("m_textureSize").GetValue<int>(); if (x < 0 || y < 0 || x >= value || y >= value) { return; } lock (PendingExploration) { PendingExploration.Add(y * value + x); } } } public const string PluginGuid = "nl.ricoscripts.valheimprogress"; public const string PluginName = "Valheim Progress"; public const string PluginVersion = "1.0.0"; private const string MapRpc = "ValheimProgress_MapData_v1"; private const int ProtocolVersion = 1; internal static ValheimProgressPlugin Instance; private static readonly object MapLock = new object(); private static readonly HashSet<int> PendingExploration = new HashSet<int>(); private static bool[] _explored = new bool[4194304]; private static int _mapSize = 2048; private static float _pixelSize = 12f; private static bool _mapDirty; private static DateTime _worldStartedUtc = DateTime.UtcNow; private static DateTime _lastStatusUtc = DateTime.MinValue; private static DateTime _lastMapSaveUtc = DateTime.MinValue; private static DateTime _lastDeltaUtc = DateTime.MinValue; private ConfigEntry<string> _statusFile; private ConfigEntry<int> _statusInterval; private ConfigEntry<int> _samplingStep; private ConfigEntry<float> _worldRadius; private ConfigEntry<string> _bosses; private string DataDirectory => Path.Combine(Paths.ConfigPath, "rs.valheimprogress"); private string ExplorationFile => Path.Combine(DataDirectory, "exploration.bin"); private void Awake() { Instance = this; Directory.CreateDirectory(DataDirectory); _statusFile = ((BaseUnityPlugin)this).Config.Bind<string>("General", "StatusFile", Path.Combine(DataDirectory, "status.json"), "JSON-bestand dat door de Discord-bot wordt gelezen."); _statusInterval = ((BaseUnityPlugin)this).Config.Bind<int>("General", "StatusIntervalSeconds", 15, "Hoe vaak de server status.json vernieuwt."); _samplingStep = ((BaseUnityPlugin)this).Config.Bind<int>("Exploration", "BiomeSamplingStep", 4, "Gebruik iedere N-de kaartpixel voor biomepercentages. Lager is nauwkeuriger maar zwaarder."); _worldRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Exploration", "WorldRadius", 10500f, "Straal in wereldmeters die voor het totale kaartpercentage meetelt."); _bosses = ((BaseUnityPlugin)this).Config.Bind<string>("Progress", "Bosses", "Eikthyr=defeated_eikthyr;The Elder=defeated_gdking;Bonemass=defeated_bonemass;Moder=defeated_dragon;Yagluth=defeated_goblinking;The Queen=defeated_queen;Fader=defeated_fader", "Naam=globalkey, gescheiden door puntkomma's. Hierdoor kunnen nieuwe eindbazen later worden toegevoegd."); LoadExploration(); Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "nl.ricoscripts.valheimprogress"); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Valheim Progress 1.0.0 geladen."); } private void Update() { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { return; } if (instance.IsServer()) { if ((DateTime.UtcNow - _lastStatusUtc).TotalSeconds >= (double)Math.Max(5, _statusInterval.Value)) { _lastStatusUtc = DateTime.UtcNow; WriteStatus(); } if (_mapDirty && (DateTime.UtcNow - _lastMapSaveUtc).TotalSeconds >= 60.0) { SaveExploration(); } } else if ((DateTime.UtcNow - _lastDeltaUtc).TotalSeconds >= 10.0) { _lastDeltaUtc = DateTime.UtcNow; SendPendingExploration(); } } private void OnDestroy() { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { WriteStatus(); if (_mapDirty) { SaveExploration(); } } } private static void EnsureMap(int size, float pixelSize) { if (size < 256 || size > 8192) { return; } lock (MapLock) { if (_mapSize != size || _explored.Length != size * size) { _mapSize = size; _explored = new bool[size * size]; } if (pixelSize > 0.1f && pixelSize < 100f) { _pixelSize = pixelSize; } } } private void LoadExploration() { try { if (!File.Exists(ExplorationFile)) { return; } using (BinaryReader binaryReader = new BinaryReader(File.OpenRead(ExplorationFile))) { if (binaryReader.ReadInt32() != 1) { return; } int num = binaryReader.ReadInt32(); float pixelSize = binaryReader.ReadSingle(); int num2 = binaryReader.ReadInt32(); if (num < 256 || num > 8192 || num2 != (num * num + 7) / 8) { return; } EnsureMap(num, pixelSize); MergePacked(binaryReader.ReadBytes(num2), 0, num * num); _mapDirty = false; } ((BaseUnityPlugin)this).Logger.LogInfo((object)"Opgeslagen kaartverkenning geladen."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Kaartverkenning kon niet worden geladen: " + ex.Message)); } } private void SaveExploration() { try { Directory.CreateDirectory(DataDirectory); byte[] array; lock (MapLock) { array = PackBits(_explored, 0, _explored.Length); } using (BinaryWriter binaryWriter = new BinaryWriter(File.Create(ExplorationFile))) { binaryWriter.Write(1); binaryWriter.Write(_mapSize); binaryWriter.Write(_pixelSize); binaryWriter.Write(array.Length); binaryWriter.Write(array); } _mapDirty = false; _lastMapSaveUtc = DateTime.UtcNow; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Kaartverkenning kon niet worden opgeslagen: " + ex.Message)); } } private static byte[] PackBits(bool[] values, int start, int length) { byte[] array = new byte[(length + 7) / 8]; for (int i = 0; i < length; i++) { if (values[start + i]) { array[i / 8] |= (byte)(1 << i % 8); } } return array; } private static void MergePacked(byte[] packed, int start, int length) { lock (MapLock) { int num = Math.Min(length, _explored.Length - start); for (int i = 0; i < num; i++) { if ((packed[i / 8] & (1 << i % 8)) != 0 && !_explored[start + i]) { _explored[start + i] = true; _mapDirty = true; } } } } private static void ReceiveMapData(ZRpc rpc, ZPackage package) { try { package.SetPos(0); if (package.ReadInt() != 1) { return; } int num = package.ReadInt(); int size = package.ReadInt(); float pixelSize = package.ReadSingle(); EnsureMap(size, pixelSize); switch (num) { case 1: { int num4 = package.ReadInt(); int num5 = package.ReadInt(); int num6 = package.ReadInt(); if (num4 >= 0 && num5 >= 0 && num4 + num5 <= _explored.Length && num6 == (num5 + 7) / 8) { byte[] array = new byte[num6]; for (int j = 0; j < num6; j++) { array[j] = package.ReadByte(); } MergePacked(array, num4, num5); } break; } case 2: { int num2 = package.ReadInt(); if (num2 < 0 || num2 > 100000) { break; } lock (MapLock) { for (int i = 0; i < num2; i++) { int num3 = package.ReadInt(); if (num3 >= 0 && num3 < _explored.Length && !_explored[num3]) { _explored[num3] = true; _mapDirty = true; } } break; } } } } catch (Exception ex) { ((BaseUnityPlugin)Instance).Logger.LogWarning((object)("Ongeldige kaartdata genegeerd: " + ex.Message)); } } private static IEnumerator SendFullMap(Minimap map) { yield return (object)new WaitForSeconds(8f); while ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.GetServerRPC() == null) { yield return (object)new WaitForSeconds(1f); } if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || (Object)(object)map == (Object)null) { yield break; } Traverse mapFields = Traverse.Create((object)map); bool[] explored = mapFields.Field("m_explored").GetValue<bool[]>(); int size = mapFields.Field("m_textureSize").GetValue<int>(); float pixelSize = mapFields.Field("m_pixelSize").GetValue<float>(); if (explored == null || explored.Length != size * size) { yield break; } int chunkSize = (explored.Length + 64 - 1) / 64; for (int chunk = 0; chunk < 64; chunk++) { int start = chunk * chunkSize; if (start >= explored.Length) { break; } int length = Math.Min(chunkSize, explored.Length - start); byte[] packed = PackBits(explored, start, length); ZPackage package = new ZPackage(); package.Write(1); package.Write(1); package.Write(size); package.Write(pixelSize); package.Write(start); package.Write(length); package.Write(packed.Length); for (int i = 0; i < packed.Length; i++) { package.Write(packed[i]); } ZNet.instance.GetServerRPC().Invoke("ValheimProgress_MapData_v1", new object[1] { package }); yield return null; } } private static void SendPendingExploration() { //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Expected O, but got Unknown ZNet instance = ZNet.instance; Minimap instance2 = Minimap.instance; if ((Object)(object)instance == (Object)null || instance.IsServer() || instance.GetServerRPC() == null || (Object)(object)instance2 == (Object)null) { return; } int[] array; lock (PendingExploration) { if (PendingExploration.Count == 0) { return; } array = PendingExploration.Take(50000).ToArray(); int[] array2 = array; foreach (int item in array2) { PendingExploration.Remove(item); } } ZPackage val = new ZPackage(); val.Write(1); val.Write(2); Traverse val2 = Traverse.Create((object)instance2); val.Write(val2.Field("m_textureSize").GetValue<int>()); val.Write(val2.Field("m_pixelSize").GetValue<float>()); val.Write(array.Length); int[] array3 = array; foreach (int num in array3) { val.Write(num); } instance.GetServerRPC().Invoke("ValheimProgress_MapData_v1", new object[1] { val }); } private void WriteStatus() { try { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || !instance.IsServer()) { return; } Directory.CreateDirectory(Path.GetDirectoryName(_statusFile.Value)); List<string> source = (from name in (from player in instance.GetPlayerList() select player.m_name into name where !string.IsNullOrWhiteSpace(name) select name).Distinct() orderby name select name).ToList(); int value = 0; if ((Object)(object)EnvMan.instance != (Object)null) { value = Traverse.Create((object)EnvMan.instance).Method("GetCurrentDay", Array.Empty<object>()).GetValue<int>() + 1; } BiomeResult biomeResult = CalculateBiomeStats(); StringBuilder stringBuilder = new StringBuilder(4096); stringBuilder.Append("{\n \"schemaVersion\": 1,"); stringBuilder.Append("\n \"generatedAt\": \"").Append(DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture)).Append("\","); stringBuilder.Append("\n \"online\": true,"); stringBuilder.Append("\n \"world\": \"").Append(Json(instance.GetWorldName())).Append("\","); stringBuilder.Append("\n \"day\": ").Append(value).Append(','); stringBuilder.Append("\n \"uptimeSeconds\": ").Append((long)(DateTime.UtcNow - _worldStartedUtc).TotalSeconds).Append(','); stringBuilder.Append("\n \"players\": [").Append(string.Join(", ", source.Select((string name) => "\"" + Json(name) + "\""))).Append("],"); stringBuilder.Append("\n \"bosses\": ["); List<KeyValuePair<string, string>> list = ParseBosses(); for (int num = 0; num < list.Count; num++) { if (num > 0) { stringBuilder.Append(','); } stringBuilder.Append("\n { \"name\": \"").Append(Json(list[num].Key)).Append("\", \"key\": \"") .Append(Json(list[num].Value)) .Append("\", \"defeated\": ") .Append((!((Object)(object)ZoneSystem.instance != (Object)null) || !ZoneSystem.instance.GetGlobalKey(list[num].Value)) ? "false" : "true") .Append(" }"); } stringBuilder.Append("\n ],"); stringBuilder.Append("\n \"biomes\": ["); for (int num2 = 0; num2 < biomeResult.Items.Count; num2++) { BiomeItem biomeItem = biomeResult.Items[num2]; if (num2 > 0) { stringBuilder.Append(','); } stringBuilder.Append("\n { \"name\": \"").Append(Json(biomeItem.Name)).Append("\", \"discovered\": ") .Append((biomeItem.Explored <= 0) ? "false" : "true") .Append(", \"exploredPercent\": ") .Append(Percent(biomeItem.Explored, biomeItem.Total)) .Append(" }"); } stringBuilder.Append("\n ],"); stringBuilder.Append("\n \"worldExploredPercent\": ").Append(Percent(biomeResult.Explored, biomeResult.Total)).Append(','); stringBuilder.Append("\n \"explorationSamples\": ").Append(biomeResult.Total).Append("\n}\n"); File.WriteAllText(_statusFile.Value, stringBuilder.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("status.json kon niet worden geschreven: " + ex.Message)); } } private BiomeResult CalculateBiomeStats() { //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) BiomeResult biomeResult = new BiomeResult(); WorldGenerator instance = WorldGenerator.instance; if (instance == null) { return biomeResult; } int num = Math.Max(1, Math.Min(32, _samplingStep.Value)); int num2 = _mapSize / 2; float num3 = _worldRadius.Value * _worldRadius.Value; Dictionary<string, BiomeItem> stats = new Dictionary<string, BiomeItem>(StringComparer.OrdinalIgnoreCase); lock (MapLock) { for (int i = 0; i < _mapSize; i += num) { float num4 = (float)(i - num2) * _pixelSize; for (int j = 0; j < _mapSize; j += num) { float num5 = (float)(j - num2) * _pixelSize; if (!(num5 * num5 + num4 * num4 > num3)) { string text = FriendlyBiome(((object)instance.GetBiome(num5, num4, 0.02f, false)/*cast due to .constrained prefix*/).ToString()); if (!stats.TryGetValue(text, out var value)) { BiomeItem biomeItem = new BiomeItem(); biomeItem.Name = text; value = biomeItem; stats[text] = value; } value.Total++; biomeResult.Total++; if (_explored[i * _mapSize + j]) { value.Explored++; biomeResult.Explored++; } } } } } string[] order = new string[9] { "Meadows", "Black Forest", "Swamp", "Mountain", "Plains", "Mistlands", "Ashlands", "Deep North", "Ocean" }; biomeResult.Items = order.Select((string name) => (!stats.ContainsKey(name)) ? new BiomeItem { Name = name } : stats[name]).ToList(); foreach (BiomeItem item in from item in stats.Values where !order.Contains(item.Name) orderby item.Name select item) { biomeResult.Items.Add(item); } return biomeResult; } private List<KeyValuePair<string, string>> ParseBosses() { List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>(); string[] array = (_bosses.Value ?? string.Empty).Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text in array) { string[] array2 = text.Split(new char[1] { '=' }, 2); if (array2.Length == 2 && array2[0].Trim().Length > 0 && array2[1].Trim().Length > 0) { list.Add(new KeyValuePair<string, string>(array2[0].Trim(), array2[1].Trim())); } } return list; } private static string FriendlyBiome(string name) { return name switch { "BlackForest" => "Black Forest", "AshLands" => "Ashlands", "DeepNorth" => "Deep North", _ => name, }; } private static string Percent(long explored, long total) { return ((total > 0) ? ((double)explored * 100.0 / (double)total) : 0.0).ToString("0.000", CultureInfo.InvariantCulture); } private static string Json(string value) { if (value == null) { return string.Empty; } return value.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\r", "\\r") .Replace("\n", "\\n") .Replace("\t", "\\t"); } }