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("анимация снята");