Мод для 7 Days to Die 3.2: навык «Некромантия», растущий от счётчика убитых зомби, тёмное оружие с шестью собственными модами, призывная нежить, пирамида духов и сюжетный финал через Чёрный портал. Локализация на 13 языках. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MaNro5hAGTzcQ7rJNN2tCX
120 lines
12 KiB
XML
120 lines
12 KiB
XML
<windows>
|
||
<!-- Black portal video (BACKLOG.md, "чёрный портал" dialog/video feature, 2026-08-30 follow-up
|
||
bugfix). Root cause of "пауза встала, видео не вижу": XUiC_VideoPlayer.PlayVideo opens a
|
||
window/group named "videoPlayer" - real and fully functional (it's exactly what plays the
|
||
TFP studio intro and the main-menu background loop), but that window is declared ONLY in
|
||
Data/Config/XUi_Menu/windows.xml (the MAIN MENU's own UI layout) - it does not exist at
|
||
all in Data/Config/XUi_InGame/windows.xml (confirmed by grepping both files directly), so
|
||
GUIWindowManager.GetWindow/Open logged "Window \"videoPlayer\" unknown!" the moment the
|
||
in-game code tried to open it. This appends the EXACT SAME window block vanilla's own menu
|
||
already uses (copied verbatim from XUi_Menu/windows.xml, not reinvented) into the in-game
|
||
UI instead, so the exact same controller="VideoPlayer" (XUiC_VideoPlayer, already compiled
|
||
into the base game, no Harmony needed) and the <video> element (a generic engine-level UI
|
||
primitive, not menu-specific) can be found by the in-game windowManager too. Needs a
|
||
matching window_group entry - see Config/XUi_InGame/xui.xml.
|
||
|
||
THIS ONE WINDOW IS SHARED - both the Black Portal video AND Duke's note flashback
|
||
(NoteFlashbackPatch.cs) call the same XUiC_VideoPlayer.PlayVideo, which always opens this
|
||
same "videoPlayer" window/group by id - one fix here covers both.
|
||
|
||
"видео полупрозрачное" fix (user report 2026-08-30, after first successful in-game test):
|
||
added globalopacitymod="0" to videoBackground/videoTexture/skipPrompt below. Root cause,
|
||
decompiled, not guessed: every XUiV_ImageBased-derived view (sprite AND video both are -
|
||
XUiV_Sprite/XUiV_TextureBased both extend it) has a globalOpacityModifier defaulting to 1,
|
||
meaning by default its final alpha gets multiplied by xui.BackgroundGlobalOpacity (unless
|
||
foregroundlayer="true", which uses a separate ForegroundGlobalOpacity instead) - this is a
|
||
REAL, already-live game option, decompiled straight from EnumGamePrefs.Options:
|
||
OptionsBackgroundGlobalOpacity defaults to 0.95, not 1.0, and is user-adjustable (further
|
||
down is common - players lower it to see more of the game world through menu backgrounds).
|
||
This window's copy (like vanilla's own menu copy) never set globalopacitymod, so both the
|
||
"opaque" black background AND the video picture itself were always getting dimmed by
|
||
whatever fraction of full opacity the user's own Background Opacity option is set to -
|
||
in the menu this is invisible (nothing behind the menu to bleed through), but in-game the
|
||
actual 3D world sits right behind this window and shows through the gap. The vanilla menu
|
||
copy has this exact same latent issue, just never exposed because it's never used with a
|
||
3D scene behind it - not something to also go "fix" in Data/Config, just worth knowing this
|
||
mod's copy needed the explicit opt-out that context actually requires. -->
|
||
<append xpath="/windows">
|
||
<window name="videoPlayer" controller="VideoPlayer" pos="0,0" depth="900" cursor_area="true">
|
||
<sprite name="videoBackground" depth="0" sprite="menu_empty" type="sliced" color="[black]" anchor_left="#cam,0,-10" anchor_right="#cam,1,10" anchor_bottom="#cam,0,-10" anchor_top="#cam,1,10" style="press" gamepad_selectable="false" globalopacitymod="0" />
|
||
<video name="videoTexture" depth="1" anchor_left="#cam,0,0" anchor_right="#cam,1,0" anchor_bottom="#cam,0,0" anchor_top="#cam,1,0" globalopacitymod="0"/>
|
||
|
||
<sprite name="skipPrompt" depth="5" sprite="ui_game_panel_header" color="[black]" anchor_left="#cam,1,-200" anchor_right="#cam,1,0" anchor_bottom="#cam,0,-0" anchor_top="#cam,0,60" globalopacitymod="0">
|
||
<label name="lblSkip" depth="6" text_key="Skip" justify="left" pivot="left" font_size="32" width="180" height="100" pos="20,-30" parse_actions="true" actions_default_format="( ### )"/>
|
||
</sprite>
|
||
</window>
|
||
</append>
|
||
|
||
<!-- ФИНАЛЬНЫЕ СЛАЙДЫ ЧЁРНОГО ПОРТАЛА, 2026-09-09 (BACKLOG.md "концовка серией диалоговых окон
|
||
вместо видео"). Шесть полноэкранных картинок, по одному окну на слайд; текст и кнопки
|
||
Назад/Дальше/выбор рисует поверх них ванильный messageBox - см. HarmonySrc/FinalSlides.cs,
|
||
там же вся логика листания.
|
||
|
||
ПОЧЕМУ ШЕСТЬ ОТДЕЛЬНЫХ ОКОН, А НЕ ОДНО С ПЕРЕКЛЮЧЕНИЕМ СПРАЙТА. Смена спрайта на лету
|
||
требует либо привязки ({binding}), либо своего XUiController - а в разведке (BACKLOG.md)
|
||
как раз осталось непроверенным, найдёт ли движок контроллер, объявленный в сборке мода.
|
||
Шесть окон с ЖЁСТКО прописанным sprite= обходят вопрос целиком: ни привязок, ни
|
||
контроллера, окно вообще без атрибута controller= (так объявлены и ванильные
|
||
HUDLeftStatBars/windowFuel, проверено в Data/Config/XUi_InGame/windows.xml). Листание -
|
||
это просто Close старого окна + Open нового из C#.
|
||
|
||
depth="900" - ниже messageBox (depth="1001" в шаблоне <messagebox>,
|
||
Data/Config/XUi_Common/templates.xml:143), так что текстовая коробка 1200x400 ложится
|
||
поверх картинки, а картинка видна вокруг неё. Открывать эти окна НЕ модально: в
|
||
GUIWindowManager.openInternal модальное открытие зовёт CloseAllOpenModalWindows(), то есть
|
||
модальный messageBox закрыл бы модальный слайд - а немодальный переживает это спокойно.
|
||
|
||
globalopacitymod="0" - та же правка, что у videoPlayer выше: без неё альфа умножается на
|
||
пользовательскую настройку Background Opacity (по умолчанию 0.95), и сквозь "финальную"
|
||
картинку просвечивал бы игровой мир.
|
||
|
||
ПРО ПРОПОРЦИИ: исходники 1536x1024 (3:2), а окно растягивается на весь экран (16:9). Чтобы
|
||
не плющить лица на крупных планах, картинки залиты чёрными полями по бокам до 1824x1024
|
||
(16:9) ЗАРАНЕЕ, при копировании в UIAtlases/NecroFinal - у XUiV_Sprite нет атрибута
|
||
сохранения пропорций (декомпилировано: он парсит только atlas/sprite/color/fill*/gradient*),
|
||
так что letterbox приходится запекать в сам PNG. Второй чёрный спрайт под картинкой -
|
||
страховка на неродных соотношениях сторон (ультраширокие мониторы).
|
||
|
||
Атлас NecroFinal - папка UIAtlases/NecroFinal, механизм тот же, что у ItemIconAtlas.
|
||
Шесть картинок 1824x1024 пакуются UIAtlasFromFolder.createUiAtlasFromTextures через
|
||
PackTextures(..., 2, 8192): суммарно ~11.2 Мпикс, влезает в один атлас 4096x4096. -->
|
||
<append xpath="/windows">
|
||
<window name="necroFinalSlide1" pos="0,0" depth="900">
|
||
<sprite name="slideLetterbox" depth="0" sprite="menu_empty" type="sliced" color="[black]" anchor_left="#cam,0,-10" anchor_right="#cam,1,10" anchor_bottom="#cam,0,-10" anchor_top="#cam,1,10" globalopacitymod="0"/>
|
||
<sprite name="slideArt" depth="1" atlas="NecroFinal" sprite="NecroFinal1" type="simple" anchor_left="#cam,0,0" anchor_right="#cam,1,0" anchor_bottom="#cam,0,0" anchor_top="#cam,1,0" globalopacitymod="0"/>
|
||
</window>
|
||
<window name="necroFinalSlide2" pos="0,0" depth="900">
|
||
<sprite name="slideLetterbox" depth="0" sprite="menu_empty" type="sliced" color="[black]" anchor_left="#cam,0,-10" anchor_right="#cam,1,10" anchor_bottom="#cam,0,-10" anchor_top="#cam,1,10" globalopacitymod="0"/>
|
||
<sprite name="slideArt" depth="1" atlas="NecroFinal" sprite="NecroFinal2" type="simple" anchor_left="#cam,0,0" anchor_right="#cam,1,0" anchor_bottom="#cam,0,0" anchor_top="#cam,1,0" globalopacitymod="0"/>
|
||
</window>
|
||
<window name="necroFinalSlide3" pos="0,0" depth="900">
|
||
<sprite name="slideLetterbox" depth="0" sprite="menu_empty" type="sliced" color="[black]" anchor_left="#cam,0,-10" anchor_right="#cam,1,10" anchor_bottom="#cam,0,-10" anchor_top="#cam,1,10" globalopacitymod="0"/>
|
||
<sprite name="slideArt" depth="1" atlas="NecroFinal" sprite="NecroFinal3" type="simple" anchor_left="#cam,0,0" anchor_right="#cam,1,0" anchor_bottom="#cam,0,0" anchor_top="#cam,1,0" globalopacitymod="0"/>
|
||
</window>
|
||
<window name="necroFinalSlide4" pos="0,0" depth="900">
|
||
<sprite name="slideLetterbox" depth="0" sprite="menu_empty" type="sliced" color="[black]" anchor_left="#cam,0,-10" anchor_right="#cam,1,10" anchor_bottom="#cam,0,-10" anchor_top="#cam,1,10" globalopacitymod="0"/>
|
||
<sprite name="slideArt" depth="1" atlas="NecroFinal" sprite="NecroFinal4" type="simple" anchor_left="#cam,0,0" anchor_right="#cam,1,0" anchor_bottom="#cam,0,0" anchor_top="#cam,1,0" globalopacitymod="0"/>
|
||
</window>
|
||
<window name="necroFinalSlide5" pos="0,0" depth="900">
|
||
<sprite name="slideLetterbox" depth="0" sprite="menu_empty" type="sliced" color="[black]" anchor_left="#cam,0,-10" anchor_right="#cam,1,10" anchor_bottom="#cam,0,-10" anchor_top="#cam,1,10" globalopacitymod="0"/>
|
||
<sprite name="slideArt" depth="1" atlas="NecroFinal" sprite="NecroFinal5" type="simple" anchor_left="#cam,0,0" anchor_right="#cam,1,0" anchor_bottom="#cam,0,0" anchor_top="#cam,1,0" globalopacitymod="0"/>
|
||
</window>
|
||
<window name="necroFinalSlide6" pos="0,0" depth="900">
|
||
<sprite name="slideLetterbox" depth="0" sprite="menu_empty" type="sliced" color="[black]" anchor_left="#cam,0,-10" anchor_right="#cam,1,10" anchor_bottom="#cam,0,-10" anchor_top="#cam,1,10" globalopacitymod="0"/>
|
||
<sprite name="slideArt" depth="1" atlas="NecroFinal" sprite="NecroFinal6" type="simple" anchor_left="#cam,0,0" anchor_right="#cam,1,0" anchor_bottom="#cam,0,0" anchor_top="#cam,1,0" globalopacitymod="0"/>
|
||
</window>
|
||
</append>
|
||
|
||
<!-- ЭПИЛОГ, 2026-09-09. После выбора на шестом слайде картинки заканчиваются и текст идёт по
|
||
чёрному - отсюда отдельное окно без спрайта из атласа, только чёрная заливка. Тот же
|
||
depth="900", что у слайдов, так что текстовая коробка (depth 1001) снова ложится сверху.
|
||
Заливка копирует приём videoPlayer'а выше: menu_empty, растянутый по #cam с запасом в
|
||
10px по каждой стороне, плюс globalopacitymod="0" - без него сквозь "чёрный экран"
|
||
просвечивал бы игровой мир на величину пользовательской настройки Background Opacity. -->
|
||
<append xpath="/windows">
|
||
<window name="necroFinalBlack" pos="0,0" depth="900">
|
||
<sprite name="blackout" depth="0" sprite="menu_empty" type="sliced" color="[black]" anchor_left="#cam,0,-10" anchor_right="#cam,1,10" anchor_bottom="#cam,0,-10" anchor_top="#cam,1,10" globalopacitymod="0"/>
|
||
</window>
|
||
</append>
|
||
</windows>
|