using System.Diagnostics.CodeAnalysis; using ADOFAIRenderer.Core; using HarmonyLib; namespace ADOFAIRenderer.Patch.Game; /// /// Suppresses the end-screen lanterns (level complete / accuracy / speed trial lamps) shown after a clear. /// /// Patched members: EndscreenLanterns.Show(scrPlayer, float, int, bool), which activates the lantern set, sets the /// lantern texts and starts DOTween sequences (chainSequence for the chain drop, lampSequences[3] for the lamp /// effects), returning the number of lanterns shown; and EndscreenLanterns.Hide(), which deactivates the GameObject. /// /// /// Active while (stage >= OnScene), so the lanterns and their tweens do not appear in /// the rendered video during the delay after the level end. See also . /// /// [HarmonyPatch(typeof(EndscreenLanterns))] // ReSharper disable once InconsistentNaming public static class EndscreenLanternsPatch { /// /// Prefix of EndscreenLanterns.Show. While rendering, hides the lantern set (which also runs /// ) and reports zero lanterns shown. /// /// The lantern set being shown. /// Return value of Show: number of lanterns shown; set to 0 when the original is skipped. /// True to run the original method (not rendering); false to skip it (rendering). [HarmonyPrefix] [HarmonyPatch("Show")] [SuppressMessage("ReSharper", "InconsistentNaming")] public static bool ForceHide(EndscreenLanterns __instance, ref int __result) { if (!RenderManager.IsActive) return true; if (__instance != null) { __instance.Hide(); } __result = 0; // Return 0 lanterns shown return false; // Skip original Show method } /// /// Prefix of EndscreenLanterns.Hide. While rendering, releases the lantern tweens started by an earlier Show /// before the original deactivates the GameObject. /// /// The private fields lampSequences (DOTween Sequence[]) and chainSequence (Sequence) are read /// through Harmony . For each sequence, a public instance method Kill(bool) is looked up on the /// object's runtime type by reflection and invoked with false when found; then the field slot is set to null. /// In the game's DOTween.dll, Kill(this Tween, bool complete = false) is an extension method of /// DG.Tweening.TweenExtensions, so the lookup on Sequence returns null and no Kill is invoked; the effect is /// that the fields stop referencing the sequences, while the sequences themselves keep running in DOTween. /// Reflection failures on a single sequence (including a null array element) are ignored. The original Hide always runs /// afterwards. /// /// /// The lantern set being hidden. [HarmonyPrefix] [HarmonyPatch("Hide")] [SuppressMessage("ReSharper", "InconsistentNaming")] public static void PrefixHide(EndscreenLanterns __instance) { if (__instance == null) return; if (!RenderManager.IsActive) return; var traverse = Traverse.Create(__instance); // Kill lampSequences (private Sequence[] lampSequences) via reflection var lampSequencesField = traverse.Field("lampSequences"); var lampSequencesObj = lampSequencesField.GetValue(); if (lampSequencesObj is System.Array lampSequencesArray) { for (var i = 0; i < lampSequencesArray.Length; i++) { var item = lampSequencesArray.GetValue(i); try { var killMethod = item.GetType().GetMethod("Kill", new[] { typeof(bool) }); killMethod?.Invoke(item, new object[] { false }); } catch { // Ignore reflection errors } lampSequencesArray.SetValue(null, i); } } // Kill chainSequence (private Sequence chainSequence) via reflection var chainSequenceField = traverse.Field("chainSequence"); var chainSequenceObj = chainSequenceField.GetValue(); if (chainSequenceObj == null) return; try { var killMethod = chainSequenceObj.GetType().GetMethod("Kill", new[] { typeof(bool) }); killMethod?.Invoke(chainSequenceObj, new object[] { false }); } catch { // Ignore reflection errors } chainSequenceField.SetValue(null); } }