Одержимость питомцев, стервятник-разведчик, метки на карте, череп, глава письма

- Одержимость питомца: ПКМ с книгой - вселиться, E - отозвать (и изнутри питомца),
  пояс скрыт, надпись о враге у тела.
- Одержимый стервятник: полёт, рывок на ЛКМ с 10 м с прямым уроном, звуком и
  кровотечением, обычный прицел краснеет на цели; посадка в позу 0 и взлёт на пробел;
  ПКМ - клич-приманка (паникующая курица, откат 0.5 с); F - питаться трупами и
  блоками-падалью, доеденное исчезает.
- Дух змеи атакует только нежить; фиолетовая метка на карте у змеи и одержимого.
- Череп из красного трупа (этап 1 цинерария); глава книги «Письмо архимага» с видео.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlexCube
2026-10-06 22:09:28 +03:00
co-authored by Claude Opus 5.5
parent a79bb085ab
commit beab1640b0
16 changed files with 1287 additions and 57 deletions
+74 -1
View File
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Scripting;
using UnityEngine.Video;
namespace NecromancerTome
{
@@ -24,11 +25,29 @@ namespace NecromancerTome
public string TitleKey;
public string[] PageKeys;
public Func<EntityPlayerLocal, bool> Unlocked;
/// <summary>Иллюстрация справа от текста на всех страницах главы: картинка (путь вида
/// "@modfolder(NecromancerTome):Textures/x.png") или зацикленное видео без звука. Ни того,
/// ни другого - текст на всю ширину.</summary>
public string Image, Video;
}
/// <summary>cvar «письмо архимага прочитано» - ставит записка noteDuke01 (Config/items.xml).</summary>
public const string LetterReadCVar = "necroTomeLetterRead";
public static readonly List<Chapter> Chapters = new List<Chapter>
{
// Глава 1 (указание 03.10): статья о некроэнергии, сокращённая - без примеров и бытовой речи.
// Глава 1 (указание 06.10): письмо архимага - та самая стартовая записка, текст берётся из её
// же ключа. Открывается ТОЛЬКО когда записку прочли («появляется в книге, когда прочитано
// письмо», 06.10): cvar ставит сама записка по «ОК» (Config/items.xml). Запасного условия по
// уровню нет - в старых сейвах, где записку прочли до 1.7.0, главы не будет.
new Chapter
{
TitleKey = "necroTomeLetterTitle",
PageKeys = new[] { "noteDuke01Desc" },
Video = Patch_XUiC_MessageBoxWindowGroup_ShowOkCancel_NoteFlashback.NoteFlashbackVideoPath,
Unlocked = _p => _p.Buffs.GetCustomVar(LetterReadCVar) > 0f
},
// Глава 2 (указание 03.10): статья о некроэнергии, сокращённая - без примеров и бытовой речи.
new Chapter
{
TitleKey = "necroTomeCh1Title",
@@ -50,6 +69,10 @@ namespace NecromancerTome
const int MaxButtons = 8;
XUiV_Label _title, _text, _page;
XUiV_Texture _img;
XUiV_Video _vid;
/// <summary>Ширина текста без иллюстрации и с ней (windows.xml: lblText 1180, картинка с x 680).</summary>
const int TextWidthFull = 1180, TextWidthHalf = 590;
XUiC_SimpleButton _toc, _prev, _next, _close;
readonly XUiC_SimpleButton[] _chapterButtons = new XUiC_SimpleButton[MaxButtons];
/// <summary>Глава по кнопке оглавления (только открытые главы, по порядку).</summary>
@@ -62,6 +85,8 @@ namespace NecromancerTome
_title = GetChildById("lblTitle")?.ViewComponent as XUiV_Label;
_text = GetChildById("lblText")?.ViewComponent as XUiV_Label;
_page = GetChildById("lblPage")?.ViewComponent as XUiV_Label;
_img = GetChildById("imgPage")?.ViewComponent as XUiV_Texture;
_vid = GetChildById("vidPage")?.ViewComponent as XUiV_Video;
_toc = GetChildById("btnToc") as XUiC_SimpleButton;
_prev = GetChildById("btnPrev") as XUiC_SimpleButton;
_next = GetChildById("btnNext") as XUiC_SimpleButton;
@@ -93,6 +118,7 @@ namespace NecromancerTome
SetLabel(_title, Localization.Get("necroTomeToc"));
SetLabel(_text, "");
SetLabel(_page, "");
SetMedia(null, null);
EntityPlayerLocal player = xui.playerUI.entityPlayer;
int b = 0;
for (int c = 0; c < NecroTomeBook.Chapters.Count && b < MaxButtons; c++)
@@ -138,6 +164,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);
SetVisible(_toc, true);
SetVisible(_prev, true);
SetVisible(_next, true);
@@ -145,6 +172,52 @@ namespace NecromancerTome
if (_next != null) _next.Enabled = _pageIndex < ch.PageKeys.Length - 1;
}
public override void OnClose()
{
SetMedia(null, null);
base.OnClose();
}
/// <summary>Показать картинку или видео справа (или убрать обе). Видео задаётся только здесь,
/// а не в вёрстке: XUiV_Video сам запускает ролик при открытии окна, и в оглавлении он играл
/// бы невидимым. Пустой uri - XUiV_Video.startVideo сам освобождает свою текстуру.</summary>
void SetMedia(string _image, string _video)
{
bool img = !string.IsNullOrEmpty(_image), vid = !img && !string.IsNullOrEmpty(_video);
if (_img != null)
{
if (img && _img.Path != _image)
{
_img.Path = _image;
}
_img.IsVisible = img;
}
if (_vid != null)
{
string uri = vid ? _video : "";
if (_vid.VideoUri != uri)
{
_vid.VideoUri = uri;
}
if (_vid.videoPlayer != null)
{
_vid.videoPlayer.audioOutputMode = VideoAudioOutputMode.None;
// Проверка на утечки 06.10: XUiV_Video гасит ролик при смене uri, только если тот уже
// играет; ушли в оглавление во время подготовки - догрузился бы и играл невидимым до
// закрытия окна. Останавливаем сами (Stop освобождает декодер).
if (!vid)
{
_vid.videoPlayer.Stop();
}
}
_vid.IsVisible = vid;
}
if (_text != null)
{
_text.Size = new Vector2i(img || vid ? TextWidthHalf : TextWidthFull, _text.Size.y);
}
}
void Turn(int _d)
{
if (_chapter >= 0)