Files
necromants-tome-7d2d-3-2/HarmonySrc/NecroTome.cs
T
AlexCubeandClaude Opus 5.5 beab1640b0 Одержимость питомцев, стервятник-разведчик, метки на карте, череп, глава письма
- Одержимость питомца: ПКМ с книгой - вселиться, E - отозвать (и изнутри питомца),
  пояс скрыт, надпись о враге у тела.
- Одержимый стервятник: полёт, рывок на ЛКМ с 10 м с прямым уроном, звуком и
  кровотечением, обычный прицел краснеет на цели; посадка в позу 0 и взлёт на пробел;
  ПКМ - клич-приманка (паникующая курица, откат 0.5 с); F - питаться трупами и
  блоками-падалью, доеденное исчезает.
- Дух змеи атакует только нежить; фиолетовая метка на карте у змеи и одержимого.
- Череп из красного трупа (этап 1 цинерария); глава книги «Письмо архимага» с видео.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 22:09:28 +03:00

263 lines
9.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}
}
}