Книга некроманта 1.0 — первая публичная версия
Мод для 7 Days to Die 3.2: навык «Некромантия», растущий от счётчика убитых зомби, тёмное оружие с шестью собственными модами, призывная нежить, пирамида духов и сюжетный финал через Чёрный портал. Локализация на 13 языках. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MaNro5hAGTzcQ7rJNN2tCX
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NecromancerTome
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.**
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(XUiC_MessageBoxWindowGroup), "ShowOkCancel")]
|
||||
public static class Patch_XUiC_MessageBoxWindowGroup_ShowOkCancel_NoteFlashback
|
||||
{
|
||||
public const string NoteFlashbackVideoPath = "@modfolder(NecromancerTome):Video/DukeNoteFlashback.mp4";
|
||||
|
||||
/// <summary>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.</summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user