using System; using HarmonyLib; using UnityEngine; namespace NecromancerTome { /// /// Duke's note ("Записка от Дюка", item noteDuke01) - user request 2026-08-30: "в момент /// открытия записки, ставить игру на паузу и проигрывать флэшбек" (at the moment the note is /// opened, pause the game and play a flashback). Reuses the exact pause+video pipeline /// already built and tested for the Black Portal Stone (see PortalStonePatch.cs's /// ActivateBlackPortal - GameManager.Instance.Pause/XUiC_VideoPlayer.PlayVideo, both APIs /// decompiled there already, same reasoning applies unchanged here). /// /// FINDING THE RIGHT PATCH POINT: noteDuke01 has no custom C# class of its own - it's a /// plain Class="Eat" item (items.xml) whose entire "reading" experience is a vanilla trick: /// PromptTitle="noteDuke01"/PromptDescription="noteDuke01Desc" make the ENGINE ITSELF show /// the note as a XUiC_MessageBoxWindowGroup.ShowOkCancel(...) confirm box - decompiled /// ItemActionEat directly and confirmed it has no UI-showing code of its own at all /// (NeedPrompt/PromptTitle/PromptDescription/bPromptChecked are all read, never acted on, /// inside that class); the actual ShowOkCancel call lives in the CALLERS instead - two /// separate, decompiled call sites: /// 1. ItemClass.ExecuteAction(int, ItemInventoryData, bool, PlayerActionsLocal) - the /// holding-the-item-and-clicking path. /// 2. XUiC_ItemStack's inventory "Use" (double-click / context-menu) path. /// Both funnel through the exact same static XUiC_MessageBoxWindowGroup.ShowOkCancel call - /// patching THAT one method, instead of either call site separately, covers both input paths /// with a single patch. /// /// IDENTIFYING OUR NOTE: ShowOkCancel receives only already-localized strings, not an item /// reference - decompilation confirms this overload has no ItemValue/ItemClass parameter at /// all. Matched by comparing the incoming title against Localization.Get("noteDuke01") (the /// exact PromptTitle key from items.xml) - unique to this one item in the whole game, not a /// generic vanilla dialog string, so this is a safe match, not a guess. /// /// FLOW: on match, suppress the real dialog for now (Prefix returns false), pause the game, /// and play the flashback; only once the video finishes (or is skipped/errors - PlayVideo's /// own onFinished callback fires in every case, confirmed by decompiling /// XUiC_VideoPlayer.OnClose/FinishAndClose, so this can never soft-lock the pause) does it /// unpause and open the REAL note-text dialog (a re-entrant call to ShowOkCancel itself, via /// a bypass flag so the Prefix doesn't intercept its own follow-up call) - "open note -> /// flashback -> read text -> confirm", rather than overlapping the video with the text box. /// /// VIDEO FILE: Video/DukeNoteFlashback.mp4 - the user's real flashback clip (delivered /// 2026-08-30 as exch/flashbback.mp4), kept as .mp4 rather than renamed to .webm like the /// Black Portal placeholder: Unity's VideoPlayer component (confirmed by decompiling /// XUiV_Video - it wraps a plain UnityEngine.Video.VideoPlayer) natively decodes MP4/H.264 on /// Windows via Media Foundation, and re-labeling an actual MP4 container as .webm would just /// make it fail to decode (VP8/VP9 container expected, not H.264) - not decompiled/proven /// that MP4 plays correctly in THIS build, but there is no reason implied by the decompiled /// code to expect otherwise, and even a decode failure only degrades to a skipped video (see /// FLOW above), never a stuck pause. **Not confirmed in game.** /// [HarmonyPatch(typeof(XUiC_MessageBoxWindowGroup), "ShowOkCancel")] public static class Patch_XUiC_MessageBoxWindowGroup_ShowOkCancel_NoteFlashback { public const string NoteFlashbackVideoPath = "@modfolder(NecromancerTome):Video/DukeNoteFlashback.mp4"; /// Guards the re-entrant call this patch makes to the very method it patches /// (to actually show the note text once the flashback is done) - without this, that /// second call would just trigger the Prefix again and loop back into another flashback /// instead of showing the dialog. Not [ThreadStatic]: XUi/UI code in this game only ever /// runs on the main thread (every other UI-touching patch in this mod makes the same /// assumption, e.g. PortalStonePatch.cs's local-player-only UI calls), so a plain static /// bool is enough here. public static bool bypass; public static bool Prefix(XUi _xuiInstance, string _title, string _text, string _icon, Action _onOk, Action _onCancel, bool _openMainMenuOnClose, bool _modal, bool _cancelOnOutsideClick) { if (bypass) { return true; } if (_xuiInstance == null || _title != Localization.Get("noteDuke01")) { return true; } Debug.Log("[NecromancerTome] NoteFlashbackPatch: Duke's note opened, pausing + playing flashback"); GameManager.Instance.Pause(true); VideoData videoData = new VideoData { url = NoteFlashbackVideoPath }; XUiC_VideoPlayer.PlayVideo(_xuiInstance, videoData, true, delegate(bool skipped) { Debug.Log("[NecromancerTome] NoteFlashbackPatch: flashback finished (skipped=" + skipped + "), unpausing and showing note text"); GameManager.Instance.Pause(false); bypass = true; try { XUiC_MessageBoxWindowGroup.ShowOkCancel(_xuiInstance, _title, _text, _icon, _onOk, _onCancel, _openMainMenuOnClose, _modal, _cancelOnOutsideClick); } finally { bypass = false; } }); return false; } } }