using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using GameNetcodeStuff;
using HarmonyLib;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Video;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("LitanyEntity")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("LitanyEntity")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("21440c59-62c0-4f6c-ade7-0e1d5c5b2345")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace LitanyEntity;
public class LitanyBehaviour : MonoBehaviour
{
private enum LitanyState
{
Hidden,
Preparing,
Warning,
EyeOpen,
BetweenEyes,
Screamer
}
private AudioSource idleSource;
private AudioSource screamsSource;
private AudioSource oneShotSource;
private AudioClip idleClip;
private AudioClip preparaClip;
private AudioClip[] openClips = (AudioClip[])(object)new AudioClip[3];
private AudioClip screamsClip;
private VideoPlayer videoPlayer;
private RawImage videoImage;
private RenderTexture videoTexture;
private bool videoFinished = false;
private LitanyState currentState = LitanyState.Hidden;
private float timer = 0f;
private float nextAppearTime = 0f;
private int currentEye = 0;
private float shakeTimer = 0f;
private float shakeSpeed = 0.08f;
private bool isOnCooldown = false;
private float cooldownTimer = 0f;
private float cooldownTime = 0f;
private GameObject rootObject;
private Image bodyImage;
private Image eyeImage;
private Sprite bodySprites;
private Sprite eyesClosedSprite;
private Sprite[] eyeWarningSprites = (Sprite[])(object)new Sprite[3];
private Sprite[] eyeOpenedSprites = (Sprite[])(object)new Sprite[3];
private float eyeAnimTimer = 0f;
private int eyeAnimFrame = 0;
private Vector2 basePosition;
private string modFolder;
private void Start()
{
modFolder = Path.Combine(Paths.PluginPath, "LitanyEntity");
string[] directories = Directory.GetDirectories(Paths.PluginPath, "LitanyEntity", SearchOption.AllDirectories);
if (directories.Length != 0)
{
modFolder = directories[0];
}
LoadSprites();
CreateOverlay();
LoadAudio();
nextAppearTime = Random.Range(Plugin.MinSpawnTime.Value, Plugin.MaxSpawnTime.Value);
}
private void LoadAudio()
{
idleClip = LoadAudioClip("idle.mp3");
preparaClip = LoadAudioClip("prepare.mp3");
openClips[0] = LoadAudioClip("open1.mp3");
openClips[1] = LoadAudioClip("open2.mp3");
openClips[2] = LoadAudioClip("open3.mp3");
screamsClip = LoadAudioClip("screams.mp3");
idleSource = ((Component)this).gameObject.AddComponent<AudioSource>();
idleSource.loop = true;
idleSource.playOnAwake = false;
screamsSource = ((Component)this).gameObject.AddComponent<AudioSource>();
screamsSource.loop = true;
screamsSource.playOnAwake = false;
oneShotSource = ((Component)this).gameObject.AddComponent<AudioSource>();
oneShotSource.loop = false;
oneShotSource.playOnAwake = false;
}
private AudioClip LoadAudioClip(string filename)
{
//IL_003c: Unknown result type (might be due to invalid IL or missing references)
//IL_0042: Expected O, but got Unknown
string text = Path.Combine(modFolder, filename);
if (!File.Exists(text))
{
Debug.LogError((object)("Litany Entity: звук не найден — " + text));
return null;
}
WWW val = new WWW("file://" + text);
try
{
while (!val.isDone)
{
}
return val.GetAudioClip(false, false, (AudioType)13);
}
finally
{
((IDisposable)val)?.Dispose();
}
}
private void LoadSprites()
{
bodySprites = LoadSprite("body.png");
eyesClosedSprite = LoadSprite("eyes_closed.png");
for (int i = 0; i < 3; i++)
{
eyeWarningSprites[i] = LoadSprite($"eye{i + 1}_warning.png");
eyeOpenedSprites[i] = LoadSprite($"eye{i + 1}_opened.png");
}
}
private Sprite LoadSprite(string filename)
{
//IL_003b: Unknown result type (might be due to invalid IL or missing references)
//IL_0041: Expected O, but got Unknown
//IL_0062: Unknown result type (might be due to invalid IL or missing references)
//IL_0071: Unknown result type (might be due to invalid IL or missing references)
string text = Path.Combine(modFolder, filename);
if (!File.Exists(text))
{
Debug.LogError((object)("Litany Entity: file not found — " + text));
return null;
}
byte[] array = File.ReadAllBytes(text);
Texture2D val = new Texture2D(2, 2);
ImageConversion.LoadImage(val, array);
return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f));
}
private void Update()
{
PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController;
if ((Object)(object)val == (Object)null)
{
return;
}
if (currentState != 0)
{
AnimateBody();
}
if (isOnCooldown)
{
cooldownTimer += Time.deltaTime;
if (cooldownTimer >= cooldownTime)
{
isOnCooldown = false;
cooldownTimer = 0f;
timer = 0f;
nextAppearTime = Random.Range(Plugin.MinSpawnTime.Value, Plugin.MaxSpawnTime.Value);
}
}
else if (val.isPlayerDead)
{
ForceHide();
}
else
{
if (!IsPlayerInGame())
{
return;
}
timer += Time.deltaTime;
switch (currentState)
{
case LitanyState.Hidden:
if (timer >= nextAppearTime)
{
Appear();
}
break;
case LitanyState.Preparing:
if (timer >= Plugin.PrepareDuration.Value)
{
StartWarning();
}
break;
case LitanyState.Warning:
AnimateWarning();
if (timer >= Plugin.WarningDuration.Value)
{
OpenEye();
}
break;
case LitanyState.EyeOpen:
if (!IsPlayerCrouching())
{
TriggerDeath();
}
else if (timer >= Plugin.OpenEyeDuration.Value)
{
CloseEye();
}
break;
case LitanyState.BetweenEyes:
if (timer >= Plugin.BetweenEyesDuration.Value)
{
if (currentEye >= 3)
{
Hide();
}
else
{
StartWarning();
}
}
break;
case LitanyState.Screamer:
Debug.Log((object)$"Litany: Screamer state, videoFinished={videoFinished}");
if (videoFinished)
{
KillPlayer();
}
break;
}
}
}
private void AnimateBody()
{
//IL_008c: Unknown result type (might be due to invalid IL or missing references)
//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
shakeTimer += Time.deltaTime;
if (shakeTimer >= shakeSpeed)
{
shakeTimer = 0f;
float num = Random.Range(-10f, 10f);
float num2 = Random.Range(-10f, 10f);
float num3 = Random.Range(-6f, 6f);
rootObject.GetComponent<RectTransform>().anchoredPosition = new Vector2(basePosition.x + num, basePosition.y + num2);
((Transform)rootObject.GetComponent<RectTransform>()).localRotation = Quaternion.Euler(0f, 0f, num3);
}
}
private void AnimateWarning()
{
eyeAnimTimer += Time.deltaTime;
if (eyeAnimTimer >= 0.15f)
{
eyeAnimTimer = 0f;
eyeAnimFrame = (eyeAnimFrame + 1) % 2;
eyeImage.sprite = ((eyeAnimFrame == 0) ? eyesClosedSprite : eyeWarningSprites[currentEye]);
}
}
private void Appear()
{
//IL_0047: Unknown result type (might be due to invalid IL or missing references)
//IL_0055: Unknown result type (might be due to invalid IL or missing references)
//IL_005a: Unknown result type (might be due to invalid IL or missing references)
currentState = LitanyState.Preparing;
timer = 0f;
currentEye = 0;
float num = Random.Range(-500f, 500f);
float num2 = Random.Range(-250f, 250f);
rootObject.GetComponent<RectTransform>().anchoredPosition = new Vector2(num, num2);
basePosition = new Vector2(num, num2);
rootObject.SetActive(true);
if ((Object)(object)bodySprites != (Object)null)
{
bodyImage.sprite = bodySprites;
}
if ((Object)(object)eyesClosedSprite != (Object)null)
{
eyeImage.sprite = eyesClosedSprite;
}
if ((Object)(object)idleClip != (Object)null)
{
idleSource.clip = idleClip;
idleSource.Play();
}
}
private void StartWarning()
{
currentState = LitanyState.Warning;
timer = 0f;
eyeAnimFrame = 0;
eyeAnimTimer = 0f;
if ((Object)(object)preparaClip != (Object)null)
{
oneShotSource.clip = preparaClip;
oneShotSource.Play();
}
}
private void OpenEye()
{
currentState = LitanyState.EyeOpen;
timer = 0f;
if ((Object)(object)eyeOpenedSprites[currentEye] != (Object)null)
{
eyeImage.sprite = eyeOpenedSprites[currentEye];
}
if ((Object)(object)openClips[currentEye] != (Object)null)
{
oneShotSource.Stop();
oneShotSource.clip = openClips[currentEye];
oneShotSource.Play();
}
if (currentEye == 0 && (Object)(object)screamsClip != (Object)null)
{
screamsSource.clip = screamsClip;
screamsSource.Play();
}
}
private void CloseEye()
{
//IL_0078: 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)
currentEye++;
if (currentEye >= 3)
{
Hide();
return;
}
if ((Object)(object)eyesClosedSprite != (Object)null)
{
eyeImage.sprite = eyesClosedSprite;
}
float num = Random.Range(-500f, 500f);
float num2 = Random.Range(-250f, 250f);
rootObject.GetComponent<RectTransform>().anchoredPosition = new Vector2(num, num2);
basePosition = new Vector2(num, num2);
currentState = LitanyState.BetweenEyes;
timer = 0f;
}
private void TriggerDeath()
{
//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
currentState = LitanyState.Screamer;
timer = 0f;
videoFinished = false;
rootObject.SetActive(false);
string text = Path.Combine(modFolder, "Death.mp4");
Debug.Log((object)("Litany Entity: видео путь — " + text));
Debug.Log((object)$"Litany Entity: файл существует — {File.Exists(text)}");
videoPlayer.url = "file://" + text;
videoPlayer.Play();
((Graphic)videoImage).color = new Color(1f, 1f, 1f, 1f);
}
private void KillPlayer()
{
//IL_0067: Unknown result type (might be due to invalid IL or missing references)
//IL_002a: Unknown result type (might be due to invalid IL or missing references)
//IL_0034: Unknown result type (might be due to invalid IL or missing references)
//IL_003a: Unknown result type (might be due to invalid IL or missing references)
Debug.Log((object)"Litany Entity: KillPlayer вызван!");
PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController;
if ((Object)(object)val != (Object)null)
{
val.KillPlayer(Vector3.zero, true, (CauseOfDeath)0, 1, default(Vector3));
}
videoPlayer.Stop();
((Graphic)videoImage).color = new Color(1f, 1f, 1f, 0f);
rootObject.SetActive(false);
Hide();
idleSource.Stop();
screamsSource.Stop();
oneShotSource.Stop();
}
private void Hide()
{
currentState = LitanyState.Hidden;
timer = 0f;
rootObject.SetActive(false);
isOnCooldown = true;
cooldownTimer = 0f;
cooldownTime = Plugin.CooldownTime.Value;
idleSource.Stop();
screamsSource.Stop();
oneShotSource.Stop();
}
private void ForceHide()
{
currentState = LitanyState.Hidden;
timer = 0f;
rootObject.SetActive(false);
isOnCooldown = false;
nextAppearTime = Random.Range(Plugin.MinSpawnTime.Value, Plugin.MaxSpawnTime.Value);
}
public void ResetLitany()
{
currentState = LitanyState.Hidden;
timer = 0f;
nextAppearTime = Random.Range(Plugin.MinSpawnTime.Value, Plugin.MaxSpawnTime.Value);
rootObject.SetActive(false);
isOnCooldown = false;
cooldownTimer = 0f;
}
private bool IsPlayerCrouching()
{
PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController;
if ((Object)(object)val == (Object)null)
{
return true;
}
return val.isCrouching;
}
private void OnVideoFinished(VideoPlayer vp)
{
Debug.Log((object)"Litany Entity: видео закончилось!");
videoFinished = true;
}
private bool IsPlayerInGame()
{
PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController;
if ((Object)(object)val == (Object)null)
{
return false;
}
if (val.isPlayerDead)
{
return false;
}
return !val.isInHangarShipRoom && (Object)(object)StartOfRound.Instance != (Object)null && StartOfRound.Instance.shipHasLanded;
}
private void CreateOverlay()
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_000c: Expected O, but got Unknown
//IL_0031: Unknown result type (might be due to invalid IL or missing references)
//IL_003b: Expected O, but got Unknown
//IL_006a: Unknown result type (might be due to invalid IL or missing references)
//IL_0080: Unknown result type (might be due to invalid IL or missing references)
//IL_0096: Unknown result type (might be due to invalid IL or missing references)
//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
//IL_00b8: Expected O, but got Unknown
//IL_00e6: 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_0100: Unknown result type (might be due to invalid IL or missing references)
//IL_0110: Unknown result type (might be due to invalid IL or missing references)
//IL_0117: Expected O, but got Unknown
//IL_0152: Unknown result type (might be due to invalid IL or missing references)
//IL_0169: Unknown result type (might be due to invalid IL or missing references)
//IL_0180: Unknown result type (might be due to invalid IL or missing references)
//IL_018d: Unknown result type (might be due to invalid IL or missing references)
//IL_019d: Unknown result type (might be due to invalid IL or missing references)
//IL_01a4: Expected O, but got Unknown
//IL_01eb: Unknown result type (might be due to invalid IL or missing references)
//IL_01f5: Expected O, but got Unknown
//IL_0214: Unknown result type (might be due to invalid IL or missing references)
//IL_021e: Expected O, but got Unknown
//IL_0224: Unknown result type (might be due to invalid IL or missing references)
//IL_022b: Expected O, but got Unknown
//IL_0278: Unknown result type (might be due to invalid IL or missing references)
//IL_028e: Unknown result type (might be due to invalid IL or missing references)
//IL_029b: Unknown result type (might be due to invalid IL or missing references)
//IL_02a8: Unknown result type (might be due to invalid IL or missing references)
GameObject val = new GameObject("LitanyCanvas");
Canvas val2 = val.AddComponent<Canvas>();
val2.renderMode = (RenderMode)0;
val2.sortingOrder = 100;
Object.DontDestroyOnLoad((Object)(object)val);
rootObject = new GameObject("LitanyRoot");
rootObject.transform.SetParent(val.transform, false);
RectTransform val3 = rootObject.AddComponent<RectTransform>();
val3.anchorMin = new Vector2(0.5f, 0.5f);
val3.anchorMax = new Vector2(0.5f, 0.5f);
val3.sizeDelta = new Vector2(500f, 500f);
val3.anchoredPosition = Vector2.zero;
GameObject val4 = new GameObject("LitanyBody");
val4.transform.SetParent(rootObject.transform, false);
bodyImage = val4.AddComponent<Image>();
RectTransform component = val4.GetComponent<RectTransform>();
component.anchorMin = Vector2.zero;
component.anchorMax = Vector2.one;
component.sizeDelta = Vector2.zero;
GameObject val5 = new GameObject("LitanyEyes");
val5.transform.SetParent(rootObject.transform, false);
eyeImage = val5.AddComponent<Image>();
RectTransform component2 = val5.GetComponent<RectTransform>();
component2.anchorMin = new Vector2(0.5f, 0.5f);
component2.anchorMax = new Vector2(0.5f, 0.5f);
component2.sizeDelta = new Vector2(500f, 500f);
component2.anchoredPosition = Vector2.zero;
GameObject val6 = new GameObject("LitanyVideoPlayer");
val6.transform.SetParent(val.transform, false);
videoPlayer = val6.AddComponent<VideoPlayer>();
videoPlayer.playOnAwake = false;
videoPlayer.renderMode = (VideoRenderMode)2;
videoTexture = new RenderTexture(1920, 1080, 0);
videoPlayer.targetTexture = videoTexture;
videoPlayer.loopPointReached += new EventHandler(OnVideoFinished);
GameObject val7 = new GameObject("LitanyVideo");
val7.transform.SetParent(val.transform, false);
videoImage = val7.AddComponent<RawImage>();
videoImage.texture = (Texture)(object)videoTexture;
((Graphic)videoImage).color = new Color(1f, 1f, 1f, 0f);
RectTransform component3 = val7.GetComponent<RectTransform>();
component3.anchorMin = Vector2.zero;
component3.anchorMax = Vector2.one;
component3.sizeDelta = Vector2.zero;
rootObject.SetActive(false);
}
}
[HarmonyPatch(typeof(PlayerControllerB), "Start")]
public class LitanyPatch
{
private static void Postfix(PlayerControllerB __instance)
{
//IL_005e: Unknown result type (might be due to invalid IL or missing references)
//IL_0064: Expected O, but got Unknown
if (((NetworkBehaviour)__instance).IsOwner && !((Object)(object)Object.FindObjectOfType<LitanyBehaviour>() != (Object)null))
{
int num = Random.Range(0, 100);
if (num >= Plugin.SpawnChance.Value)
{
Debug.Log((object)$"Litany Entity: won't appear this round (roll {num})");
return;
}
GameObject val = new GameObject("LitanyManager");
val.AddComponent<LitanyBehaviour>();
Object.DontDestroyOnLoad((Object)(object)val);
Debug.Log((object)"Litany Entity: manager created!");
}
}
}
[HarmonyPatch(typeof(PlayerControllerB), "SpawnDeadAnimation")]
public class LitanyRespawnPatch
{
private static void Postfix(PlayerControllerB __instance)
{
if (((NetworkBehaviour)__instance).IsOwner)
{
LitanyBehaviour litanyBehaviour = Object.FindObjectOfType<LitanyBehaviour>();
if ((Object)(object)litanyBehaviour != (Object)null)
{
litanyBehaviour.ResetLitany();
Debug.Log((object)"Litany Entity: reset after respawn!");
}
}
}
}
[BepInPlugin("com.yourname.litanyentity", "Litany Entity", "1.0.0")]
public class Plugin : BaseUnityPlugin
{
private readonly Harmony harmony = new Harmony("com.yourname.litanyentity");
public static ConfigEntry<int> SpawnChance;
public static ConfigEntry<float> PrepareDuration;
public static ConfigEntry<float> WarningDuration;
public static ConfigEntry<float> OpenEyeDuration;
public static ConfigEntry<float> BetweenEyesDuration;
public static ConfigEntry<float> MinSpawnTime;
public static ConfigEntry<float> MaxSpawnTime;
public static ConfigEntry<float> CooldownTime;
private void Awake()
{
SpawnChance = ((BaseUnityPlugin)this).Config.Bind<int>("General", "SpawnChance", 35, "Chance of Litany appearing each round (0-100)");
PrepareDuration = ((BaseUnityPlugin)this).Config.Bind<float>("Timing", "PrepareDuration", 4f, "Time before first eye warning");
WarningDuration = ((BaseUnityPlugin)this).Config.Bind<float>("Timing", "WarningDuration", 1.5f, "Duration of warning animation");
OpenEyeDuration = ((BaseUnityPlugin)this).Config.Bind<float>("Timing", "OpenEyeDuration", 0.5f, "How long eye stays open");
BetweenEyesDuration = ((BaseUnityPlugin)this).Config.Bind<float>("Timing", "BetweenEyesDuration", 2f, "Time between each eye attack");
MinSpawnTime = ((BaseUnityPlugin)this).Config.Bind<float>("Timing", "MinSpawnTime", 10f, "Minimum time before Litany appears");
MaxSpawnTime = ((BaseUnityPlugin)this).Config.Bind<float>("Timing", "MaxSpawnTime", 30f, "Maximum time before Litany appears");
CooldownTime = ((BaseUnityPlugin)this).Config.Bind<float>("Timing", "CooldownTime", 60f, "Cooldown time in seconds before Litany can appear again");
((BaseUnityPlugin)this).Logger.LogInfo((object)"Litany Entity loaded!");
harmony.PatchAll();
}
}