Книга: главы ножа и Могильной крысы; плакат ножа, грифельная доска, дверь-шкаф; чертоги 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
+128
View File
@@ -0,0 +1,128 @@
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.Scripting;
namespace NecromancerTome
{
/// <summary>
/// necromodel &lt;блок&gt; - выписать в лог устройство модели блока (09.10.2026, для своей двери-книжного
/// шкафа: где у ванильной двери петля, куда она открывается, какие коллайдеры). Иерархия с местом, поворотом,
/// масштабом, мешами и коллайдерами; у аниматоров - параметры и клипы, а створка в состояниях «Open» и
/// «Close» - пробой клипа на копии префаба. Копия создаётся и тут же уничтожается.
/// </summary>
[Preserve]
public class ConsoleCmdNecroModel : ConsoleCmdAbstract
{
public override string[] getCommands() => new[] { "necromodel" };
public override string getDescription() => "NecromancerTome: устройство модели блока в лог (necromodel <имя блока>)";
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
{
if (_params.Count < 1)
{
SdtdConsole.Instance.Output("necromodel <имя блока>");
return;
}
Block block = Block.GetBlockByName(_params[0]);
string model = block?.Properties.GetString("Model");
if (string.IsNullOrEmpty(model))
{
SdtdConsole.Instance.Output("Нет блока или у него нет Model: " + _params[0]);
return;
}
if (!model.StartsWith("@") && !model.StartsWith("#"))
{
model = "@:" + model;
}
GameObject prefab = DataLoader.LoadAsset<GameObject>(model);
if (prefab == null)
{
SdtdConsole.Instance.Output("Модель не загрузилась: " + model);
return;
}
var sb = new StringBuilder();
sb.AppendLine("[NecromancerTome] necromodel " + _params[0] + " = " + model + ", ModelOffset " +
block.Properties.GetString("ModelOffset") + ", MultiBlockDim " + block.Properties.GetString("MultiBlockDim"));
Dump(prefab.transform, 0, sb);
GameObject copy = Object.Instantiate(prefab);
try
{
foreach (Animator a in copy.GetComponentsInChildren<Animator>(true))
{
RuntimeAnimatorController rc = a.runtimeAnimatorController;
sb.AppendLine("Animator на " + a.name + ": " + (rc != null ? rc.name : "нет контроллера"));
foreach (AnimatorControllerParameter p in a.parameters)
{
sb.AppendLine(" параметр " + p.name + " " + p.type);
}
if (rc == null)
{
continue;
}
foreach (AnimationClip clip in rc.animationClips)
{
sb.AppendLine(" клип " + clip.name + ", " + clip.length.ToString("F2") + " с");
clip.SampleAnimation(copy, clip.length);
foreach (Transform t in copy.GetComponentsInChildren<Transform>(true))
{
sb.AppendLine(" в конце: " + Path(t, copy.transform) + " pos " + t.localPosition.ToString("F3") +
" rot " + t.localEulerAngles.ToString("F1"));
}
}
}
}
finally
{
Object.Destroy(copy);
}
Debug.Log(sb.ToString());
SdtdConsole.Instance.Output("necromodel: выписано в лог");
}
static void Dump(Transform _t, int _depth, StringBuilder _sb)
{
var line = new StringBuilder(new string(' ', _depth * 2));
line.Append(_t.name).Append(" pos ").Append(_t.localPosition.ToString("F3")).Append(" rot ")
.Append(_t.localEulerAngles.ToString("F1")).Append(" scale ").Append(_t.localScale.ToString("F2"))
.Append(" tag ").Append(_t.tag).Append(" layer ").Append(_t.gameObject.layer);
MeshFilter mf = _t.GetComponent<MeshFilter>();
if (mf != null && mf.sharedMesh != null)
{
line.Append(" mesh ").Append(mf.sharedMesh.name).Append(" bounds c").Append(mf.sharedMesh.bounds.center.ToString("F3"))
.Append(" size").Append(mf.sharedMesh.bounds.size.ToString("F3"));
}
foreach (Collider c in _t.GetComponents<Collider>())
{
line.Append(" | ").Append(c.GetType().Name).Append(c.isTrigger ? "(trigger)" : "");
if (c is BoxCollider b)
{
line.Append(" c").Append(b.center.ToString("F3")).Append(" s").Append(b.size.ToString("F3"));
}
}
foreach (Component comp in _t.GetComponents<Component>())
{
if (!(comp is Transform) && !(comp is MeshFilter) && !(comp is MeshRenderer) && !(comp is Collider))
{
line.Append(" [").Append(comp != null ? comp.GetType().Name : "null").Append("]");
}
}
_sb.AppendLine(line.ToString());
foreach (Transform ch in _t)
{
Dump(ch, _depth + 1, _sb);
}
}
static string Path(Transform _t, Transform _root)
{
string p = _t.name;
while (_t.parent != null && _t != _root)
{
_t = _t.parent;
p = _t.name + "/" + p;
}
return p;
}
}
}