Книга некроманта 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,119 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user