- Одержимость питомца: ПКМ с книгой - вселиться, E - отозвать (и изнутри питомца), пояс скрыт, надпись о враге у тела. - Одержимый стервятник: полёт, рывок на ЛКМ с 10 м с прямым уроном, звуком и кровотечением, обычный прицел краснеет на цели; посадка в позу 0 и взлёт на пробел; ПКМ - клич-приманка (паникующая курица, откат 0.5 с); F - питаться трупами и блоками-падалью, доеденное исчезает. - Дух змеи атакует только нежить; фиолетовая метка на карте у змеи и одержимого. - Череп из красного трупа (этап 1 цинерария); глава книги «Письмо архимага» с видео. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
263 lines
9.6 KiB
C#
263 lines
9.6 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.Scripting;
|
||
using UnityEngine.Video;
|
||
|
||
namespace NecromancerTome
|
||
{
|
||
/// <summary>
|
||
/// Книга с оглавлением (03.10.2026, BACKLOG «Книга с оглавлением и главами»). Окно necroTome
|
||
/// (Config/XUi_InGame/windows.xml): оглавление - кнопки глав (до восьми), глава - страницы
|
||
/// с «Назад/Далее», «Оглавление» возвращает к списку, «Закрыть» и Esc закрывают.
|
||
///
|
||
/// СОДЕРЖАНИЕ - список Chapters ниже: ключ названия, ключи страниц (Localization.csv,
|
||
/// necroTome*), условие открытия. Закрытая глава в оглавлении не показывается. Условия брать
|
||
/// из того, что игра уже хранит (уровень некромантии, рецепты, испытания), своё хранение - только
|
||
/// для чистых событий, по cvar на событие.
|
||
/// </summary>
|
||
public static class NecroTomeBook
|
||
{
|
||
public const string WindowGroup = "necroTome";
|
||
|
||
public class Chapter
|
||
{
|
||
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 (указание 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",
|
||
PageKeys = new[] { "necroTomeCh1P1", "necroTomeCh1P2", "necroTomeCh1P3", "necroTomeCh1P4" },
|
||
Unlocked = _p => true
|
||
},
|
||
};
|
||
|
||
public static void Open(EntityPlayerLocal _player)
|
||
{
|
||
LocalPlayerUI ui = LocalPlayerUI.GetUIForPlayer(_player);
|
||
ui?.windowManager.Open(WindowGroup, true);
|
||
}
|
||
}
|
||
|
||
[Preserve]
|
||
public class XUiC_NecroTome : XUiController
|
||
{
|
||
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>
|
||
readonly int[] _buttonChapter = new int[MaxButtons];
|
||
int _chapter = -1, _pageIndex;
|
||
|
||
public override void Init()
|
||
{
|
||
base.Init();
|
||
_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;
|
||
_close = GetChildById("btnClose") as XUiC_SimpleButton;
|
||
if (_toc != null) _toc.OnPressed += (_s, _m) => ShowToc();
|
||
if (_prev != null) _prev.OnPressed += (_s, _m) => Turn(-1);
|
||
if (_next != null) _next.OnPressed += (_s, _m) => Turn(1);
|
||
if (_close != null) _close.OnPressed += (_s, _m) => xui.playerUI.windowManager.Close(NecroTomeBook.WindowGroup);
|
||
for (int i = 0; i < MaxButtons; i++)
|
||
{
|
||
int idx = i;
|
||
_chapterButtons[i] = GetChildById("btnCh" + (i + 1)) as XUiC_SimpleButton;
|
||
if (_chapterButtons[i] != null)
|
||
{
|
||
_chapterButtons[i].OnPressed += (_s, _m) => ShowChapter(_buttonChapter[idx], 0);
|
||
}
|
||
}
|
||
}
|
||
|
||
public override void OnOpen()
|
||
{
|
||
base.OnOpen();
|
||
ShowToc();
|
||
}
|
||
|
||
void ShowToc()
|
||
{
|
||
_chapter = -1;
|
||
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++)
|
||
{
|
||
NecroTomeBook.Chapter ch = NecroTomeBook.Chapters[c];
|
||
if (ch.Unlocked != null && !ch.Unlocked(player))
|
||
{
|
||
continue;
|
||
}
|
||
_buttonChapter[b] = c;
|
||
if (_chapterButtons[b] != null)
|
||
{
|
||
_chapterButtons[b].Text = Localization.Get(ch.TitleKey);
|
||
_chapterButtons[b].ViewComponent.IsVisible = true;
|
||
}
|
||
b++;
|
||
}
|
||
for (; b < MaxButtons; b++)
|
||
{
|
||
if (_chapterButtons[b] != null)
|
||
{
|
||
_chapterButtons[b].ViewComponent.IsVisible = false;
|
||
}
|
||
}
|
||
SetVisible(_toc, false);
|
||
SetVisible(_prev, false);
|
||
SetVisible(_next, false);
|
||
}
|
||
|
||
void ShowChapter(int _c, int _p)
|
||
{
|
||
if (_c < 0 || _c >= NecroTomeBook.Chapters.Count)
|
||
{
|
||
return;
|
||
}
|
||
NecroTomeBook.Chapter ch = NecroTomeBook.Chapters[_c];
|
||
_chapter = _c;
|
||
_pageIndex = Mathf.Clamp(_p, 0, ch.PageKeys.Length - 1);
|
||
foreach (XUiC_SimpleButton btn in _chapterButtons)
|
||
{
|
||
SetVisible(btn, false);
|
||
}
|
||
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);
|
||
if (_prev != null) _prev.Enabled = _pageIndex > 0;
|
||
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)
|
||
{
|
||
ShowChapter(_chapter, _pageIndex + _d);
|
||
}
|
||
}
|
||
|
||
static void SetLabel(XUiV_Label _l, string _s)
|
||
{
|
||
if (_l != null)
|
||
{
|
||
_l.Text = _s;
|
||
}
|
||
}
|
||
|
||
static void SetVisible(XUiController _c, bool _v)
|
||
{
|
||
if (_c?.ViewComponent != null)
|
||
{
|
||
_c.ViewComponent.IsVisible = _v;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>necrotome - открыть книгу (проверка окна без чертогов, в том числе через почтовый ящик).</summary>
|
||
[Preserve]
|
||
public class ConsoleCmdNecroTome : ConsoleCmdAbstract
|
||
{
|
||
public override string[] getCommands() => new[] { "necrotome" };
|
||
public override string getDescription() => "NecromancerTome: открыть книгу с оглавлением";
|
||
|
||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
|
||
{
|
||
EntityPlayerLocal player = GameManager.Instance.World?.GetPrimaryPlayer();
|
||
if (player != null)
|
||
{
|
||
NecroTomeBook.Open(player);
|
||
}
|
||
}
|
||
}
|
||
}
|