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 WorldCache v0.1.10
plugins/WorldCache.dll
Decompiled 2 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; using Valheim.SettingsGui; using WorldCache; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("WorldCacheTest")] [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.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace WorldCache { internal static class Codec { private const int Magic = 826622807; internal const int Shuffle4 = 1; private static readonly uint[] table = MakeTable(); internal static byte[] Encode(byte[] raw, string key, string revision, int flags, bool fast = false) { MemoryStream memoryStream = new MemoryStream(raw.Length / 3 + 64); BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write(826622807); binaryWriter.Write(flags); byte[] bytes = Encoding.UTF8.GetBytes(key ?? ""); binaryWriter.Write(bytes.Length); binaryWriter.Write(bytes); byte[] bytes2 = Encoding.UTF8.GetBytes(revision ?? ""); binaryWriter.Write(bytes2.Length); binaryWriter.Write(bytes2); binaryWriter.Write(raw.Length); binaryWriter.Write(Crc32(raw)); binaryWriter.Flush(); byte[] array = (((flags & 1) != 0) ? Shuffle(raw) : raw); using (DeflateStream deflateStream = new DeflateStream(memoryStream, fast ? CompressionLevel.Fastest : CompressionLevel.Optimal, leaveOpen: true)) { deflateStream.Write(array, 0, array.Length); } return memoryStream.ToArray(); } internal static byte[] Decode(Stream stream, string revision) { string key; return Decode(stream, revision, out key); } internal static byte[] Decode(Stream stream, string revision, out string key) { BinaryReader binaryReader = new BinaryReader(stream); if (binaryReader.ReadInt32() != 826622807) { throw new InvalidDataException("not a WorldCache entry"); } int num = binaryReader.ReadInt32(); int num2 = binaryReader.ReadInt32(); if (num2 < 0 || num2 > 4096) { throw new InvalidDataException("bad key length"); } key = Encoding.UTF8.GetString(binaryReader.ReadBytes(num2)); int num3 = binaryReader.ReadInt32(); if (num3 < 0 || num3 > 4096) { throw new InvalidDataException("bad revision length"); } if (!string.Equals(Encoding.UTF8.GetString(binaryReader.ReadBytes(num3)), revision ?? "", StringComparison.Ordinal)) { return null; } int num4 = binaryReader.ReadInt32(); uint num5 = binaryReader.ReadUInt32(); if (num4 < 0 || num4 > 536870912) { throw new InvalidDataException("bad length"); } byte[] array = new byte[num4]; using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress, leaveOpen: true)) { int num6; for (int i = 0; i < num4; i += num6) { num6 = deflateStream.Read(array, i, num4 - i); if (num6 <= 0) { throw new InvalidDataException("truncated entry"); } } } byte[] obj = (((num & 1) != 0) ? Unshuffle(array) : array); if (Crc32(obj) != num5) { throw new InvalidDataException("checksum mismatch"); } return obj; } private static byte[] Shuffle(byte[] data) { int num = data.Length / 4; int count = data.Length - num * 4; byte[] array = new byte[data.Length]; for (int i = 0; i < num; i++) { int num2 = i * 4; array[i] = data[num2]; array[num + i] = data[num2 + 1]; array[2 * num + i] = data[num2 + 2]; array[3 * num + i] = data[num2 + 3]; } Buffer.BlockCopy(data, num * 4, array, num * 4, count); return array; } private static byte[] Unshuffle(byte[] data) { int num = data.Length / 4; int count = data.Length - num * 4; byte[] array = new byte[data.Length]; for (int i = 0; i < num; i++) { int num2 = i * 4; array[num2] = data[i]; array[num2 + 1] = data[num + i]; array[num2 + 2] = data[2 * num + i]; array[num2 + 3] = data[3 * num + i]; } Buffer.BlockCopy(data, num * 4, array, num * 4, count); return array; } private static uint[] MakeTable() { uint[] array = new uint[256]; for (uint num = 0u; num < 256; num++) { uint num2 = num; for (int i = 0; i < 8; i++) { num2 = (((num2 & 1) != 0) ? (0xEDB88320u ^ (num2 >> 1)) : (num2 >> 1)); } array[num] = num2; } return array; } internal static uint Crc32(byte[] data) { uint num = uint.MaxValue; for (int i = 0; i < data.Length; i++) { num = table[(num ^ data[i]) & 0xFF] ^ (num >> 8); } return num ^ 0xFFFFFFFFu; } internal static string Hash(string text) { ulong num = 14695981039346656037uL; byte[] bytes = Encoding.UTF8.GetBytes(text ?? ""); foreach (byte b in bytes) { num ^= b; num *= 1099511628211L; } return num.ToString("x16"); } internal static string FileName(string key) { StringBuilder stringBuilder = new StringBuilder(Math.Min(key.Length, 80) + 20); foreach (char c in key) { if (stringBuilder.Length >= 80) { break; } stringBuilder.Append((char.IsLetterOrDigit(c) || c == '-' || c == '.') ? c : '_'); } stringBuilder.Append('~').Append(Hash(key).Substring(0, 12)).Append(".wce"); return stringBuilder.ToString(); } } internal static class EntryPrompt { [HarmonyPatch(typeof(Hud), "UpdateBlackScreen")] private static class LoadingTip { private static void Postfix(Hud __instance) { try { if (Wanted && !((Object)(object)__instance.m_loadingTip == (Object)null) && !((Object)(object)__instance.m_loadingProgress == (Object)null) && __instance.m_loadingProgress.activeSelf && __instance.m_loadingTip.text != Tip) { __instance.m_loadingTip.text = Tip; } } catch (Exception ex) { Plugin.Debug("loading tip: " + ex.Message); } } } [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static PopupButtonCallback <>9__8_0; public static PopupButtonCallback <>9__8_1; public static RetrieveFromStringSource <>9__10_0; public static RetrieveFromStringSource <>9__10_1; public static RetrieveFromBoolSource <>9__10_2; public static PopupButtonCallback <>9__10_3; internal void <Ask>b__8_0() { UnifiedPopup.Pop(); Wait(); } internal void <Ask>b__8_1() { UnifiedPopup.Pop(); Play(); } internal string <Wait>b__10_0() { return Plugin.T("Подготовка мира", "Preparing the world"); } internal string <Wait>b__10_1() { return $"{Prepare.Progress * 100f:0} % · {Plugin.Megabytes(Prepare.WrittenBytes)} / {Plugin.Megabytes(Prepare.Budget)}\n{Prepare.Phase}\n\n" + Plugin.T("«Играть» - в игру сейчас, подготовка продолжится в фоне.", "\"Play\": into the game now; the preparation goes on in the background."); } internal bool <Wait>b__10_2() { if (Prepare.Running) { return !Prepare.Fast; } return true; } internal void <Wait>b__10_3() { Prepare.ToBackground(); UnifiedPopup.Pop(); } } private static bool asked; private static bool Wanted { get { if (Plugin.Enabled.Value && Plugin.AskOnFirstEntry.Value && Stages.Open && Stages.Fresh) { return !asked; } return false; } } internal static string Tip => Plugin.T("WorldCache: этот мир ещё не в кэше - места генерируются и сохраняются на диск, поэтому первая загрузка может идти дольше. Следующие входы будут быстрее.", "WorldCache: this world is not cached yet - places are generated and written to the disk, so this first load may take longer. The next ones will be faster."); internal static void WorldOpened() { asked = false; } internal static void OnSpawned() { if (!Wanted || Prepare.Running) { if (Stages.Open) { asked = true; } } else { asked = true; Stages.Fresh = false; Ask(); } } internal static void Ask() { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Expected O, but got Unknown //IL_0077: 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) //IL_0082: Expected O, but got Unknown string text = Plugin.Megabytes(Plugin.VolumeChoice() << 20); string text2 = Plugin.T("Этот мир ещё не в кэше WorldCache: места генерируются и сохраняются на диск, поэтому первая загрузка и первые минуты могут идти медленнее.\n\n«Ждать» - сейчас подготовить места вокруг вас, у баз и порталов, открытое на карте и найденные места (до " + text + "), затем играть.\n«Играть» - сразу в игру: кэш пополняется по ходу игры и подготовкой в фоне.", "This world is not in the WorldCache yet: places are generated and written to the disk, so the first load and the first minutes may be slower.\n\n\"Wait\": prepare the places around you, at the bases and portals, explored and found places now (up to " + text + "), then play.\n\"Play\": play at once; the cache fills as you go and from the preparation in the background."); try { string text3 = Plugin.T("Кэш мира", "World cache"); object obj = <>c.<>9__8_0; if (obj == null) { PopupButtonCallback val = delegate { UnifiedPopup.Pop(); Wait(); }; <>c.<>9__8_0 = val; obj = (object)val; } object obj2 = <>c.<>9__8_1; if (obj2 == null) { PopupButtonCallback val2 = delegate { UnifiedPopup.Pop(); Play(); }; <>c.<>9__8_1 = val2; obj2 = (object)val2; } UnifiedPopup.Push((PopupBase)new YesNoPopup(text3, text2, (PopupButtonCallback)obj, (PopupButtonCallback)obj2, false, false)); Relabel("buttonRightText", Plugin.T("Ждать", "Wait")); Relabel("buttonLeftText", Plugin.T("Играть", "Play")); } catch (Exception ex) { Plugin.Warn("first entry question not shown: " + ex.Message); } } private static void Play() { if (Prepare.Start(PrepareMode.Smart, Plugin.VolumeChoice() << 20)) { Plugin.Message(Plugin.T("Кэш пополняется по ходу игры и подготовкой в фоне.", "The cache fills as you go and in the background.")); } } private static void Wait() { //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: Expected O, but got Unknown //IL_0046: 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_0051: Expected O, but got Unknown //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Expected O, but got Unknown if (!Prepare.Start(PrepareMode.Entry, Plugin.VolumeChoice() << 20, null, fastMode: true)) { return; } object obj = <>c.<>9__10_0; if (obj == null) { RetrieveFromStringSource val = () => Plugin.T("Подготовка мира", "Preparing the world"); <>c.<>9__10_0 = val; obj = (object)val; } object obj2 = <>c.<>9__10_1; if (obj2 == null) { RetrieveFromStringSource val2 = () => $"{Prepare.Progress * 100f:0} % · {Plugin.Megabytes(Prepare.WrittenBytes)} / {Plugin.Megabytes(Prepare.Budget)}\n{Prepare.Phase}\n\n" + Plugin.T("«Играть» - в игру сейчас, подготовка продолжится в фоне.", "\"Play\": into the game now; the preparation goes on in the background."); <>c.<>9__10_1 = val2; obj2 = (object)val2; } object obj3 = <>c.<>9__10_2; if (obj3 == null) { RetrieveFromBoolSource val3 = () => !Prepare.Running || !Prepare.Fast; <>c.<>9__10_2 = val3; obj3 = (object)val3; } object obj4 = <>c.<>9__10_3; if (obj4 == null) { PopupButtonCallback val4 = delegate { Prepare.ToBackground(); UnifiedPopup.Pop(); }; <>c.<>9__10_3 = val4; obj4 = (object)val4; } UnifiedPopup.Push((PopupBase)new CancelableTaskPopup((RetrieveFromStringSource)obj, (RetrieveFromStringSource)obj2, (RetrieveFromBoolSource)obj3, (PopupButtonCallback)obj4)); Relabel("buttonCenterText", Plugin.T("Играть", "Play")); } private static void Relabel(string field, string text) { UnifiedPopup value = Traverse.Create(typeof(UnifiedPopup)).Field("instance").GetValue<UnifiedPopup>(); TMP_Text val = (((Object)(object)value != (Object)null) ? Traverse.Create((object)value).Field(field).GetValue<TMP_Text>() : null); if ((Object)(object)val != (Object)null) { val.text = text; } } } internal static class HeightmapProvider { [HarmonyPatch(typeof(HeightmapBuilder), "IsTerrainReady")] private static class IsReadyPatch { private static bool Prefix(HeightmapBuilder __instance, Vector3 center, int width, float scale, bool distantLod, WorldGenerator worldGen, ref bool __result) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) try { if (Offer(__instance, center, width, scale, distantLod, worldGen)) { __result = false; return false; } } catch (Exception ex) { Plugin.Warn("IsTerrainReady: " + ex.Message); } return true; } } [HarmonyPatch(typeof(HeightmapBuilder), "RequestTerrain")] private static class RequestPatch { private static bool Prefix(HeightmapBuilder __instance, Vector3 center, int width, float scale, bool distantLod, WorldGenerator worldGen, ref HMBuildData __result) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) try { if (Offer(__instance, center, width, scale, distantLod, worldGen)) { __result = null; return false; } } catch (Exception ex) { Plugin.Warn("RequestTerrain: " + ex.Message); } return true; } } [HarmonyPatch(typeof(HeightmapBuilder), "Build")] private static class BuildPatch { private static void Postfix(HMBuildData data) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (generating || data == null || data.m_baseHeights == null || !Usable(data.m_worldGen) || (data.m_distantLod && !Plugin.CacheDistantTerrain.Value)) { return; } try { string key = Key(data.m_center, data.m_width, data.m_scale, data.m_distantLod); missing.TryRemove(key, out var _); Stages.Save("game.heightmap", key, "0", Serialize(data)); } catch (Exception ex) { Plugin.Warn("heightmap not cached: " + ex.Message); } } } internal const string Id = "game.heightmap"; private static readonly FieldRef<HeightmapBuilder, object> Lock = AccessTools.FieldRefAccess<HeightmapBuilder, object>("m_lock"); private static readonly FieldRef<HeightmapBuilder, List<HMBuildData>> Ready = AccessTools.FieldRefAccess<HeightmapBuilder, List<HMBuildData>>("m_ready"); private static readonly FieldRef<HeightmapBuilder, List<HMBuildData>> ToBuild = AccessTools.FieldRefAccess<HeightmapBuilder, List<HMBuildData>>("m_toBuild"); private static readonly Action<HeightmapBuilder, HMBuildData> BuildMethod = AccessTools.MethodDelegate<Action<HeightmapBuilder, HMBuildData>>(AccessTools.Method(typeof(HeightmapBuilder), "Build", (Type[])null, (Type[])null), (object)null, true); private static readonly ConcurrentDictionary<string, HMBuildData> loaded = new ConcurrentDictionary<string, HMBuildData>(StringComparer.Ordinal); private static readonly ConcurrentDictionary<string, byte> loading = new ConcurrentDictionary<string, byte>(StringComparer.Ordinal); private static readonly ConcurrentDictionary<string, byte> missing = new ConcurrentDictionary<string, byte>(StringComparer.Ordinal); [ThreadStatic] private static bool generating; internal static volatile bool ZonesUsed; private static int zoneWidth = 64; private static float zoneScale = 1f; internal static Stage Stage; internal static void Register() { Stage = new Stage { Id = "game.heightmap", Owner = "chistix.worldcache", Version = 1, Flags = 1, Title = Plugin.P("Высоты и биомы зон (игра)", "Zone heights and biomes (game)"), Parts = () => (!Plugin.AllModsInKey.Value) ? new SortedDictionary<string, string>() : WorldKey.Mods(), CanVerify = () => ZonesUsed && WorldGenerator.instance != null, Generate = GenerateKey, KeysNear = KeysNear }; Stages.AddBuiltin(Stage); } internal static void Reset() { loaded.Clear(); loading.Clear(); missing.Clear(); ZonesUsed = false; } private static string Key(Vector3 center, int width, float scale, bool distantLod) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0039: 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) return (distantLod ? "L" : "Z") + "_" + Bits(center.x) + "_" + Bits(center.y) + "_" + Bits(center.z) + "_" + width.ToString(CultureInfo.InvariantCulture) + "_" + Bits(scale); } private static string Bits(float f) { return BitConverter.ToInt32(BitConverter.GetBytes(f), 0).ToString("x8"); } private static float FromBits(string hex) { return BitConverter.ToSingle(BitConverter.GetBytes(int.Parse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture)), 0); } private static bool Parse(string key, out Vector3 center, out int width, out float scale, out bool distantLod) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) center = default(Vector3); width = 0; scale = 0f; distantLod = false; string[] array = key.Split('_'); if (array.Length != 6 || (array[0] != "L" && array[0] != "Z")) { return false; } distantLod = array[0] == "L"; center = new Vector3(FromBits(array[1]), FromBits(array[2]), FromBits(array[3])); width = int.Parse(array[4], CultureInfo.InvariantCulture); scale = FromBits(array[5]); if (width > 0) { return width <= 2048; } return false; } private static byte[] Serialize(HMBuildData data) { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) int count = data.m_baseHeights.Count; MemoryStream memoryStream = new MemoryStream(8 + count * 20); BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write(count); binaryWriter.Write(data.m_baseMask.Length); for (int i = 0; i < count; i++) { binaryWriter.Write(data.m_baseHeights[i]); } for (int j = 0; j < 4; j++) { Color[] baseMask = data.m_baseMask; for (int k = 0; k < baseMask.Length; k++) { Color val = baseMask[k]; binaryWriter.Write(((Color)(ref val))[j]); } } binaryWriter.Flush(); return memoryStream.ToArray(); } private static HMBuildData Deserialize(byte[] raw, Vector3 center, int width, float scale, bool distantLod, WorldGenerator worldGen) { //IL_0033: 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_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0178: 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) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) BinaryReader binaryReader = new BinaryReader(new MemoryStream(raw)); int num = binaryReader.ReadInt32(); int num2 = binaryReader.ReadInt32(); if (num != (width + 1) * (width + 1) || num2 != num) { throw new InvalidDataException("heightmap of another size"); } HMBuildData val = new HMBuildData(center, width, scale, distantLod, worldGen); val.m_baseHeights = new List<float>(num); for (int i = 0; i < num; i++) { val.m_baseHeights.Add(binaryReader.ReadSingle()); } val.m_baseMask = (Color[])(object)new Color[num2]; for (int j = 0; j < 4; j++) { for (int k = 0; k < num2; k++) { Color val2 = val.m_baseMask[k]; ((Color)(ref val2))[j] = binaryReader.ReadSingle(); val.m_baseMask[k] = val2; } } Vector3 val3 = center + new Vector3((float)width * scale * -0.5f, 0f, (float)width * scale * -0.5f); double num3 = (double)width * (double)scale; val.m_cornerBiomes = (BiomeSector[])(object)new BiomeSector[4]; val.m_cornerBiomes[0] = worldGen.GetBiomeSector(val3.x, val3.z, false); val.m_cornerBiomes[1] = worldGen.GetBiomeSector((float)((double)val3.x + num3), val3.z, false); val.m_cornerBiomes[2] = worldGen.GetBiomeSector(val3.x, (float)((double)val3.z + num3), false); val.m_cornerBiomes[3] = worldGen.GetBiomeSector((float)((double)val3.x + num3), (float)((double)val3.z + num3), false); return val; } private static bool Usable(WorldGenerator worldGen) { if (Plugin.Enabled.Value && Stages.Open && worldGen != null && worldGen == WorldGenerator.instance && worldGen.m_world != null) { return !worldGen.m_world.m_menu; } return false; } private static bool Offer(HeightmapBuilder builder, Vector3 center, int width, float scale, bool distantLod, WorldGenerator worldGen) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) if (!distantLod && !ZonesUsed) { ZonesUsed = true; zoneWidth = width; zoneScale = scale; } if (!Usable(worldGen) || Stage.State != StageState.Ready) { return false; } if (distantLod && !Plugin.CacheDistantTerrain.Value) { return false; } string key = Key(center, width, scale, distantLod); if (loaded.TryRemove(key, out var value)) { if (value.m_worldGen != worldGen) { return false; } lock (Lock.Invoke(builder)) { List<HMBuildData> list = Ready.Invoke(builder); if (!list.Exists((HMBuildData d) => d.IsEqual(center, width, scale, distantLod, worldGen))) { list.Add(value); } } return false; } if (loading.ContainsKey(key)) { return true; } if (missing.ContainsKey(key) || !Stages.Has("game.heightmap", key)) { return false; } lock (Lock.Invoke(builder)) { if (ToBuild.Invoke(builder).Exists((HMBuildData d) => d.IsEqual(center, width, scale, distantLod, worldGen))) { return false; } if (Ready.Invoke(builder).Exists((HMBuildData d) => d.IsEqual(center, width, scale, distantLod, worldGen))) { return false; } } if (!loading.TryAdd(key, 0)) { return true; } ThreadPool.QueueUserWorkItem(delegate { //IL_0026: Unknown result type (might be due to invalid IL or missing references) try { byte[] array = Stages.Load("game.heightmap", key, "0"); if (array != null) { loaded[key] = Deserialize(array, center, width, scale, distantLod, worldGen); } else { missing[key] = 0; } } catch (Exception ex) { Plugin.Warn("heightmap " + key + ": " + ex.Message + "; built again"); Stages.Invalidate("game.heightmap", key); missing[key] = 0; } finally { loading.TryRemove(key, out var _); } }); return true; } private static byte[] GenerateKey(string key) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown WorldGenerator instance = WorldGenerator.instance; if (instance == null || BuildMethod == null || HeightmapBuilder.instance == null || !Parse(key, out var center, out var width, out var scale, out var distantLod)) { return null; } HMBuildData val = new HMBuildData(center, width, scale, distantLod, instance); generating = true; try { BuildMethod(HeightmapBuilder.instance, val); } finally { generating = false; } return Serialize(val); } private static string[] KeysNear(float x, float y, float z, float radius) { //IL_0045: 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_004f: 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_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008d: 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_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: 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_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) if (!ZonesUsed) { return new string[0]; } float num = (((Object)(object)ZoneSystem.instance != (Object)null) ? ZoneSystem.instance.m_zoneSize : 64f); radius = Mathf.Min(radius, 10500f + num); Vector2s zone = ZoneSystem.GetZone(new Vector3(x, 0f, z)); int num2 = Mathf.CeilToInt(radius / num); List<KeyValuePair<float, string>> list = new List<KeyValuePair<float, string>>(); for (int i = -num2; i <= num2; i++) { for (int j = -num2; j <= num2; j++) { Vector3 zonePos = ZoneSystem.GetZonePos(new Vector2s(zone.x + j, zone.y + i)); Vector2 val = new Vector2(zonePos.x - x, zonePos.z - z); float magnitude = ((Vector2)(ref val)).magnitude; if (!(magnitude > radius)) { val = new Vector2(zonePos.x, zonePos.z); if (!(((Vector2)(ref val)).magnitude > 10500f + num)) { list.Add(new KeyValuePair<float, string>(magnitude, Key(zonePos, zoneWidth, zoneScale, distantLod: false))); } } } } list.Sort((KeyValuePair<float, string> a, KeyValuePair<float, string> b) => a.Key.CompareTo(b.Key)); string[] array = new string[list.Count]; for (int num3 = 0; num3 < array.Length; num3++) { array[num3] = list[num3].Value; } return array; } } internal static class Invite { [HarmonyPatch(typeof(Game), "Start")] private static class RegisterPatch { private static void Postfix() { if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.Register<ZPackage>("WorldCache_Invite", (Action<long, ZPackage>)OnInvite); ZRoutedRpc.instance.Register<int>("WorldCache_InviteRequest", (Action<long, int>)OnRequest); ZRoutedRpc.instance.Register<int>("WorldCache_InviteSent", (Action<long, int>)delegate(long sender, int count) { Status = Plugin.T($"Приглашение разослано: {count} мест.", $"Invitation sent: {count} places."); requestedAt = -1f; Plugin.Message(Status); }); } } } [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static PopupButtonCallback <>9__18_1; internal void <OnInvite>b__18_1() { UnifiedPopup.Pop(); } } private const string InviteRpc = "WorldCache_Invite"; private const string RequestRpc = "WorldCache_InviteRequest"; private const string SentRpc = "WorldCache_InviteSent"; private static List<Vector3> hostPoints = new List<Vector3>(); private static bool collecting; private static float requestedAt = -1f; internal static string Status; private static readonly string[] Prefabs = new string[5] { "portal_wood", "portal_stone", "portal", "bed", "piece_bed02" }; internal static bool CanInvite { get { if ((Object)(object)ZNet.instance != (Object)null) { if (!ZNet.instance.IsServer()) { return ZNet.instance.LocalPlayerIsAdminOrHost(); } return true; } return false; } } internal static IEnumerable<Vector3> HostPoints() { return hostPoints; } internal static void Send() { if (!((Object)(object)ZNet.instance == (Object)null) && ZRoutedRpc.instance != null) { int value = Plugin.InviteMegabytes.Value; if (ZNet.instance.IsServer()) { ((MonoBehaviour)Plugin.Instance).StartCoroutine(CollectAndSend(value, 0L)); return; } ZRoutedRpc.instance.InvokeRoutedRPC("WorldCache_InviteRequest", new object[1] { value }); requestedAt = Time.unscaledTime; Status = Plugin.T("Запрос отправлен серверу…", "Asked the server…"); } } internal static void Update() { if (requestedAt > 0f && Time.unscaledTime - requestedAt > 6f) { requestedAt = -1f; Status = Plugin.T("Сервер не ответил: на нём нет WorldCache или вы не админ.", "No answer: the server has no WorldCache or you are not an admin."); Plugin.Message(Status); } } private static void OnRequest(long sender, int megabytes) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } ZNetPeer peer = ZNet.instance.GetPeer(sender); object obj; if (peer == null) { obj = null; } else { ZRpc rpc = peer.m_rpc; if (rpc == null) { obj = null; } else { ISocket socket = rpc.GetSocket(); obj = ((socket != null) ? socket.GetHostName() : null); } } string text = (string)obj; if (text == null || !ZNet.instance.IsAdmin(text)) { Plugin.Log("invitation asked by " + (peer?.m_playerName ?? sender.ToString()) + ": not an admin, refused"); } else { ((MonoBehaviour)Plugin.Instance).StartCoroutine(CollectAndSend(megabytes, sender)); } } private static IEnumerator Collect() { if (collecting) { yield break; } collecting = true; List<Vector3> points = new List<Vector3>(); List<ZDO> zdos = new List<ZDO>(); string[] prefabs = Prefabs; foreach (string prefab in prefabs) { int index = 0; while (ZDOMan.instance != null && !ZDOMan.instance.GetAllZDOsWithPrefabIterative(prefab, zdos, ref index)) { yield return null; } } foreach (ZDO item in zdos) { Vector3 p = item.GetPosition(); if (!points.Any(delegate(Vector3 q) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) Vector3 val = q - p; return ((Vector3)(ref val)).sqrMagnitude < 4096f; })) { points.Add(p); } } hostPoints = points; collecting = false; } private static IEnumerator CollectAndSend(int megabytes, long requester) { yield return Collect(); ZPackage val = new ZPackage(); val.Write(1); val.Write(megabytes); val.Write(hostPoints.Count); foreach (Vector3 hostPoint in hostPoints) { val.Write(hostPoint); } ZRoutedRpc.instance.InvokeRoutedRPC(0L, "WorldCache_Invite", new object[1] { val }); Plugin.Log($"invitation to prepare {hostPoints.Count} places of bases and portals sent"); if (requester != 0L) { ZRoutedRpc.instance.InvokeRoutedRPC(requester, "WorldCache_InviteSent", new object[1] { hostPoints.Count }); } else { Status = Plugin.T($"Приглашение разослано: {hostPoints.Count} мест.", $"Invitation sent: {hostPoints.Count} places."); Plugin.Message(Status); } } internal static void RefreshHost() { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && (Object)(object)Plugin.Instance != (Object)null) { ((MonoBehaviour)Plugin.Instance).StartCoroutine(Collect()); } } private static void OnInvite(long sender, ZPackage package) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Expected O, but got Unknown //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Expected O, but got Unknown //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Expected O, but got Unknown if ((Object)(object)ZNet.instance == (Object)null || sender == ZNet.GetUID() || (Object)(object)Player.m_localPlayer == (Object)null) { return; } if (!Plugin.Enabled.Value) { Plugin.Log("invitation to prepare the world ignored: the cache is off"); } else { if (package.ReadInt() != 1) { return; } int megabytes = package.ReadInt(); int num = package.ReadInt(); List<Vector3> points = new List<Vector3>(num); for (int i = 0; i < num; i++) { points.Add(package.ReadVector3()); } if (points.Count == 0) { return; } string text = Plugin.T($"Хост предлагает заранее подготовить мир у баз и порталов: {points.Count} мест, до {megabytes} МБ на диске. Подготовка идёт в фоне и встаёт на паузу в бою. Подготовить?", $"The host suggests preparing the world at the bases and portals ahead: {points.Count} places, up to {megabytes} MB on the disk. It runs in the background and pauses in combat. Prepare?"); string text2 = Plugin.T("Подготовить мир", "Prepare the world"); PopupButtonCallback val = delegate { UnifiedPopup.Pop(); Prepare.Start(PrepareMode.Points, (long)megabytes << 20, points); }; object obj = <>c.<>9__18_1; if (obj == null) { PopupButtonCallback val2 = delegate { UnifiedPopup.Pop(); }; <>c.<>9__18_1 = val2; obj = (object)val2; } UnifiedPopup.Push((PopupBase)new YesNoPopup(text2, text, val, (PopupButtonCallback)obj, false, false)); } } } internal static class MinimapProvider { [HarmonyPatch(typeof(Minimap), "GenerateWorldMap")] private static class GeneratePatch { [HarmonyPriority(200)] private static bool Prefix(Minimap __instance) { try { if (!Applies(__instance) || Stage.State == StageState.Waiting) { return true; } Stopwatch stopwatch = Stopwatch.StartNew(); bool flag = Stage.State == StageState.Ready; byte[] array = (flag ? Stages.Load("game.minimap", "world", "0") : Stages.Store?.Load("game.minimap", "world", "0")); if (array == null) { return true; } if (!flag && VerifyPixels("world", array) == false) { Stages.InvalidateStage("game.minimap"); Plugin.Warn("world map in the cache differs from the generation now: thrown away. Mods patching the generation: " + WorldKey.GenerationOwners()); return true; } int expected = Size.Invoke(__instance); if (!Deserialize(array, expected, out var mask, out var biome, out var heights)) { Stages.Invalidate("game.minimap", "world"); return true; } Color[] array2 = (Color[])(object)new Color[heights.Length]; for (int i = 0; i < heights.Length; i++) { array2[i].r = heights[i]; } Texture2D obj = MaskTexture.Invoke(__instance); obj.SetPixels32(mask); obj.Apply(); Texture2D obj2 = MapTexture.Invoke(__instance); obj2.SetPixels32(biome); obj2.Apply(); Texture2D obj3 = HeightTexture.Invoke(__instance); obj3.SetPixels(array2); obj3.Apply(); Plugin.Log($"world map read from the cache in {stopwatch.ElapsedMilliseconds} ms"); return false; } catch (Exception ex) { Plugin.Warn("world map not read: " + ex.Message); return true; } } } [HarmonyPatch(typeof(Minimap), "SaveMapTextureDataToDisk")] private static class SavePatch { private static void Prefix(Minimap __instance, Color32[] maskPixels, Color32[] biomePixels, float[] savedHeightPixels) { try { if (Applies(__instance) && maskPixels != null && biomePixels != null && savedHeightPixels != null) { Stages.Save("game.minimap", "world", "0", Serialize(Size.Invoke(__instance), maskPixels, biomePixels, savedHeightPixels)); Plugin.Log("world map kept for the next visit"); } } catch (Exception ex) { Plugin.Warn("world map not kept: " + ex.Message); } } } internal const string Id = "game.minimap"; internal const string Key = "world"; private static readonly FieldRef<Minimap, string> CachePath = AccessTools.FieldRefAccess<Minimap, string>("m_cachedMinimapMaskTexturePath"); private static readonly FieldRef<Minimap, int> Size = AccessTools.FieldRefAccess<Minimap, int>("m_textureSize"); private static readonly FieldRef<Minimap, float> PixelSize = AccessTools.FieldRefAccess<Minimap, float>("m_pixelSize"); private static readonly FieldRef<Minimap, Texture2D> MapTexture = AccessTools.FieldRefAccess<Minimap, Texture2D>("m_mapTexture"); private static readonly FieldRef<Minimap, Texture2D> MaskTexture = AccessTools.FieldRefAccess<Minimap, Texture2D>("m_forestMaskTexture"); private static readonly FieldRef<Minimap, Texture2D> HeightTexture = AccessTools.FieldRefAccess<Minimap, Texture2D>("m_heightTexture"); private static readonly Func<Minimap, Biome, Color> PixelColor = AccessTools.MethodDelegate<Func<Minimap, Biome, Color>>(AccessTools.Method(typeof(Minimap), "GetPixelColor", (Type[])null, (Type[])null), (object)null, true); internal static Stage Stage; internal static void Register() { Stage = new Stage { Id = "game.minimap", Owner = "chistix.worldcache", Version = 1, Flags = 1, Title = Plugin.P("Карта мира (клиент сервера)", "World map (server client)"), Parts = delegate { SortedDictionary<string, string> sortedDictionary = WorldKey.Map(); if (Plugin.AllModsInKey.Value) { foreach (KeyValuePair<string, string> item in WorldKey.Mods()) { sortedDictionary[item.Key] = item.Value; } } return sortedDictionary; }, Verify = VerifyPixels }; Stages.AddBuiltin(Stage); } private static bool Applies(Minimap map) { if (!Plugin.Enabled.Value || !Stages.Open || (Object)(object)map == (Object)null || ZNet.World == null) { return false; } if (!string.IsNullOrEmpty(CachePath.Invoke(map))) { return false; } return Harmony.GetPatchInfo((MethodBase)AccessTools.Method(typeof(Minimap), "GenerateWorldMap", (Type[])null, (Type[])null))?.Prefixes.All((Patch p) => p.PatchMethod.DeclaringType.Assembly == typeof(Plugin).Assembly) ?? true; } private static byte[] Serialize(int size, Color32[] mask, Color32[] biome, float[] heights) { int num = size * size; byte[] array = new byte[4 + num * 12]; Buffer.BlockCopy(BitConverter.GetBytes(size), 0, array, 0, 4); Copy(mask, array, 4, num * 4, toRaw: true); Copy(biome, array, 4 + num * 4, num * 4, toRaw: true); Buffer.BlockCopy(heights, 0, array, 4 + num * 8, num * 4); return array; } private static bool Deserialize(byte[] raw, int expected, out Color32[] mask, out Color32[] biome, out float[] heights) { mask = (biome = null); heights = null; if (!Fits(raw, expected)) { return false; } int num = expected * expected; mask = (Color32[])(object)new Color32[num]; biome = (Color32[])(object)new Color32[num]; heights = new float[num]; Copy(mask, raw, 4, num * 4, toRaw: false); Copy(biome, raw, 4 + num * 4, num * 4, toRaw: false); Buffer.BlockCopy(raw, 4 + num * 8, heights, 0, num * 4); return true; } private static bool Fits(byte[] raw, int size) { if (raw != null && raw.Length >= 4 && BitConverter.ToInt32(raw, 0) == size) { return raw.Length == 4 + size * size * 12; } return false; } private static void Copy(Color32[] colors, byte[] raw, int offset, int length, bool toRaw) { GCHandle gCHandle = GCHandle.Alloc(colors, GCHandleType.Pinned); try { if (toRaw) { Marshal.Copy(gCHandle.AddrOfPinnedObject(), raw, offset, length); } else { Marshal.Copy(raw, offset, gCHandle.AddrOfPinnedObject(), length); } } finally { gCHandle.Free(); } } private static bool? VerifyPixels(string key, byte[] raw) { //IL_00b8: 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_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) Minimap instance = Minimap.instance; WorldGenerator instance2 = WorldGenerator.instance; if ((Object)(object)instance == (Object)null || instance2 == null || PixelColor == null) { return null; } int num = Size.Invoke(instance); float num2 = PixelSize.Invoke(instance); if (!Fits(raw, num)) { return false; } int num3 = num * num; Random random = new Random(); int num4 = num / 2; float num5 = num2 / 2f; Color val = default(Color); for (int i = 0; i < 16; i++) { int num6 = random.Next(num); int num7 = random.Next(num); int num8 = num6 * num + num7; float num9 = (float)(num6 - num4) * num2 + num5; float num10 = (float)(num7 - num4) * num2 + num5; Biome biome = instance2.GetBiome(num10, num9, 0.02f, false); if (BitConverter.ToInt32(BitConverter.GetBytes(instance2.GetBiomeHeight(biome, num10, num9, ref val, false, true)), 0) != BitConverter.ToInt32(raw, 4 + num3 * 8 + num8 * 4)) { return false; } Color32 val2 = Color32.op_Implicit(PixelColor(instance, biome)); int num11 = 4 + num3 * 4 + num8 * 4; if (val2.r != raw[num11] || val2.g != raw[num11 + 1] || val2.b != raw[num11 + 2]) { return false; } } return true; } } [BepInPlugin("chistix.worldcache", "WorldCache", "0.1.10")] public sealed class Plugin : BaseUnityPlugin { [HarmonyPatch(typeof(WorldGenerator), "Initialize")] private static class OpenPatch { private static void Postfix(World world) { if (world == null || world.m_menu) { return; } try { HeightmapProvider.Reset(); Stages.OpenWorld(world); } catch (Exception ex) { Warn("world cache not opened: " + ex); } } } [HarmonyPatch(typeof(WorldGenerator), "Deitialize")] private static class ClosePatch { private static void Prefix() { try { Stages.CloseWorld(); HeightmapProvider.Reset(); } catch (Exception ex) { Warn("world cache not closed: " + ex); } } } [HarmonyPatch(typeof(Player), "OnSpawned")] private static class SpawnPatch { private static void Postfix(Player __instance) { if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } try { Stages.Recheck("the player spawned", makeFinal: true); Stages.LogEntry(); Invite.RefreshHost(); Prepare.OnPlayerSpawned(); EntryPrompt.OnSpawned(); } catch (Exception ex) { Warn("spawn: " + ex); } } } [HarmonyPatch(typeof(Terminal), "InitTerminal")] private static class CommandPatch { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__0_0; internal void <Postfix>b__0_0(ConsoleEventArgs args) { <>c__DisplayClass0_0 CS$<>8__locals9 = new <>c__DisplayClass0_0 { args = args }; switch ((CS$<>8__locals9.args.Args.Length > 1) ? CS$<>8__locals9.args.Args[1].ToLowerInvariant() : "status") { case "prepare": { PrepareMode m = PrepareModeSetting.Value; if (CS$<>8__locals9.args.Args.Length > 2) { string text = CS$<>8__locals9.args.Args[2].ToLowerInvariant(); m = (text.StartsWith("around") ? PrepareMode.AroundMe : ((text.StartsWith("world") || text.StartsWith("whole")) ? PrepareMode.WholeWorld : PrepareMode.Smart)); } long result; long num = ((CS$<>8__locals9.args.Args.Length > 3 && long.TryParse(CS$<>8__locals9.args.Args[3], out result)) ? result : VolumeChoice()); Out(Prepare.Start(m, num << 20) ? $"WorldCache: подготовка {Prepare.ModeName(m)}, {num} МБ" : "WorldCache: не начато"); return; } case "stop": Prepare.Stop(byUser: true); Out("WorldCache: остановлено"); return; case "clear": ClearCache(CS$<>8__locals9.args.Args.Length > 2 && CS$<>8__locals9.args.Args[2] == "all", Out); return; case "invite": Invite.Send(); Out("WorldCache: приглашение…"); return; } foreach (string item in StatusLines()) { Out(item); } string Out(string s) { Terminal context = CS$<>8__locals9.args.Context; if (context != null) { context.AddString(s); } return s; } } } [CompilerGenerated] private sealed class <>c__DisplayClass0_0 { public ConsoleEventArgs args; } private static void Postfix() { //IL_0033: 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) //IL_0029: Expected O, but got Unknown object obj = <>c.<>9__0_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { switch ((args.Args.Length > 1) ? args.Args[1].ToLowerInvariant() : "status") { case "prepare": { PrepareMode m = PrepareModeSetting.Value; if (args.Args.Length > 2) { string text = args.Args[2].ToLowerInvariant(); m = (text.StartsWith("around") ? PrepareMode.AroundMe : ((text.StartsWith("world") || text.StartsWith("whole")) ? PrepareMode.WholeWorld : PrepareMode.Smart)); } long result; long num = ((args.Args.Length > 3 && long.TryParse(args.Args[3], out result)) ? result : VolumeChoice()); Out(Prepare.Start(m, num << 20) ? $"WorldCache: подготовка {Prepare.ModeName(m)}, {num} МБ" : "WorldCache: не начато"); break; } case "stop": Prepare.Stop(byUser: true); Out("WorldCache: остановлено"); break; case "clear": ClearCache(args.Args.Length > 2 && args.Args[2] == "all", Out); break; case "invite": Invite.Send(); Out("WorldCache: приглашение…"); break; default: { foreach (string item in StatusLines()) { Out(item); } break; } } string Out(string s) { Terminal context = args.Context; if (context != null) { context.AddString(s); } return s; } }; <>c.<>9__0_0 = val; obj = (object)val; } new ConsoleCommand("worldcache", "status | prepare [smart|around|world] [MB] | stop | clear [all] | invite - кэш мира", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } } public const string Guid = "chistix.worldcache"; public const string Version = "0.1.10"; internal static Plugin Instance; private static ManualLogSource log; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<bool> AllModsInKey; internal static ConfigEntry<bool> CacheDistantTerrain; internal static ConfigEntry<bool> ShowProgress; internal static ConfigEntry<bool> PauseInCombat; internal static ConfigEntry<bool> DebugLog; internal static ConfigEntry<bool> AskOnFirstEntry; internal static ConfigEntry<int> LimitMegabytes; internal static ConfigEntry<int> SpotChecks; internal static ConfigEntry<int> VolumeMegabytes; internal static ConfigEntry<int> CustomMegabytes; internal static ConfigEntry<int> MinFps; internal static ConfigEntry<int> PrepareThreads; internal static ConfigEntry<int> PrepareSleepMs; internal static ConfigEntry<int> InviteMegabytes; internal static ConfigEntry<float> AroundRadius; internal static ConfigEntry<PrepareMode> PrepareModeSetting; private static ConfigEntry<string> ignoredMods; internal static HashSet<string> IgnoredMods = new HashSet<string>(StringComparer.Ordinal); internal static readonly int[] Volumes = new int[4] { 500, 1024, 2048, 5120 }; internal static readonly int[] Limits = new int[6] { 500, 1024, 2048, 5120, 10240, 20480 }; internal static bool Quitting; private static volatile bool russian = true; internal static long LimitBytes => Math.Max(100L, LimitMegabytes.Value) << 20; private void Awake() { //IL_03b3: Unknown result type (might be due to invalid IL or missing references) Instance = this; log = ((BaseUnityPlugin)this).Logger; MoveOldConfig(); Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, D("The world's generation kept on the disk (off: the game generates as usual, the files are not touched).", "Кэш генерации мира на диске (выкл - игра генерирует как обычно, файлы не трогаются).")); LimitMegabytes = ((BaseUnityPlugin)this).Config.Bind<int>("General", "LimitMB", 2048, D("Disk space for the caches of all worlds together (MB). Beyond it, what was unused the longest goes: other worlds first, then old places of this one.", "Сколько места на диске занимают кэши всех миров вместе (МБ). Сверх - удаляется давно не нужное: сначала другие миры, потом старые места этого мира.")); AskOnFirstEntry = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "AskOnFirstEntry", true, D("Entering a world without a cache: a note on the loading screen and a choice - Wait (prepare the nearest places now) or Play (cache as you go).", "Первый вход в мир без кэша: предупредить на экране загрузки и спросить - «Ждать» (подготовить ближайшее сейчас) или «Играть» (кэшировать по ходу игры).")); ShowProgress = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowProgress", true, D("The preparation's bar at the top of the screen.", "Полоска подготовки мира вверху экрана.")); DebugLog = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DebugLog", false, D("More details in the log (spot checks, read errors).", "Подробности в журнал (сверки, ошибки чтения).")); AllModsInKey = ((BaseUnityPlugin)this).Config.Bind<bool>("Key", "AllModsInKey", true, D("Every mod and its version is part of the key of the game's own stages (zone heights, the map): a new or updated mod clears them. Off: only mods patching the generation, plus the spot checks.", "Список всех модов и их версий входит в ключ кэша игры (высоты зон, карта): любой новый или обновлённый мод сбрасывает их. Выкл - только моды, перехватывающие генерацию, и выборочная сверка.")); ignoredMods = ((BaseUnityPlugin)this).Config.Bind<string>("Key", "IgnoredMods", "local.valheim.probe", D("GUIDs of mods (comma separated) that patch generation methods without changing the results (diagnostics): left out of the key.", "GUID модов через запятую, которые не меняют генерацию, хоть и перехватывают её методы (диагностика): не входят в ключ.")); SpotChecks = ((BaseUnityPlugin)this).Config.Bind<int>("Key", "SpotChecks", 3, D("How many cached pieces of each stage are generated again on entering a world and compared (0: no checks).", "Сколько сохранённых кусков каждого этапа пересчитать при входе в мир и сравнить с генерацией (0 - не сверять).")); CacheDistantTerrain = ((BaseUnityPlugin)this).Config.Bind<bool>("Game", "CacheDistantTerrain", true, D("Cache the distant terrain too (large tiles around the player): faster return to the same place (bed, start), more disk space.", "Кэшировать и дальний рельеф (большие плитки вокруг игрока): быстрее возврат на то же место (кровать, старт), но больше места.")); PrepareModeSetting = ((BaseUnityPlugin)this).Config.Bind<PrepareMode>("Prepare", "Mode", PrepareMode.Smart, D("Preparing the world: Smart - around the player, bases and portals, explored on the map, altars and found places, then the rest by distance; AroundMe; WholeWorld.", "Режим подготовки мира: Smart - вокруг игрока, базы и порталы, открытое на карте, алтари и найденные места, потом остальное по расстоянию; AroundMe; WholeWorld.")); VolumeMegabytes = ((BaseUnityPlugin)this).Config.Bind<int>("Prepare", "VolumeMB", 1024, D("Preparation volume (MB on the disk): 500, 1024, 2048, 5120, or 0 for CustomMB.", "Объём подготовки (МБ на диске): 500, 1024, 2048, 5120 или 0 - своё значение (CustomMB).")); CustomMegabytes = ((BaseUnityPlugin)this).Config.Bind<int>("Prepare", "CustomMB", 3072, D("The custom preparation volume (MB).", "Своё значение объёма подготовки (МБ).")); AroundRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Prepare", "AroundRadius", 300f, D("Radius of \"around me\", the first in the order (m).", "Радиус «вокруг меня» в первой очереди (м).")); MinFps = ((BaseUnityPlugin)this).Config.Bind<int>("Prepare", "MinFps", 30, D("Below this FPS the preparation pauses (resumes after 3 s above it).", "Ниже этого FPS подготовка встаёт на паузу (продолжает через 3 с нормы).")); PauseInCombat = ((BaseUnityPlugin)this).Config.Bind<bool>("Prepare", "PauseInCombat", true, D("Pause the preparation in combat (sensed or targeted).", "Пауза подготовки в бою (вас заметили или выбрали целью).")); PrepareThreads = ((BaseUnityPlugin)this).Config.Bind<int>("Prepare", "Threads", 1, D("Preparation threads (1-8): more is faster but loads the CPU more.", "Потоков подготовки (1-8). Больше - быстрее, но сильнее грузит процессор.")); PrepareSleepMs = ((BaseUnityPlugin)this).Config.Bind<int>("Prepare", "SleepMs", 5, D("A thread's rest after each piece (ms): small portions.", "Пауза потока после каждого куска (мс): малые порции.")); InviteMegabytes = ((BaseUnityPlugin)this).Config.Bind<int>("Prepare", "InviteMB", 500, D("Disk space the host suggests to the players for preparing the bases and portals (MB).", "Сколько места хост предлагает игрокам на подготовку баз и порталов (МБ).")); ParseIgnored(); ignoredMods.SettingChanged += delegate { ParseIgnored(); }; LimitMegabytes.SettingChanged += delegate { Stages.Store?.Trim(); }; Enabled.SettingChanged += delegate { if (!Enabled.Value) { Prepare.Stop(byUser: true); } }; HeightmapProvider.Register(); MinimapProvider.Register(); new Harmony("chistix.worldcache").PatchAll(typeof(Plugin).Assembly); Log("WorldCache 0.1.10: limit " + Megabytes(LimitBytes)); } private void MoveOldConfig() { try { string text = Path.Combine(Paths.ConfigPath, "local.valheim.worldcache.cfg"); if (File.Exists(text) && !File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath)) { File.Copy(text, ((BaseUnityPlugin)this).Config.ConfigFilePath); ((BaseUnityPlugin)this).Config.Reload(); Log("settings taken over from local.valheim.worldcache.cfg"); } } catch (Exception ex) { Warn("old settings not taken over: " + ex.Message); } } private static string D(string en, string ru) { return en + "\n" + ru; } private static void ParseIgnored() { IgnoredMods = new HashSet<string>(from s in (ignoredMods.Value ?? "").Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries) select s.Trim() into s where s.Length > 0 select s, StringComparer.Ordinal); } private void Update() { try { if (Localization.instance != null) { russian = Localization.instance.GetSelectedLanguage() == "Russian"; } Stages.Update(); Stages.Tick(); Prepare.Tick(); Invite.Update(); } catch (Exception ex) { Warn("update: " + ex); } } private void OnGUI() { try { Prepare.DrawOverlay(); } catch (Exception ex) { Debug("overlay: " + ex.Message); } } private void OnApplicationQuit() { Quitting = true; Stages.CloseWorld(); } internal static long VolumeChoice() { return (VolumeMegabytes.Value > 0) ? VolumeMegabytes.Value : Math.Max(100, CustomMegabytes.Value); } internal static void ClearCache(bool allWorlds, Func<string, string> report) { Prepare.Stop(byUser: false); Store store = Stages.Store; if (store == null) { long bytes = Store.Worlds().Sum((Store.WorldInfo w) => w.Bytes); foreach (Store.WorldInfo item in Store.Worlds()) { Store.TryDeleteFolder(item.Path); } report?.Invoke(T("Кэш всех миров очищен (" + Megabytes(bytes) + ").", "Every world's cache cleared (" + Megabytes(bytes) + ").")); return; } long bytes2 = store.Total + (allWorlds ? store.Others : 0); store.Clear(delegate { Log("cache of this world cleared"); }); if (allWorlds) { store.ClearOthers(delegate { Log("cache of other worlds cleared"); }); } report?.Invoke(T("Кэш очищен (" + Megabytes(bytes2) + ").", "Cache cleared (" + Megabytes(bytes2) + ").")); } internal static IEnumerable<string> StatusLines() { Store store = Stages.Store; yield return "WorldCache 0.1.10: " + (Enabled.Value ? T("вкл", "on") : T("выкл", "off")) + ", " + T("лимит", "limit") + " " + Megabytes(LimitBytes) + ", " + UsageText(); if (store != null) { foreach (Stage item in Stages.All()) { yield return string.Format(" {0} ({1}): {2}, {3}, {4} {5}, {6} {7}, {8} {9}", item.Id, item.Owner, StateName(item.State), Megabytes(store.StageBytes(item.Id)), T("попаданий", "hits"), item.Hits, T("промахов", "misses"), item.Misses, T("записей", "saves"), item.Saves); } } if (Prepare.Running) { yield return string.Format(" {0}: {1} {2:0} % ({3} / {4}) {5}", T("подготовка", "preparing"), Prepare.ModeName(Prepare.Mode), Prepare.Progress * 100f, Megabytes(Prepare.WrittenBytes), Megabytes(Prepare.Budget), Prepare.Phase) + ((Prepare.PauseReason != null) ? (" - " + T("пауза", "paused") + ": " + Prepare.PauseReason) : ""); } } internal static string StateName(StageState s) { return s switch { StageState.Checking => T("сверка", "checking"), StageState.Ready => T("готов", "ready"), _ => T("ждёт", "waiting"), }; } internal static string UsageText() { Store store = Stages.Store; if (store != null) { return T("занято", "used") + " " + Megabytes(store.Total + store.Others) + " (" + T("этот мир", "this world") + " " + Megabytes(store.Total) + ")"; } return T("занято", "used") + " " + Megabytes(Store.Worlds().Sum((Store.WorldInfo w) => w.Bytes)); } internal static string T(string ru, string en) { if (!russian) { return en; } return ru; } internal static string P(string ru, string en) { return ru + "\u0001" + en; } internal static string Pick(string text) { int num = text?.IndexOf('\u0001') ?? (-1); if (num >= 0) { return T(text.Substring(0, num), text.Substring(num + 1)); } return text; } internal static string Megabytes(long bytes) { CultureInfo cultureInfo = CultureInfo.GetCultureInfo(russian ? "ru-RU" : "en-US"); if (bytes < 1073741824) { return ((double)bytes / 1048576.0).ToString("0", cultureInfo) + T(" МБ", " MB"); } return ((double)bytes / 1073741824.0).ToString("0.##", cultureInfo) + T(" ГБ", " GB"); } internal static void Message(string text) { if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false, true); } Log(text); } internal static void Log(string text) { ManualLogSource obj = log; if (obj != null) { obj.LogInfo((object)text); } } internal static void Warn(string text) { ManualLogSource obj = log; if (obj != null) { obj.LogWarning((object)text); } } internal static void Debug(string text) { if (DebugLog != null && DebugLog.Value) { ManualLogSource obj = log; if (obj != null) { obj.LogInfo((object)text); } } } } internal enum PrepareMode { Smart, AroundMe, WholeWorld, Points, Entry } internal static class Prepare { private sealed class Area { internal Vector3 Pos; internal float Radius; internal string Phase; } private sealed class State { internal bool Running; internal PrepareMode Mode; internal long Budget; internal long Written; internal List<Vector3> Points; } private static readonly object gate = new object(); private static volatile int job; private static volatile bool running; private static volatile bool fast; private static volatile bool continueSmart; private static long continueBudget; private static PrepareMode mode; private static long budget; private static long written; private static long itemsDone; private static List<Area> areas = new List<Area>(); private static int areaCursor; private static int busy; private static readonly ConcurrentQueue<KeyValuePair<Stage, string>> items = new ConcurrentQueue<KeyValuePair<Stage, string>>(); private static readonly List<Thread> threads = new List<Thread>(); private static volatile string pauseReason; private static string phase = ""; private static string finished; private static float finishedAt = -100f; private static readonly Stopwatch clock = Stopwatch.StartNew(); internal static List<Vector3> InvitePoints = new List<Vector3>(); private static readonly FieldRef<Minimap, List<PinData>> Pins = AccessTools.FieldRefAccess<Minimap, List<PinData>>("m_pins"); private static readonly FieldRef<Minimap, BitArray> ExploredBits = AccessTools.FieldRefAccess<Minimap, BitArray>("m_explored"); private static readonly FieldRef<Minimap, int> TextureSize = AccessTools.FieldRefAccess<Minimap, int>("m_textureSize"); private static readonly FieldRef<Minimap, float> PixelSize = AccessTools.FieldRefAccess<Minimap, float>("m_pixelSize"); private static readonly Func<Minimap, float, float, Vector3> MapPointToWorld = AccessTools.MethodDelegate<Func<Minimap, float, float, Vector3>>(AccessTools.Method(typeof(Minimap), "MapPointToWorld", (Type[])null, (Type[])null), (object)null, true); private static readonly FieldRef<ZoneSystem, Dictionary<Vector3, string>> LocationIcons = AccessTools.FieldRefAccess<ZoneSystem, Dictionary<Vector3, string>>("m_locationIcons"); private static float exploredCell = 256f; private static float fpsAverage = 60f; private static float goodSince; private static float startDelay; private static GUIStyle label; private static Texture2D white; internal static bool Fast => fast; private static float Clock => (float)clock.Elapsed.TotalSeconds; internal static bool Running => running; internal static PrepareMode Mode => mode; internal static long Budget => budget; internal static long WrittenBytes => Interlocked.Read(in written); internal static string PauseReason => pauseReason; internal static string Phase => Plugin.Pick(phase); internal static string Finished => finished; internal static float Progress { get { if (budget > 0) { return Mathf.Clamp01((float)WrittenBytes / (float)budget); } return 0f; } } internal static void ToBackground() { fast = false; } internal static string ModeName(PrepareMode m) { return m switch { PrepareMode.Smart => Plugin.T("Умно", "Smart"), PrepareMode.AroundMe => Plugin.T("Вокруг меня", "Around me"), PrepareMode.WholeWorld => Plugin.T("Весь мир", "Whole world"), PrepareMode.Entry => Plugin.T("Ближайшее", "Nearest"), _ => Plugin.T("Базы и порталы хоста", "Host's bases and portals"), }; } internal static bool Start(PrepareMode m, long bytes, List<Vector3> points = null, bool fastMode = false) { if (!Stages.Open || (Object)(object)Player.m_localPlayer == (Object)null) { Plugin.Message(Plugin.T("Подготовка - только в мире.", "Preparing works in a world only.")); return false; } if (!Plugin.Enabled.Value) { Plugin.Message(Plugin.T("Кэш мира выключен.", "The world cache is off.")); return false; } Stop(byUser: false); lock (gate) { mode = m; budget = ((m == PrepareMode.WholeWorld) ? Plugin.LimitBytes : Math.Max(16777216L, bytes)); Interlocked.Exchange(ref written, 0L); Interlocked.Exchange(ref itemsDone, 0L); if (points != null) { InvitePoints = points; } fast = fastMode; Begin(); } Save(); Plugin.Log($"preparing the world: {ModeName(mode)}, {Plugin.Megabytes(budget)}, {areas.Count} areas"); return true; } private static void Begin() { areas = Plan(mode); areaCursor = 0; KeyValuePair<Stage, string> result; while (items.TryDequeue(out result)) { } finished = null; phase = ""; int id = ++job; running = true; threads.Clear(); int num = (fast ? Math.Max(1, Environment.ProcessorCount - 1) : Mathf.Clamp(Plugin.PrepareThreads.Value, 1, 8)); for (int i = 0; i < num; i++) { int index = i; Thread thread = new Thread((ThreadStart)delegate { Work(id, index); }) { Name = "WorldCache prepare " + i, IsBackground = true, Priority = ((!fast) ? ThreadPriority.BelowNormal : ThreadPriority.Normal) }; threads.Add(thread); thread.Start(); } } internal static void Stop(bool byUser) { lock (gate) { if (!running) { return; } running = false; job++; KeyValuePair<Stage, string> result; while (items.TryDequeue(out result)) { } fast = false; continueSmart = false; if (byUser) { finished = Plugin.T("Остановлено", "Stopped"); finishedAt = Clock; Plugin.Log("preparing stopped"); } } if (byUser) { Save(); } } internal static void WorldClosing() { if (running) { Save(); } fast = false; continueSmart = false; running = false; job++; KeyValuePair<Stage, string> result; while (items.TryDequeue(out result)) { } } private static void Finish(int id, string why, bool exhausted = false) { lock (gate) { if (id != job || !running) { return; } running = false; job++; finished = why; finishedAt = Clock; if (mode == PrepareMode.Entry && exhausted && budget - WrittenBytes > 16777216) { continueSmart = true; continueBudget = budget - WrittenBytes; } fast = false; } Plugin.Log($"preparing finished: {why} ({Plugin.Megabytes(WrittenBytes)}, {Interlocked.Read(in itemsDone)} pieces)"); Save(); } internal static void OnWritten(string folder, long delta) { if (running && delta > 0) { Interlocked.Add(ref written, delta); } } private static List<Area> Plan(PrepareMode m) { //IL_002e: 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_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) List<Area> list = new List<Area>(); Player localPlayer = Player.m_localPlayer; Vector3 me = (((Object)(object)localPlayer != (Object)null) ? ((Component)localPlayer).transform.position : Vector3.zero); float value = Plugin.AroundRadius.Value; switch (m) { case PrepareMode.Points: Add(InvitePoints, 200f, Plugin.P("Базы и порталы хоста", "Host's bases and portals")); break; case PrepareMode.AroundMe: list.Add(new Area { Pos = me, Radius = value, Phase = Plugin.P("Вокруг меня", "Around me") }); Rings(value * 2f); break; case PrepareMode.WholeWorld: list.Add(new Area { Pos = me, Radius = value, Phase = Plugin.P("Вокруг меня", "Around me") }); Rings(value * 2f); break; case PrepareMode.Entry: list.Add(new Area { Pos = me, Radius = value, Phase = Plugin.P("Вокруг меня", "Around me") }); Add(Bases(), 200f, Plugin.P("Базы и порталы", "Bases and portals")); Add(Explored(), 0f, Plugin.P("Открытое на карте", "Explored on the map")); Add(Places(), 200f, Plugin.P("Алтари и найденные места", "Altars and found places")); break; default: list.Add(new Area { Pos = me, Radius = value, Phase = Plugin.P("Вокруг меня", "Around me") }); Add(Bases(), 200f, Plugin.P("Базы и порталы", "Bases and portals")); Add(Explored(), 0f, Plugin.P("Открытое на карте", "Explored on the map")); Add(Places(), 200f, Plugin.P("Алтари и найденные места", "Altars and found places")); Rings(value * 2f); break; } foreach (Area item in list) { if (item.Radius <= 0f) { item.Radius = exploredCell * 0.75f; } } return list; void Add(IEnumerable<Vector3> points, float radius, string name) { //IL_0033: 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_0045: 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) foreach (Vector3 item2 in points.Distinct().OrderBy(delegate(Vector3 p) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) Vector3 val = p - me; return ((Vector3)(ref val)).sqrMagnitude; })) { list.Add(new Area { Pos = item2, Radius = radius, Phase = name }); } } void Rings(float from) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) for (float num = from; num <= 80000f; num *= 2f) { list.Add(new Area { Pos = me, Radius = num, Phase = Plugin.P("Остальное по расстоянию", "The rest by distance") }); } } } private static IEnumerable<Vector3> Bases() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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: Invalid comparison between Unknown and I4 //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Invalid comparison between Unknown and I4 //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Invalid comparison between Unknown and I4 //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Invalid comparison between Unknown and I4 //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Invalid comparison between Unknown and I4 List<Vector3> list = new List<Vector3>(); Game instance = Game.instance; PlayerProfile val = ((instance != null) ? instance.GetPlayerProfile() : null); if (val != null) { if (val.HaveCustomSpawnPoint()) { list.Add(val.GetCustomSpawnPoint()); } list.Add(val.GetHomePoint()); } if ((Object)(object)Minimap.instance != (Object)null) { foreach (PinData item in Pins.Invoke(Minimap.instance)) { if ((int)item.m_type == 5 || (int)item.m_type == 0 || (int)item.m_type == 1 || (int)item.m_type == 2 || (int)item.m_type == 3 || (int)item.m_type == 6) { list.Add(item.m_pos); } } } list.AddRange(InvitePoints); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { list.AddRange(Invite.HostPoints()); } return list.Where((Vector3 p) => ((Vector3)(ref p)).sqrMagnitude > 1f); } private static IEnumerable<Vector3> Explored() { //IL_00b7: Unknown result type (might be due to invalid IL or missing references) Minimap instance = Minimap.instance; List<Vector3> list = new List<Vector3>(); if ((Object)(object)instance == (Object)null || MapPointToWorld == null) { return list; } BitArray bitArray = ExploredBits.Invoke(instance); int num = TextureSize.Invoke(instance); float num2 = PixelSize.Invoke(instance); if (bitArray == null || num <= 0) { return list; } int num3 = Mathf.Max(1, Mathf.RoundToInt(256f / Mathf.Max(1f, num2))); exploredCell = (float)num3 * num2; for (int i = num3 / 2; i < num; i += num3) { for (int j = num3 / 2; j < num; j += num3) { if (bitArray[i * num + j]) { list.Add(MapPointToWorld(instance, ((float)j + 0.5f) / (float)num, ((float)i + 0.5f) / (float)num)); } } } return list; } private static IEnumerable<Vector3> Places() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Invalid comparison between Unknown and I4 //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Invalid comparison between Unknown and I4 //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Invalid comparison between Unknown and I4 //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Invalid comparison between Unknown and I4 List<Vector3> list = new List<Vector3>(); if ((Object)(object)Minimap.instance != (Object)null) { foreach (PinData item in Pins.Invoke(Minimap.instance)) { if ((int)item.m_type == 9 || (int)item.m_type == 14 || (int)item.m_type == 15 || (int)item.m_type == 16) { list.Add(item.m_pos); } } } if ((Object)(object)ZoneSystem.instance != (Object)null) { list.AddRange(LocationIcons.Invoke(ZoneSystem.instance).Keys); } return list; } private static void Work(int id, int index) { try { while (id == job) { Store store = Stages.Store; if (store == null || (!fast && index >= Math.Max(1, Math.Min(8, Plugin.PrepareThreads.Value)))) { break; } if (pauseReason != null || !Plugin.Enabled.Value) { Thread.Sleep(250); continue; } if (WrittenBytes >= budget) { Finish(id, Plugin.T("Готово: набран объём", "Done: the volume is reached")); break; } if (store.Total + store.Others >= (long)((double)Plugin.LimitBytes * 0.9)) { Finish(id, Plugin.T("Достигнут лимит кэша", "The cache limit is reached")); break; } if (store.PendingBytes > 16777216) { Thread.Sleep(50); continue; } if (!items.TryDequeue(out var result)) { if (!Expand(id)) { if (Volatile.Read(in busy) == 0 && items.IsEmpty) { Finish(id, Plugin.T("Готово: всё подготовлено", "Done: everything is prepared"), exhausted: true); break; } Thread.Sleep(100); } continue; } Stage key = result.Key; if (key.State != StageState.Ready || Stages.Has(key.Id, result.Value)) { continue; } Interlocked.Increment(ref busy); try { byte[] array = key.Generate(result.Value); if (array != null && id == job) { Stages.Save(key.Id, result.Value, "0", array); Interlocked.Increment(ref itemsDone); } } catch (Exception ex) { Plugin.Warn("prepare " + key.Id + " " + result.Value + ": " + ex.GetBaseException().Message); } finally { Interlocked.Decrement(ref busy); } int num = ((!fast) ? Plugin.PrepareSleepMs.Value : 0); if (num > 0) { Thread.Sleep(num); } } } catch (Exception ex2) { Plugin.Warn("prepare worker: " + ex2); } } private static bool Expand(int id) { Area area; lock (gate) { if (id != job) { return false; } if (!items.IsEmpty) { return true; } if (areaCursor >= areas.Count) { return false; } area = areas[areaCursor++]; phase = area.Phase; } Interlocked.Increment(ref busy); try { foreach (Stage item in Stages.All()) { if (item.KeysNear == null || item.Generate == null || item.State != StageState.Ready) { continue; } string[] array; try { array = item.KeysNear(area.Pos.x, area.Pos.y, area.Pos.z, area.Radius); } catch (Exception ex) { Plugin.Warn("prepare " + item.Id + ": keysNear failed: " + ex.GetBaseException().Message); continue; } if (array == null) { continue; } string[] array2 = array; foreach (string text in array2) { if (text != null && !Stages.Has(item.Id, text)) { items.Enqueue(new KeyValuePair<Stage, string>(item, text)); } } } } finally { Interlocked.Decrement(ref busy); } return true; } internal static void Tick() { float num = Mathf.Max(0.0001f, Time.unscaledDeltaTime); fpsAverage = Mathf.Lerp(fpsAverage, 1f / num, Mathf.Clamp01(num * 2f)); if (!running) { pauseReason = null; if (continueSmart && Stages.Open && (Object)(object)Player.m_localPlayer != (Object)null) { continueSmart = false; Start(PrepareMode.Smart, continueBudget); } return; } if (fast) { pauseReason = null; return; } Player localPlayer = Player.m_localPlayer; string text = null; if ((Object)(object)localPlayer == (Object)null) { text = Plugin.T("нет игрока", "no player"); } else if (((Character)localPlayer).IsTeleporting() || Time.unscaledTime < startDelay) { text = Plugin.T("загрузка", "loading"); } else if (Plugin.PauseInCombat.Value && (localPlayer.IsSensed() || localPlayer.IsTargeted())) { text = Plugin.T("бой", "combat"); } else if (fpsAverage < (float)Plugin.MinFps.Value) { text = Plugin.T("мало FPS", "low FPS"); } if (text != null) { goodSince = Time.unscaledTime; pauseReason = text; } else if (pauseReason == null || !(Time.unscaledTime - goodSince < 3f)) { pauseReason = null; } } internal static void OnPlayerSpawned() { startDelay = Time.unscaledTime + 10f; continueSmart = false; if (running || !Stages.Open) { return; } State state = Load(); if (state == null || !state.Running) { return; } lock (gate) { mode = ((state.Mode != PrepareMode.Entry) ? state.Mode : PrepareMode.Smart); budget = state.Budget; fast = false; Interlocked.Exchange(ref written, state.Written); Interlocked.Exchange(ref itemsDone, 0L); if (state.Points != null) { InvitePoints = state.Points; } Begin(); } Plugin.Log("preparing the world carries on: " + ModeName(mode) + ", " + Plugin.Megabytes(WrittenBytes) + " of " + Plugin.Megabytes(budget)); } private static void Save() { Store store = Stages.Store; if (store == null) { return; } try { CultureInfo c = CultureInfo.InvariantCulture; List<string> contents = new List<string> { "running=" + (running ? "1" : "0"), "mode=" + mode, "budget=" + budget.ToString(c), "written=" + WrittenBytes.ToString(c), "points=" + string.Join(";", InvitePoints.Select((Vector3 p) => p.x.ToString("R", c) + "," + p.y.ToString("R", c) + "," + p.z.ToString("R", c))) }; File.WriteAllLines(Path.Combine(store.Root, "prepare.txt"), contents); } catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException) { Plugin.Warn("prepare.txt: " + ex.Message); } } private static State Load() { //IL_01c8: Unknown result type (might be due to invalid IL or missing references) Store store = Stages.Store; string text = ((store != null) ? Path.Combine(store.Root, "prepare.txt") : null); if (text == null || !File.Exists(text)) { return null; } try { CultureInfo invariantCulture = CultureInfo.InvariantCulture; Dictionary<string, string> dictionary = (from l in File.ReadAllLines(text) select l.Split(new char[1] { '=' }, 2) into p where p.Length == 2 select p).ToDictionary((string[] p) => p[0], (string[] p) => p[1]); string value; string value2; PrepareMode result; string value3; string value4; State state = new State { Running = (dictionary.TryGetValue("running", out value) && value == "1"), Mode = ((dictionary.TryGetValue("mode", out value2) && Enum.TryParse<PrepareMode>(value2, out result)) ? result : PrepareMode.Smart), Budget = (dictionary.TryGetValue("budget", out value3) ? long.Parse(value3, invariantCulture) : 1073741824), Written = (dictionary.TryGetValue("written", out value4) ? long.Parse(value4, invariantCulture) : 0), Points = new List<Vector3>() }; if (dictionary.TryGetValue("points", out var value5)) { string[] array = value5.Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries); for (int num = 0; num < array.Length; num++) { string[] array2 = array[num].Split(','); if (array2.Length == 3) { state.Points.Add(new Vector3(float.Parse(array2[0], invariantCulture), float.Parse(array2[1], invariantCulture), float.Parse(array2[2], invariantCulture))); } } } return state; } catch (Exception ex) { Plugin.Warn("prepare.txt unreadable: " + ex.Message); return null; } } internal static void DrawOverlay() { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown //IL_0063: 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_011b: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Expected O, but got Unknown //IL_0247: Unknown result type (might be due to invalid IL or missing references) bool flag = finished != null && Clock - finishedAt < 6f; if (Plugin.ShowProgress.Value && (running || flag) && !((Object)(object)Player.m_localPlayer == (Object)null)) { if ((Object)(object)white == (Object)null) { white = new Texture2D(1, 1); white.SetPixel(0, 0, Color.white); white.Apply(); } if (label == null) { label = new GUIStyle(GUI.skin.label) { alignment = (TextAnchor)4, fontSize = Mathf.RoundToInt(13f * Mathf.Max(1f, (float)Screen.height / 1080f)) }; } float num = Mathf.Max(1f, (float)Screen.height / 1080f); float num2 = 420f * num; float num3 = 40f * num; float num4 = ((float)Screen.width - num2) / 2f; float num5 = 8f * num; Color color = GUI.color; GUI.color = new Color(0f, 0f, 0f, 0.55f); GUI.DrawTexture(new Rect(num4, num5, num2, num3), (Texture)(object)white); GUI.color = new Color(0.35f, 0.75f, 1f, 0.85f); GUI.DrawTexture(new Rect(num4 + 3f, num5 + num3 - 7f * num, (num2 - 6f) * Progress, 4f * num), (Texture)(object)white); GUI.color = color; string text = (running ? (Plugin.T("Подготовка мира", "Preparing the world") + $": {Progress * 100f:0} % · {Plugin.Megabytes(WrittenBytes)} / {Plugin.Megabytes(budget)} · {Phase}" + ((pauseReason != null) ? (" · " + Plugin.T("пауза", "paused") + ": " + pauseReason) : "")) : finished); GUI.Label(new Rect(num4, num5, num2, num3 - 6f * num), text, label); } } } [HarmonyPatch(typeof(GraphicsSettings), "Initialize")] internal static class SettingsMenu { private sealed class Live : MonoBehaviour { internal Action Refresh; private float next; private void Update() { if (Time.unscaledTime < next) { return; } next = Time.unscaledTime + 0.5f; try { Refresh?.Invoke(); } catch (Exception ex) { Plugin.Debug("settings row: " + ex.Message); } } } private sealed class Button2 { internal Func<string> Label; internal Action Click; internal Func<bool> Enabled; internal Button2(string label, Action click, Func<bool> enabled) { Label = () => label; Click = click; Enabled = enabled; } internal Button2(Func<string> label, Action click, Func<bool> enabled) { Label = label; Click = click; Enabled = enabled; } } private static GameObject toggleRow; private static GameObject stepperRow; private static GameObject button; private static GameObject warning; private static Transform parent; private static float nextUsage; private static string usage = ""; private static void Postfix(GraphicsSettings __instance) { try { Add(__instance); } catch (Exception ex) { Plugin.Warn("game settings: the cache's rows were not added: " + ex); } } private static void Add(GraphicsSettings tab) { //IL_05a3: Unknown result type (might be due to invalid IL or missing references) //IL_05aa: Expected O, but got Unknown //IL_05bc: Unknown result type (might be due to invalid IL or missing references) //IL_05c1: Unknown result type (might be due to invalid IL or missing references) Traverse obj = Traverse.Create((object)tab); Toggle value = obj.Field("m_vsyncToggle").GetValue<Toggle>(); Button value2 = obj.Field("m_graphicPresetLeft").GetValue<Button>(); Component value3 = obj.Field("m_testResolutionButton").GetValue<Component>(); GameObject value4 = obj.Field("m_qualitySliderPrefab").GetValue<GameObject>(); RectTransform value5 = obj.Field("m_listRoot").GetValue<RectTransform>(); VerticalLayoutGroup value6 = obj.Field("m_verticalLayoutGroup").GetValue<VerticalLayoutGroup>(); toggleRow = (((Object)(object)value != (Object)null) ? ((Component)value).gameObject : null); object obj2; if (!((Object)(object)value2 != (Object)null)) { obj2 = null; } else { Transform obj3 = ((Component)value2).transform.parent; if (obj3 == null) { obj2 = null; } else { Transform obj4 = obj3.parent; obj2 = ((obj4 != null) ? ((Component)obj4).gameObject : null); } } stepperRow = (GameObject)obj2; button = (((Object)(object)value3 != (Object)null) ? value3.gameObject : null); object obj5; if (!((Object)(object)value4 != (Object)null)) { obj5 = null; } else { Transform obj6 = value4.transform.Find("Info/Warning"); obj5 = ((obj6 != null) ? ((Component)obj6).gameObject : null); } warning = (GameObject)obj5; if ((Object)(object)toggleRow == (Object)null || (Object)(object)stepperRow == (Object)null || (Object)(object)stepperRow.transform.Find("Stepper/Value/Text") == (Object)null || (Object)(object)button == (Object)null || (Object)(object)value5 == (Object)null) { Plugin.Warn("game settings: the graphics tab is not as expected (the game changed?): the cache's rows are not added"); return; } parent = stepperRow.transform.parent; Heading(Plugin.T("Кэш мира - WorldCache", "World cache - WorldCache")); Toggle(Plugin.T("Кэш мира", "World cache"), Plugin.T("Сгенерированные места хранятся на диске и при следующем визите читаются, а не считаются заново: быстрее загрузка ценой места. Сохранение мира не меняется; кэш можно удалить в любой момент.", "Generated places are kept on the disk and read on the next visit instead of being generated again. The world's save does not change; the cache can be deleted at any time."), () => Plugin.Enabled.Value, delegate(bool v) { Plugin.Enabled.Value = v; }); Toggle(Plugin.T("Спрашивать при первом входе", "Ask on the first entry"), Plugin.T("Вход в мир без кэша: предупреждение на экране загрузки и выбор - «Ждать» (подготовить ближайшие места сейчас) или «Играть» (кэшировать по ходу игры и в фоне).", "Entering a world without a cache: a note on the loading screen and a choice - Wait (prepare the nearest places now) or Play (cache as you go and in the background)."), () => Plugin.AskOnFirstEntry.Value, delegate(bool v) { Plugin.AskOnFirstEntry.Value = v; }); Stepper(Plugin.T("Лимит места", "Disk limit"), Plugin.T("Сколько места занимают кэши всех миров вместе. Сверх - удаляется давно не нужное: сначала другие миры, потом старые места этого.", "How much the caches of all worlds take together. Beyond it, what was unused the longest goes: other worlds first, then old places of this one."), Plugin.Limits.Length, () => Nearest(Plugin.Limits, Plugin.LimitMegabytes.Value), delegate(int i) { Plugin.LimitMegabytes.Value = Plugin.Limits[i]; }, (int i) => Size(Plugin.Limits[i])); Field(Plugin.T("Занято", "Used"), Plugin.T("Сколько сейчас занимают кэши всех миров (в скобках - этот мир).", "What the caches of all worlds take now (this world in brackets)."), UsageShort); Buttons(Plugin.T("Очистить кэш", "Clear the cache"), Plugin.T("Удаляет кэш. Из сохранения ничего не теряется: места просто будут сгенерированы заново.", "Deletes the cache. Nothing of the save is lost: places are simply generated again."), new Button2(Plugin.T("Этот мир", "This world"), delegate { Plugin.ClearCache(allWorlds: false, Report); }, () => Stages.Open), new Button2(Plugin.T("Все миры", "All worlds"), delegate { Plugin.ClearCache(allWorlds: true, Report); }, null)); Stepper(Plugin.T("Подготовка: режим", "Preparing: mode"), Plugin.T("«Умно» - вокруг вас, базы и порталы, открытое на карте, алтари и найденные места, потом остальное по расстоянию. «Вокруг меня» - кольцами от вас. «Весь мир» - до лимита места.", "Smart: around you, bases and portals, what the map shows explored, altars and found places, then the rest by distance. Around me: rings from you. Whole world: up to the disk limit."), 3, () => Math.Min(2, (int)Plugin.PrepareModeSetting.Value), delegate(int i) { Plugin.PrepareModeSetting.Value = (PrepareMode)i; }, (int i) => Prepare.ModeName((PrepareMode)i)); Stepper(Plugin.T("Подготовка: объём", "Preparing: volume"), Plugin.T("Сколько места на диске занять подготовкой. «Своё» - значение CustomMB в настройках мода.", "How much disk space the preparation may take. Custom: CustomMB in the mod's configuration."), Plugin.Volumes.Length + 1, () => (Plugin.VolumeMegabytes.Value > 0) ? Nearest(Plugin.Volumes, Plugin.VolumeMegabytes.Value) : Plugin.Volumes.Length, delegate(int i) { Plugin.VolumeMegabytes.Value = ((i < Plugin.Volumes.Length) ? Plugin.Volumes[i] : 0); }, (int i) => (i >= Plugin.Volumes.Length) ? (Plugin.T("Своё ", "Custom ") + Size(Math.Max(100, Plugin.CustomMegabytes.Value))) : Size(Plugin.Volumes[i])); Buttons(Plugin.T("Подготовить мир", "Prepare the world"), Plugin.T("Заранее генерирует данные мест (без объектов мира) в фоне малыми порциями; пауза при низком FPS, в бою и при загрузке. Вверху экрана - полоска. Продолжает при следующем входе, пока не готово или не остановлено.", "Generates the places' data ahead (no objects of the world) in the background in small portions; pauses on low FPS, in combat and while loading. A bar at the top of the screen. Carries on at the next visit until done or stopped."), new Button2(() => (!Prepare.Running) ? Plugin.T("Подготовить", "Prepare") : Plugin.T("Остановить", "Stop"), delegate { if (Prepare.Running) { Prepare.Stop(byUser: true); } else { Prepare.Start(Plugin.PrepareModeSetting.Value, Plugin.VolumeChoice() << 20); } }, () => Stages.Open && (Object)(object)Player.m_localPlayer != (Object)null)); Field(Plugin.T("Ход подготовки", "Progress"), Plugin.T("Сколько подготовлено из выбранного объёма и что сейчас готовится.", "How much of the chosen volume is prepared and what is being prepared now."), ProgressShort); float num = 0f; for (int num2 = 0; num2 < ((Transform)value5).childCount; num2++) { RectTransform val = (RectTransform)((Transform)value5).GetChild(num2); if (((Component)val).gameObject.activeSelf) { float num3 = num; Rect rect = val.rect;