using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Jotunn.Extensions; using Jotunn.Managers; using Jotunn.Utils; 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("GlitnirFaderEnding")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("GlitnirFaderEnding")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("3e8757f3-4e74-4e03-9abb-82f3a0280cc2")] [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 Glitnir.FaderEnding; [BepInPlugin("glitnir.faderending", "Glitnir Fader Ending", "0.2.2")] [BepInDependency("com.jotunn.jotunn", "2.29.0")] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] [SynchronizationMode(/*Could not decode attribute arguments.*/)] public class GlitnirFaderEndingPlugin : BaseUnityPlugin { [HarmonyPatch(typeof(Character), "OnDeath")] private static class Character_OnDeath_Patch { [HarmonyPrefix] private static void Prefix(Character __instance) { if ((Object)(object)Instance != (Object)null) { Instance.TryHandleBossDeath(__instance); } } } [HarmonyPatch(typeof(Player), "TakeInput")] private static class Player_TakeInput_Patch { [HarmonyPrefix] private static bool Prefix(Player __instance, ref bool __result) { if (!LockPlayerInput.Value || !EndingOverlay.IsActive) { return true; } if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer) { __result = false; return false; } return true; } } [CompilerGenerated] private sealed class d__64 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public long killerPlayerId; public string killerName; public Vector3 bossDeathPosition; public List> drops; public GlitnirFaderEndingPlugin <>4__this; private Player 5__1; private float 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__64(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__1 = null; <>1__state = -2; } private bool MoveNext() { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown //IL_00bb: 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_011a: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(Mathf.Max(0f, TeleportDelaySeconds.Value)); <>1__state = 1; return true; case 1: <>1__state = -1; 5__1 = Player.m_localPlayer; if ((Object)(object)5__1 == (Object)null) { return false; } if (killerPlayerId != 0L && 5__1.GetPlayerID() != killerPlayerId) { LogDebug("Instância local não é o jogador vencedor."); return false; } <>4__this.ApplyAuthoritativeVictoryToPlayer(5__1, bossDeathPosition, drops); if (!EndingEnabled.Value) { break; } 5__2 = Mathf.Max(0f, DelayBeforeEndingVideoSeconds.Value); if (5__2 > 0f) { <>2__current = (object)new WaitForSeconds(5__2); <>1__state = 2; return true; } goto IL_012b; case 2: { <>1__state = -1; goto IL_012b; } IL_012b: <>4__this.BroadcastVictoryToClients(5__1.GetPlayerID()); break; } LogDebug($"Vitória aplicada localmente para {killerName} ({killerPlayerId}) com teleporte em ({TeleportX.Value}, {TeleportY.Value}, {TeleportZ.Value})."); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__63 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public long killerPlayerId; public string killerName; public Vector3 bossDeathPosition; public List> drops; public GlitnirFaderEndingPlugin <>4__this; private Player 5__1; private float 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__63(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__1 = null; <>1__state = -2; } private bool MoveNext() { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(Mathf.Max(0f, TeleportDelaySeconds.Value)); <>1__state = 1; return true; case 1: <>1__state = -1; 5__1 = FindPlayerById(killerPlayerId); if ((Object)(object)5__1 == (Object)null) { LogWarning($"Não foi possível encontrar o jogador vencedor no servidor. PlayerID={killerPlayerId}"); return false; } <>4__this.ApplyAuthoritativeVictoryToPlayer(5__1, bossDeathPosition, drops); if (!EndingEnabled.Value) { break; } 5__2 = Mathf.Max(0f, DelayBeforeEndingVideoSeconds.Value); if (5__2 > 0f) { <>2__current = (object)new WaitForSeconds(5__2); <>1__state = 2; return true; } goto IL_0117; case 2: { <>1__state = -1; goto IL_0117; } IL_0117: <>4__this.BroadcastVictoryToClients(killerPlayerId); break; } LogDebug($"Vitória aplicada no servidor para {killerName} ({killerPlayerId}) com teleporte em ({TeleportX.Value}, {TeleportY.Value}, {TeleportZ.Value})."); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public const string PluginGuid = "glitnir.faderending"; public const string PluginName = "Glitnir Fader Ending"; public const string PluginVersion = "0.2.2"; internal const string RpcVictory = "glitnir.faderending.victory"; internal static GlitnirFaderEndingPlugin Instance; internal static Harmony HarmonyInstance; private bool _rpcRegistered; private const string SectionGeneral = "General"; private const string SectionTrigger = "Trigger"; private const string SectionTeleport = "Teleport"; private const string SectionLoot = "Loot"; private const string SectionEnding = "Ending"; private const string SectionVideo = "Video"; private const string SectionFinalize = "Finalize"; internal static ConfigEntry ModEnabled; internal static ConfigEntry DebugLogging; internal static ConfigEntry BossPrefabName; internal static ConfigEntry MatchPrefabNameExactly; internal static ConfigEntry RequirePlayerAsKiller; internal static ConfigEntry TeleportEnabled; internal static ConfigEntry TeleportX; internal static ConfigEntry TeleportY; internal static ConfigEntry TeleportZ; internal static ConfigEntry TeleportYaw; internal static ConfigEntry TeleportDelaySeconds; internal static ConfigEntry ResetVelocityAfterTeleport; internal static ConfigEntry LootToInventoryEnabled; internal static ConfigEntry OverflowModeConfig; internal static ConfigEntry DisableVanillaWorldDrops; internal static ConfigEntry ShowLootDebugLog; internal static ConfigEntry EndingEnabled; internal static ConfigEntry LockPlayerInput; internal static ConfigEntry AllowSkip; internal static ConfigEntry SkipKey; internal static ConfigEntry FadeInSeconds; internal static ConfigEntry FadeOutSeconds; internal static ConfigEntry MuteGameAudioWhileEnding; internal static ConfigEntry DelayBeforeEndingVideoSeconds; internal static ConfigEntry MinimumSkipDelaySeconds; internal static ConfigEntry VideoEnabled; internal static ConfigEntry UseEmbeddedVideo; internal static ConfigEntry EmbeddedVideoResourceName; internal static ConfigEntry EmbeddedVideoOutputFileName; internal static ConfigEntry VideoPath; internal static ConfigEntry LoopVideo; internal static ConfigEntry VideoVolume; internal static ConfigEntry MuteVideoAudio; internal static ConfigEntry HoldOnLastFrameSeconds; internal static ConfigEntry MaxVideoDurationSeconds; internal static ConfigEntry AutoCloseEnding; internal static ConfigEntry KeepPlayerLockedUntilEnd; internal static ConfigEntry IgnoreDuplicateVictoryEvents; private static readonly FieldInfo LastHitField = AccessTools.Field(typeof(Character), "m_lastHit"); private static readonly FieldInfo DropsEnabledField = AccessTools.Field(typeof(CharacterDrop), "m_dropsEnabled"); private void Awake() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown Instance = this; BindConfig(); HarmonyInstance = new Harmony("glitnir.faderending"); HarmonyInstance.PatchAll(Assembly.GetExecutingAssembly()); SynchronizationManager.OnConfigurationSynchronized += delegate(object _, ConfigurationSynchronizationEventArgs args) { if (DebugLogging != null && DebugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"[DEBUG] Config sync recebido. Initial={args.InitialSynchronization} | Teleport=({TeleportX.Value}, {TeleportY.Value}, {TeleportZ.Value})"); } }; LogInfo("Glitnir Fader Ending 0.2.2 carregado."); } private void Update() { EnsureRpcRegistered(); } private void OnDestroy() { if (HarmonyInstance != null) { HarmonyInstance.UnpatchSelf(); } } private ConfigEntry BindSynced(string section, string key, T defaultValue, string description, AcceptableValueBase acceptable = null) { return ConfigFileExtensions.BindConfigInOrder(((BaseUnityPlugin)this).Config, section, key, defaultValue, description, true, true, true, acceptable, (Action)null, (ConfigurationManagerAttributes)null); } private ConfigEntry BindLocal(string section, string key, T defaultValue, string description, AcceptableValueBase acceptable = null) { return ConfigFileExtensions.BindConfigInOrder(((BaseUnityPlugin)this).Config, section, key, defaultValue, description, false, true, true, acceptable, (Action)null, (ConfigurationManagerAttributes)null); } private void BindConfig() { ModEnabled = BindSynced("General", "ModEnabled", defaultValue: true, "Liga ou desliga completamente o mod."); DebugLogging = BindLocal("General", "DebugLogging", defaultValue: false, "Ativa logs extras no console local."); BossPrefabName = BindSynced("Trigger", "BossPrefabName", "Fader", "Nome do prefab do boss que dispara a sequência."); MatchPrefabNameExactly = BindSynced("Trigger", "MatchPrefabNameExactly", defaultValue: true, "Se true, compara o nome do prefab exatamente. Se false, usa Contains."); RequirePlayerAsKiller = BindSynced("Trigger", "RequirePlayerAsKiller", defaultValue: true, "Se true, a sequência só dispara se o golpe final vier de um Player."); TeleportEnabled = BindSynced("Teleport", "TeleportEnabled", defaultValue: true, "Teleporta o jogador vencedor para um ponto seguro."); TeleportX = BindSynced("Teleport", "TeleportX", 0f, "Coordenada X do teleporte."); TeleportY = BindSynced("Teleport", "TeleportY", 100f, "Coordenada Y do teleporte."); TeleportZ = BindSynced("Teleport", "TeleportZ", 0f, "Coordenada Z do teleporte."); TeleportYaw = BindSynced("Teleport", "TeleportYaw", 180f, "Rotação Y do jogador após o teleporte.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 360f)); TeleportDelaySeconds = BindSynced("Teleport", "TeleportDelaySeconds", 0.25f, "Delay entre a morte do boss e o teleporte.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f)); ResetVelocityAfterTeleport = BindSynced("Teleport", "ResetVelocityAfterTeleport", defaultValue: true, "Zera a velocidade do rigidbody do jogador depois do teleporte."); LootToInventoryEnabled = BindSynced("Loot", "LootToInventoryEnabled", defaultValue: true, "Envia os drops do Fader direto para o inventário do vencedor."); OverflowModeConfig = BindSynced("Loot", "OverflowModeConfig", "SpawnAtTeleportLocation", "Quando não couber no inventário: SpawnAtTeleportLocation | SpawnAtPlayer | SpawnAtBossDeath | Discard"); DisableVanillaWorldDrops = BindSynced("Loot", "DisableVanillaWorldDrops", defaultValue: true, "Desliga o drop vanilla no chão para evitar duplicação."); ShowLootDebugLog = BindLocal("Loot", "ShowLootDebugLog", defaultValue: false, "Log local detalhado do processamento dos drops."); EndingEnabled = BindSynced("Ending", "EndingEnabled", defaultValue: true, "Mostra a sequência final após matar o boss."); LockPlayerInput = BindSynced("Ending", "LockPlayerInput", defaultValue: true, "Bloqueia input do jogador enquanto a sequência final estiver ativa."); AllowSkip = BindLocal("Ending", "AllowSkip", defaultValue: true, "Permite pular a sequência final no cliente local."); SkipKey = BindLocal("Ending", "SkipKey", "Escape", "Tecla local para pular a sequência final."); FadeInSeconds = BindSynced("Ending", "FadeInSeconds", 1.5f, "Tempo de fade-in da tela.", (AcceptableValueBase)(object)new AcceptableValueRange(0.01f, 10f)); FadeOutSeconds = BindSynced("Ending", "FadeOutSeconds", 1.25f, "Tempo de fade-out da tela.", (AcceptableValueBase)(object)new AcceptableValueRange(0.01f, 10f)); MuteGameAudioWhileEnding = BindSynced("Ending", "MuteGameAudioWhileEnding", defaultValue: true, "Zera o áudio do jogo enquanto o vídeo final estiver rodando."); DelayBeforeEndingVideoSeconds = BindSynced("Ending", "DelayBeforeEndingVideoSeconds", 2f, "Tempo de espera após o teleporte antes de abrir o vídeo.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 15f)); MinimumSkipDelaySeconds = BindSynced("Ending", "MinimumSkipDelaySeconds", 3f, "Tempo mínimo antes de permitir pular o vídeo com Esc.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 30f)); VideoEnabled = BindSynced("Video", "VideoEnabled", defaultValue: true, "Liga o vídeo final."); UseEmbeddedVideo = BindSynced("Video", "UseEmbeddedVideo", defaultValue: true, "Se true, usa o vídeo embutido dentro da DLL."); EmbeddedVideoResourceName = BindSynced("Video", "EmbeddedVideoResourceName", "", "Nome exato do recurso embutido. Pode ficar vazio para autodetectar."); EmbeddedVideoOutputFileName = BindSynced("Video", "EmbeddedVideoOutputFileName", "glitnir_fader_video_embedded.mp4", "Nome do arquivo temporário extraído do recurso embutido."); VideoPath = BindSynced("Video", "VideoPath", "glitnir_fader_video.mp4", "Caminho do vídeo externo. Só é usado quando UseEmbeddedVideo = false."); LoopVideo = BindSynced("Video", "LoopVideo", defaultValue: false, "Se true, o vídeo fica em loop até o jogador pular."); VideoVolume = BindLocal("Video", "VideoVolume", 1f, "Volume local do vídeo.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f)); MuteVideoAudio = BindSynced("Video", "MuteVideoAudio", defaultValue: false, "Se true, o vídeo toca sem áudio."); HoldOnLastFrameSeconds = BindSynced("Video", "HoldOnLastFrameSeconds", 1f, "Quanto tempo segura o último frame antes de fechar.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f)); MaxVideoDurationSeconds = BindSynced("Video", "MaxVideoDurationSeconds", 600f, "Proteção para não ficar preso se o vídeo não reportar o fim corretamente.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 7200f)); AutoCloseEnding = BindSynced("Finalize", "AutoCloseEnding", defaultValue: true, "Fecha a UI ao fim do vídeo."); KeepPlayerLockedUntilEnd = BindSynced("Finalize", "KeepPlayerLockedUntilEnd", defaultValue: true, "Mantém input bloqueado até o fim total da sequência."); IgnoreDuplicateVictoryEvents = BindSynced("Finalize", "IgnoreDuplicateVictoryEvents", defaultValue: true, "Ignora RPCs duplicados do mesmo evento."); } private void EnsureRpcRegistered() { if (!_rpcRegistered && ZRoutedRpc.instance != null) { ZRoutedRpc.instance.Register("glitnir.faderending.victory", (Action)OnVictoryRpc); _rpcRegistered = true; LogDebug("ZRoutedRpc registrado."); } } private void OnVictoryRpc(long sender, ZPackage pkg) { try { if (!EndingEnabled.Value || (Object)(object)Player.m_localPlayer == (Object)null) { return; } long num = pkg.ReadLong(); if (Player.m_localPlayer.GetPlayerID() == num) { if (IgnoreDuplicateVictoryEvents.Value && EndingOverlay.IsActive) { LogDebug("RPC duplicado ignorado porque a sequência final já está ativa."); } else { EndingOverlay.Show(); } } } catch (Exception arg) { LogError($"Falha ao processar RPC de vitória: {arg}"); } } internal void TryHandleBossDeath(Character victim) { //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) try { if (!ModEnabled.Value || (Object)(object)victim == (Object)null || victim.IsPlayer() || !IsConfiguredBoss(victim)) { return; } if (!IsAuthoritativeForDeathHandling(victim)) { LogDebug("Instância não autoritativa para tratar morte do boss. Ignorando."); return; } CharacterDrop component = ((Component)victim).GetComponent(); if ((Object)(object)component == (Object)null) { LogWarning("Fader morreu, mas CharacterDrop não foi encontrado."); return; } string killerName; long num = TryGetKillerPlayerId(victim, out killerName); if (RequirePlayerAsKiller.Value && num == 0) { LogWarning("Fader morreu, mas não foi possível identificar um Player como golpe final."); return; } List> drops = new List>(); if (LootToInventoryEnabled.Value) { drops = component.GenerateDropList(); if (DisableVanillaWorldDrops.Value && DropsEnabledField != null) { DropsEnabledField.SetValue(component, false); } } Vector3 position = ((Component)victim).transform.position; if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { ((MonoBehaviour)this).StartCoroutine(HandleVictoryAuthoritativeServer(num, killerName, position, drops)); } else { ((MonoBehaviour)this).StartCoroutine(HandleVictoryAuthoritativeLocal(num, killerName, position, drops)); } } catch (Exception arg) { LogError($"Erro ao tratar morte do boss: {arg}"); } } [IteratorStateMachine(typeof(d__63))] private IEnumerator HandleVictoryAuthoritativeServer(long killerPlayerId, string killerName, Vector3 bossDeathPosition, List> drops) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__63(0) { <>4__this = this, killerPlayerId = killerPlayerId, killerName = killerName, bossDeathPosition = bossDeathPosition, drops = drops }; } [IteratorStateMachine(typeof(d__64))] private IEnumerator HandleVictoryAuthoritativeLocal(long killerPlayerId, string killerName, Vector3 bossDeathPosition, List> drops) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__64(0) { <>4__this = this, killerPlayerId = killerPlayerId, killerName = killerName, bossDeathPosition = bossDeathPosition, drops = drops }; } private void ApplyAuthoritativeVictoryToPlayer(Player targetPlayer, Vector3 bossDeathPosition, List> drops) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: 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) if ((Object)(object)targetPlayer == (Object)null) { return; } if (TeleportEnabled.Value) { Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(TeleportX.Value, TeleportY.Value, TeleportZ.Value); Quaternion val2 = Quaternion.Euler(0f, TeleportYaw.Value, 0f); ((Character)targetPlayer).TeleportTo(val, val2, true); if (ResetVelocityAfterTeleport.Value) { Rigidbody component = ((Component)targetPlayer).GetComponent(); if ((Object)(object)component != (Object)null) { component.velocity = Vector3.zero; component.angularVelocity = Vector3.zero; } } LogDebug($"Teleport aplicado para {targetPlayer.GetPlayerName()} em {val}"); } if (LootToInventoryEnabled.Value && drops != null && drops.Count > 0) { GiveDropsToInventory(targetPlayer, drops, bossDeathPosition); } } private void GiveDropsToInventory(Player player, List> drops, Vector3 bossDeathPosition) { //IL_01c8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null) { return; } List> list = new List>(); foreach (KeyValuePair drop in drops) { GameObject key = drop.Key; int value = drop.Value; if ((Object)(object)key == (Object)null || value <= 0) { continue; } ItemDrop component = key.GetComponent(); if ((Object)(object)component == (Object)null) { LogWarning("Prefab sem ItemDrop: " + NormalizePrefabName(key)); continue; } int num = Mathf.Max(1, component.m_itemData.m_shared.m_maxStackSize); int num2 = value; while (num2 > 0) { int num3 = Mathf.Min(num, num2); GameObject val = Object.Instantiate(key); ((Object)val).hideFlags = (HideFlags)61; ItemDrop component2 = val.GetComponent(); if ((Object)(object)component2 == (Object)null) { Object.Destroy((Object)(object)val); break; } component2.m_itemData.m_stack = num3; if (!inventory.AddItem(component2.m_itemData)) { list.Add(new KeyValuePair(key, num3)); } Object.Destroy((Object)(object)val); num2 -= num3; } if (ShowLootDebugLog.Value) { LogDebug($"Drop processado: {NormalizePrefabName(key)} x{value}"); } } if (list.Count > 0) { HandleOverflowDrops(list, player, bossDeathPosition); } } private void HandleOverflowDrops(List> overflow, Player player, Vector3 bossDeathPosition) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: 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_00a1: Unknown result type (might be due to invalid IL or missing references) string text = (OverflowModeConfig.Value ?? "SpawnAtTeleportLocation").Trim(); Vector3 val = bossDeathPosition; if (text.Equals("Discard", StringComparison.OrdinalIgnoreCase)) { LogWarning("Drops excedentes foram descartados por configuração."); return; } if (text.Equals("SpawnAtPlayer", StringComparison.OrdinalIgnoreCase)) { val = ((Component)player).transform.position; } else if (text.Equals("SpawnAtTeleportLocation", StringComparison.OrdinalIgnoreCase)) { ((Vector3)(ref val))..ctor(TeleportX.Value, TeleportY.Value, TeleportZ.Value); } SpawnOverflowDrops(overflow, val); LogWarning($"Alguns drops não couberam no inventário e foram spawnados em {val}."); } private void SpawnOverflowDrops(List> overflow, Vector3 spawnPosition) { //IL_008c: 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_009b: Unknown result type (might be due to invalid IL or missing references) //IL_009f: 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_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) foreach (KeyValuePair item in overflow) { GameObject key = item.Key; int value = item.Value; if ((Object)(object)key == (Object)null || value <= 0) { continue; } ItemDrop component = key.GetComponent(); if ((Object)(object)component == (Object)null) { continue; } int num = Mathf.Max(1, component.m_itemData.m_shared.m_maxStackSize); int num2 = value; while (num2 > 0) { int num3 = Mathf.Min(num, num2); Vector3 val = Random.insideUnitSphere * 0.5f; val.y = Mathf.Abs(val.y); GameObject val2 = Object.Instantiate(key, spawnPosition + Vector3.up + val, Quaternion.identity); ItemDrop component2 = val2.GetComponent(); if ((Object)(object)component2 != (Object)null) { component2.m_itemData.m_stack = num3; } num2 -= num3; } } } private void BroadcastVictoryToClients(long winnerPlayerId) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown if (ZRoutedRpc.instance == null) { LogWarning("ZRoutedRpc.instance ainda não está pronto. Vitória não foi transmitida."); return; } ZPackage val = new ZPackage(); val.Write(winnerPlayerId); ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "glitnir.faderending.victory", new object[1] { val }); LogDebug($"RPC de vitória enviado para o player {winnerPlayerId}."); } private static long TryGetKillerPlayerId(Character victim, out string killerName) { killerName = string.Empty; try { HitData val = (HitData)((LastHitField != null) ? /*isinst with value type is only supported in some contexts*/: null); if (val == null) { return 0L; } Character attacker = val.GetAttacker(); Player val2 = (Player)(object)((attacker is Player) ? attacker : null); if ((Object)(object)val2 == (Object)null) { return 0L; } killerName = val2.GetPlayerName(); return val2.GetPlayerID(); } catch (Exception ex) { LogWarning("Falha ao obter killer via m_lastHit: " + ex.Message); return 0L; } } private static bool IsAuthoritativeForDeathHandling(Character victim) { ZNetView component = ((Component)victim).GetComponent(); if ((Object)(object)component == (Object)null) { return true; } if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { return true; } return component.IsOwner(); } private static bool IsConfiguredBoss(Character victim) { string text = (BossPrefabName.Value ?? "Fader").Trim(); string text2 = NormalizePrefabName(((Component)victim).gameObject); if (MatchPrefabNameExactly.Value) { return string.Equals(text2, text, StringComparison.OrdinalIgnoreCase); } return text2.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0; } private static Player FindPlayerById(long playerId) { if (playerId == 0) { return null; } try { MethodInfo[] methods = typeof(Player).GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); MethodInfo[] array = methods; foreach (MethodInfo methodInfo in array) { if (methodInfo.Name != "GetAllPlayers") { continue; } ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 0) { object obj = methodInfo.Invoke(null, null); if (!(obj is IEnumerable enumerable)) { continue; } foreach (object item in enumerable) { Player val = (Player)((item is Player) ? item : null); if ((Object)(object)val != (Object)null && val.GetPlayerID() == playerId) { return val; } } } else { if (parameters.Length != 1 || !typeof(List).IsAssignableFrom(parameters[0].ParameterType)) { continue; } List list = new List(); methodInfo.Invoke(null, new object[1] { list }); foreach (Player item2 in list) { if ((Object)(object)item2 != (Object)null && item2.GetPlayerID() == playerId) { return item2; } } } } } catch (Exception ex) { LogWarning("Falha ao localizar jogador por ID: " + ex.Message); } return null; } internal static string NormalizePrefabName(GameObject go) { if ((Object)(object)go == (Object)null) { return string.Empty; } return NormalizePrefabName(((Object)go).name); } internal static string NormalizePrefabName(string name) { if (string.IsNullOrWhiteSpace(name)) { return string.Empty; } string text = name.Trim(); if (text.EndsWith("(Clone)", StringComparison.Ordinal)) { text = text.Substring(0, text.Length - "(Clone)".Length).Trim(); } return text; } internal static string GetPreparedVideoPath() { if (UseEmbeddedVideo.Value) { return ExtractEmbeddedVideoToCache(); } return GetResolvedExternalVideoPath(); } internal static string GetResolvedExternalVideoPath() { string text = ((VideoPath.Value != null) ? VideoPath.Value.Trim() : string.Empty); if (string.IsNullOrWhiteSpace(text)) { return string.Empty; } if (Path.IsPathRooted(text)) { return text; } return Path.Combine(Paths.ConfigPath, text); } internal static string ExtractEmbeddedVideoToCache() { try { Assembly executingAssembly = Assembly.GetExecutingAssembly(); string text = ResolveEmbeddedVideoResourceName(executingAssembly); if (string.IsNullOrWhiteSpace(text)) { LogWarning("Nenhum vídeo embutido foi encontrado no assembly."); return string.Empty; } string text2 = Path.Combine(Paths.PluginPath, "GlitnirFaderEndingCache"); Directory.CreateDirectory(text2); string text3 = ((EmbeddedVideoOutputFileName.Value != null) ? EmbeddedVideoOutputFileName.Value.Trim() : string.Empty); if (string.IsNullOrWhiteSpace(text3)) { string text4 = Path.GetExtension(text); if (string.IsNullOrWhiteSpace(text4)) { text4 = ".mp4"; } text3 = "glitnir_fader_video_embedded" + text4; } text3 = SanitizeFileName(text3); string text5 = Path.Combine(text2, text3); using (Stream stream = executingAssembly.GetManifestResourceStream(text)) { if (stream == null) { LogWarning("Não foi possível abrir o recurso embutido: " + text); return string.Empty; } bool flag = true; if (File.Exists(text5)) { FileInfo fileInfo = new FileInfo(text5); if (stream.CanSeek && fileInfo.Length == stream.Length) { flag = false; } } if (flag) { if (stream.CanSeek) { stream.Position = 0L; } using (FileStream destination = new FileStream(text5, FileMode.Create, FileAccess.Write, FileShare.Read)) { stream.CopyTo(destination); } LogDebug("Vídeo embutido extraído para: " + text5); } } return text5; } catch (Exception arg) { LogError($"Falha ao extrair vídeo embutido: {arg}"); return string.Empty; } } private static string ResolveEmbeddedVideoResourceName(Assembly asm) { string configured = ((EmbeddedVideoResourceName.Value != null) ? EmbeddedVideoResourceName.Value.Trim() : string.Empty); string[] manifestResourceNames = asm.GetManifestResourceNames(); if (!string.IsNullOrWhiteSpace(configured)) { string text = manifestResourceNames.FirstOrDefault((string n) => string.Equals(n, configured, StringComparison.Ordinal)); if (!string.IsNullOrWhiteSpace(text)) { return text; } string text2 = manifestResourceNames.FirstOrDefault((string n) => n.EndsWith(configured, StringComparison.OrdinalIgnoreCase)); if (!string.IsNullOrWhiteSpace(text2)) { return text2; } } string text3 = manifestResourceNames.FirstOrDefault((string n) => n.EndsWith(".mp4", StringComparison.OrdinalIgnoreCase) || n.EndsWith(".webm", StringComparison.OrdinalIgnoreCase) || n.EndsWith(".mov", StringComparison.OrdinalIgnoreCase)); return text3 ?? string.Empty; } private static string SanitizeFileName(string fileName) { char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { fileName = fileName.Replace(oldChar, '_'); } return fileName; } internal static void LogInfo(string msg) { if ((Object)(object)Instance != (Object)null) { ((BaseUnityPlugin)Instance).Logger.LogInfo((object)msg); } } internal static void LogWarning(string msg) { if ((Object)(object)Instance != (Object)null) { ((BaseUnityPlugin)Instance).Logger.LogWarning((object)msg); } } internal static void LogError(string msg) { if ((Object)(object)Instance != (Object)null) { ((BaseUnityPlugin)Instance).Logger.LogError((object)msg); } } internal static void LogDebug(string msg) { if ((Object)(object)Instance != (Object)null && DebugLogging != null && DebugLogging.Value) { ((BaseUnityPlugin)Instance).Logger.LogInfo((object)("[DEBUG] " + msg)); } } } internal sealed class EndingOverlay : MonoBehaviour { [CompilerGenerated] private sealed class d__22 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public float from; public float to; public float duration; public EndingOverlay <>4__this; private float 5__1; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__22(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; 5__1 = 0f; <>4__this._canvasGroup.alpha = from; break; case 1: <>1__state = -1; break; } if (5__1 < duration) { 5__1 += Time.unscaledDeltaTime; <>4__this._canvasGroup.alpha = Mathf.Lerp(from, to, 5__1 / duration); <>2__current = null; <>1__state = 1; return true; } <>4__this._canvasGroup.alpha = to; return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__19 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public EndingOverlay <>4__this; private float 5__1; private float 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__19(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0283: Unknown result type (might be due to invalid IL or missing references) //IL_028d: Expected O, but got Unknown //IL_02f2: Unknown result type (might be due to invalid IL or missing references) //IL_02fc: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = ((MonoBehaviour)<>4__this).StartCoroutine(<>4__this.FadeCanvas(0f, 1f, Mathf.Max(0.01f, GlitnirFaderEndingPlugin.FadeInSeconds.Value))); <>1__state = 1; return true; case 1: <>1__state = -1; if (GlitnirFaderEndingPlugin.VideoEnabled.Value && (Object)(object)<>4__this._videoPlayer != (Object)null && !string.IsNullOrWhiteSpace(<>4__this._videoPlayer.url)) { 5__1 = 10f; goto IL_0116; } goto IL_0189; case 2: <>1__state = -1; goto IL_0116; case 3: <>1__state = -1; goto IL_0253; case 4: <>1__state = -1; goto IL_029e; case 5: <>1__state = -1; <>2__current = ((MonoBehaviour)<>4__this).StartCoroutine(<>4__this.FadeCanvas(1f, 0f, Mathf.Max(0.01f, GlitnirFaderEndingPlugin.FadeOutSeconds.Value))); <>1__state = 6; return true; case 6: { <>1__state = -1; <>4__this.RestoreGameAudioMute(); <>4__this._isActive = false; <>4__this.SetOverlayState(visible: false, instantAlphaReset: true); return false; } IL_0189: if (<>4__this._videoPreparedSuccessfully) { 5__2 = Mathf.Max(1f, GlitnirFaderEndingPlugin.MaxVideoDurationSeconds.Value); goto IL_0253; } <>2__current = (object)new WaitForSecondsRealtime(Mathf.Max(0.1f, GlitnirFaderEndingPlugin.HoldOnLastFrameSeconds.Value)); <>1__state = 4; return true; IL_029e: if ((Object)(object)<>4__this._videoPlayer != (Object)null && <>4__this._videoPlayer.isPlaying) { <>4__this._videoPlayer.Stop(); } <>2__current = (object)new WaitForSecondsRealtime(Mathf.Max(0f, GlitnirFaderEndingPlugin.HoldOnLastFrameSeconds.Value)); <>1__state = 5; return true; IL_0116: if (!<>4__this._videoPlayer.isPrepared && 5__1 > 0f) { 5__1 -= Time.unscaledDeltaTime; <>2__current = null; <>1__state = 2; return true; } <>4__this._videoPreparedSuccessfully = <>4__this._videoPlayer.isPrepared; if (<>4__this._videoPreparedSuccessfully) { <>4__this._videoPlayer.Play(); } else { GlitnirFaderEndingPlugin.LogWarning("O vídeo não ficou pronto a tempo. Encerrando sequência com fade."); } goto IL_0189; IL_0253: if (!<>4__this._skipRequested) { 5__2 -= Time.unscaledDeltaTime; if (5__2 <= 0f) { GlitnirFaderEndingPlugin.LogWarning("Tempo máximo do vídeo atingido. Encerrando sequência."); } else if (GlitnirFaderEndingPlugin.LoopVideo.Value || <>4__this._videoPlayer.isPlaying || <>4__this._videoPlayer.frame <= 0) { <>2__current = null; <>1__state = 3; return true; } } goto IL_029e; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } internal static EndingOverlay Instance; private bool _isActive; private bool _skipRequested; private bool _videoPreparedSuccessfully; private bool _audioMutedByOverlay; private float _previousGameAudioVolume = 1f; private float _sequenceStartTime; private Canvas _canvas; private CanvasGroup _canvasGroup; private RawImage _background; private VideoPlayer _videoPlayer; private RenderTexture _renderTexture; internal static bool IsActive => (Object)(object)Instance != (Object)null && Instance._isActive; internal static void EnsureRuntimeObject() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown if (!((Object)(object)Instance != (Object)null)) { GameObject val = new GameObject("GlitnirFaderEndingOverlay"); Object.DontDestroyOnLoad((Object)(object)val); Instance = val.AddComponent(); Instance.BuildUi(); Instance.SetOverlayState(visible: false, instantAlphaReset: true); } } internal static void Show() { EnsureRuntimeObject(); Instance.Begin(); } private void BuildUi() { //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Expected O, but got Unknown //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) _canvas = ((Component)this).gameObject.AddComponent(); _canvas.renderMode = (RenderMode)0; _canvas.sortingOrder = 9999; ((Behaviour)_canvas).enabled = false; ((Component)this).gameObject.AddComponent(); _canvasGroup = ((Component)this).gameObject.AddComponent(); _canvasGroup.alpha = 0f; _canvasGroup.interactable = false; _canvasGroup.blocksRaycasts = false; GameObject val = new GameObject("Background"); val.transform.SetParent(((Component)this).transform, false); RectTransform val2 = val.AddComponent(); val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; _background = val.AddComponent(); ((Graphic)_background).color = Color.black; _videoPlayer = ((Component)this).gameObject.AddComponent(); _videoPlayer.playOnAwake = false; _videoPlayer.isLooping = false; _videoPlayer.renderMode = (VideoRenderMode)2; _videoPlayer.audioOutputMode = (VideoAudioOutputMode)2; _videoPlayer.skipOnDrop = true; } private void Begin() { ((MonoBehaviour)this).StopAllCoroutines(); _skipRequested = false; _isActive = true; _videoPreparedSuccessfully = false; _sequenceStartTime = Time.unscaledTime; SetOverlayState(visible: true, instantAlphaReset: false); ApplyGameAudioMute(); ConfigureVideo(); ((MonoBehaviour)this).StartCoroutine(RunSequence()); } private void ConfigureVideo() { //IL_0026: 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_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Expected O, but got Unknown //IL_0101: Unknown result type (might be due to invalid IL or missing references) if (!GlitnirFaderEndingPlugin.VideoEnabled.Value) { _background.texture = null; ((Graphic)_background).color = Color.black; _videoPreparedSuccessfully = false; return; } string preparedVideoPath = GlitnirFaderEndingPlugin.GetPreparedVideoPath(); if (string.IsNullOrWhiteSpace(preparedVideoPath) || !File.Exists(preparedVideoPath)) { GlitnirFaderEndingPlugin.LogWarning("Vídeo não encontrado: " + preparedVideoPath); _background.texture = null; ((Graphic)_background).color = Color.black; _videoPreparedSuccessfully = false; return; } if ((Object)(object)_renderTexture != (Object)null) { _renderTexture.Release(); Object.Destroy((Object)(object)_renderTexture); } _renderTexture = new RenderTexture(1920, 1080, 0); _videoPlayer.targetTexture = _renderTexture; _background.texture = (Texture)(object)_renderTexture; ((Graphic)_background).color = Color.white; _videoPlayer.url = preparedVideoPath; _videoPlayer.isLooping = GlitnirFaderEndingPlugin.LoopVideo.Value; _videoPlayer.SetDirectAudioMute((ushort)0, GlitnirFaderEndingPlugin.MuteVideoAudio.Value); _videoPlayer.SetDirectAudioVolume((ushort)0, Mathf.Clamp01(GlitnirFaderEndingPlugin.VideoVolume.Value)); _videoPlayer.Prepare(); } [IteratorStateMachine(typeof(d__19))] private IEnumerator RunSequence() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__19(0) { <>4__this = this }; } private void ApplyGameAudioMute() { if (GlitnirFaderEndingPlugin.MuteGameAudioWhileEnding.Value) { _previousGameAudioVolume = AudioListener.volume; AudioListener.volume = 0f; _audioMutedByOverlay = true; } } private void RestoreGameAudioMute() { if (_audioMutedByOverlay) { AudioListener.volume = _previousGameAudioVolume; _audioMutedByOverlay = false; } } [IteratorStateMachine(typeof(d__22))] private IEnumerator FadeCanvas(float from, float to, float duration) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__22(0) { <>4__this = this, from = from, to = to, duration = duration }; } private void SetOverlayState(bool visible, bool instantAlphaReset) { if ((Object)(object)_canvas != (Object)null) { ((Behaviour)_canvas).enabled = visible; } if ((Object)(object)_canvasGroup != (Object)null) { _canvasGroup.interactable = visible; _canvasGroup.blocksRaycasts = visible; if (instantAlphaReset) { _canvasGroup.alpha = (visible ? 1f : 0f); } } } private void Update() { //IL_005c: Unknown result type (might be due to invalid IL or missing references) if (_isActive && GlitnirFaderEndingPlugin.AllowSkip.Value) { float num = Time.unscaledTime - _sequenceStartTime; if (!(num < Mathf.Max(0f, GlitnirFaderEndingPlugin.MinimumSkipDelaySeconds.Value)) && TryGetSkipKey(out var key) && Input.GetKeyDown(key)) { _skipRequested = true; } } } private bool TryGetSkipKey(out KeyCode key) { return Enum.TryParse(GlitnirFaderEndingPlugin.SkipKey.Value, ignoreCase: true, out key); } private void OnDestroy() { RestoreGameAudioMute(); } }