Команда necroskulldecor: посадка черепа-декорации без перезапуска игры (center|back, pos/add/rot/scale/off/show, хитбокс следом, проверка «в клетке»)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlexCube
2026-10-15 15:04:39 +03:00
co-authored by Claude Opus 5.5
parent 346cc31041
commit 59bb0083c9
+182
View File
@@ -0,0 +1,182 @@
using System.Collections.Generic;
using System.Globalization;
using UnityEngine;
using UnityEngine.Scripting;
namespace NecromancerTome
{
/// <summary>
/// Подбор посадки черепа-декорации прямо в игре (15.10.2026, по образцу necroheld): «чтобы если нужно будет
/// спозиционировать, не перезапускать игру». Правка ставится узлу skull во всех экземплярах вида (поставленные
/// блоки) и в загруженном префабе этого вида - новые блоки ставятся уже с ней. Коллайдер корня (хитбокс)
/// пересчитывается по черепу. Итог - строкой в лог, затем переносится в NecroSkullDecorSetup (Unity) и бандл
/// necroskull пересобирается при закрытой игре.
/// </summary>
public static class SkullDecorTweak
{
public const string CenterPrefab = "necroSkullDecorCenterPrefab";
public const string BackPrefab = "necroSkullDecorBackPrefab";
public class State
{
public Vector3 Pos; // метры, добавка к исходному положению, в осях блока
public Vector3 Rot; // градусы, добавка к исходному повороту
public float Scale = 1f;
}
public static readonly Dictionary<string, State> States = new Dictionary<string, State>
{
{ CenterPrefab, new State() }, { BackPrefab, new State() }
};
/// <summary>Исходные значения узла skull (по экземпляру узла).</summary>
static readonly Dictionary<int, (Vector3 p, Quaternion r, Vector3 s)> _base = new Dictionary<int, (Vector3, Quaternion, Vector3)>();
static string KindOf(Transform _root)
{
string n = _root.name;
if (n.StartsWith(CenterPrefab)) return CenterPrefab;
if (n.StartsWith(BackPrefab)) return BackPrefab;
return null;
}
/// <summary>Применяет правку вида _kind ко всем найденным черепам; возвращает их число.</summary>
public static int Apply(string _kind)
{
State st = States[_kind];
int n = 0;
foreach (Transform t in Resources.FindObjectsOfTypeAll<Transform>())
{
if (t == null || t.name != "skull" || t.parent == null || KindOf(t.parent) != _kind)
{
continue;
}
int id = t.GetInstanceID();
if (!_base.TryGetValue(id, out var b))
{
b = (t.localPosition, t.localRotation, t.localScale);
_base[id] = b;
}
t.localPosition = b.p + st.Pos;
t.localRotation = Quaternion.Euler(st.Rot) * b.r;
t.localScale = b.s * st.Scale;
FitCollider(t);
n++;
}
return n;
}
/// <summary>Хитбокс корня - по габариту черепа в осях корня.</summary>
static void FitCollider(Transform _skull)
{
BoxCollider bc = _skull.parent.GetComponent<BoxCollider>();
MeshFilter mf = _skull.GetComponent<MeshFilter>();
if (bc == null || mf == null || mf.sharedMesh == null)
{
return;
}
Bounds mb = mf.sharedMesh.bounds;
Matrix4x4 m = Matrix4x4.TRS(_skull.localPosition, _skull.localRotation, _skull.localScale);
Bounds r = new Bounds(m.MultiplyPoint3x4(mb.center), Vector3.zero);
for (int sx = -1; sx <= 1; sx += 2) for (int sy = -1; sy <= 1; sy += 2) for (int sz = -1; sz <= 1; sz += 2)
r.Encapsulate(m.MultiplyPoint3x4(mb.center + Vector3.Scale(mb.extents, new Vector3(sx, sy, sz))));
bc.center = r.center;
bc.size = r.size;
}
/// <summary>Габарит черепа вида в осях блока (по первому найденному экземпляру): «влезает ли в клетку».</summary>
public static string Box(string _kind)
{
foreach (Transform t in Resources.FindObjectsOfTypeAll<Transform>())
{
if (t != null && t.name == "skull" && t.parent != null && KindOf(t.parent) == _kind)
{
BoxCollider bc = t.parent.GetComponent<BoxCollider>();
if (bc != null)
{
Vector3 a = bc.center - bc.size / 2f, z = bc.center + bc.size / 2f;
bool inside = a.x >= -0.5001f && z.x <= 0.5001f && a.y >= -0.0001f && z.y <= 1.0001f && a.z >= -0.5001f && z.z <= 0.5001f;
return "габарит (м) от " + F(a) + " до " + F(z) + (inside ? " - в клетке" : " - ВЫЛЕЗАЕТ ИЗ КЛЕТКИ");
}
}
}
return "черепов этого вида не найдено";
}
public static string F(float _v) => _v.ToString("0.###", CultureInfo.InvariantCulture);
public static string F(Vector3 _v) => F(_v.x) + " " + F(_v.y) + " " + F(_v.z);
}
/// <summary>
/// necroskulldecor center|back - посадка черепа-декорации:
/// pos x y z (см) | add x y z (см) | rot x y z (градусы) | scale s | off | show
/// </summary>
[Preserve]
public class ConsoleCmdNecroSkullDecor : ConsoleCmdAbstract
{
public override string[] getCommands() => new[] { "necroskulldecor" };
public override string getDescription() =>
"NecromancerTome: посадка черепа-декорации - center|back, затем pos x y z (см)|add x y z (см)|rot x y z|scale s|off|show";
static bool Num(List<string> _p, int _i, out float _v)
{
_v = 0f;
return _p.Count > _i && float.TryParse(_p[_i].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out _v);
}
static bool Vec(List<string> _p, out Vector3 _v)
{
bool ok = Num(_p, 2, out float x) & Num(_p, 3, out float y) & Num(_p, 4, out float z);
_v = new Vector3(x, y, z);
return ok;
}
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
{
string which = _params.Count > 0 ? _params[0] : "";
string kind = which == "center" ? SkullDecorTweak.CenterPrefab : which == "back" ? SkullDecorTweak.BackPrefab : null;
if (kind == null)
{
Out(getDescription());
return;
}
SkullDecorTweak.State st = SkullDecorTweak.States[kind];
string cmd = _params.Count > 1 ? _params[1] : "show";
Vector3 v;
switch (cmd)
{
case "pos" when Vec(_params, out v):
st.Pos = v / 100f;
break;
case "add" when Vec(_params, out v):
st.Pos += v / 100f;
break;
case "rot" when Vec(_params, out v):
st.Rot = v;
break;
case "scale" when Num(_params, 2, out float s) && s > 0f:
st.Scale = s;
break;
case "off":
st.Pos = Vector3.zero;
st.Rot = Vector3.zero;
st.Scale = 1f;
break;
case "show":
break;
default:
Out(getDescription());
return;
}
int n = SkullDecorTweak.Apply(kind);
Out(which + ": черепов " + n + ", сдвиг (см) " + SkullDecorTweak.F(st.Pos * 100f) + ", поворот " + SkullDecorTweak.F(st.Rot) +
", масштаб " + SkullDecorTweak.F(st.Scale) + "; " + SkullDecorTweak.Box(kind));
}
static void Out(string _s)
{
SdtdConsole.Instance.Output(_s);
Debug.Log("[NecromancerTome] necroskulldecor: " + _s);
}
}
}