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 LCBridgeOverlay v1.3.0
LCBridgeOverlay.dll
Decompiled 12 hours 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.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using DunGen.Graph; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("LCBridgeOverlay")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.3.0.0")] [assembly: AssemblyInformationalVersion("1.3.0+e471728a4a7d97b5c72ce26f5d901ac7f673b150")] [assembly: AssemblyProduct("LCBridgeOverlay")] [assembly: AssemblyTitle("LCBridgeOverlay")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.3.0.0")] [module: UnverifiableCode] [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 LCBridgeOverlay { public static class BcmerEvents { public struct EventInfo { public string Name; public string ColorHex; } private static Type _emType; private static FieldInfo _curEventsField; private static bool _searched; private static readonly Dictionary<Type, MethodInfo> _nameCache = new Dictionary<Type, MethodInfo>(); private static readonly Dictionary<Type, Func<object, string>> _typeGetterCache = new Dictionary<Type, Func<object, string>>(); private static string ColorFor(string typeName) { string text = (typeName ?? "").ToLowerInvariant(); if (text.Contains("verygood") || text.Contains("great")) { return "#00FF00"; } if (text.Contains("good")) { return "#008000"; } if (text.Contains("verybad")) { return "#8B0000"; } if (text.Contains("bad")) { return "#FF0000"; } if (text.Contains("black") || text.Contains("deadly") || text.Contains("impossible") || text.Contains("death")) { return "#000000"; } return "#FFFFFF"; } public static List<EventInfo> GetEvents() { List<EventInfo> list = new List<EventInfo>(); try { EnsureSearched(); if (_curEventsField == null) { return list; } if (!(_curEventsField.GetValue(null) is IEnumerable enumerable)) { return list; } foreach (object item in enumerable) { if (item == null) { continue; } string text = GetName(item); if (!string.IsNullOrEmpty(text)) { if (ConfigSettings.RussianActive) { text = EventTranslate.ToRu(text); } list.Add(new EventInfo { Name = text, ColorHex = ColorFor(GetEventTypeName(item)) }); } } } catch { } return list; } public static bool BcmePresent() { try { foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos) { if (pluginInfo.Key.IndexOf("BrutalCompany", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } PluginInfo value = pluginInfo.Value; object obj; if (value == null) { obj = null; } else { BepInPlugin metadata = value.Metadata; obj = ((metadata != null) ? metadata.Name : null); } if (obj == null) { obj = ""; } if (((string)obj).IndexOf("BrutalCompany", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } } catch { } return false; } private static void EnsureSearched() { if (_searched) { return; } _searched = true; if (!BcmePresent()) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"BCME не найден в Chainloader.PluginInfos — плашка ивентов возьмёт данные моста."); } return; } _emType = FindTypeByFullName("BrutalCompanyMinus.Minus.EventManager") ?? FindTypeFuzzy("BrutalCompany", "EventManager"); if (_emType != null) { _curEventsField = _emType.GetField("currentEvents", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)("[reflection] BCMER EventManager=" + _emType.FullName + ", currentEvents=" + ((_curEventsField != null) ? "OK" : "НЕ НАЙДЕНО"))); } } else { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogInfo((object)"[reflection] BCMER не найден (не установлен?) — плашка ивентов возьмёт данные моста."); } } } private static string GetName(object ev) { Type type = ev.GetType(); if (!_nameCache.TryGetValue(type, out var value)) { value = type.GetMethod("Name", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); _nameCache[type] = value; } if (value != null) { try { return value.Invoke(ev, null) as string; } catch { } } return ev.ToString(); } private static string GetEventTypeName(object ev) { Type type = ev.GetType(); if (!_typeGetterCache.TryGetValue(type, out var value)) { value = BuildTypeGetter(type); _typeGetterCache[type] = value; } try { return value?.Invoke(ev); } catch { return null; } } private static Func<object, string> BuildTypeGetter(Type t) { string[] array = new string[6] { "Type", "EventType", "type", "eventType", "Rarity", "rarity" }; string[] array2 = array; foreach (string name in array2) { MethodInfo m = t.GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (m != null && (m.ReturnType.IsEnum || m.ReturnType == typeof(string))) { return (object o) => m.Invoke(o, null)?.ToString(); } } array2 = array; foreach (string name2 in array2) { PropertyInfo p = t.GetProperty(name2, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (p != null && p.CanRead && (p.PropertyType.IsEnum || p.PropertyType == typeof(string))) { return (object o) => p.GetValue(o)?.ToString(); } } array2 = array; foreach (string name3 in array2) { FieldInfo f = t.GetField(name3, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (f != null && (f.FieldType.IsEnum || f.FieldType == typeof(string))) { return (object o) => f.GetValue(o)?.ToString(); } } return null; } private static Type FindTypeByFullName(string fullName) { try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type type = null; try { type = assembly.GetType(fullName, throwOnError: false); } catch { } if (type != null) { return type; } } } catch { } return null; } private static Type FindTypeFuzzy(string asmNameContains, string typeName) { try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { if ((assembly.GetName().Name ?? "").IndexOf(asmNameContains, StringComparison.OrdinalIgnoreCase) < 0) { continue; } Type[] source; try { source = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { source = ex.Types.Where((Type x) => x != null).ToArray(); } Type type = source.FirstOrDefault((Type x) => string.Equals(x.Name, typeName, StringComparison.OrdinalIgnoreCase)); if (type != null) { return type; } } } catch { } return null; } } public static class BridgeServer { private static TcpListener _listener; private static Thread _acceptThread; private static volatile bool _running; private static readonly List<TcpClient> _clients = new List<TcpClient>(); private static readonly object _lock = new object(); private static volatile string _lastPayload; public static void Start(int port) { if (!_running) { _running = true; _listener = new TcpListener(IPAddress.Loopback, port); _listener.Start(); _acceptThread = new Thread(AcceptLoop) { IsBackground = true, Name = "LCBridge-Accept" }; _acceptThread.Start(); } } public static void Stop() { _running = false; try { _listener?.Stop(); } catch { } lock (_lock) { foreach (TcpClient client in _clients) { try { client.Close(); } catch { } } _clients.Clear(); } } private static void AcceptLoop() { while (_running) { try { TcpClient tcpClient = _listener.AcceptTcpClient(); if (Handshake(tcpClient)) { string lastPayload = _lastPayload; if (lastPayload != null) { try { byte[] array = EncodeTextFrame(lastPayload); tcpClient.GetStream().Write(array, 0, array.Length); } catch { } } lock (_lock) { _clients.Add(tcpClient); } ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"Оверлей подключился к мосту."); } } else { try { tcpClient.Close(); } catch { } } } catch { if (!_running) { break; } } } } private static bool Handshake(TcpClient client) { try { NetworkStream stream = client.GetStream(); byte[] array = new byte[4096]; int num = stream.Read(array, 0, array.Length); if (num <= 0) { return false; } string text = Encoding.UTF8.GetString(array, 0, num); string text2 = null; string[] array2 = text.Split(new string[1] { "\r\n" }, StringSplitOptions.None); foreach (string text3 in array2) { if (text3.StartsWith("Sec-WebSocket-Key:", StringComparison.OrdinalIgnoreCase)) { text2 = text3.Substring("Sec-WebSocket-Key:".Length).Trim(); break; } } if (text2 == null) { return false; } string text4; using (SHA1 sHA = SHA1.Create()) { text4 = Convert.ToBase64String(sHA.ComputeHash(Encoding.UTF8.GetBytes(text2 + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))); } string s = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: " + text4 + "\r\n\r\n"; byte[] bytes = Encoding.UTF8.GetBytes(s); stream.Write(bytes, 0, bytes.Length); return true; } catch { return false; } } public static void Broadcast(string text) { _lastPayload = text; byte[] array = EncodeTextFrame(text); List<TcpClient> list = null; lock (_lock) { foreach (TcpClient client in _clients) { try { if (!client.Connected) { (list ?? (list = new List<TcpClient>())).Add(client); } else { client.GetStream().Write(array, 0, array.Length); } } catch { (list ?? (list = new List<TcpClient>())).Add(client); } } if (list == null) { return; } foreach (TcpClient item in list) { _clients.Remove(item); try { item.Close(); } catch { } } } } private static byte[] EncodeTextFrame(string text) { byte[] bytes = Encoding.UTF8.GetBytes(text); int num = bytes.Length; byte[] array; if (num <= 125) { array = new byte[2] { 0, (byte)num }; } else if (num <= 65535) { array = new byte[4] { 0, 126, (byte)((num >> 8) & 0xFF), (byte)(num & 0xFF) }; } else { array = new byte[10] { 0, 127, 0, 0, 0, 0, 0, 0, 0, 0 }; for (int i = 0; i < 8; i++) { array[9 - i] = (byte)((num >> 8 * i) & 0xFF); } } array[0] = 129; byte[] array2 = new byte[array.Length + bytes.Length]; Buffer.BlockCopy(array, 0, array2, 0, array.Length); Buffer.BlockCopy(bytes, 0, array2, array.Length, bytes.Length); return array2; } } public class BridgeTicker : MonoBehaviour { private float _timer; private const float Interval = 1f; private string _lastPayload; private int _lastMobCount = -1; private void Update() { _timer += Time.deltaTime; if (!(_timer < 1f)) { _timer = 0f; DataParser.Heartbeat = Time.unscaledTime; GameState.TickStats(); RunStats.Tick(); string text = BuildJson(); if (text != _lastPayload) { _lastPayload = text; BridgeServer.Broadcast(text); DataParser.PushLocal(text); } } } private string BuildJson() { (int alive, int total) crew = GameState.GetCrew(); int item = crew.alive; int item2 = crew.total; int deaths = GameState.GetDeaths(); int localHealth = GameState.GetLocalHealth(); string moonName = GameState.GetMoonName(); string weatherTweaksWeather = GameState.GetWeatherTweaksWeather(); string s = ((!string.IsNullOrEmpty(weatherTweaksWeather)) ? weatherTweaksWeather : GameState.GetVanillaWeather()); string brutalEvent = GameState.GetBrutalEvent(); (List<string> outside, List<string> inside) monsters = GameState.GetMonsters(); List<string> item3 = monsters.outside; List<string> item4 = monsters.inside; List<string> traps = GameState.GetTraps(); bool onMoon = GameState.GetOnMoon(); bool loading = GameState.GetLoading(); bool inGame = GameState.GetInGame(); int resetToken = GameState.GetResetToken(); int levelScrap = GameState.GetLevelScrap(); (int quota, int fulfilled) quotaProgress = GameState.GetQuotaProgress(); int item5 = quotaProgress.quota; int item6 = quotaProgress.fulfilled; int shipScrapSafe = GameState.GetShipScrapSafe(); int quotaIndexSafe = GameState.GetQuotaIndexSafe(); int dayCount = GameState.GetDayCount(); int daysLeft = GameState.GetDaysLeft(); string interior = GameState.GetInterior(); (int hives, int inside, int outside) lootBreakdown = GameState.GetLootBreakdown(); int item7 = lootBreakdown.hives; int item8 = lootBreakdown.inside; int item9 = lootBreakdown.outside; bool oldBird = GameState.GetOldBird(); bool onShip = GameState.GetOnShip(); string topKiller = GameState.GetTopKiller(); string topMonster = GameState.GetTopMonster(); string deadliestEvent = GameState.GetDeadliestEvent(); int num = item3.Count + item4.Count; if (num != _lastMobCount) { _lastMobCount = num; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)string.Format("[monsters] улица={0} ({1}) | комплекс={2} ({3})", item3.Count, string.Join(",", item3), item4.Count, string.Join(",", item4))); } } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append('{'); stringBuilder.Append("\"type\":\"bridge\","); stringBuilder.Append("\"deaths\":").Append(deaths).Append(','); stringBuilder.Append("\"alive\":").Append(item).Append(','); stringBuilder.Append("\"total\":").Append(item2).Append(','); stringBuilder.Append("\"health\":").Append(localHealth).Append(','); stringBuilder.Append("\"moonName\":").Append(JsonStr(moonName)).Append(','); stringBuilder.Append("\"weatherFull\":").Append(JsonStr(s)).Append(','); stringBuilder.Append("\"brutalEvent\":").Append(JsonStr(brutalEvent ?? "")).Append(','); stringBuilder.Append("\"onMoon\":").Append(onMoon ? "true" : "false").Append(','); stringBuilder.Append("\"loading\":").Append(loading ? "true" : "false").Append(','); stringBuilder.Append("\"inGame\":").Append(inGame ? "true" : "false").Append(','); stringBuilder.Append("\"resetToken\":").Append(resetToken).Append(','); stringBuilder.Append("\"levelScrap\":").Append(levelScrap).Append(','); stringBuilder.Append("\"quotaValue\":").Append(item5).Append(','); stringBuilder.Append("\"quotaFulfilled\":").Append(item6).Append(','); stringBuilder.Append("\"shipLoot\":").Append(shipScrapSafe).Append(','); stringBuilder.Append("\"quotaIndex\":").Append(quotaIndexSafe).Append(','); stringBuilder.Append("\"dayCount\":").Append(dayCount).Append(','); stringBuilder.Append("\"daysLeft\":").Append(daysLeft).Append(','); stringBuilder.Append("\"interiorType\":").Append(JsonStr(interior ?? "")).Append(','); stringBuilder.Append("\"beehiveCount\":").Append(item7).Append(','); stringBuilder.Append("\"itemsInside\":").Append(item8).Append(','); stringBuilder.Append("\"itemsOutside\":").Append(item9).Append(','); stringBuilder.Append("\"hasOldBird\":").Append(oldBird ? "true" : "false").Append(','); stringBuilder.Append("\"onShip\":").Append(onShip ? "true" : "false").Append(','); stringBuilder.Append("\"topKiller\":").Append(JsonStr(topKiller ?? "")).Append(','); stringBuilder.Append("\"topMonster\":").Append(JsonStr(topMonster ?? "")).Append(','); stringBuilder.Append("\"deadliestEvent\":").Append(JsonStr(deadliestEvent ?? "")).Append(','); stringBuilder.Append("\"monstersOutside\":").Append(JsonArr(item3)).Append(','); stringBuilder.Append("\"monstersInside\":").Append(JsonArr(item4)).Append(','); stringBuilder.Append("\"traps\":").Append(JsonArr(traps)).Append(','); stringBuilder.Append("\"run\":").Append(RunStats.ToJson()); stringBuilder.Append('}'); return stringBuilder.ToString(); } private static string JsonArr(List<string> items) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append('['); for (int i = 0; i < items.Count; i++) { if (i > 0) { stringBuilder.Append(','); } stringBuilder.Append(JsonStr(items[i])); } stringBuilder.Append(']'); return stringBuilder.ToString(); } private static string JsonStr(string s) { if (s == null) { return "\"\""; } StringBuilder stringBuilder = new StringBuilder(s.Length + 2); stringBuilder.Append('"'); foreach (char c in s) { switch (c) { case '"': stringBuilder.Append("\\\""); continue; case '\\': stringBuilder.Append("\\\\"); continue; case '\n': stringBuilder.Append("\\n"); continue; case '\r': stringBuilder.Append("\\r"); continue; case '\t': stringBuilder.Append("\\t"); continue; } if (c < ' ') { StringBuilder stringBuilder2 = stringBuilder.Append("\\u"); int num = c; stringBuilder2.Append(num.ToString("x4", CultureInfo.InvariantCulture)); } else { stringBuilder.Append(c); } } stringBuilder.Append('"'); return stringBuilder.ToString(); } } internal static class EnemyResolver { private static readonly string[] _turretMarkers = new string[3] { "ToilHeadController", "MantiToilController", "TurretHeadController" }; private static readonly string[] _slayerMarkers = new string[3] { "ToilSlayerController", "MantiSlayerController", "SlayerController" }; public static string Resolve(object enemyAiObj) { string baseName = GetBaseName(enemyAiObj); if (string.IsNullOrEmpty(baseName)) { return null; } try { switch (TurretKind(enemyAiObj)) { case 2: return baseName + "+Turret+Slayer"; case 1: return baseName + "+Turret"; } } catch { } return baseName; } private static string GetBaseName(object enemyAiObj) { try { EnemyAI val = (EnemyAI)((enemyAiObj is EnemyAI) ? enemyAiObj : null); if ((Object)(object)val != (Object)null && (Object)(object)val.enemyType != (Object)null && !string.IsNullOrEmpty(val.enemyType.enemyName)) { return val.enemyType.enemyName; } } catch { } return null; } private static int TurretKind(object enemyAiObj) { MonoBehaviour val = (MonoBehaviour)((enemyAiObj is MonoBehaviour) ? enemyAiObj : null); if ((Object)(object)val == (Object)null) { return 0; } Component[] componentsInChildren = ((Component)val).GetComponentsInChildren<Component>(true); if (componentsInChildren == null) { return 0; } int num = 0; Component[] array = componentsInChildren; foreach (Component val2 in array) { if ((Object)(object)val2 == (Object)null) { continue; } string name = ((object)val2).GetType().Name; if (string.IsNullOrEmpty(name)) { continue; } string[] slayerMarkers = _slayerMarkers; foreach (string value in slayerMarkers) { if (name.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) { return 2; } } if (num != 0) { continue; } slayerMarkers = _turretMarkers; foreach (string value2 in slayerMarkers) { if (name.IndexOf(value2, StringComparison.OrdinalIgnoreCase) >= 0) { num = 1; break; } } } return num; } } public static class GameState { private static int _deaths = 0; private static readonly HashSet<int> _deadThisRound = new HashSet<int>(); private static int _resetToken = 0; private static readonly Dictionary<string, int> _killerCounts = new Dictionary<string, int>(); private static readonly Dictionary<string, int> _eventDeaths = new Dictionary<string, int>(); private static readonly Dictionary<string, int> _monsterSeen = new Dictionary<string, int>(); private static Type _mgSpawnerType; private static bool _mgSearched; private static FieldInfo _mgOwnedField; private static PropertyInfo _mgInstanceProp; private static FieldInfo _mgInstanceField; private static int _landedScrap; private static bool _wasLanded; private static bool _scrapLocked; private static Type _emType; private static FieldInfo _curEventsField; private static bool _bcSearched; private static readonly Dictionary<Type, MethodInfo> _nameMethodCache = new Dictionary<Type, MethodInfo>(); private static Type _wtVarsType; private static MethodInfo _wtGetCurrent; private static bool _wtSearched; private static float _lastLootLog; private static int _lastInside = -1; private static int _lastOutside = -1; public static int GetResetToken() { return _resetToken; } public static void RegisterDeath(PlayerControllerB p, string killer = null) { bool flag = false; try { int item = (int)p.playerClientId; if (_deadThisRound.Add(item)) { _deaths++; flag = true; } } catch { _deaths++; flag = true; } if (!flag) { return; } try { if (!string.IsNullOrEmpty(killer)) { _killerCounts.TryGetValue(killer, out var value); _killerCounts[killer] = value + 1; } string brutalEvent = GetBrutalEvent(); if (!string.IsNullOrEmpty(brutalEvent) && brutalEvent != "—") { _eventDeaths.TryGetValue(brutalEvent, out var value2); _eventDeaths[brutalEvent] = value2 + 1; } RunStats.OnDeath(killer); } catch { } } public static void OnNewRound() { _deadThisRound.Clear(); } public static void ResetDeaths() { _deaths = 0; _deadThisRound.Clear(); _resetToken++; _killerCounts.Clear(); _eventDeaths.Clear(); _monsterSeen.Clear(); RunStats.ResetRun(); } public static int GetDeaths() { return _deaths; } public static void TickStats() { try { HashSet<string> hashSet = new HashSet<string>(); foreach (EnemyAI allLiveEnemy in GetAllLiveEnemies()) { if (!((Object)(object)allLiveEnemy == (Object)null) && !allLiveEnemy.isEnemyDead) { string text = EnemyResolver.Resolve(allLiveEnemy); if (text != null) { hashSet.Add(text); } } } foreach (string item in hashSet) { _monsterSeen.TryGetValue(item, out var value); _monsterSeen[item] = value + 1; } } catch { } } private static string TopOf(Dictionary<string, int> dict) { string result = null; int num = 0; foreach (KeyValuePair<string, int> item in dict) { if (item.Value > num) { num = item.Value; result = item.Key; } } return result; } public static string GetTopKiller() { return TopOf(_killerCounts); } public static string GetTopMonster() { return TopOf(_monsterSeen); } public static string GetDeadliestEvent() { return TopOf(_eventDeaths); } public static (int alive, int total) GetCrew() { try { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { return (alive: 0, total: 0); } int num = 0; int num2 = 0; PlayerControllerB[] allPlayerScripts = instance.allPlayerScripts; foreach (PlayerControllerB val in allPlayerScripts) { if (!((Object)(object)val == (Object)null) && (val.isPlayerControlled || val.isPlayerDead)) { num++; if (!val.isPlayerDead) { num2++; } } } if (num == 0) { num = 1; } return (alive: num2, total: num); } catch { return (alive: 0, total: 0); } } public static int GetLocalHealth() { try { PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)val == (Object)null) { return 0; } return val.health; } catch { return 0; } } private static void EnsureMonstersGordion() { if (_mgSearched) { return; } _mgSearched = true; _mgSpawnerType = FindTypeByFullName("MonstersGordion.CompanyMonsterSpawner") ?? FindTypeFuzzy("MonstersGordion", new string[1] { "CompanyMonsterSpawner" }); if (_mgSpawnerType != null) { _mgOwnedField = _mgSpawnerType.GetField("_ownedEnemies", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? _mgSpawnerType.GetField("ownedEnemies", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _mgInstanceProp = _mgSpawnerType.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public); _mgInstanceField = _mgSpawnerType.GetField("Instance", BindingFlags.Static | BindingFlags.Public); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("[reflection] MonstersGordion=" + _mgSpawnerType.FullName + ", _ownedEnemies=" + ((_mgOwnedField != null) ? "OK" : "НЕ НАЙДЕНО"))); } } else { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)"[reflection] MonstersGordion не найден (не установлен?)"); } } } public static List<EnemyAI> GetGordionEnemies() { List<EnemyAI> list = new List<EnemyAI>(); try { EnsureMonstersGordion(); if (_mgSpawnerType == null || _mgOwnedField == null) { return list; } object obj = _mgInstanceProp?.GetValue(null) ?? _mgInstanceField?.GetValue(null) ?? GetSingletonInstance(_mgSpawnerType); if (obj == null) { return list; } if (!(_mgOwnedField.GetValue(obj) is IEnumerable enumerable)) { return list; } foreach (object item in enumerable) { EnemyAI val = (EnemyAI)((item is EnemyAI) ? item : null); if (val != null && (Object)(object)val != (Object)null && !val.isEnemyDead) { list.Add(val); } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("GetGordionEnemies fail: " + ex.Message)); } } return list; } public static List<EnemyAI> GetAllLiveEnemies() { List<EnemyAI> list = new List<EnemyAI>(); HashSet<int> hashSet = new HashSet<int>(); try { RoundManager instance = RoundManager.Instance; if ((Object)(object)instance != (Object)null && instance.SpawnedEnemies != null) { foreach (EnemyAI spawnedEnemy in instance.SpawnedEnemies) { if ((Object)(object)spawnedEnemy != (Object)null && !spawnedEnemy.isEnemyDead && hashSet.Add(((Object)spawnedEnemy).GetInstanceID())) { list.Add(spawnedEnemy); } } } } catch { } foreach (EnemyAI gordionEnemy in GetGordionEnemies()) { if ((Object)(object)gordionEnemy != (Object)null && !gordionEnemy.isEnemyDead && hashSet.Add(((Object)gordionEnemy).GetInstanceID())) { list.Add(gordionEnemy); } } return list; } public static (List<string> outside, List<string> inside) GetMonsters() { List<string> list = new List<string>(); List<string> list2 = new List<string>(); try { Dictionary<string, int> dictionary = new Dictionary<string, int>(); Dictionary<string, int> dictionary2 = new Dictionary<string, int>(); foreach (EnemyAI allLiveEnemy in GetAllLiveEnemies()) { if ((Object)(object)allLiveEnemy == (Object)null || allLiveEnemy.isEnemyDead) { continue; } string key = "Unknown"; try { string text = EnemyResolver.Resolve(allLiveEnemy); if (!string.IsNullOrEmpty(text)) { key = text; } } catch { } Dictionary<string, int> obj2 = (allLiveEnemy.isOutside ? dictionary : dictionary2); obj2.TryGetValue(key, out var value); obj2[key] = value + 1; } foreach (KeyValuePair<string, int> item in dictionary) { list.Add((item.Value > 1) ? $"{item.Key} x{item.Value}" : item.Key); } foreach (KeyValuePair<string, int> item2 in dictionary2) { list2.Add((item2.Value > 1) ? $"{item2.Key} x{item2.Value}" : item2.Key); } list.Sort(StringComparer.Ordinal); list2.Sort(StringComparer.Ordinal); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("GetMonsters fail: " + ex.Message)); } } return (outside: list, inside: list2); } public static List<string> GetTraps() { List<string> list = new List<string>(); try { Dictionary<string, int> counts = new Dictionary<string, int>(); CountType<Turret>("Turret"); CountType<Landmine>("Landmine"); CountType<SpikeRoofTrap>("Spike Trap"); foreach (KeyValuePair<string, int> item in counts) { list.Add((item.Value > 1) ? $"{item.Key} x{item.Value}" : item.Key); } void CountType<T>(string label) where T : Object { try { T[] array = Object.FindObjectsOfType<T>(); if (array != null && array.Length != 0) { counts.TryGetValue(label, out var value); counts[label] = value + array.Length; } } catch { } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("GetTraps fail: " + ex.Message)); } } return list; } public static int GetLevelScrap() { try { RoundManager instance = RoundManager.Instance; if (GetOnMoon()) { if (!_wasLanded) { _wasLanded = true; _scrapLocked = false; _landedScrap = 0; } if (!_scrapLocked && (Object)(object)instance != (Object)null) { int num = (int)instance.totalScrapValueInLevel; if (num > 0) { _landedScrap = num; _scrapLocked = true; } } } else { _wasLanded = false; _scrapLocked = false; _landedScrap = 0; } return _landedScrap; } catch { return _landedScrap; } } private static string GetEventName(object ev) { Type type = ev.GetType(); if (!_nameMethodCache.TryGetValue(type, out var value)) { value = type.GetMethod("Name", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); _nameMethodCache[type] = value; } if (value != null) { try { return value.Invoke(ev, null) as string; } catch { } } return ExtractName(ev); } public static string GetBrutalEvent() { try { if (!_bcSearched) { _bcSearched = true; _emType = FindTypeByFullName("BrutalCompanyMinus.Minus.EventManager") ?? FindTypeFuzzy("BrutalCompany", new string[1] { "EventManager" }); if (_emType != null) { _curEventsField = _emType.GetField("currentEvents", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("[reflection] BCMER EventManager=" + _emType.FullName + ", currentEvents field=" + ((_curEventsField != null) ? "OK" : "НЕ НАЙДЕНО"))); } } else { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)"[reflection] BCMER EventManager не найден (мод выключен?)"); } } } if (_curEventsField == null) { return null; } if (!(_curEventsField.GetValue(null) is IEnumerable enumerable)) { return null; } List<string> list = new List<string>(); foreach (object item in enumerable) { if (item != null) { string eventName = GetEventName(item); if (!string.IsNullOrEmpty(eventName)) { list.Add(eventName); } } } if (list.Count == 0) { return null; } return string.Join(", ", list); } catch (Exception ex) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogDebug((object)("GetBrutalEvent fail: " + ex.Message)); } return null; } } public static string GetWeatherTweaksWeather() { try { if (!_wtSearched) { _wtSearched = true; _wtVarsType = FindTypeByFullName("WeatherTweaks.Variables") ?? FindTypeFuzzy("WeatherTweaks", new string[1] { "Variables" }); if (_wtVarsType != null) { _wtGetCurrent = _wtVarsType.GetMethod("GetCurrentWeather", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("[reflection] WeatherTweaks Variables=" + _wtVarsType.FullName + ", GetCurrentWeather=" + ((_wtGetCurrent != null) ? "OK" : "НЕ НАЙДЕНО"))); } } else { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)"[reflection] WeatherTweaks Variables не найден (мод выключен?)"); } } } if (_wtGetCurrent == null) { return null; } object obj = _wtGetCurrent.Invoke(null, null); if (obj == null) { return null; } string text = ExtractName(obj); return string.IsNullOrEmpty(text) ? null : text; } catch (Exception ex) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogDebug((object)("GetWeatherTweaks fail: " + ex.Message)); } return null; } } public static string GetVanillaWeather() { try { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.currentLevel == (Object)null) { return "None"; } return ((object)Unsafe.As<LevelWeatherType, LevelWeatherType>(ref instance.currentLevel.currentWeather)/*cast due to .constrained prefix*/).ToString(); } catch { return "None"; } } public static string GetMoonName() { try { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.currentLevel == (Object)null) { return "—"; } return instance.currentLevel.PlanetName; } catch { return "—"; } } public static bool GetOnMoon() { try { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.currentLevel == (Object)null) { return false; } return instance.shipHasLanded; } catch { return false; } } public static bool GetLoading() { try { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { return false; } return instance.travellingToNewLevel; } catch { return false; } } public static bool GetInGame() { try { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { return false; } return instance.shipHasLanded || instance.travellingToNewLevel; } catch { return false; } } public static int GetDayCount() { try { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance != (Object)null && instance.gameStats != null) { return instance.gameStats.daysSpent + 1; } TimeOfDay instance2 = TimeOfDay.Instance; if ((Object)(object)instance2 == (Object)null) { return 1; } return (instance2.daysUntilDeadline < 0) ? 1 : (3 - instance2.daysUntilDeadline); } catch { return 1; } } public static int GetQuotaIndexSafe() { try { TimeOfDay instance = TimeOfDay.Instance; if ((Object)(object)instance == (Object)null) { return 1; } return instance.timesFulfilledQuota + 1; } catch { return 1; } } public static int GetShipScrapSafe() { try { int num = 0; GameObject[] array = GameObject.FindGameObjectsWithTag("PhysicsProp"); for (int i = 0; i < array.Length; i++) { GrabbableObject component = array[i].GetComponent<GrabbableObject>(); if (!((Object)(object)component == (Object)null) && !((Object)(object)component.itemProperties == (Object)null) && component.itemProperties.isScrap && (component.isInShipRoom || component.isInElevator)) { num += component.scrapValue; } } return num; } catch { return 0; } } public static List<KeyValuePair<int, int>> GetShipScrapItems() { List<KeyValuePair<int, int>> list = new List<KeyValuePair<int, int>>(); try { GameObject[] array = GameObject.FindGameObjectsWithTag("PhysicsProp"); for (int i = 0; i < array.Length; i++) { GrabbableObject component = array[i].GetComponent<GrabbableObject>(); if (!((Object)(object)component == (Object)null) && !((Object)(object)component.itemProperties == (Object)null) && component.itemProperties.isScrap && (component.isInShipRoom || component.isInElevator)) { list.Add(new KeyValuePair<int, int>(((Object)component).GetInstanceID(), component.scrapValue)); } } } catch { } return list; } public static List<KeyValuePair<int, string>> GetMonsterInstances() { List<KeyValuePair<int, string>> list = new List<KeyValuePair<int, string>>(); try { foreach (EnemyAI allLiveEnemy in GetAllLiveEnemies()) { if (!((Object)(object)allLiveEnemy == (Object)null) && !allLiveEnemy.isEnemyDead) { string text = EnemyResolver.Resolve(allLiveEnemy); if (text != null) { list.Add(new KeyValuePair<int, string>(((Object)allLiveEnemy).GetInstanceID(), text)); } } } } catch { } return list; } public static (List<string> outside, List<string> inside) GetMonstersRaw() { List<string> list = new List<string>(); List<string> list2 = new List<string>(); try { foreach (EnemyAI allLiveEnemy in GetAllLiveEnemies()) { if (!((Object)(object)allLiveEnemy == (Object)null) && !allLiveEnemy.isEnemyDead) { string text = EnemyResolver.Resolve(allLiveEnemy); if (text != null) { (allLiveEnemy.isOutside ? list : list2).Add(text); } } } } catch { } return (outside: list, inside: list2); } public static (int quota, int fulfilled) GetQuotaProgress() { try { TimeOfDay instance = TimeOfDay.Instance; if ((Object)(object)instance == (Object)null) { return (quota: 0, fulfilled: 0); } return (quota: instance.profitQuota, fulfilled: instance.quotaFulfilled); } catch { return (quota: 0, fulfilled: 0); } } public static int GetDaysLeft() { try { TimeOfDay instance = TimeOfDay.Instance; return ((Object)(object)instance != (Object)null) ? instance.daysUntilDeadline : (-1); } catch { return -1; } } public static string GetInterior() { try { if (!GetOnMoon()) { return null; } DungeonFlow val = RoundManager.Instance?.dungeonGenerator?.Generator?.DungeonFlow; if ((Object)(object)val == (Object)null) { return null; } string text = ((Object)val).name ?? ""; if (text.IndexOf("Level1", StringComparison.OrdinalIgnoreCase) >= 0) { return "Facility"; } if (text.IndexOf("Level2", StringComparison.OrdinalIgnoreCase) >= 0) { return "Mansion"; } if (text.IndexOf("Level3", StringComparison.OrdinalIgnoreCase) >= 0) { return "Mineshaft"; } text = text.Replace("DungeonFlow", "").Replace("Flow", "").Replace("flow", "") .Trim(); return string.IsNullOrEmpty(text) ? null : text; } catch { return null; } } public static (int hives, int inside, int outside) GetLootBreakdown() { int num = 0; int num2 = 0; int num3 = 0; try { if (!GetOnMoon()) { return (hives: 0, inside: 0, outside: 0); } GameObject[] array = GameObject.FindGameObjectsWithTag("PhysicsProp"); for (int i = 0; i < array.Length; i++) { GrabbableObject component = array[i].GetComponent<GrabbableObject>(); if (!((Object)(object)component == (Object)null) && !((Object)(object)component.itemProperties == (Object)null) && component.itemProperties.isScrap && !component.isInShipRoom && !component.isInElevator && !component.isHeld && !component.isHeldByEnemy) { if ((component.itemProperties.itemName ?? "").IndexOf("hive", StringComparison.OrdinalIgnoreCase) >= 0) { num++; } else if (component.isInFactory) { num2++; } else { num3++; } } } if (Time.time - _lastLootLog > 5f && (num2 != _lastInside || num3 != _lastOutside)) { _lastLootLog = Time.time; _lastInside = num2; _lastOutside = num3; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)$"[loot] внутри={num2} снаружи={num3} ульи={num}"); } } } catch { } return (hives: num, inside: num2, outside: num3); } public static bool GetOldBird() { try { foreach (EnemyAI allLiveEnemy in GetAllLiveEnemies()) { if (!((Object)(object)allLiveEnemy == (Object)null) && !allLiveEnemy.isEnemyDead) { string text = (((Object)(object)allLiveEnemy.enemyType != (Object)null) ? (allLiveEnemy.enemyType.enemyName ?? "") : ""); if (text.IndexOf("RadMech", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Old Bird", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } } } catch { } return false; } public static bool GetOnShip() { try { PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; return (Object)(object)val != (Object)null && val.isInHangarShipRoom; } catch { return false; } } public static bool GetInsideFactorySafe() { try { PlayerControllerB val = StartOfRound.Instance?.localPlayerController; return (Object)(object)val != (Object)null && val.isInsideFactory; } catch { return false; } } private static Type FindTypeByFullName(string fullName) { try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type type = null; try { type = assembly.GetType(fullName, throwOnError: false); } catch { } if (type != null) { return type; } } } catch { } return null; } private static Type FindTypeFuzzy(string asmNameContains, string[] typeNameCandidates) { try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { string text = assembly.GetName().Name ?? ""; if (text.IndexOf(asmNameContains, StringComparison.OrdinalIgnoreCase) < 0) { continue; } Type[] source; try { source = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { source = ex.Types.Where((Type t) => t != null).ToArray(); } foreach (string cand in typeNameCandidates) { Type type = source.FirstOrDefault((Type t) => string.Equals(t.Name, cand, StringComparison.OrdinalIgnoreCase)); if (type != null) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("[reflection] нашёл тип " + type.FullName + " в " + text)); } return type; } } } } catch (Exception ex2) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogDebug((object)("FindTypeFuzzy fail: " + ex2.Message)); } } return null; } private static object ReadStaticMember(Type t, string name) { try { FieldInfo field = t.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { if (field.IsStatic) { return field.GetValue(null); } object singletonInstance = GetSingletonInstance(t); if (singletonInstance != null) { return field.GetValue(singletonInstance); } } PropertyInfo property = t.GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanRead) { MethodInfo? getMethod = property.GetGetMethod(nonPublic: true); if ((object)getMethod != null && getMethod.IsStatic) { return property.GetValue(null); } object singletonInstance2 = GetSingletonInstance(t); if (singletonInstance2 != null) { return property.GetValue(singletonInstance2); } } } catch { } return null; } private static object GetSingletonInstance(Type t) { try { PropertyInfo propertyInfo = t.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public) ?? t.GetProperty("instance", BindingFlags.Static | BindingFlags.Public); if (propertyInfo != null) { return propertyInfo.GetValue(null); } FieldInfo fieldInfo = t.GetField("Instance", BindingFlags.Static | BindingFlags.Public) ?? t.GetField("instance", BindingFlags.Static | BindingFlags.Public); if (fieldInfo != null) { return fieldInfo.GetValue(null); } } catch { } return null; } private static string ExtractName(object val) { if (val == null) { return null; } try { if (val is string result) { return result; } if (val.GetType().IsEnum) { return val.ToString(); } Type type = val.GetType(); PropertyInfo propertyInfo = type.GetProperty("Name") ?? type.GetProperty("name"); if (propertyInfo != null) { string text = propertyInfo.GetValue(val) as string; if (!string.IsNullOrEmpty(text)) { return text; } } FieldInfo fieldInfo = type.GetField("Name") ?? type.GetField("name"); if (fieldInfo != null) { string text2 = fieldInfo.GetValue(val) as string; if (!string.IsNullOrEmpty(text2)) { return text2; } } string text3 = val.ToString(); if (!string.IsNullOrEmpty(text3) && text3 != type.FullName && text3 != type.Name) { return text3; } } catch { } return null; } } [HarmonyPatch(typeof(PlayerControllerB))] public static class PlayerControllerB_Patches { [HarmonyPatch("KillPlayer")] [HarmonyPostfix] public static void OnKillPlayer(PlayerControllerB __instance, CauseOfDeath causeOfDeath) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance != (Object)null && __instance.isPlayerDead) { string killer = ResolveKiller(__instance, causeOfDeath); GameState.RegisterDeath(__instance, killer); } } private static string ResolveKiller(PlayerControllerB player, CauseOfDeath cause) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected I4, but got Unknown //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) try { if ((int)cause != 2) { switch (cause - 5) { case 4: return "Drowning"; case 0: return "Suffocation"; case 8: return "Fire"; case 6: return "Shock"; case 3: return "Crushed"; default: { RoundManager instance = RoundManager.Instance; if ((Object)(object)instance == (Object)null || instance.SpawnedEnemies == null) { return "Unknown"; } Vector3 position = ((Component)player).transform.position; float num = 25f; string text = null; foreach (EnemyAI spawnedEnemy in instance.SpawnedEnemies) { if (!((Object)(object)spawnedEnemy == (Object)null) && !spawnedEnemy.isEnemyDead) { float num2 = Vector3.Distance(position, ((Component)spawnedEnemy).transform.position); if (num2 < num) { num = num2; text = EnemyResolver.Resolve(spawnedEnemy); } } } return string.IsNullOrEmpty(text) ? "Unknown" : text; } } } return "Fall"; } catch { return "Unknown"; } } } public static class RunStats { public class QuotaSlice { public int index; public int scrapStart; public int scrapEnd; public int seconds; public int deaths; } private class MoonStat { public int visits; public int profit; public int seconds; } private static readonly List<QuotaSlice> _quotas = new List<QuotaSlice>(); private static QuotaSlice _curQuota; private static int _lastQuotaIndex = -1; private static readonly Dictionary<string, MoonStat> _moons = new Dictionary<string, MoonStat>(); private static string _curMoon; private static int _moonScrapStart; private static readonly Dictionary<string, int> _monsterTime = new Dictionary<string, int>(); private static readonly Dictionary<string, int> _monsterCount = new Dictionary<string, int>(); private static readonly HashSet<int> _seenMonsterIds = new HashSet<int>(); private static int _peakMonsters; private static readonly Dictionary<int, int> _collectedScrap = new Dictionary<int, int>(); private static int _secInside; private static int _secOutside; private static readonly List<string> _timeline = new List<string>(); private static int _lastDayLogged = -1; private static string _lastEventLogged; private static int _runSeconds; public static void ResetRun() { _quotas.Clear(); _curQuota = null; _lastQuotaIndex = -1; _moons.Clear(); _curMoon = null; _moonScrapStart = 0; _monsterTime.Clear(); _peakMonsters = 0; _monsterCount.Clear(); _seenMonsterIds.Clear(); _collectedScrap.Clear(); _secInside = 0; _secOutside = 0; _timeline.Clear(); _lastDayLogged = -1; _lastEventLogged = null; _runSeconds = 0; } public static void OnDeath(string killer) { try { int dayCount = GameState.GetDayCount(); string moonName = GameState.GetMoonName(); string arg = (string.IsNullOrEmpty(killer) ? "?" : killer); _timeline.Add($"{dayCount}|death|{arg}@{moonName}"); if (_curQuota != null) { _curQuota.deaths++; } } catch { } } public static void Tick() { try { if (!GameState.GetInGame()) { return; } _runSeconds++; int dayCount = GameState.GetDayCount(); int quotaIndexSafe = GameState.GetQuotaIndexSafe(); bool onMoon = GameState.GetOnMoon(); string moonName = GameState.GetMoonName(); string brutalEvent = GameState.GetBrutalEvent(); foreach (KeyValuePair<int, int> shipScrapItem in GameState.GetShipScrapItems()) { if (!_collectedScrap.ContainsKey(shipScrapItem.Key)) { _collectedScrap[shipScrapItem.Key] = shipScrapItem.Value; } } int num = 0; foreach (int value in _collectedScrap.Values) { num += value; } if (quotaIndexSafe != _lastQuotaIndex) { if (_curQuota != null) { _curQuota.scrapEnd = num; _quotas.Add(_curQuota); } _curQuota = new QuotaSlice { index = quotaIndexSafe, scrapStart = num, scrapEnd = num, seconds = 0, deaths = 0 }; _lastQuotaIndex = quotaIndexSafe; } if (_curQuota != null) { _curQuota.seconds++; _curQuota.scrapEnd = num; } if (onMoon && !string.IsNullOrEmpty(moonName)) { if (_curMoon != moonName) { _curMoon = moonName; _moonScrapStart = num; if (!_moons.ContainsKey(moonName)) { _moons[moonName] = new MoonStat(); } _moons[moonName].visits++; } MoonStat moonStat = _moons[moonName]; moonStat.seconds++; int num2 = num - _moonScrapStart; if (num2 > 0) { moonStat.profit += num2; _moonScrapStart = num; } } else { _curMoon = null; } List<KeyValuePair<int, string>> monsterInstances = GameState.GetMonsterInstances(); foreach (KeyValuePair<int, string> item in monsterInstances) { Add(_monsterTime, item.Value); if (_seenMonsterIds.Add(item.Key)) { Add(_monsterCount, item.Value); } } int count = monsterInstances.Count; if (count > _peakMonsters) { _peakMonsters = count; } if (onMoon) { if (GameState.GetInsideFactorySafe()) { _secInside++; } else { _secOutside++; } } if (onMoon && !string.IsNullOrEmpty(moonName) && moonName != "—" && dayCount > 0 && dayCount != _lastDayLogged) { _lastDayLogged = dayCount; _timeline.Add($"{dayCount}|day|{moonName}"); } if (!string.IsNullOrEmpty(brutalEvent) && brutalEvent != "—" && brutalEvent != _lastEventLogged) { _lastEventLogged = brutalEvent; _timeline.Add($"{dayCount}|event|{brutalEvent}"); } } catch { } } private static void Add(Dictionary<string, int> d, string k) { if (!string.IsNullOrEmpty(k)) { d.TryGetValue(k, out var value); d[k] = value + 1; } } public static string ToJson() { try { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append('{'); stringBuilder.Append("\"quotas\":["); List<QuotaSlice> list = new List<QuotaSlice>(_quotas); if (_curQuota != null) { list.Add(_curQuota); } for (int i = 0; i < list.Count; i++) { QuotaSlice quotaSlice = list[i]; if (i > 0) { stringBuilder.Append(','); } int value = Math.Max(0, quotaSlice.scrapEnd - quotaSlice.scrapStart); stringBuilder.Append('{').Append("\"i\":").Append(quotaSlice.index) .Append(',') .Append("\"money\":") .Append(value) .Append(',') .Append("\"sec\":") .Append(quotaSlice.seconds) .Append(',') .Append("\"deaths\":") .Append(quotaSlice.deaths) .Append('}'); } stringBuilder.Append("],"); stringBuilder.Append("\"moons\":["); int num = 0; foreach (KeyValuePair<string, MoonStat> item in _moons.OrderByDescending((KeyValuePair<string, MoonStat> x) => x.Value.profit)) { if (num++ > 0) { stringBuilder.Append(','); } stringBuilder.Append('{').Append("\"name\":").Append(JsonStr(item.Key)) .Append(',') .Append("\"visits\":") .Append(item.Value.visits) .Append(',') .Append("\"profit\":") .Append(item.Value.profit) .Append(',') .Append("\"sec\":") .Append(item.Value.seconds) .Append('}'); } stringBuilder.Append("],"); stringBuilder.Append("\"monsters\":["); int num2 = 0; foreach (KeyValuePair<string, int> item2 in _monsterCount.OrderByDescending((KeyValuePair<string, int> x) => x.Value).Take(20)) { if (num2++ > 0) { stringBuilder.Append(','); } _monsterTime.TryGetValue(item2.Key, out var value2); stringBuilder.Append('{').Append("\"name\":").Append(JsonStr(item2.Key)) .Append(',') .Append("\"count\":") .Append(item2.Value) .Append(',') .Append("\"sec\":") .Append(value2) .Append('}'); } stringBuilder.Append("],"); stringBuilder.Append("\"peak\":").Append(_peakMonsters).Append(','); stringBuilder.Append("\"inside\":").Append(_secInside).Append(','); stringBuilder.Append("\"outside\":").Append(_secOutside).Append(','); stringBuilder.Append("\"runSec\":").Append(_runSeconds).Append(','); stringBuilder.Append("\"timeline\":["); for (int num3 = 0; num3 < _timeline.Count && num3 < 120; num3++) { if (num3 > 0) { stringBuilder.Append(','); } stringBuilder.Append(JsonStr(_timeline[num3])); } stringBuilder.Append(']'); stringBuilder.Append('}'); return stringBuilder.ToString(); } catch { return "{}"; } } private static string JsonStr(string s) { if (s == null) { return "\"\""; } StringBuilder stringBuilder = new StringBuilder("\""); foreach (char c in s) { switch (c) { case '"': case '\\': stringBuilder.Append('\\').Append(c); break; case '\n': case '\r': stringBuilder.Append(' '); break; default: stringBuilder.Append(c); break; } } stringBuilder.Append('"'); return stringBuilder.ToString(); } } [HarmonyPatch(typeof(StartOfRound))] public static class StartOfRound_Patches { [HarmonyPatch("ResetShip")] [HarmonyPostfix] public static void OnResetShip() { GameState.ResetDeaths(); } [HarmonyPatch("SetTimeAndPlanetToSavedSettings")] [HarmonyPostfix] public static void OnLoadSavedSettings() { try { StartOfRound instance = StartOfRound.Instance; if (!((Object)(object)instance == (Object)null) && ((instance.gameStats != null && instance.gameStats.daysSpent <= 0) || ((Object)(object)TimeOfDay.Instance != (Object)null && TimeOfDay.Instance.timesFulfilledQuota <= 0 && TimeOfDay.Instance.daysUntilDeadline >= 3))) { GameState.ResetDeaths(); } } catch { } } [HarmonyPatch("StartGame")] [HarmonyPostfix] public static void OnStartGame() { GameState.OnNewRound(); } } public static class ConfigSettings { public static ConfigEntry<bool> Enabled; public static ConfigEntry<string> Style; public static ConfigEntry<bool> AlwaysVisible; public static ConfigEntry<string> ToggleKey; public static ConfigEntry<string> Language; public static ConfigEntry<float> Scale; public static ConfigEntry<int> RightOffsetPx; public static ConfigEntry<float> PerspectiveStrength; public static ConfigEntry<bool> FadeWhenIdle; public static ConfigEntry<float> IdleFadeSeconds; public static ConfigEntry<float> IdleMinOpacity; public static ConfigEntry<bool> ShowPanel; public static ConfigEntry<bool> ShowTimer; public static ConfigEntry<bool> ShowLocation; public static ConfigEntry<bool> ShowQuota; public static ConfigEntry<bool> ShowDayDeaths; public static ConfigEntry<bool> ShowMonsters; public static ConfigEntry<bool> ShowTraps; public static ConfigEntry<bool> ShowBrutalEvent; public static ConfigEntry<bool> ShowVictoryBanner; public static ConfigEntry<bool> ShowTicker; public static ConfigEntry<bool> AutoTimer; public static ConfigEntry<bool> ShowAllEvents; public static ConfigEntry<string> TimerPauseKey; public static ConfigEntry<string> TimerResetKey; public static ConfigEntry<bool> Scanlines; public static ConfigEntry<bool> ScaleMonstersByCount; public static ConfigEntry<int> Port; public static Key ToggleKeyParsed { get; private set; } = (Key)23; public static Key TimerPauseKeyParsed { get; private set; } = (Key)29; public static Key TimerResetKeyParsed { get; private set; } = (Key)0; public static bool LegacyStyleActive => string.Equals(Style.Value?.Trim(), "Legacy", StringComparison.OrdinalIgnoreCase); public static bool RussianActive { get { string a = Language.Value?.Trim(); if (string.Equals(a, "ru", StringComparison.OrdinalIgnoreCase)) { return true; } if (string.Equals(a, "en", StringComparison.OrdinalIgnoreCase)) { return false; } return Rtlc.Present; } } public static void Bind(ConfigFile cfg) { Enabled = cfg.Bind<bool>("General", "Enabled", true, "Включить/выключить оверлей целиком."); Style = cfg.Bind<string>("General", "Style", "Game", "Стиль оверлея: \"Legacy\" (старый пиксельный из HTML) или \"Game\" (как внутриигровой чат). Требует перезапуска игры."); AlwaysVisible = cfg.Bind<bool>("General", "AlwaysVisible", false, "true — оверлей виден всегда (даже вне корабля), клавиша показа/скрытия работает везде. false — оверлей виден только когда игрок на корабле."); ToggleKey = cfg.Bind<string>("General", "ToggleKey", "I", "Клавиша для переключения видимости (имя из UnityEngine.InputSystem.Key, например I, F7, Numpad0)."); Language = cfg.Bind<string>("General", "Language", "auto", "Язык надписей оверлея: \"auto\" (русский, если установлен русификатор RTLC, иначе английский), \"en\" или \"ru\"."); Scale = cfg.Bind<float>("General", "Scale", 1f, "Масштаб оверлея (0.5–2.0)."); RightOffsetPx = cfg.Bind<int>("General", "RightOffsetPx", 20, "Отступ оверлея от правого края экрана в пикселях."); PerspectiveStrength = cfg.Bind<float>("General", "PerspectiveStrength", 0f, "ЭКСПЕРИМЕНТ: эффект перспективы (как чат «уходит вдаль») — ближняя к центру экрана сторона сужается. 0 — выключить (по умолчанию). Попробуй 0.16. Искажает и текст, и рамки. Требует перезапуска."); FadeWhenIdle = cfg.Bind<bool>("General", "FadeWhenIdle", true, "Приглушать оверлей, если долго не двигать камерой (возвращается при движении камеры)."); IdleFadeSeconds = cfg.Bind<float>("General", "IdleFadeSeconds", 4f, "Сколько секунд без движения камеры до приглушения оверлея."); IdleMinOpacity = cfg.Bind<float>("General", "IdleMinOpacity", 0.32f, "Насколько приглушать: итоговая непрозрачность в бездействии (0 — полностью прозрачный, 1 — без приглушения)."); ShowPanel = cfg.Bind<bool>("Widgets", "ShowPanel", true, "Общая панель: фон, рамка/уголки и логотип GDLP."); ShowTimer = cfg.Bind<bool>("Widgets", "ShowTimer", true, "Таймер (запуск/пауза/сброс — клавиши в секции Behavior; авто-режим — AutoTimer)."); ShowLocation = cfg.Bind<bool>("Widgets", "ShowLocation", true, "Блок локации: луна, погода, тип интерьера, ульи, предметы внутри/снаружи, Old Bird."); ShowQuota = cfg.Bind<bool>("Widgets", "ShowQuota", true, "Прогресс квоты: табы Q1–Q3, полоса выполнения, собранный лут."); ShowDayDeaths = cfg.Bind<bool>("Widgets", "ShowDayDeaths", true, "День и счётчик смертей."); ShowMonsters = cfg.Bind<bool>("Widgets", "ShowMonsters", true, "Монстры: слева — наружные (outside), справа — внутренние (inside)."); ShowTraps = cfg.Bind<bool>("Widgets", "ShowTraps", true, "Ловушки (турели, мины, шипы) в нижней части оверлея. При турельном ивенте — анимация стрельбы."); ShowBrutalEvent = cfg.Bind<bool>("Widgets", "ShowBrutalEvent", true, "Плашка с ивентом от BCME (BrutalCompanyMinusExtraReborn). Видна только на луне."); ShowVictoryBanner = cfg.Bind<bool>("Widgets", "ShowVictoryBanner", true, "Баннер победы после выполнения 3-й квоты (с аналитикой забега: квоты, луны, монстры, хроника)."); ShowTicker = cfg.Bind<bool>("Widgets", "ShowTicker", true, "Бегущая строка с краткой сводкой: экипаж, луна, погода, день, квота, смерти."); AutoTimer = cfg.Bind<bool>("Behavior", "AutoTimer", true, "true — таймер автоматически запускается при начале рейда и встаёт на паузу при загрузках/меню. false — только ручное управление."); ShowAllEvents = cfg.Bind<bool>("Behavior", "ShowAllEvents", false, "true — показывать ВСЕ ивенты BCME (через запятую). false — только первый из списка."); TimerPauseKey = cfg.Bind<string>("Behavior", "TimerPauseKey", "O", "Клавиша паузы/запуска таймера. None — отключить."); TimerResetKey = cfg.Bind<string>("Behavior", "TimerResetKey", "None", "Клавиша сброса таймера. None — отключить (сброс всё равно происходит при новом сейве)."); Scanlines = cfg.Bind<bool>("Behavior", "Scanlines", true, "Едва заметные горизонтальные полосы (CRT/LSD-эффект как в ванильных меню). Требует перезапуска игры."); ScaleMonstersByCount = cfg.Bind<bool>("Behavior", "ScaleMonstersByCount", false, "ЭКСПЕРИМЕНТ: убрать цифры количества у монстров И ловушек — вместо этого чем их больше, тем крупнее иконка и тем сильнее она трясётся."); Port = cfg.Bind<int>("WebSocket", "Port", 8181, "Порт встроенного WebSocket-моста. По нему HTML-оверлей (OBS) получает те же данные. Если у тебя ещё стоит отдельный мод LCBridge — удали его, иначе порт будет занят."); ReparseKeys(); ToggleKey.SettingChanged += delegate { ReparseKeys(); }; TimerPauseKey.SettingChanged += delegate { ReparseKeys(); }; TimerResetKey.SettingChanged += delegate { ReparseKeys(); }; } private static void ReparseKeys() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) ToggleKeyParsed = ParseKey(ToggleKey.Value, (Key)23); TimerPauseKeyParsed = ParseKey(TimerPauseKey.Value, (Key)0); TimerResetKeyParsed = ParseKey(TimerResetKey.Value, (Key)0); } private static Key ParseKey(string s, Key fallback) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(s)) { return (Key)0; } if (Enum.TryParse<Key>(s.Trim(), ignoreCase: true, out Key result)) { return result; } ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)$"Не удалось распознать клавишу '{s}', использую {fallback}."); } return fallback; } } [Serializable] public class BridgePayload { public string type; public int deaths; public int alive; public int total; public int health; public string moonName; public string weatherFull; public string brutalEvent; public bool onMoon; public bool loading; public bool inGame; public int resetToken; public int levelScrap; public string topKiller; public string topMonster; public string deadliestEvent; public string[] monstersOutside; public string[] monstersInside; public string[] traps; public RunInfo run; public int quotaValue; public int quotaFulfilled; public int shipLoot; public int quotaIndex = 1; public int dayCount = 1; public int daysLeft = -1; public string interiorType; public int beehiveCount; public int itemsInside; public int itemsOutside; public bool hasOldBird; public bool onShip; } [Serializable] public class RunInfo { public RunQuota[] quotas; public RunMoon[] moons; public RunMonster[] monsters; public int peak; public int inside; public int outside; public int runSec; public string[] timeline; } [Serializable] public class RunQuota { public int i; public int money; public int sec; public int deaths; } [Serializable] public class RunMoon { public string name; public int visits; public int profit; public int sec; } [Serializable] public class RunMonster { public string name; public int count; public int sec; } public static class DataParser { private static string _pendingLocal; private static readonly object _localLock = new object(); public static float Heartbeat = -999f; public static BridgePayload Current { get; private set; } public static void PushLocal(string json) { lock (_localLock) { _pendingLocal = json; } } public static string TakeLocal() { lock (_localLock) { string pendingLocal = _pendingLocal; _pendingLocal = null; return pendingLocal; } } public static bool TryParse(string json) { if (string.IsNullOrEmpty(json)) { return false; } BridgePayload bridgePayload; try { bridgePayload = JsonUtility.FromJson<BridgePayload>(json); } catch { return false; } if (bridgePayload == null || bridgePayload.type != "bridge") { return false; } Current = bridgePayload; return true; } public static void Clear() { Current = null; } } public static class EventTranslate { private static readonly HashSet<string> _loggedMiss = new HashSet<string>(); private static readonly Dictionary<string, string> Map = new Dictionary<string, string> { ["allweather"] = "Вся погода сразу", ["antibounty"] = "Награда снята", ["anticoilhead"] = "Анти-койлхед", ["arachnophobia"] = "Арахнофобия", ["baboonhorde"] = "Орда павианов", ["badproduce"] = "Гнилой урожай", ["bees"] = "Пчёлы", ["berserkturrets"] = "Турели в ярости", ["bigbonus"] = "Большой бонус", ["bigdelivery"] = "Большая доставка", ["birds"] = "Птицы", ["blackfriday"] = "Чёрная пятница", ["bonus"] = "Бонус", ["bounty"] = "Награда за голову", ["bracken"] = "Бракен", ["bughorde"] = "Орда жуков", ["butlers"] = "Дворецкие", ["cadaver"] = "Трупоцвет", ["catsanddogs"] = "Кошки и собаки", ["clock"] = "Часы", ["coilhead"] = "Койлхед", ["controlpad"] = "Пульт управления", ["cruiserfailure"] = "Поломка крейсера", ["dentures"] = "Вставная челюсть", ["dogs"] = "Слепые псы", ["doorcircuitfailure"] = "Сбой цепи дверей", ["doorfailure"] = "Поломка дверей", ["dooroverdriveev"] = "Двери вразнос", ["dustpans"] = "Совки", ["dweller"] = "Пещерный житель", ["earlyship"] = "Ранний отлёт", ["eastereggs"] = "Пасхалки", ["explodingitems"] = "Взрывающиеся предметы", ["facilityghost"] = "Призрак комплекса", ["flashlightsfailure"] = "Сбой фонарей", ["flowersnake"] = "Тюльпановая змейка", ["footballscrap"] = "Ценный мяч", ["forestgiant"] = "Лесной великан", ["fragileenemies"] = "Хрупкие враги", ["fullaccess"] = "Полный доступ", ["garbagelid"] = "Крышка мусорки", ["giantsoutside"] = "Великаны снаружи", ["gloomy"] = "Пасмурно", ["goldenbars"] = "Золотые слитки", ["goldenfacility"] = "Золотой комплекс", ["grabbablelandmines"] = "Переносные мины", ["grabbableturrets"] = "Переносные турели", ["heavyrain"] = "Ливень", ["hell"] = "Ад", ["higherscrapvalue"] = "Лут дороже", ["hoardingbugs"] = "Жуки-барахольщики", ["holidayseason"] = "Праздники", ["honk"] = "Гудок", ["insidebees"] = "Пчёлы внутри", ["ismetal"] = "Металлический день", ["itemchargerfailure"] = "Сбой зарядной станции", ["jester"] = "Шут", ["jetpackfailure"] = "Сбой джетпака", ["kamikaziebugs"] = "Жуки-камикадзе", ["kidnapperfox"] = "Лис-похититель", ["kiwibird"] = "Гигантский киви", ["landmines"] = "Мины", ["lateship"] = "Поздний отлёт", ["leaflessbrowntrees"] = "Голые бурые деревья", ["leaflesstrees"] = "Голые деревья", ["leverfailure"] = "Сбой рычага отлёта", ["littlegirl"] = "Девочка-призрак", ["lizard"] = "Спор-ящер", ["lockedentrance"] = "Запертый вход", ["locusts"] = "Саранча", ["manualcamerafailure"] = "Сбой камер", ["masked"] = "Маскед", ["maskitem"] = "Маска", ["metalswitch"] = "Металлический рубильник", ["meteors"] = "Метеоры", ["moreexits"] = "Больше выходов", ["morescrap"] = "Больше лута", ["nobaboons"] = "Без павианов", ["nobirds"] = "Без птиц", ["nobracken"] = "Без бракена", ["nobutlers"] = "Без дворецких", ["nocoilhead"] = "Без койлхедов", ["nodogs"] = "Без псов", ["noghosts"] = "Без призраков", ["nogiants"] = "Без великанов", ["nohoardingbugs"] = "Без жуков-барахольщиков", ["nojester"] = "Без шута", ["nolandmines"] = "Без мин", ["nolizards"] = "Без ящеров", ["nomasks"] = "Без масок", ["nonutcracker"] = "Без щелкунчиков", ["nooldbird"] = "Без старых птиц", ["noslimes"] = "Без слизней", ["nosnarefleas"] = "Без блох", ["nospiders"] = "Без пауков", ["nospiketraps"] = "Без шипов", ["nothing"] = "Ничего", ["nothumpers"] = "Без тамперов", ["notmetal"] = "Не металл", ["noturrets"] = "Без турелей", ["noworm"] = "Без червя", ["nutcracker"] = "Щелкунчик", ["nutslayer"] = "Щелкунчик-палач", ["nutslayersmore"] = "Щелкунчики-палачи", ["oldbirds"] = "Старые птицы (мехи)", ["outsidelandmines"] = "Мины снаружи", ["outsideturrets"] = "Турели снаружи", ["pickles"] = "Огурчики", ["plasticcup"] = "Пластиковый стаканчик", ["plentyoutsidescrap"] = "Много лута снаружи", ["puma"] = "Феиопар", ["raining"] = "Дождь", ["realityshift"] = "Сдвиг реальности", ["safeoutside"] = "Снаружи безопасно", ["scarceoutsidescrap"] = "Мало лута снаружи", ["scrapgalore"] = "Лут в изобилии", ["severedbits"] = "Расчленёнка", ["shipcorefailure"] = "Сбой ядра корабля", ["shiplightsfailure"] = "Сбой света корабля", ["shipmentfees"] = "Плата за доставку", ["sid"] = "SID", ["slimeinside"] = "Слизень внутри", ["slimes"] = "Слизни", ["smalldelivery"] = "Малая доставка", ["smallermap"] = "Карта поменьше", ["snarefleas"] = "Блохи-душители", ["spiders"] = "Пауки", ["spiketraps"] = "Шипы-ловушки", ["stingray"] = "Скат", ["strongenemies"] = "Сильные враги", ["sussypaintings"] = "Подозрительные картины", ["targetingfailureevent"] = "Сбой наведения", ["teleporterfailure"] = "Сбой телепорта", ["teleportertraps"] = "Ловушки-телепорты", ["teleportin"] = "Телепортация внутрь", ["terminalfailure"] = "Сбой терминала", ["thumpers"] = "Тамперы", ["timechaos"] = "Хаос времени", ["toiletpaper"] = "Туалетная бумага", ["train"] = "Поезд", ["transmutescrapbig"] = "Трансмутация лута (много)", ["transmutescrapsmall"] = "Трансмутация лута (мало)", ["trapsfailure"] = "Сбой ловушек", ["trees"] = "Деревья", ["turrets"] = "Турели", ["turretseverywhere"] = "Турели повсюду", ["veryearlyship"] = "Очень ранний отлёт", ["verylateship"] = "Очень поздний отлёт", ["walkiefailure"] = "Сбой рации", ["warzone"] = "Зона боевых действий", ["welcometothefactory"] = "Добро пожаловать на завод", ["worms"] = "Земляные левиафаны", ["zeddog"] = "Пёс-зомби", ["allslayers"] = "Все палачи", ["baddice"] = "Дурные кости", ["baldi"] = "Балди", ["barbers"] = "Барберы", ["bellcrab"] = "Краб-звоночек", ["bertha"] = "Берта", ["bloodmoon"] = "Кровавая луна", ["cityofgold"] = "Золотой город", ["cleaners"] = "Чистильщики", ["critters"] = "Мелкие твари", ["dice"] = "Кости", ["football"] = "Футбол", ["forsaken"] = "Забытый", ["foxy"] = "Фокси", ["giantshowdown"] = "Битва великанов", ["hallowed"] = "Хэллоуин", ["heatwave"] = "Аномальная жара", ["herobrine"] = "Херобрин", ["hotbarhassle"] = "Свистопляска инвентаря", ["hotbarmania"] = "Мания инвентаря", ["hurricane"] = "Ураган", ["immortalsnail"] = "Бессмертная улитка", ["itsplaytime"] = "Playtime", ["leafboys"] = "Листовые парни", ["lighteaterenemy"] = "Пожиратель света", ["lockers"] = "Локеры", ["majoramoon"] = "Луна Majora", ["manstalker"] = "Преследователь", ["mantitoil"] = "Мантикоил с турелью", ["mantitoilslayer"] = "Мантикоил-палач", ["meltdown"] = "Расплавление реактора", ["meteorshower"] = "Метеоритный дождь", ["moaienemy"] = "Моаи", ["mobileturrets"] = "Мобильные турели", ["needycats"] = "Назойливые коты", ["nemo"] = "Немо", ["nofiend"] = "Без Изверга", ["noimmortalsnails"] = "Без улиток", ["nolockers"] = "Без локеров", ["nomantitoil"] = "Без мантитоила", ["nomantitoilslayer"] = "Без мантитоила-палача", ["nopeepers"] = "Без Пиперов", ["noshyguy"] = "Без Шайгая", ["noslayers"] = "Без палачей", ["notoilslayer"] = "Без тойл-палача", ["peepers"] = "Пиперы", ["phonesout"] = "Телефоны наружу", ["playtimebig"] = "Playtime (большой)", ["rollinggiants"] = "Катящиеся великаны", ["roomba"] = "Робот-пылесос", ["scp682"] = "SCP-682", ["scp939"] = "SCP-939", ["seamine"] = "Морская мина", ["shiba"] = "Сиба-ину", ["shockwavedrones"] = "Дроны с ударной волной", ["shrimp"] = "Креветка", ["shyguy"] = "Шайгай", ["sirenhead"] = "Сиреноголовый", ["skullenemy"] = "Череп", ["slenderman"] = "Слендермен", ["solarflare"] = "Солнечная вспышка", ["souldev"] = "Souldev", ["takeygokubracken"] = "Бракен-Гоку", ["thefiend"] = "Изверг", ["toilhead"] = "Тойлхед (койл с турелью)", ["toilslayer"] = "Тойл-палач", ["walkers"] = "Ходоки", ["welcometoooblterra"] = "Добро пожаловать в Ooblterra", ["windy"] = "Ветрено", ["yeetbomb"] = "Бомба-йит" }; public static string ToRu(string raw) { if (string.IsNullOrEmpty(raw)) { return raw; } string text = Norm(raw); if (text.Length == 0) { return raw; } if (Map.TryGetValue(text, out var value)) { return value; } if (_loggedMiss.Add(text)) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("[event-ru] нет перевода для ивента \"" + raw + "\" (ключ: " + text + ") — показываю как есть.")); } } return raw; } private static string Norm(string s) { StringBuilder stringBuilder = new StringBuilder(s.Length); string text = s.ToLowerInvariant(); foreach (char c in text) { if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) { stringBuilder.Append(c); } } return stringBuilder.ToString(); } } [HarmonyPatch(typeof(GameNetworkManager), "Disconnect")] internal static class Patch_GameNetworkManager_Disconnect { private static void Postfix() { OverlayManager.Instance?.NotifyDisconnectedFromGame(); } } public static class Localization { private static readonly Dictionary<string, string> En = new Dictionary<string, string> { ["location"] = "LOCATION", ["deadline"] = "DEADLINE PROGRESS", ["lootQuota"] = "LOOT / QUOTA", ["onPlanet"] = "ON PLANET", ["day"] = "DAY", ["left"] = "LEFT", ["crew"] = "CREW", ["deaths"] = "DEATHS", ["outside"] = "OUTSIDE", ["inside"] = "INSIDE", ["traps"] = "TRAPS", ["brutalEvent"] = "!! BRUTAL EVENT", ["noData"] = "- no data -", ["offline"] = "OFFLINE", ["bridge"] = "BRIDGE", ["interior"] = "INTERIOR", ["items"] = "ITEMS", ["in"] = "IN", ["out"] = "OUT", ["hives"] = "HIVES", ["oldBird"] = "!! OLD BIRD !!", ["met"] = "MET", ["tMoon"] = "MOON", ["tWx"] = "WX", ["tQuota"] = "QUOTA", ["tEvent"] = "EVENT", ["tObjective"] = "OBJECTIVE: SURVIVE 3 QUOTAS", ["vicStamp"] = "CHALLENGE", ["vicTitle"] = "COMPLETE", ["vicSub"] = "3 QUOTAS DONE - YOU SURVIVED", ["vicTime"] = "TIME", ["vicLoot"] = "LOOT", ["vicDeaths"] = "DEATHS", ["vicQuotas"] = "BY QUOTA", ["vicMoons"] = "MOONS", ["vicMonsters"] = "MOST ENCOUNTERED", ["vicTimeline"] = "DAY BY DAY", ["vicVisits"] = "visits", ["vicNoLosses"] = "no losses", ["vicEvent"] = "event", ["vicDeath"] = "deaths" }; private static readonly Dictionary<string, string> Ru = new Dictionary<string, string> { ["location"] = "ЛОКАЦИЯ", ["deadline"] = "ПРОГРЕСС КВОТЫ", ["lootQuota"] = "ЛУТ / КВОТА", ["onPlanet"] = "НА ПЛАНЕТЕ", ["day"] = "ДЕНЬ", ["left"] = "ОСТ.", ["crew"] = "ЭКИПАЖ", ["deaths"] = "СМЕРТИ", ["outside"] = "УЛИЦА", ["inside"] = "КОМПЛЕКС", ["traps"] = "ЛОВУШКИ", ["brutalEvent"] = "!! BRUTAL ИВЕНТ", ["noData"] = "— нет данных —", ["offline"] = "НЕТ СВЯЗИ", ["bridge"] = "МОСТ", ["interior"] = "ИНТЕРЬЕР", ["items"] = "ПРЕДМЕТЫ", ["in"] = "ВНУТРИ", ["out"] = "СНАРУЖИ", ["hives"] = "УЛЬИ", ["oldBird"] = "!! OLD BIRD !!", ["met"] = "ЕСТЬ", ["tMoon"] = "ЛУНА", ["tWx"] = "ПОГОДА", ["tQuota"] = "КВОТА", ["tEvent"] = "ИВЕНТ", ["tObjective"] = "ЦЕЛЬ: ПЕРЕЖИТЬ 3 КВОТЫ", ["vicStamp"] = "ИСПЫТАНИЕ", ["vicTitle"] = "ПРОЙДЕНО", ["vicSub"] = "3 КВОТЫ ВЫПОЛНЕНЫ — ТЫ ВЫЖИЛ", ["vicTime"] = "ВРЕМЯ", ["vicLoot"] = "ЛУТ", ["vicDeaths"] = "СМЕРТИ", ["vicQuotas"] = "ПО КВОТАМ", ["vicMoons"] = "ЛУНЫ", ["vicMonsters"] = "КОГО ВСТРЕЧАЛИ ЧАЩЕ", ["vicTimeline"] = "ХРОНИКА ПО ДНЯМ", ["vicVisits"] = "визитов", ["vicNoLosses"] = "без потерь", ["vicEvent"] = "ивент", ["vicDeath"] = "смерти" }; public static string T(string key) { if ((ConfigSettings.RussianActive ? Ru : En).TryGetValue(key, out var value)) { return value; } if (En.TryGetValue(key, out var value2)) { return value2; } return key; } } public class OverlayManager : MonoBehaviour { private const float PanelWidth = 340f; private const float SlideTime = 0.3f; private const float RailReserve = 92f; private const float TiltDeg = 1f; private Canvas _canvas; private bool _dirty; private OverlayStyle S; private RectTransform _root; private CanvasGroup _group; private Image _bgImage; private readonly List<Image> _frameImages = new List<Image>(); private readonly List<PixbitFlicker> _pixbits = new List<PixbitFlicker>(); private readonly List<TextMeshProUGUI> _allTexts = new List<TextMeshProUGUI>(); private readonly List<TextMeshProUGUI> _bigTexts = new List<TextMeshProUGUI>(); private TMP_FontAsset _fontBody; private TMP_FontAsset _fontBig; private TMP_FontAsset _dynFont; private bool _fontsResolved; private float _fontRetryT; private static readonly Dictionary<string, TMP_FontAsset> _osFontCache = new Dictionary<string, TMP_FontAsset>(); private GameObject _headerGo; private GameObject _headerDivider; private GameObject _locationGo; private GameObject _quotaGo; private GameObject _dayDeathsGo; private GameObject _tickerGo; private TextMeshProUGUI _timerText; private EyeWidget _topEye; private TextMeshProUGUI _moonText; private TextMeshProUGUI _interiorText; private TextMeshProUGUI _itemsText; private TextMeshProUGUI _oldBirdText; private readonly Image[] _qtabBgs = (Image[])(object)new Image[3]; private readonly TextMeshProUGUI[] _qtabTexts = (TextMeshProUGUI[])(object)new TextMeshProUGUI[3]; private TextMeshProUGUI _lootQuotaText; private TextMeshProUGUI _barText; private GameObject _onPlanetGo; private TextMeshProUGUI _onPlanetVal; private RectTransform _barFill; private Image _barFillImg; private TextMeshProUGUI _dayText; private TextMeshProUGUI _deathsText; private MobRailWidget _mobRail; private TrapFireEffect _trapFire; private EventPlateWidget _eventPlate; private RectTransform _eventPlateRt; private RectTransform _trapRailRt; private RectTransform _trapFxRt; private TextMeshProUGUI _eventText; private TickerWidget _ticker; private VictoryWidget _victory; private bool _userHidden; private float _vis; private Quaternion _lastCamRot = Quaternion.identity; private float _idleT; private float _idleAlpha = 1f; private const float IdleFadeTime = 0.8f; private float _timerSec; private bool _timerRunning; private bool? _prevLoading; private bool? _prevInGame; private int? _prevResetToken; private int? _lastQuotaIndex; private List<BcmerEvents.EventInfo> _events = new List<BcmerEvents.EventInfo>(); private bool _loggedFirstPacket; private bool _loggedParseFail; private bool? _loggedOnShip; private static readonly string[] TurretEventKeys = new string[11] { "turret", "турел", "berserk", "mobile", "everywhere", "toilhead", "toil", "hell", "quad", "artillery", "артилл" }; private const float PauseDimAlpha = 0.06f; private float _pauseFade; private static readonly (string key, string hex)[] WxColors = new(string, string)[6] { ("eclips", "#FF3A2E"), ("storm", "#FFCF3A"), ("rain", "#5FB6E6"), ("flood", "#3FA9C9"), ("fog", "#C8C2B0"), ("dust", "#D9A05A") }; private float _lastPanelH = -1f; private static Texture2D _scanTex; private static Sprite _grunge; public static OverlayManager Instance { get; private set; } internal OverlayStyle Style => S; private void Awake() { Instance = this; S = (ConfigSettings.LegacyStyleActive ? OverlayStyle.Legacy() : OverlayStyle.Game()); BuildUi(); if (DataParser.Current != null) { OnPayload(DataParser.Current); } } private void OnDestroy() { if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } private void Update() { //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) string text = DataParser.TakeLocal(); if (text != null) { if (DataParser.TryParse(text)) { if (!_loggedFirstPacket) { _loggedFirstPacket = true; BridgePayload current = DataParser.Current; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)$"Первый пакет моста разобран: moon={current.moonName}, onShip={current.onShip}, inGame={current.inGame}, quota={current.shipLoot}/{current.quotaValue}"); } } OnPayload(DataParser.Current); } else if (!_loggedParseFail) { _loggedParseFail = true; ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)("Не удалось разобрать пакет моста (начало): " + text.Substring(0, Math.Min(300, text.Length)))); } } } HandleInput(); if (_timerRunning) { _timerSec += Time.unscaledDeltaTime; } if (!_fontsResolved) { _fontRetryT += Time.unscaledDeltaTime; if (_fontRetryT >= 1f) { _fontRetryT = 0f; EnsureFonts(); if (_fontsResolved) { _dirty = true; } } } UpdateVisibility(Time.unscaledDeltaTime); PositionTrapRail(); if (((Component)_root).gameObject.activeSelf && ConfigSettings.ShowTimer.Value) { ((TMP_Text)_timerText).text = FmtTimeMs(_timerSec); ((Graphic)_timerText).color = (Color)(_timerRunning ? Color.white : new Color(1f, 1f, 1f, 0.6f)); } if (_dirty) { _dirty = false; EnsureFonts(); Refresh(); } RewarpOnResize(); } public void NotifyDisconnectedFromGame() { DataParser.Clear(); _timerRunning = false; _prevLoading = null; _prevInGame = null; _victory?.Hide(); _dirty = true; } private void OnPayload(BridgePayload p) { _dirty = true; if (_loggedOnShip != p.onShip) { _loggedOnShip = p.onShip; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)string.Format("onShip={0} → панель {1} (AlwaysVisible={2})", p.onShip, p.onShip ? "разрешена" : "будет скрыта", ConfigSettings.AlwaysVisible.Value)); } } if (_prevResetToken.HasValue && p.resetToken != _prevResetToken) { _timerSec = 0f; _timerRunning = false; _prevLoading = null; _prevInGame = null; _lastQuotaIndex = null; _victory?.Hide(); } _prevResetToken = p.resetToken; if (ConfigSettings.AutoTimer.Value && (p.loading != _prevLoading || p.inGame != _prevInGame)) { _timerRunning = p.inGame && !p.loading; _prevLoading = p.loading; _prevInGame = p.inGame; } _events = BcmerEvents.GetEvents(); if (_events.Count == 0 && !string.IsNullOrEmpty(p.brutalEvent)) { string[] array = p.brutalEvent.Split(','); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0) { if (ConfigSettings.RussianActive) { text = EventTranslate.ToRu(text); } _events.Add(new BcmerEvents.EventInfo { Name = text, ColorHex = "#FFFFFF" }); } } } int num = Mathf.Max(1, p.quotaIndex); if (_lastQuotaIndex.HasValue && num > _lastQuotaIndex && num >= 4 && ConfigSettings.ShowVictoryBanner.Value) { _victory.Show(p, (int)_timerSec); } _lastQuotaIndex = num; } private void HandleInput() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) if (IsTypingChat()) { return; } if (KeyPressed(ConfigSettings.ToggleKeyParsed)) { bool flag = DataParser.Current?.onShip ?? false; if (ConfigSettings.AlwaysVisible.Value || flag) { _userHidden = !_userHidden; } } if (KeyPressed(ConfigSettings.TimerPauseKeyParsed)) { _timerRunning = !_timerRunning; } if (KeyPressed(ConfigSettings.TimerResetKeyParsed)) { _timerSec = 0f; } } private static bool IsTypingChat() { try { PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; return (Object)(object)val != (Object)null && val.isTypingChat; } catch { return false; } } private static bool KeyPressed(Key k) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) if ((int)k == 0) { return false; } Keyboard current = Keyboard.current; if (current == null) { return false; } try { return ((ButtonControl)current[k]).wasPressedThisFrame; } catch { return false; } } private void UpdateVisibility(float dt) { //IL_0184: Unknown result type (might be due to invalid IL or missing references) BridgePayload current = DataParser.Current; bool flag = DataParser.Current != null && Time.unscaledTime - DataParser.Heartbeat < 5f; bool flag2 = false; bool flag3 = false; try { GameNetworkManager instance = GameNetworkManager.Instance; PlayerControllerB val = (((Object)(object)instance != (Object)null) ? instance.localPlayerController : null); flag2 = (Object)(object)val != (Object)null; if ((Object)(object)val != (Object)null && (Object)(object)val.quickMenuManager != (Object)null) { flag3 = val.quickMenuManager.isMenuOpen; } } catch { } if ((Object)(object)_canvas != (Object)null) { _canvas.sortingOrder = (flag3 ? (-1000) : 500); } UpdateIdleFade(dt); bool flag4 = ConfigSettings.Enabled.Value && flag2 && (ConfigSettings.AlwaysVisible.Value || (flag && current != null && current.onShip)) && !_userHidden; _topEye?.SetOpen(flag4 ? 1f : 0f); _vis = Mathf.Clamp01(_vis + (flag4 ? 1f : (-1f)) * (dt / 0.3f)); float num = EaseOutCubic(_vis); float num2 = 340f * ConfigSettings.Scale.Value + 200f; float num3 = 0f - Mathf.Max((float)ConfigSettings.RightOffsetPx.Value, 92f); _root.anchoredPosition = new Vector2(Mathf.Lerp(num2, num3, num), 0f); _pauseFade = Mathf.MoveTowards(_pauseFade, flag3 ? 1f : 0f, dt / 0.2f); float num4 = Mathf.Lerp(1f, 0.06f, _pauseFade); _group.alpha = num * _idleAlpha * num4; bool flag5 = _vis > 0.001f; if (((Component)_root).gameObject.activeSelf != flag5) { ((Component)_root).gameObject.SetActive(flag5); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)(flag5 ? "Оверлей появляется." : "Оверлей скрыт.")); } if (flag5) { _dirty = true; } } } private static float EaseOutCubic(float t) { return 1f - Mathf.Pow(1f - t, 3f); } private void UpdateIdleFade(float dt) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) if (!ConfigSettings.FadeWhenIdle.Value) { _idleAlpha = 1f; return; } float num = 999f; try { PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; Camera val2 = (((Object)(object)val != (Object)null) ? val.gameplayCamera : null); if ((Object)(object)val2 == (Object)null && (Object)(object)Camera.main != (Object)null) { val2 = Camera.main; } if ((Object)(object)val2 != (Object)null) { num = Quaternion.Angle(((Component)val2).transform.rotation, _lastCamRot); _lastCamRot = ((Component)val2).transform.rotation; } } catch { } if (num > 0.4f) { _idleT = 0f; } else { _idleT += dt; } float num2 = ((_idleT > ConfigSettings.IdleFadeSeconds.Value) ? Mathf.Clamp01(ConfigSettings.IdleMinOpacity.Value) : 1f); _idleAlpha = Mathf.MoveTowards(_idleAlpha, num2, dt / 0.8f); } private void Refresh() { //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_045a: Unknown result type (might be due to invalid IL or missing references) //IL_042f: Unknown result type (might be due to invalid IL or missing references) //IL_04fb: Unknown result type (might be due to invalid IL or missing references) //IL_04a3: Unknown result type (might be due to invalid IL or missing references) //IL_049c: Unknown result type (might be due to invalid IL or missing references) //IL_0488: Unknown result type (might be due to invalid IL or missing references) //IL_047b: Unknown result type (might be due to invalid IL or missing references) //IL_044d: Unknown result type (might be due to invalid IL or missing references) //IL_0422: Unknown result type (might be due to invalid IL or missing references) //IL_053d: Unknown result type (might be due to invalid IL or missing references) //IL_0573: Unknown result type (might be due to invalid IL or missing references) //IL_055c: Unknown result type (might be due to invalid IL or missing references) //IL_0566: Unknown result type (might be due to invalid IL or missing references) //IL_0604: Unknown result type (might be due to invalid IL or missing references) if (!((Component)_root).gameObject.activeSelf) { return; } BridgePayload current = DataParser.Current; bool flag = DataParser.Current != null && Time.unscaledTime - DataParser.Heartbeat < 5f; bool flag2 = current?.onMoon ?