Книга: главы ножа и Могильной крысы; плакат ножа, грифельная доска, дверь-шкаф; чертоги 25x8x25; плата силовой ножа 20/40

- Книга некроманта: главы «Нож некроманта» и «Могильная крыса» - лист-чертёж сверху (Textures/,
  три вида из Blender, подписи на эсперанто), управление и лор; окно книги непрозрачное, текст с контуром
- «Дух крысы» переименован в «Могильную крысу» на 13 языках (ключи прежние)
- Блоки (Dev): плакат-синька ножа (necroPosterKnife, 3:2 под полотно рамки), грифельная доска
  necroChalkboard (надписи - TEFeatureSignable, писать только в редакторе/творческом; шрифт Neucha OFL,
  свой шейдер текста с тестом глубины), дверь-книжный шкаф necroBookDoor (своя модель с аниматором,
  хитбокс створки через RootTransformRefParent)
- Чертоги: префаб 25x8x25 (промежуточный); открываются только межкомнатные двери; надписи на досках
  по дням игры: «Отомсти архимагу!», с 5-го дня «Берегись излучения красной луны»
- Нож: силовая атака стоит 20 здоровья, с «Мёртвой бурей» 40; не хватает - itembreak, «Недостаточно
  здоровья» и обычный удар; описания на 13 языках
- Консольная команда necromodel - устройство модели блока в лог

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlexCube
2026-10-09 18:26:17 +03:00
co-authored by Claude Opus 5.5
parent e94e967b8e
commit 4e14a0562b
23 changed files with 609 additions and 41 deletions
+42 -4
View File
@@ -29,6 +29,9 @@ namespace NecromancerTome
/// "@modfolder(NecromancerTome):Textures/x.png") или зацикленное видео без звука. Ни того,
/// ни другого - текст на всю ширину.</summary>
public string Image, Video;
/// <summary>Картинка полосой сверху во всю ширину, текст под ней (главы питомцев, указание 09.10:
/// «под картинкой - управление схематично, под ним - описание питомца»).</summary>
public bool ImageTop;
}
/// <summary>cvar «письмо архимага прочитано» - ставит записка noteDuke01 (Config/items.xml).</summary>
@@ -54,6 +57,26 @@ namespace NecromancerTome
PageKeys = new[] { "necroTomeCh1P1", "necroTomeCh1P2", "necroTomeCh1P3", "necroTomeCh1P4" },
Unlocked = _p => true
},
// Нож некроманта (указание 09.10): лист-чертёж ножа (тот же рецепт, что у крысы), управление ножом и духом
// змеи, описание. Перед питомцами - «основной инструмент», с него всё начинается.
new Chapter
{
TitleKey = "necroTomeKnifeTitle",
PageKeys = new[] { "necroTomeKnifeP1", "necroTomeKnifeP2", "necroTomeKnifeP3" },
Image = "@modfolder(NecromancerTome):Textures/necroTomeKnife.png",
ImageTop = true,
Unlocked = _p => true
},
// Главы питомцев (указание 09.10): лист-чертёж модели (три вида из Blender, подписи на эсперанто) сверху, на первой
// странице - управление, дальше - описание. Крыса - учебная модель, глава открыта сразу.
new Chapter
{
TitleKey = "necroTomeRatTitle",
PageKeys = new[] { "necroTomeRatP1", "necroTomeRatP2", "necroTomeRatP3" },
Image = "@modfolder(NecromancerTome):Textures/necroTomeRat.png",
ImageTop = true,
Unlocked = _p => true
},
};
public static void Open(EntityPlayerLocal _player)
@@ -73,6 +96,11 @@ namespace NecromancerTome
XUiV_Video _vid;
/// <summary>Ширина текста без иллюстрации и с ней (windows.xml: lblText 1180, картинка с x 680).</summary>
const int TextWidthFull = 1180, TextWidthHalf = 590;
/// <summary>Раскладка «картинка сверху»: лист-чертёж 1180x400 (как Textures/necroTomeRat.png), текст ниже.</summary>
static readonly Vector2i ImgSidePos = new Vector2i(680, -104), ImgSideSize = new Vector2i(560, 640);
static readonly Vector2i ImgTopPos = new Vector2i(60, -104), ImgTopSize = new Vector2i(1180, 400);
static readonly Vector2i TextPos = new Vector2i(60, -104), TextTopPos = new Vector2i(60, -518);
const int TextHeightFull = 640, TextHeightTop = 245;
XUiC_SimpleButton _toc, _prev, _next, _close;
readonly XUiC_SimpleButton[] _chapterButtons = new XUiC_SimpleButton[MaxButtons];
/// <summary>Глава по кнопке оглавления (только открытые главы, по порядку).</summary>
@@ -164,7 +192,7 @@ namespace NecromancerTome
SetLabel(_title, Localization.Get(ch.TitleKey));
SetLabel(_text, Localization.Get(ch.PageKeys[_pageIndex]));
SetLabel(_page, (_pageIndex + 1) + " / " + ch.PageKeys.Length);
SetMedia(ch.Image, ch.Video);
SetMedia(ch.Image, ch.Video, ch.ImageTop);
SetVisible(_toc, true);
SetVisible(_prev, true);
SetVisible(_next, true);
@@ -181,12 +209,15 @@ namespace NecromancerTome
/// <summary>Показать картинку или видео справа (или убрать обе). Видео задаётся только здесь,
/// а не в вёрстке: XUiV_Video сам запускает ролик при открытии окна, и в оглавлении он играл
/// бы невидимым. Пустой uri - XUiV_Video.startVideo сам освобождает свою текстуру.</summary>
void SetMedia(string _image, string _video)
void SetMedia(string _image, string _video, bool _top = false)
{
bool img = !string.IsNullOrEmpty(_image), vid = !img && !string.IsNullOrEmpty(_video);
if (_img != null)
{
if (img && _img.Path != _image)
// Без чтения _img.Path: у пустой картинки (в вёрстке texture не задан) его геттер берёт
// textureUris[0] из пустого массива - IndexOutOfRange (тест 09.10). Сеттер сам сравнивает
// с текущим путём и не перезагружает тот же файл.
if (img)
{
_img.Path = _image;
}
@@ -212,9 +243,16 @@ namespace NecromancerTome
}
_vid.IsVisible = vid;
}
if (_img != null)
{
_img.Position = _top ? ImgTopPos : ImgSidePos;
_img.Size = _top ? ImgTopSize : ImgSideSize;
}
if (_text != null)
{
_text.Size = new Vector2i(img || vid ? TextWidthHalf : TextWidthFull, _text.Size.y);
bool top = _top && img;
_text.Position = top ? TextTopPos : TextPos;
_text.Size = new Vector2i(top || !(img || vid) ? TextWidthFull : TextWidthHalf, top ? TextHeightTop : TextHeightFull);
}
}