using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Dialogue;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using NineSolsAPI;
using TMPro;
using UnityEngine;
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("CutsceneSkip")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("A Nine Sols mod that enables skipping any cutscene or dialogue (that won't break the game).")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+9d52dfec2871d129ba0320f0a08d5925bb401877")]
[assembly: AssemblyProduct("CutsceneSkip")]
[assembly: AssemblyTitle("CutsceneSkip")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
internal sealed class NullableAttribute : Attribute
{
public readonly byte[] NullableFlags;
public NullableAttribute(byte P_0)
{
NullableFlags = new byte[1] { P_0 };
}
public NullableAttribute(byte[] P_0)
{
NullableFlags = P_0;
}
}
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
internal sealed class NullableContextAttribute : Attribute
{
public readonly byte Flag;
public NullableContextAttribute(byte P_0)
{
Flag = P_0;
}
}
[CompilerGenerated]
[Microsoft.CodeAnalysis.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 CutsceneSkip
{
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInPlugin("CutsceneSkip", "CutsceneSkip", "1.0.0")]
public class CutsceneSkip : BaseUnityPlugin
{
private static ConfigEntry<KeyboardShortcut> skipKeybind = null;
private Harmony harmony;
public static (A2_SG4_Logic?, string) activeA2SG4 = (null, "");
public static (A4_S5_Logic?, string) activeA4S5 = (null, "");
public static string dialogueSkipNotificationId = "";
public static (SimpleCutsceneManager?, string) activeCutscene = (null, "");
public static (VideoPlayAction?, string) activeVideo = (null, "");
public static string SkipKeybindText()
{
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
KeyboardShortcut value = skipKeybind.Value;
return ((KeyboardShortcut)(ref value)).Serialize();
}
private void Awake()
{
//IL_0051: Unknown result type (might be due to invalid IL or missing references)
Log.Init(((BaseUnityPlugin)this).Logger);
RCGLifeCycle.DontDestroyForever(((Component)this).gameObject);
harmony = Harmony.CreateAndPatchAll(typeof(CutsceneSkip).Assembly, (string)null);
skipKeybind = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("", "Skip Keybind", new KeyboardShortcut((KeyCode)107, (KeyCode[])(object)new KeyCode[1] { (KeyCode)306 }), "The keyboard shortcut to actually skip cutscenes and dialogue.");
KeybindManager.Add((MonoBehaviour)(object)this, (Action)SkipActiveCutsceneOrDialogue, (Func<KeyboardShortcut>)(() => skipKeybind.Value));
((BaseUnityPlugin)this).Logger.LogInfo((object)"Plugin CutsceneSkip is loaded!");
Notifications.Awake();
}
private void SkipActiveCutsceneOrDialogue()
{
if ((Object)(object)activeA2SG4.Item1 != (Object)null)
{
A2_SG4_Logic? item = activeA2SG4.Item1;
Log.Info("Found A2_SG4_Logic a.k.a. Heng Power Reservoir flashback, calling A2_SG4_Logic.TrySkip() as a special case");
item.TrySkip();
Notifications.CancelNotification(activeA2SG4.Item2);
return;
}
if ((Object)(object)activeA4S5.Item1 != (Object)null)
{
A4_S5_Logic item2 = activeA4S5.Item1;
if (item2.BossKilled.CurrentValue)
{
Log.Info("Found A4_S5_Logic a.k.a. Sky Rending Claw fight. Claw already killed. Applying special case logic to skip post-fight scene.");
item2.FinishCutscene.TrySkip();
}
else
{
if (((Component)item2.GianMechClawMonsterBase).gameObject.activeSelf)
{
Log.Info("Found A4_S5_Logic a.k.a. Sky Rending Claw fight. Claw not yet killed. But claw is already active, so trying to skip this now would just softlock. Doing nothing.");
return;
}
Log.Info("Found A4_S5_Logic a.k.a. Sky Rending Claw fight. Claw not yet killed. Applying special case logic to skip pre-fight scenes.");
GameObject.Find("A4_S5/A4_S5_Logic(DisableMeForBossDesign)/CUTSCENE_START/MangaView_OriginalPrefab/MANGACanvas").SetActive(false);
item2.BeforeMangaBubble.TrySkip();
item2.BubbleDialogue.TrySkip();
item2.TrySkip();
}
Notifications.CancelNotification(activeA4S5.Item2);
return;
}
GameObject obj = GameObject.Find("GameCore(Clone)/RCG LifeCycle/UIManager/GameplayUICamera/Always Canvas/DialoguePlayer(KeepThisEnable)");
DialoguePlayer val = ((obj != null) ? obj.GetComponent<DialoguePlayer>() : null);
if ((Object)(object)val != (Object)null && (Object)(object)AccessTools.FieldRefAccess<DialoguePlayer, DialogueGraph>("playingDialogueGraph").Invoke(val) != (Object)null)
{
Log.Info("calling DialoguePlayer.playingDialogueGraph.TrySkip()");
val.TrySkip();
if (dialogueSkipNotificationId != "")
{
Notifications.CancelNotification(dialogueSkipNotificationId);
dialogueSkipNotificationId = "";
}
}
else if ((Object)(object)activeCutscene.Item1 != (Object)null)
{
SimpleCutsceneManager item3 = activeCutscene.Item1;
Log.Info("calling TrySkip() on " + ((Object)item3).name);
AccessTools.Method(typeof(SimpleCutsceneManager), "TrySkip", (Type[])null, (Type[])null).Invoke(item3, Array.Empty<object>());
if (AccessTools.FieldRefAccess<SimpleCutsceneManager, bool>("isMangaPauseing").Invoke(item3))
{
Log.Info("also calling Resume() since it was 'manga paused'");
AccessTools.Method(typeof(SimpleCutsceneManager), "Resume", (Type[])null, (Type[])null).Invoke(item3, Array.Empty<object>());
}
Notifications.CancelNotification(activeCutscene.Item2);
activeCutscene = (null, "");
}
else if ((Object)(object)activeVideo.Item1 != (Object)null)
{
VideoPlayAction item4 = activeVideo.Item1;
Log.Info("calling TrySkip() on " + ((Object)item4).name);
AccessTools.Method(typeof(VideoPlayAction), "TrySkip", (Type[])null, (Type[])null).Invoke(item4, Array.Empty<object>());
Notifications.CancelNotification(activeVideo.Item2);
activeVideo = (null, "");
}
}
private void Update()
{
Notifications.Update();
}
private void OnDestroy()
{
harmony.UnpatchSelf();
Notifications.OnDestroy();
}
}
internal static class Log
{
private static ManualLogSource? logSource;
internal static void Init(ManualLogSource logSource)
{
Log.logSource = logSource;
}
internal static void Debug(object data)
{
ManualLogSource? obj = logSource;
if (obj != null)
{
obj.LogDebug(data);
}
}
internal static void Error(object data)
{
ManualLogSource? obj = logSource;
if (obj != null)
{
obj.LogError(data);
}
}
internal static void Fatal(object data)
{
ManualLogSource? obj = logSource;
if (obj != null)
{
obj.LogFatal(data);
}
}
internal static void Info(object data)
{
ManualLogSource? obj = logSource;
if (obj != null)
{
obj.LogInfo(data);
}
}
internal static void Message(object data)
{
ManualLogSource? obj = logSource;
if (obj != null)
{
obj.LogMessage(data);
}
}
internal static void Warning(object data)
{
ManualLogSource? obj = logSource;
if (obj != null)
{
obj.LogWarning(data);
}
}
}
internal class Notifications
{
private struct Notification
{
public string id;
public DateTimeOffset timestamp;
public string displayText;
}
private static Canvas CanvasComponent = null;
private static TextMeshProUGUI TextComponent = null;
private static bool isDirty = false;
private static List<Notification> notificationStack = new List<Notification>();
private static readonly TimeSpan expiry = TimeSpan.FromSeconds(10.0);
public static void Awake()
{
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
//IL_0010: Expected O, but got Unknown
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_0053: Unknown result type (might be due to invalid IL or missing references)
GameObject val = new GameObject("NineSolsAPI-FullscreenCanvas");
RCGLifeCycle.DontDestroyForever(val);
CanvasComponent = val.AddComponent<Canvas>();
CanvasComponent.renderMode = (RenderMode)0;
TextComponent = val.AddComponent<TextMeshProUGUI>();
((TMP_Text)TextComponent).alignment = (TextAlignmentOptions)1028;
((TMP_Text)TextComponent).fontSize = 20f;
((Graphic)TextComponent).color = Color.white;
}
public static void Update()
{
UpdateText();
}
public static void OnDestroy()
{
Object.Destroy((Object)(object)((Component)CanvasComponent).gameObject);
}
public static void UpdateText()
{
DateTimeOffset now = DateTimeOffset.UtcNow;
int num = notificationStack.RemoveAll((Notification notification) => now - notification.timestamp > expiry);
isDirty |= num > 0;
if (isDirty)
{
((TMP_Text)TextComponent).text = string.Join('\n', notificationStack.Select((Notification n) => n.displayText));
isDirty = false;
}
}
public static string AddNotification(string displayText)
{
Log.Info("Notifications.AddNotification(" + displayText + ")");
string text = Guid.NewGuid().ToString("N");
notificationStack.Add(new Notification
{
id = text,
timestamp = DateTimeOffset.UtcNow,
displayText = displayText
});
isDirty = true;
return text;
}
public static void CancelNotification(string id)
{
string id2 = id;
Log.Info("Notifications.CancelNotification(" + id2 + ")");
if (id2 != null && id2 != "")
{
int num = notificationStack.RemoveAll((Notification x) => x.id == id2);
isDirty |= num > 0;
}
}
}
[HarmonyPatch]
public class Patches
{
private static List<string> skipDenylist = new List<string>
{
"A1_S2_GameLevel/Room/Prefab/Gameplay2_Alina/Simple Binding Tool/SimpleCutSceneFSM_關門戰開頭演出/FSM Animator/LogicRoot/[CutScene]", "A2_S1/Room/Prefab/EnterPyramid_Acting/[CutScene]ActivePyramidAndEnter", "A3_S1/Room/Prefab/妹妹回憶_SimpleCutSceneFSM Variant/FSM Animator/LogicRoot/[CutScene]", "A4_S4/ZGunAndDoor/Shield Giant Bot Control Provider Variant_Cutscene/Hack Control Monster FSM/FSM Animator/LogicRoot/Cutscene/LogicRoot/[CutScene]", "A5_S5/Room/SimpleCutSceneFSM_JieChuan and Jee/FSM Animator/LogicRoot/[CutScene]", "AG_S2/Room/NPCs/議會演出相關Binding/ShanShan 軒軒分身 FSM/FSM Animator/CutScene/[CutScene] 食譜_團圓飯/FSM Animator/LogicRoot/[CutScene]", "GameLevel/Room/Prefab/EventBinder/General Boss Fight FSM Object Variant/FSM Animator/[CutScene] 易公死亡", "A3_S5_BossGouMang_GameLevel/Room/Simple Binding Tool/BossGouMangLogic/[CutScene]/[CutScene]Goumang_Explosion_Drop/[Timeline]Goumang_Explosion_Drop", "A5_S2/Room/SimpleCutSceneFSM_A5妹妹回憶/FSM Animator/LogicRoot/[CutScene]", "A4_S3/Room/Prefab/CutScene_ChangeScene_FSM Variant/FSM Animator/LogicRoot/[CutScene]EnterScene",
"A11_S2/CutScene_ChangeScene_FSM Variant/FSM Animator/LogicRoot/[CutScene]EnterScene", "AG_GoHome/Room/Prefab/SimpleCutSceneFSM_搭公車/FSM Animator/LogicRoot/[CutScene]", "A1_S1_GameLevel/Room/A1_S1_Tutorial_Logic/[CutScene]AfterTutorial_AI_Call/[Timeline]", "A4_S3/Room/Prefab/ElementRoom/ElementDoor FSM/ElementDoor FSM/FSM Animator/LogicRoot/[CutScene]Eenter_A4SG4", "A2_S5_ BossHorseman_GameLevel/Room/Simple Binding Tool/Boss_SpearHorse_Logic/[CutScene]SpearHorse_End", "A0_S6/Room/Prefab/SimpleCutSceneFSM_道長死亡/FSM Animator/LogicRoot/Cutscene_TaoChangPart2", "A4_S5/A4_S5_Logic(DisableMeForBossDesign)/CUTSCENE_START", "A4_S5/A4_S5_Logic(DisableMeForBossDesign)/CUTSENE_EMERGENCY", "A4_S5/A4_S5_Logic(DisableMeForBossDesign)/CUTSCENE_Finish", "A11_S2/Room/Prefab/EventBinder/OldBoy FSM Object/FSM Animator/LogicRoot/[CutScene]OldBoyFighting/[Timeline]"
};
private static HashSet<string> skippableVideos = new HashSet<string> { "GameLevel/Room/Prefab/SimpleCutSceneFSM_結局_大爆炸/--[States]/FSM/[State] PlayCutSceneEnd/[Action] VideoPlayAction", "A7_S6_Memory_Butterfly_CutScene_GameLevel/A7_S6_Cutscene FSM/--[States]/FSM/[State] PlayingVideo/[Action] VideoPlayAction" };
public static string GetFullPath(GameObject go)
{
Transform val = go.transform;
List<string> list = new List<string>();
while ((Object)(object)val != (Object)null)
{
list.Add(((Object)val).name);
val = val.parent;
}
list.Reverse();
return string.Join("/", list);
}
[HarmonyPrefix]
[HarmonyPatch(typeof(SimpleCutsceneManager), "PlayAnimation")]
private static void SimpleCutsceneManager_PlayAnimation(SimpleCutsceneManager __instance)
{
string fullPath = GetFullPath(((Component)__instance).gameObject);
Log.Debug("SimpleCutsceneManager_PlayAnimation " + fullPath);
if (skipDenylist.Contains(fullPath))
{
Log.Info("not allowing skip for cutscene " + fullPath + " because it's on the skip denylist");
return;
}
if (((Object)__instance).name.EndsWith("[TimeLine]CrateEnter_L") || ((Object)__instance).name.EndsWith("[TimeLine]CrateEnter_R"))
{
Log.Info("not allowing skip for " + fullPath + " because all crate exit 'cutscenes' I've tested instantly softlock when skipped");
return;
}
if (((Object)__instance).name == "[CutScene]調閱報告")
{
Log.Info("not allowing skip for " + fullPath + " because all \"[CutScene]調閱報告\" / Eigong lab report cutscenes risk softlocking when skipped");
return;
}
string item = "";
if (((Object)__instance).name.EndsWith("_EnterScene"))
{
Log.Info("skipping notification for " + ((Object)__instance).name + " because transition 'cutscenes' are typically over before the player can even see the toast");
}
else
{
item = Notifications.AddNotification("Press " + CutsceneSkip.SkipKeybindText() + " to Skip This Cutscene");
}
CutsceneSkip.activeCutscene = (__instance, item);
}
[HarmonyPrefix]
[HarmonyPatch(typeof(SimpleCutsceneManager), "End")]
private static void SimpleCutsceneManager_End(SimpleCutsceneManager __instance)
{
Log.Debug("SimpleCutsceneManager_End " + ((Object)__instance).name);
if ((Object)(object)CutsceneSkip.activeCutscene.Item1 == (Object)(object)__instance)
{
CutsceneSkip.activeCutscene = (null, "");
}
}
[HarmonyPrefix]
[HarmonyPatch(typeof(DialoguePlayer), "StartDialogue")]
private static void DialoguePlayer_StartDialogue(DialoguePlayer __instance)
{
Log.Debug("DialoguePlayer_StartDialogue " + ((Object)__instance).name);
CutsceneSkip.dialogueSkipNotificationId = Notifications.AddNotification("Press " + CutsceneSkip.SkipKeybindText() + " to Skip This Dialogue");
}
[HarmonyPrefix]
[HarmonyPatch(typeof(VideoPlayAction), "OnStateEnterImplement")]
private static void VideoPlayAction_OnStateEnterImplement(VideoPlayAction __instance)
{
string fullPath = GetFullPath(((Component)__instance).gameObject);
Log.Debug("VideoPlayAction_OnStateEnterImplement " + fullPath);
if (skippableVideos.Contains(fullPath))
{
string item = Notifications.AddNotification("Press " + CutsceneSkip.SkipKeybindText() + " to Skip This Video");
CutsceneSkip.activeVideo = (__instance, item);
}
}
[HarmonyPrefix]
[HarmonyPatch(typeof(VideoPlayAction), "VideoClipDone")]
private static void VideoPlayAction_VideoClipDone(VideoPlayAction __instance)
{
Log.Debug("VideoPlayAction_VideoClipDone " + ((Object)__instance).name);
if ((Object)(object)CutsceneSkip.activeVideo.Item1 == (Object)(object)__instance)
{
CutsceneSkip.activeVideo = (null, "");
}
}
[HarmonyPrefix]
[HarmonyPatch(typeof(A2_SG4_Logic), "EnterLevelStart")]
private static void A2_SG4_Logic_EnterLevelStart(A2_SG4_Logic __instance)
{
Log.Info("A2_SG4_Logic_EnterLevelStart / Heng Power Reservoir flashback");
string item = Notifications.AddNotification("Press " + CutsceneSkip.SkipKeybindText() + " to Skip This Heng Flashback");
CutsceneSkip.activeA2SG4 = (__instance, item);
}
[HarmonyPrefix]
[HarmonyPatch(typeof(A4_S5_Logic), "EnterLevelStart")]
private static void A4_S5_Logic_EnterLevelStart(A4_S5_Logic __instance)
{
Log.Info("A4_S5_Logic_EnterLevelStart / Sky Rending Claw Pre-Fight Scenes");
string item = Notifications.AddNotification("Press " + CutsceneSkip.SkipKeybindText() + " to Skip Pre-Claw Fight Cutscenes");
CutsceneSkip.activeA4S5 = (__instance, item);
}
[HarmonyPrefix]
[HarmonyPatch(typeof(A4_S5_Logic), "FooGameComplete")]
private static void A4_S5_Logic_FooGameComplete(A4_S5_Logic __instance)
{
Log.Info("A4_S5_Logic_FooGameComplete / Sky Rending Claw Post-Fight Scenes");
if ((Object)(object)CutsceneSkip.activeA4S5.Item1 != (Object)null)
{
Notifications.CancelNotification(CutsceneSkip.activeA4S5.Item2);
}
string item = Notifications.AddNotification("Press " + CutsceneSkip.SkipKeybindText() + " to Skip Post-Claw Fight Cutscene");
CutsceneSkip.activeA4S5 = (__instance, item);
}
}
public static class PluginInfo
{
public const string PLUGIN_GUID = "CutsceneSkip";
public const string PLUGIN_NAME = "CutsceneSkip";
public const string PLUGIN_VERSION = "1.0.0";
}
}