183 lines
7.0 KiB
C#
183 lines
7.0 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|