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 LastEmployeeAlive v1.0.2
plugins/LastSurvivorMod.dll
Decompiled 10 minutes agousing System; using System.Collections; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.Networking; 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("LastSurvivorMod")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("LastSurvivorMod")] [assembly: AssemblyTitle("LastSurvivorMod")] [assembly: AssemblyVersion("1.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 LastSurvivorMod { [BepInPlugin("com.zcode.lastsurvivor", "LastSurvivorMod", "1.0.1")] [BepInProcess("Lethal Company.exe")] public class Plugin : BaseUnityPlugin { public const string PLUGIN_GUID = "com.zcode.lastsurvivor"; public const string PLUGIN_NAME = "LastSurvivorMod"; public const string PLUGIN_VERSION = "1.0.1"; internal static ManualLogSource Log; private void Awake() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_008c: 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_0097: Expected O, but got Unknown //IL_0097: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; new Harmony("com.zcode.lastsurvivor").PatchAll(typeof(GameStatePatches)); Log.LogInfo((object)"Harmony-патчи применены."); ConfigEntry<int> minQuotaNormal = ((BaseUnityPlugin)this).Config.Bind<int>("Gameplay", "MinQuotaNormal", 2, "Minimum quota number (1-based) for the mod to activate in NORMAL lobbies (<= LargeLobbyThreshold). On earlier quotas the mod stays silent."); ConfigEntry<int> largeLobbyThreshold = ((BaseUnityPlugin)this).Config.Bind<int>("Gameplay", "LargeLobbyThreshold", 6, "Lobby size above which the mod activates from quota 1 (large lobbies skip the MinQuotaNormal restriction)."); ConfigEntry<int> theme2Quota = ((BaseUnityPlugin)this).Config.Bind<int>("Gameplay", "Theme2Quota", 6, "Quota number (1-based) from which THEME 2 (survivor2.png / music2.ogg / bell2.ogg) is used. Falls back to theme 1 if those files are missing."); GameObject val = new GameObject("LastSurvivorManager"); Object.DontDestroyOnLoad((Object)val); ((Object)val).hideFlags = (HideFlags)61; val.AddComponent<LastSurvivorManager>().Initialize(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), minQuotaNormal, largeLobbyThreshold, theme2Quota); Log.LogInfo((object)"LastSurvivorMod v1.0.1 загружен."); } } [HarmonyPatch] internal static class GameStatePatches { private static void SafeRun(string patchName, Action action) { try { action(); } catch (Exception arg) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogError((object)$"[{patchName}] ошибка в постфиксе (игра не затронута): {arg}"); } } } [HarmonyPostfix] [HarmonyPatch(typeof(StartOfRound), "openingDoorsSequence")] private static void AfterOpeningDoorsSequence() { SafeRun("openingDoorsSequence", delegate { LastSurvivorManager.Instance?.NotifyRoundStarted(); }); } [HarmonyPostfix] [HarmonyPatch(typeof(StartOfRound), "ShipLeave")] private static void AfterShipLeave() { SafeRun("ShipLeave", delegate { LastSurvivorManager.Instance?.NotifyRoundEnding(); }); } [HarmonyPostfix] [HarmonyPatch(typeof(HUDManager), "FillEndGameStats")] private static void AfterFillEndGameStats() { SafeRun("FillEndGameStats", delegate { LastSurvivorManager.Instance?.NotifyRoundEnding(); }); } [HarmonyPostfix] [HarmonyPatch(typeof(StartOfRound), "EndOfGame")] private static void AfterEndOfGame() { SafeRun("EndOfGame", delegate { LastSurvivorManager.Instance?.NotifyRoundEnding(); }); } [HarmonyPostfix] [HarmonyPatch(typeof(GameNetworkManager), "Disconnect")] private static void AfterDisconnect() { SafeRun("Disconnect", delegate { LastSurvivorManager.Instance?.NotifyDisconnectedFromGame(); }); } } public class LastSurvivorManager : MonoBehaviour { public const float IMAGE_WIDTH = 960f; public const float IMAGE_HEIGHT = 540f; public const float ANIM_FADE_IN = 0.4f; public const float ANIM_HOLD = 1.1f; public const float ANIM_FADE_OUT = 2.5f; public const float ANIM_TOTAL = 4f; public const float ANIM_SCALE_START = 1.1f; public const float ANIM_SCALE_END = 1.45f; public const float MUSIC_FADE_TIME = 3f; public const float BELL_VOLUME = 0.9f; public const float MUSIC_VOLUME = 0.7f; private const string ASSET_IMAGE = "survivor.png"; private const string ASSET_BELL = "bell.ogg"; private const string ASSET_MUSIC = "music.ogg"; private const string ASSET_IMAGE_2 = "survivor2.png"; private const string ASSET_BELL_2 = "bell2.ogg"; private const string ASSET_MUSIC_2 = "music2.ogg"; private bool hasTriggeredThisRound; private bool roundActive; private string pluginDirectory; private AudioSource sfxSource; private AudioSource musicSource; private Image survivorImage; private CanvasGroup survivorCanvasGroup; private RectTransform survivorRect; private AudioClip bellClip; private AudioClip musicClip; private Coroutine animCoroutine; private Coroutine musicFadeCoroutine; private Sprite survivorSprite; private Sprite survivorSprite2; private AudioClip bellClip2; private AudioClip musicClip2; private ConfigEntry<int> cfgMinQuotaNormal; private ConfigEntry<int> cfgLargeLobbyThreshold; private ConfigEntry<int> cfgTheme2Quota; public static LastSurvivorManager Instance { get; private set; } public void Initialize(string modDirectory, ConfigEntry<int> minQuotaNormal, ConfigEntry<int> largeLobbyThreshold, ConfigEntry<int> theme2Quota) { pluginDirectory = modDirectory; Instance = this; cfgMinQuotaNormal = minQuotaNormal; cfgLargeLobbyThreshold = largeLobbyThreshold; cfgTheme2Quota = theme2Quota; BuildAudioSources(); BuildUI(); ((MonoBehaviour)this).StartCoroutine(LoadAssetsCoroutine()); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("Менеджер эффектов инициализирован. Папка мода: " + pluginDirectory)); } } private void Awake() { if ((Object)(object)Instance != (Object)null && (Object)(object)Instance != (Object)(object)this) { Object.Destroy((Object)(object)((Component)this).gameObject); } else { Instance = this; } } private void BuildAudioSources() { sfxSource = ((Component)this).gameObject.AddComponent<AudioSource>(); sfxSource.playOnAwake = false; sfxSource.loop = false; sfxSource.spatialBlend = 0f; musicSource = ((Component)this).gameObject.AddComponent<AudioSource>(); musicSource.playOnAwake = false; musicSource.loop = true; musicSource.spatialBlend = 0f; } private void BuildUI() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Expected O, but got Unknown //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("LastSurvivorCanvas", new Type[2] { typeof(Canvas), typeof(CanvasScaler) }); val.transform.SetParent(((Component)this).transform, false); Canvas component = val.GetComponent<Canvas>(); component.renderMode = (RenderMode)0; component.sortingOrder = 999; CanvasScaler component2 = val.GetComponent<CanvasScaler>(); component2.uiScaleMode = (ScaleMode)1; component2.referenceResolution = new Vector2(1920f, 1080f); component2.matchWidthOrHeight = 0.5f; GameObject val2 = new GameObject("SurvivorImage", new Type[2] { typeof(Image), typeof(CanvasGroup) }); val2.transform.SetParent(val.transform, false); survivorRect = val2.GetComponent<RectTransform>(); survivorRect.anchorMin = new Vector2(0.5f, 0.5f); survivorRect.anchorMax = new Vector2(0.5f, 0.5f); survivorRect.pivot = new Vector2(0.5f, 0.5f); survivorRect.anchoredPosition = Vector2.zero; survivorRect.sizeDelta = new Vector2(960f, 540f); survivorImage = val2.GetComponent<Image>(); ((Graphic)survivorImage).raycastTarget = false; survivorImage.preserveAspect = true; survivorCanvasGroup = val2.GetComponent<CanvasGroup>(); survivorCanvasGroup.alpha = 0f; survivorCanvasGroup.interactable = false; survivorCanvasGroup.blocksRaycasts = false; } private IEnumerator LoadAssetsCoroutine() { survivorSprite = LoadSpriteFromFile("survivor.png"); if ((Object)(object)survivorSprite != (Object)null && (Object)(object)survivorImage != (Object)null) { survivorImage.sprite = survivorSprite; } yield return LoadAudioClipCoroutine("bell.ogg", delegate(AudioClip clip) { bellClip = clip; }); yield return LoadAudioClipCoroutine("music.ogg", delegate(AudioClip clip) { musicClip = clip; }); if (File.Exists(Path.Combine(pluginDirectory, "survivor2.png"))) { survivorSprite2 = LoadSpriteFromFile("survivor2.png"); } else { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"survivor2.png не найден — картинка темы 2 недоступна (откат на тему 1)."); } } if (File.Exists(Path.Combine(pluginDirectory, "bell2.ogg"))) { yield return LoadAudioClipCoroutine("bell2.ogg", delegate(AudioClip clip) { bellClip2 = clip; }); } else { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)"bell2.ogg не найден — для темы 2 будет использоваться обычный колокол."); } } if (File.Exists(Path.Combine(pluginDirectory, "music2.ogg"))) { yield return LoadAudioClipCoroutine("music2.ogg", delegate(AudioClip clip) { musicClip2 = clip; }); } else { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogInfo((object)"music2.ogg не найден — музыка темы 2 недоступна (откат на тему 1)."); } } ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogInfo((object)"Загрузка ассетов завершена."); } } private Sprite LoadSpriteFromFile(string fileName) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown //IL_0097: 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) string text = Path.Combine(pluginDirectory, fileName); if (!File.Exists(text)) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Файл не найден: " + text)); } return null; } try { byte[] array = File.ReadAllBytes(text); Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false); ((Texture)val).wrapMode = (TextureWrapMode)1; if (!ImageConversion.LoadImage(val, array)) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)("Не удалось декодировать " + fileName)); } return null; } RemoveWhiteFringe(val, fileName); Sprite result = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f)); ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogInfo((object)$"Картинка загружена: {((Texture)val).width}x{((Texture)val).height} px ({fileName})"); } return result; } catch (Exception arg) { ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogError((object)$"Ошибка загрузки {fileName}: {arg}"); } return null; } } private void RemoveWhiteFringe(Texture2D tex, string fileName) { //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) //IL_001c: 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_0032: 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_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) Color[] pixels = tex.GetPixels(); int num = 0; int num2 = 0; for (int i = 0; i < pixels.Length; i += 97) { Color val = pixels[i]; num++; if (val.g > val.r + 0.08f || val.b > val.r + 0.08f) { num2++; } } if (num != 0 && !((float)num2 / (float)num < 0.02f)) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)(fileName + ": полноцветное изображение — очистка белой каймы не применяется (только Clamp).")); } return; } for (int j = 0; j < pixels.Length; j++) { Color val2 = pixels[j]; float num3 = Mathf.Max(val2.g, val2.b); val2.r = Mathf.Max(0f, val2.r - num3); val2.g = 0f; val2.b = 0f; val2.a *= 1f - num3; pixels[j] = val2; } tex.SetPixels(pixels); tex.Apply(); } private IEnumerator LoadAudioClipCoroutine(string fileName, Action<AudioClip> onLoaded) { string text = Path.Combine(pluginDirectory, fileName); if (!File.Exists(text)) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Аудио-файл не найден: " + text)); } yield break; } string text2 = "file://" + text.Replace("\\", "/"); UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip(text2, (AudioType)14); try { yield return www.SendWebRequest(); if ((int)www.result != 1) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)("Ошибка загрузки " + fileName + ": " + www.error)); } yield break; } AudioClip content = DownloadHandlerAudioClip.GetContent(www); ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogInfo((object)$"Аудио загружено: {fileName} ({content.length:F1} сек)"); } onLoaded?.Invoke(content); } finally { ((IDisposable)www)?.Dispose(); } } private void Update() { try { UpdateInternal(); } catch (Exception arg) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogError((object)$"Ошибка в Update() (игра не затронута): {arg}"); } } } private void UpdateInternal() { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { if ((Object)(object)musicSource != (Object)null && musicSource.isPlaying) { musicSource.Stop(); roundActive = false; } return; } Keyboard current = Keyboard.current; if (current != null && instance.connectedPlayersAmount == 0 && instance.shipHasLanded && !instance.inShipPhase) { if (((ButtonControl)current.f10Key).wasPressedThisFrame) { bool isPressed = ((ButtonControl)current.leftShiftKey).isPressed; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)$"F10: принудительный тест эффекта 'Последний выживший' (тема {((!isPressed) ? 1 : 2)})."); } TriggerLastSurvivor(isPressed); } if (((ButtonControl)current.f11Key).wasPressedThisFrame) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)"F11: принудительное затухание музыки."); } StopMusicWithFade(); } } if (roundActive) { if (!hasTriggeredThisRound && instance.livingPlayers == 1) { hasTriggeredThisRound = true; TriggerLastSurvivor(); } else if (hasTriggeredThisRound && instance.livingPlayers == 0) { roundActive = false; StopMusicWithFade(); } } } public void NotifyRoundStarted() { hasTriggeredThisRound = false; StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { roundActive = false; return; } int num = instance.connectedPlayersAmount + 1; int currentQuota = GetCurrentQuota(); bool flag = num >= 2; int num2 = ((num > cfgLargeLobbyThreshold.Value) ? 1 : cfgMinQuotaNormal.Value); bool flag2 = currentQuota >= num2; roundActive = flag && flag2; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)($"Раунд начался (двери открыты). Игроков: {num}, квота: {currentQuota} " + $"(порог: {num2}). Мультиплеер: {flag}, квота подходит: {flag2}. " + $"Мод активен: {roundActive}")); } } private int GetCurrentQuota() { if (!((Object)(object)TimeOfDay.Instance != (Object)null)) { return 1; } return TimeOfDay.Instance.timesFulfilledQuota + 1; } public void NotifyRoundEnding() { roundActive = false; StopMusicWithFade(); } public void NotifyDisconnectedFromGame() { roundActive = false; hasTriggeredThisRound = false; if (musicFadeCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(musicFadeCoroutine); musicFadeCoroutine = null; } if (animCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(animCoroutine); animCoroutine = null; } if ((Object)(object)musicSource != (Object)null) { musicSource.Stop(); } if ((Object)(object)sfxSource != (Object)null) { sfxSource.Stop(); } if ((Object)(object)survivorCanvasGroup != (Object)null) { survivorCanvasGroup.alpha = 0f; } ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"Отключение от игры: все эффекты немедленно остановлены, состояние раунда сброшено."); } } private void TriggerLastSurvivor(bool forceTheme2 = false) { int currentQuota = GetCurrentQuota(); bool flag = forceTheme2 || currentQuota >= cfgTheme2Quota.Value; Sprite val = ((flag && (Object)(object)survivorSprite2 != (Object)null) ? survivorSprite2 : survivorSprite); AudioClip val2 = ((flag && (Object)(object)bellClip2 != (Object)null) ? bellClip2 : bellClip); AudioClip val3 = ((flag && (Object)(object)musicClip2 != (Object)null) ? musicClip2 : musicClip); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)($"Последний выживший! Квота: {currentQuota}, тема: {((!flag) ? 1 : 2)} " + "(картинка: " + (((Object)(object)val != (Object)null) ? "есть" : "нет") + ", музыка: " + (((Object)(object)val3 != (Object)null) ? "есть" : "нет") + "). Запускаем колокол, музыку и анимацию.")); } if ((Object)(object)survivorImage != (Object)null && (Object)(object)val != (Object)null) { survivorImage.sprite = val; } if ((Object)(object)sfxSource != (Object)null && (Object)(object)val2 != (Object)null) { sfxSource.volume = 0.9f; sfxSource.clip = val2; sfxSource.Play(); } if ((Object)(object)musicSource != (Object)null && (Object)(object)val3 != (Object)null) { musicSource.volume = 0.7f; musicSource.clip = val3; musicSource.loop = true; musicSource.Play(); } if (animCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(animCoroutine); } animCoroutine = ((MonoBehaviour)this).StartCoroutine(SurvivorImageAnimation()); } private IEnumerator SurvivorImageAnimation() { if ((Object)(object)survivorRect == (Object)null || (Object)(object)survivorCanvasGroup == (Object)null) { yield break; } Vector3 startScale = Vector3.one * 1.1f; Vector3 endScale = Vector3.one * 1.45f; ((Transform)survivorRect).localScale = startScale; survivorCanvasGroup.alpha = 0f; float elapsed = 0f; while (elapsed < 4f) { elapsed += Time.unscaledDeltaTime; float num = Mathf.Clamp01(elapsed / 4f); ((Transform)survivorRect).localScale = Vector3.Lerp(startScale, endScale, num); float alpha; if (elapsed <= 0.4f) { alpha = Mathf.Lerp(0f, 1f, elapsed / 0.4f); } else if (elapsed <= 1.5f) { alpha = 1f; } else { float num2 = (elapsed - 0.4f - 1.1f) / 2.5f; alpha = Mathf.Lerp(1f, 0f, num2); } survivorCanvasGroup.alpha = alpha; yield return null; } ((Transform)survivorRect).localScale = endScale; survivorCanvasGroup.alpha = 0f; animCoroutine = null; } public void StopMusicWithFade() { if (!((Object)(object)musicSource == (Object)null)) { if (!musicSource.isPlaying) { musicSource.volume = 0.7f; } else if (musicFadeCoroutine == null) { musicFadeCoroutine = ((MonoBehaviour)this).StartCoroutine(MusicFadeOut()); } } } private IEnumerator MusicFadeOut() { float startVolume = musicSource.volume; float elapsed = 0f; while (elapsed < 3f) { elapsed += Time.unscaledDeltaTime; float num = Mathf.Clamp01(elapsed / 3f); musicSource.volume = Mathf.Lerp(startVolume, 0f, num); yield return null; } musicSource.Stop(); musicSource.volume = 0.7f; musicFadeCoroutine = null; } } public static class MyPluginInfo { public const string PLUGIN_GUID = "LastSurvivorMod"; public const string PLUGIN_NAME = "LastSurvivorMod"; public const string PLUGIN_VERSION = "1.0.0"; } }