using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Cysharp.Threading.Tasks; using Dialogue; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [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("SpeedrunCutsceneSkip")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("A Nine Sols mod that auto-skips a handful of cutscenes for speedrunners.")] [assembly: AssemblyFileVersion("0.0.1.0")] [assembly: AssemblyInformationalVersion("0.0.1+7e0202213de13ce1eca0f524b642a209b8fba038")] [assembly: AssemblyProduct("SpeedrunCutsceneSkip")] [assembly: AssemblyTitle("SpeedrunCutsceneSkip")] [assembly: AssemblyVersion("0.0.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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] [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] [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 SpeedrunCutsceneSkip { [HarmonyPatch] internal class CentralHallPhone { [HarmonyPostfix] [HarmonyPatch(typeof(SimpleCutsceneManager), "PlayAnimation")] private static async void SimpleCutsceneManager_PlayAnimation_Postfix(SimpleCutsceneManager __instance) { if (SpeedrunCutsceneSkip.Instance.SkipSetting.Value && ((Object)((Component)__instance).transform.parent.parent.parent).name == "SimpleCutSceneFSM_Tree intro" && FullPath.GetFullPath(((Component)__instance).gameObject) == "AG_S1/Room/SimpleCutSceneFSM_Tree intro/FSM Animator/LogicRoot/[CutScene]") { Log.Info("SpeedrunCutsceneSkip waiting 1.6 seconds before skipping the Warehouse Heng call"); await UniTask.Delay(1667, false, (PlayerLoopTiming)8, default(CancellationToken)); Log.Info("SpeedrunCutsceneSkip skipping the Senate Hall call"); __instance.TrySkip(); SpeedrunCutsceneSkip.AddSkippedTimeToLivesplit(27.917f); } } } internal static class CutsceneTimingConstants { public const float CentralHallPhone = 27.917f; public const float PavillionNymphIntro_RuyiActivation = 37.283f; public const float PavillionNymphIntro_ShuanshuanDialogue = 18.683f; public const float PowerReservoirHengCall = 41.076f; public const float WarehouseHengCall = 71.131f; public const float YanlaoBoundlessRepositoryEntrance = 26.621f; public const float YanlaoBossIntro = 47.1f; public const float PrisonTortureScene = 126.754f; public const float LadyEtherealPostBoss = 134.713f; public const float EDSanctumHengCall = 63.517f; public const float EigongSoulscape = 236.528f; } [HarmonyPatch] internal class EDSanctumHengCall { private static SimpleCutsceneManager? activeSanctumCall; [HarmonyPostfix] [HarmonyPatch(typeof(SimpleCutsceneManager), "PlayAnimation")] private static async void SimpleCutsceneManager_PlayAnimation_Postfix(SimpleCutsceneManager __instance) { if (SpeedrunCutsceneSkip.Instance.SkipSetting.Value && ((Object)((Component)__instance).transform.parent.parent.parent).name == "SimpleCutSceneFSM_A9_S3妹妹回憶" && FullPath.GetFullPath(((Component)__instance).gameObject) == "A9_S3/Room/SimpleCutSceneFSM_A9_S3妹妹回憶/FSM Animator/LogicRoot/[CutScene]") { Log.Info("SpeedrunCutsceneSkip waiting for dialogue to start before skipping the ED Sanctum Heng call"); activeSanctumCall = __instance; } } [HarmonyPrefix] [HarmonyPatch(typeof(DialoguePlayer), "StartDialogue")] private static async void DialoguePlayer_StartDialogue(DialoguePlayer __instance, DialogueGraph dialogueGraph, Action callback, bool withBackground) { if (SpeedrunCutsceneSkip.Instance.SkipSetting.Value && (Object)(object)activeSanctumCall != (Object)null) { Log.Info("SpeedrunCutsceneSkip waiting 1.6 seconds before skipping the ED Sanctum Heng call"); await UniTask.Delay(1667, false, (PlayerLoopTiming)8, default(CancellationToken)); Log.Info("SpeedrunCutsceneSkip skipping the ED Sanctum Heng call"); __instance.TrySkip(); activeSanctumCall.TrySkip(); activeSanctumCall = null; SpeedrunCutsceneSkip.AddSkippedTimeToLivesplit(63.517f); } } } [HarmonyPatch] internal class EigongSoulscape { [HarmonyPostfix] [HarmonyPatch(typeof(SimpleCutsceneManager), "PlayAnimation")] private static async void SimpleCutsceneManager_PlayAnimation_Postfix(SimpleCutsceneManager __instance) { if (SpeedrunCutsceneSkip.Instance.SkipSetting.Value) { string fullPath = FullPath.GetFullPath(((Component)__instance).gameObject); Log.Info("SpeedrunCutsceneSkip eigong check " + ((Object)__instance).name + " - " + fullPath); if (((Object)((Component)__instance).transform.parent.parent.parent).name == "SimpleCutSceneFSM_易公陷阱" && FullPath.GetFullPath(((Component)__instance).gameObject) == "VR_TaoChang/Room/Prefab/Simple Binding Tool/SimpleCutSceneFSM_易公陷阱/FSM Animator/LogicRoot/[CutScene]") { Log.Info("SpeedrunCutsceneSkip waiting 1.6 seconds before skipping the Eigong first dialogue in her soulscape"); await UniTask.Delay(1667, false, (PlayerLoopTiming)8, default(CancellationToken)); Log.Info("SpeedrunCutsceneSkip skipping the Eigong confrontation inside her soulscape"); __instance.TrySkip(); } if (((Object)((Component)__instance).transform.parent.parent.parent).name == "SimpleCutSceneFSM_易公後妹妹回憶" && FullPath.GetFullPath(((Component)__instance).gameObject) == "VR_TaoChang/Room/SimpleCutSceneFSM_易公後妹妹回憶/FSM Animator/LogicRoot/[CutScene]") { Log.Info("SpeedrunCutsceneSkip waiting 1.6 seconds before skipping the Heng flashback inside of Eigong's soulscape"); await UniTask.Delay(1667, false, (PlayerLoopTiming)8, default(CancellationToken)); Log.Info("SpeedrunCutsceneSkip skipping the Heng flashback inside of Eigong's soulscape"); __instance.TrySkip(); } if (((Object)((Component)__instance).transform.parent.parent.parent).name == "[CutScene]BackFromSleeppod" && FullPath.GetFullPath(((Component)__instance).gameObject) == "A11_S2/Room/敘事相關/Sleeppod FSM_易公/[CutScene]BackFromSleeppod/FSM Animator/LogicRoot/[CutScene]") { Log.Info("SpeedrunCutsceneSkip skipping the wakeup cutscene after Eigong's soulscape"); __instance.TrySkip(); SpeedrunCutsceneSkip.AddSkippedTimeToLivesplit(236.528f); } } } } internal class FullPath { public static string GetFullPath(GameObject go) { Transform val = go.transform; List list = new List(); while ((Object)(object)val != (Object)null) { list.Add(((Object)val).name); val = val.parent; } list.Reverse(); return string.Join("/", list); } } [HarmonyPatch] internal class LadyEtherealPostBoss { [HarmonyPostfix] [HarmonyPatch(typeof(SimpleCutsceneManager), "PlayAnimation")] private static async void SimpleCutsceneManager_PlayAnimation_Postfix(SimpleCutsceneManager __instance) { if (SpeedrunCutsceneSkip.Instance.SkipSetting.Value) { if (((Object)((Component)__instance).transform.parent.parent.parent).name == "SimpleCutSceneFSM (離開幻境)" && FullPath.GetFullPath(((Component)__instance).gameObject) == "P2_R22_Savepoint_GameLevel/EventBinder/SimpleCutSceneFSM (離開幻境)/FSM Animator/LogicRoot/[CutScene]") { Log.Info("SpeedrunCutsceneSkip skipping Lady Ethereal's post-fight cutscene"); __instance.TrySkip(); } if (((Object)((Component)__instance).transform.parent.parent.parent).name == "A7_S6_Cutscene FSM" && FullPath.GetFullPath(((Component)__instance).gameObject) == "A7_S6_Memory_Butterfly_CutScene_GameLevel/A7_S6_Cutscene FSM/FSM Animator/LogicRoot/[CutScene]") { Log.Info("SpeedrunCutsceneSkip skipping the Azure Flyer flashback cutscene"); __instance.TrySkip(); } } } [HarmonyPrefix] [HarmonyPatch(typeof(VideoPlayAction), "OnStateEnterImplement")] private static async void VideoPlayAction_OnStateEnterImplement(VideoPlayAction __instance) { if (SpeedrunCutsceneSkip.Instance.SkipSetting.Value && ((Object)__instance).name == "[Action] VideoPlayAction" && FullPath.GetFullPath(((Component)__instance).gameObject) == "A7_S6_Memory_Butterfly_CutScene_GameLevel/A7_S6_Cutscene FSM/--[States]/FSM/[State] PlayingVideo/[Action] VideoPlayAction") { Log.Info("SpeedrunCutsceneSkip waiting 1.6 seconds before skipping the post-Lady Ethereal Heng flashback video"); await UniTask.Delay(1667, false, (PlayerLoopTiming)8, default(CancellationToken)); Log.Info("SpeedrunCutsceneSkip skipping the post-Lady Ethereal Heng flashback video"); AccessTools.Method(typeof(VideoPlayAction), "TrySkip", (Type[])null, (Type[])null).Invoke(__instance, Array.Empty()); SpeedrunCutsceneSkip.AddSkippedTimeToLivesplit(134.713f); } } } 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); } } } [HarmonyPatch] internal class PavillionNymphIntro { [HarmonyPostfix] [HarmonyPatch(typeof(SimpleCutsceneManager), "PlayAnimation")] private static async void SimpleCutsceneManager_PlayAnimation_Postfix(SimpleCutsceneManager __instance) { if (SpeedrunCutsceneSkip.Instance.SkipSetting.Value) { if (((Object)((Component)__instance).transform.parent.parent.parent.parent).name == "NPC_AICore_FSM" && FullPath.GetFullPath(((Component)__instance).gameObject) == "AG_S2/Room/Prefab/ControlRoom FSM Binding Tool/NPC_AICore_Base/NPC_AICore_Base_FSM/FSM Animator/LogicRoot/NPC_AICore_FSM/General FSM Object/Animator(FSM)/LogicRoot/[CutScene]初次對話演出") { Log.Info("SpeedrunCutsceneSkip waiting 1.6 seconds before skipping the Ruyi activation cutscene"); await UniTask.Delay(1667, false, (PlayerLoopTiming)8, default(CancellationToken)); Log.Info("SpeedrunCutsceneSkip skipping the Ruyi restore sequence"); __instance.TrySkip(); SpeedrunCutsceneSkip.AddSkippedTimeToLivesplit(37.283f); } if (((Object)((Component)__instance).transform.parent.parent.parent).name == "SimpleCutSceneFSM_軒軒到基地演出" && FullPath.GetFullPath(((Component)__instance).gameObject) == "AG_S2/Room/NPCs/議會演出相關Binding/SimpleCutSceneFSM_軒軒到基地演出/FSM Animator/LogicRoot/[CutScene]") { Log.Info("SpeedrunCutsceneSkip waiting 1.6 seconds before skipping the Shuanshuan intro dialogue"); await UniTask.Delay(1667, false, (PlayerLoopTiming)8, default(CancellationToken)); Log.Info("SpeedrunCutsceneSkip skipping the Shuanshuan dialogue"); __instance.TrySkip(); SpeedrunCutsceneSkip.AddSkippedTimeToLivesplit(18.683f); } } } } [HarmonyPatch] internal class PowerReservoirHengCall { [HarmonyPrefix] [HarmonyPatch(typeof(A2_SG4_Logic), "EnterLevelStart")] private static async void A2_SG4_Logic_EnterLevelStart(A2_SG4_Logic __instance) { if (SpeedrunCutsceneSkip.Instance.SkipSetting.Value) { Log.Info("SpeedrunCutsceneSkip skipping the Power Reservoir (Center) Heng call"); await UniTask.Delay(250, false, (PlayerLoopTiming)8, default(CancellationToken)); __instance.TrySkip(); SpeedrunCutsceneSkip.AddSkippedTimeToLivesplit(41.076f); } } } internal struct SimpleCutsceneSkipData { public int delayTime; public float livesplitAddedTime; } internal static class SimpleCutsceneSkipDataTable { public static readonly Dictionary Table = new Dictionary { { "A4_S5/Room/TaoChangSniper_SimpleCutSceneFSM/FSM Animator/LogicRoot/[CutScene]", new SimpleCutsceneSkipData { delayTime = 1667, livesplitAddedTime = 26.621f } }, { "A5_S2/Room/SimpleCutSceneFSM_A5妹妹回憶/FSM Animator/LogicRoot/[CutScene]", new SimpleCutsceneSkipData { delayTime = 250, livesplitAddedTime = 0f } }, { "A5_S2/Room/SimpleCutSceneFSM_A5妹妹回憶/FSM Animator/LogicRoot/[CutScene] Second", new SimpleCutsceneSkipData { delayTime = 0, livesplitAddedTime = 0f } }, { "A5_S2/Room/SimpleCutSceneFSM_A5妹妹回憶/FSM Animator/LogicRoot/[CutScene] Third", new SimpleCutsceneSkipData { delayTime = 0, livesplitAddedTime = 126.754f } } }; } [HarmonyPatch] internal class SimpleCutsceneSkip { [HarmonyPostfix] [HarmonyPatch(typeof(SimpleCutsceneManager), "PlayAnimation")] private static async void SimpleCutsceneManager_PlayAnimation_Postfix(SimpleCutsceneManager __instance) { if (!SpeedrunCutsceneSkip.Instance.SkipSetting.Value) { return; } string goPath = FullPath.GetFullPath(((Component)__instance).gameObject); if (SimpleCutsceneSkipDataTable.Table.ContainsKey(goPath)) { int delayTime = SimpleCutsceneSkipDataTable.Table[goPath].delayTime; if (delayTime > 0) { Log.Info($"SpeedrunCutsceneSkip waiting {delayTime}ms before skipping cutscene: {goPath}"); await UniTask.Delay(delayTime, false, (PlayerLoopTiming)8, default(CancellationToken)); } Log.Info("SpeedrunCutsceneSkip skipping cutscene: " + goPath); __instance.TrySkip(); SpeedrunCutsceneSkip.AddSkippedTimeToLivesplit(SimpleCutsceneSkipDataTable.Table[goPath].livesplitAddedTime); } } } [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("SpeedrunCutsceneSkip", "SpeedrunCutsceneSkip", "0.0.1")] public class SpeedrunCutsceneSkip : BaseUnityPlugin { public ConfigEntry SkipSetting; public static SpeedrunCutsceneSkip Instance; private Harmony harmony; private int LivesplitHook_SkippedCutscenesCount; private float LivesplitHook_TimeToAdd; private void Awake() { Log.Init(((BaseUnityPlugin)this).Logger); RCGLifeCycle.DontDestroyForever(((Component)this).gameObject); Instance = this; harmony = Harmony.CreateAndPatchAll(typeof(SpeedrunCutsceneSkip).Assembly, (string)null); SkipSetting = ((BaseUnityPlugin)this).Config.Bind("", "Skip Certain Cutscenes", true, "Turn this off and this mod will no longer do anything."); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Plugin SpeedrunCutsceneSkip is loaded!"); } private void OnDestroy() { harmony.UnpatchSelf(); } public static void AddSkippedTimeToLivesplit(float time) { Instance.LivesplitHook_TimeToAdd = time; Instance.LivesplitHook_SkippedCutscenesCount++; } } [HarmonyPatch] internal class WarehouseHengCall { [HarmonyPostfix] [HarmonyPatch(typeof(SimpleCutsceneManager), "PlayAnimation")] private static async void SimpleCutsceneManager_PlayAnimation_Postfix(SimpleCutsceneManager __instance) { if (SpeedrunCutsceneSkip.Instance.SkipSetting.Value && ((Object)((Component)__instance).transform.parent.parent.parent).name == "妹妹回憶_SimpleCutSceneFSM" && FullPath.GetFullPath(((Component)__instance).gameObject) == "A2_SG4/Room/妹妹回憶_SimpleCutSceneFSM/FSM Animator/LogicRoot/[CutScene]") { Log.Info("SpeedrunCutsceneSkip waiting 1.6 seconds before skipping the Warehouse Heng call"); await UniTask.Delay(1667, false, (PlayerLoopTiming)8, default(CancellationToken)); Log.Info("SpeedrunCutsceneSkip skipping the Warehouse Heng call"); __instance.TrySkip(); SpeedrunCutsceneSkip.AddSkippedTimeToLivesplit(71.131f); } } } [HarmonyPatch] internal class YanlaoPreFight { [HarmonyPrefix] [HarmonyPatch(typeof(A4_S5_Logic), "EnterLevelStart")] private static async void A4_S5_Logic_Start(A4_S5_Logic __instance) { if (SpeedrunCutsceneSkip.Instance.SkipSetting.Value && !__instance.FirstTimeAnimationPlayed.CurrentValue) { await UniTask.Delay(1000, false, (PlayerLoopTiming)8, default(CancellationToken)); Log.Info("SpeedrunCutsceneSkip skipping the Yanlao Pre-Fight Scene"); __instance.BeforeMangaBubble.TrySkip(); __instance.StartCutscene.TrySkip(); __instance.BubbleDialogue.TrySkip(); __instance.MechClawGamePlay.gameObject.SetActive(true); ((Component)__instance.GianMechClawMonsterBase).gameObject.SetActive(true); __instance.SwapClaw(); ((ICutScene)__instance.StartCutsceneEmergency).PlayCutscene((Action)delegate { AccessTools.Method(typeof(A4_S5_Logic), "StartFight", new Type[0], (Type[])null).Invoke(__instance, new object[0]); }, 1f, true, 0f); SpeedrunCutsceneSkip.AddSkippedTimeToLivesplit(47.1f); } } } public static class PluginInfo { public const string PLUGIN_GUID = "SpeedrunCutsceneSkip"; public const string PLUGIN_NAME = "SpeedrunCutsceneSkip"; public const string PLUGIN_VERSION = "0.0.1"; } }