using UnityEngine; using UnityEngine.Scripting; namespace NecromancerTome { /// /// «Книга памяти» в чертогах разума (указание 03.10): единственный предмет чертогов, с которым /// можно взаимодействовать. При наведении - «Нажмите E, чтобы прочитать», по E - окно «Память» /// (потом доделаем, как окна концовки в FinalSlides.cs). Блок подключается в blocks.xml через /// Class="NecromancerTome.NecroMemoryBook, NecromancerHarmony": игра (ReflectionHelpers. /// GetTypeWithPrefix) вставляет приставку Block перед именем после последней точки. /// Вид - одиночная книга игры (bookInvisibleKillerPrefab, как cntBookPile09 на столе префаба /// chertogi), но без хранилища и лута. /// [Preserve] public class BlockNecroMemoryBook : Block { public const string ReadCommand = "read"; static readonly BlockActivationCommand[] Commands = { new BlockActivationCommand(ReadCommand, "book", true) }; public override bool HasBlockActivationCommands(WorldBase _world, BlockValue _blockValue, Vector3i _blockPos, EntityAlive _entityFocusing) { return true; } public override BlockActivationCommand[] GetBlockActivationCommands(WorldBase _world, BlockValue _blockValue, Vector3i _blockPos, EntityAlive _entityFocusing) { return Commands; } public override string GetActivationText(WorldBase _world, BlockValue _blockValue, Vector3i _blockPos, EntityAlive _entityFocusing) { return Localization.Get("necroMemoryBookPrompt"); } public override bool OnBlockActivated(string _commandName, WorldBase _world, Vector3i _blockPos, BlockValue _blockValue, EntityPlayerLocal _player) { return _commandName == ReadCommand && OnBlockActivated(_world, _blockPos, _blockValue, _player); } public override bool OnBlockActivated(WorldBase _world, Vector3i _blockPos, BlockValue _blockValue, EntityPlayerLocal _player) { Open(_player); return true; } /// Окно книги (оглавление, главы). Отдельным методом - его же позовёт дух в чертогах (своё E). public static void Open(EntityPlayerLocal _player) { LocalPlayerUI ui = LocalPlayerUI.GetUIForPlayer(_player); if (ui == null) { return; } // 03.10: вместо окна-сообщения - книга с оглавлением (NecroTome.cs). NecroTomeBook.Open(_player); } // ---------------------------------------------------------------- автор на обложке /// Шрифт надписи - тот же готический, что у названия в текстуре (UnifrakturMaguntia, OFL). const string FontAsset = "#@modfolder(NecromancerTome):Resources/necrobooks?UnifrakturMaguntia-Book.ttf"; const string AuthorChild = "NecroAuthor"; // Место под автора - просвет под рисунком (make_rat_book_textures.py, Mind), в осях корня модели // necroBookMindPrefab: из Blender (x, y, z) -> Unity (-x, z, -y); книга в модели повёрнута на -65°. // Подбирается на ходу командой necroauthor (x y z, размер, ширина). public static Vector3 AuthorPos = new Vector3(0.0944f, 0.0885f, -0.1534f); public static Vector3 AuthorUp = new Vector3(-0.4226f, 0f, 0.9063f); public static float AuthorHeight = 0.030f, AuthorMaxWidth = 0.21f; public static Color AuthorColor = new Color(0.85f, 0.06f, 0.05f, 1f); static Font _font; public override void OnBlockEntityTransformAfterActivated(WorldBase _world, Vector3i _blockPos, BlockValue _blockValue, BlockEntityData _ebcd) { base.OnBlockEntityTransformAfterActivated(_world, _blockPos, _blockValue, _ebcd); try { if (_ebcd?.transform != null) { PlaceAuthor(_ebcd.transform); } } catch (System.Exception e) { Debug.LogWarning("[NecromancerTome] книга памяти: автор не встал - " + e.Message); } } /// Ник игрока на обложке, как подпись на ящике: TextMesh поверх модели (модель общая, /// а ник у каждого свой, в текстуру его не запечь). Чертоги у каждого свои - пишем ник своего игрока. public static void PlaceAuthor(Transform _root) { EntityPlayerLocal player = GameManager.Instance?.World?.GetPrimaryPlayer(); string name = player != null ? player.PlayerDisplayName : null; Transform old = _root.Find(AuthorChild); if (string.IsNullOrEmpty(name)) { if (old != null) Object.Destroy(old.gameObject); return; } if (_font == null) { _font = DataLoader.LoadAsset(FontAsset); } GameObject go = old != null ? old.gameObject : new GameObject(AuthorChild); go.layer = _root.gameObject.layer; Transform t = go.transform; t.SetParent(_root, false); t.localPosition = AuthorPos; // лицом вверх (обложка), низ строки - к нижнему краю обложки t.localRotation = Quaternion.LookRotation(Vector3.down, AuthorUp.normalized); t.localScale = Vector3.one; TextMesh tm = go.GetComponent() ?? go.AddComponent(); MeshRenderer mr = go.GetComponent(); if (_font != null) { tm.font = _font; mr.sharedMaterial = _font.material; if (mr.sharedMaterial != null && (mr.sharedMaterial.shader == null || !mr.sharedMaterial.shader.isSupported)) { Shader sh = Shader.Find("GUI/Text Shader"); if (sh != null) mr.sharedMaterial.shader = sh; } } tm.text = name; tm.anchor = TextAnchor.MiddleCenter; tm.alignment = TextAlignment.Center; tm.fontSize = 96; tm.color = AuthorColor; mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; mr.receiveShadows = false; tm.characterSize = 1f; // Размер подгоняется кадром позже: меш надписи TextMesh строит не сразу. NecroAuthorFit fit = go.GetComponent() ?? go.AddComponent(); fit.enabled = true; } } /// Подгонка надписи автора: высота строки AuthorHeight, длинный ник ужимается до AuthorMaxWidth. public class NecroAuthorFit : MonoBehaviour { void LateUpdate() { MeshRenderer mr = GetComponent(); Bounds b = mr != null ? mr.localBounds : default; if (b.size.y < 0.0001f) { return; // меш ещё не построен } float k = BlockNecroMemoryBook.AuthorHeight / b.size.y; if (b.size.x * k > BlockNecroMemoryBook.AuthorMaxWidth) { k = BlockNecroMemoryBook.AuthorMaxWidth / b.size.x; } transform.localScale = new Vector3(k, k, k); enabled = false; } } /// necroauthor [x y z] [h N] [w N] - подобрать место и размер ника на книге памяти на ходу. [Preserve] public class ConsoleCmdNecroAuthor : ConsoleCmdAbstract { public override string[] getCommands() => new[] { "necroauthor" }; public override string getDescription() => "NecromancerTome: ник на книге памяти - x y z | h N | w N"; public override void Execute(System.Collections.Generic.List _params, CommandSenderInfo _senderInfo) { System.Globalization.CultureInfo ci = System.Globalization.CultureInfo.InvariantCulture; if (_params.Count == 3) { BlockNecroMemoryBook.AuthorPos = new Vector3(float.Parse(_params[0], ci), float.Parse(_params[1], ci), float.Parse(_params[2], ci)); } else if (_params.Count == 2 && _params[0] == "h") { BlockNecroMemoryBook.AuthorHeight = float.Parse(_params[1], ci); } else if (_params.Count == 2 && _params[0] == "w") { BlockNecroMemoryBook.AuthorMaxWidth = float.Parse(_params[1], ci); } int n = 0; foreach (NecroAuthorFit f in Object.FindObjectsOfType(true)) { BlockNecroMemoryBook.PlaceAuthor(f.transform.parent); n++; } Vector3 p = BlockNecroMemoryBook.AuthorPos; SdtdConsole.Instance.Output(string.Format(ci, "автор: {0:0.####} {1:0.####} {2:0.####}, h {3}, w {4}; надписей {5}", p.x, p.y, p.z, BlockNecroMemoryBook.AuthorHeight, BlockNecroMemoryBook.AuthorMaxWidth, n)); } } }