diff --git a/HarmonySrc/NecroBody.cs b/HarmonySrc/NecroBody.cs
index 7ea5b54..449befe 100644
--- a/HarmonySrc/NecroBody.cs
+++ b/HarmonySrc/NecroBody.cs
@@ -88,6 +88,112 @@ namespace NecromancerTome
return "играет " + clip.name + " (" + NecroBody.F(clip.length) + " с" + (clip.isLooping ? ", петля" : "") + (clip.humanMotion ? ", гуманоид" : ", не гуманоид - может лечь криво") + "), скорость " + NecroBody.F(_speed);
}
+ /// Тело повторяет движения другого существа (16.10: танец зомби не лёг на тело игрока через Playables - клип
+ /// гуманоидный, а тело игрока - нет). Каждый кадр локальные повороты костей источника копируются на кости тела с теми же
+ /// именами (Hips, Spine, LeftArm...); у бёдер - ещё и сдвиг. Источник - зомби, которого necrobody zdance заставляет танцевать.
+ static EntityAlive _mimic;
+ static readonly List _mimicSrc = new List();
+ static readonly List _mimicDst = new List();
+
+ public static EntityAlive Nearest(float _radius, bool _zombiesOnly)
+ {
+ EntityPlayerLocal pl = Player();
+ if (pl == null)
+ {
+ return null;
+ }
+ EntityAlive best = null;
+ float bestD = _radius * _radius;
+ foreach (Entity e in GameManager.Instance.World.Entities.list)
+ {
+ if (e is EntityAlive a && a != pl && !a.IsDead() && (!_zombiesOnly || a is EntityZombie))
+ {
+ float d = (a.position - pl.position).sqrMagnitude;
+ if (d < bestD)
+ {
+ bestD = d;
+ best = a;
+ }
+ }
+ }
+ return best;
+ }
+
+ static Transform ModelRoot(EntityAlive _e)
+ {
+ if (_e == null || _e.emodel == null)
+ {
+ return null;
+ }
+ if (_e.emodel.avatarController is AvatarCharacterController ac && ac.CharacterBody?.Animator != null)
+ {
+ return ac.CharacterBody.Animator.transform;
+ }
+ return _e.emodel.GetModelTransform();
+ }
+
+ public static string Mimic(EntityAlive _src)
+ {
+ StopMimic();
+ Animator a = Anim();
+ Transform src = ModelRoot(_src);
+ if (a == null || src == null)
+ {
+ return "нет тела игрока или источника";
+ }
+ var byName = new Dictionary();
+ foreach (Transform t in src.GetComponentsInChildren(true))
+ {
+ if (!byName.ContainsKey(t.name))
+ {
+ byName[t.name] = t;
+ }
+ }
+ foreach (Transform t in a.GetComponentsInChildren(true))
+ {
+ if (t != a.transform && byName.TryGetValue(t.name, out Transform s2) && s2 != src)
+ {
+ _mimicDst.Add(t);
+ _mimicSrc.Add(s2);
+ }
+ }
+ _mimic = _src;
+ return "тело повторяет за " + _src.EntityName + " (id " + _src.entityId + "): общих костей " + _mimicDst.Count;
+ }
+
+ public static void StopMimic()
+ {
+ _mimic = null;
+ _mimicSrc.Clear();
+ _mimicDst.Clear();
+ }
+
+ static void ApplyMimic()
+ {
+ if (_mimic == null)
+ {
+ return;
+ }
+ if (_mimic.IsDead() || _mimicSrc.Count == 0 || _mimicSrc[0] == null)
+ {
+ StopMimic();
+ return;
+ }
+ for (int i = 0; i < _mimicDst.Count; i++)
+ {
+ Transform s = _mimicSrc[i], d = _mimicDst[i];
+ if (s == null || d == null)
+ {
+ continue;
+ }
+ d.localRotation = s.localRotation;
+ if (d.name == "Hips")
+ {
+ d.localPosition = s.localPosition;
+ }
+ }
+ }
+
public static void StopPlay()
{
if (_graph.IsValid())
@@ -148,6 +254,7 @@ namespace NecromancerTome
public static void Reset()
{
StopPlay();
+ StopMimic();
Params.Clear();
Pose.Clear();
Freeze = false;
@@ -226,6 +333,7 @@ namespace NecromancerTome
return;
}
LoopPlay();
+ ApplyMimic();
if (!Active || Bone("") == null && _anim == null)
{
return;
@@ -317,7 +425,7 @@ namespace NecromancerTome
public class ConsoleCmdNecroBody : ConsoleCmdAbstract
{
public override string[] getCommands() => new[] { "necrobody" };
- public override string getDescription() => "NecromancerTome: аниматор тела - params [ф]|clips|set п v|unset п|trigger п|speed k|freeze [off]|bones [ф]|show к|pose к x y z|add к dx dy dz|unpose к|item pos|rot|off|show|holdoffset [N]|anims [слово]|play имя [скорость]|play off|save|off";
+ public override string getDescription() => "NecromancerTome: аниматор тела - params [ф]|clips|set п v|unset п|trigger п|speed k|freeze [off]|bones [ф]|show к|pose к x y z|add к dx dy dz|unpose к|item pos|rot|off|show|holdoffset [N]|anims [слово]|play имя [скорость]|play off|zdance [off]|mimic [id]|mimic off|save|off";
static bool Num(List _p, int _i, out float _v)
{
@@ -524,6 +632,36 @@ namespace NecromancerTome
Out(sb.ToString());
return;
}
+ case "zdance":
+ {
+ EntityAlive z = NecroBody.Nearest(30f, true);
+ if (z == null)
+ {
+ Out("рядом (30 м) нет зомби");
+ return;
+ }
+ z.SetDancing(arg != "off");
+ Out(z.EntityName + " (id " + z.entityId + ") " + (arg != "off" ? "танцует" : "перестал танцевать"));
+ return;
+ }
+ case "mimic" when arg == "off":
+ NecroBody.StopMimic();
+ Out("тело больше не повторяет");
+ return;
+ case "mimic":
+ {
+ EntityAlive src = null;
+ if (Num(_params, 1, out float mid))
+ {
+ src = GameManager.Instance.World.GetEntity((int)mid) as EntityAlive;
+ }
+ else
+ {
+ src = NecroBody.Nearest(30f, false);
+ }
+ Out(src == null ? "нет источника (id или существо в 30 м)" : NecroBody.Mimic(src));
+ return;
+ }
case "play" when arg == "off":
NecroBody.StopPlay();
Out("анимация снята");