From 5fc04fd6689f170f4c8f2c16795551b2d51e0a93 Mon Sep 17 00:00:00 2001 From: AlexCube Date: Fri, 16 Oct 2026 23:22:55 +0300 Subject: [PATCH] =?UTF-8?q?necrobody=20anims/play:=20=D0=BB=D1=8E=D0=B1?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B6=D0=B5=D0=BD?= =?UTF-8?q?=D0=BD=D0=B0=D1=8F=20=D0=B0=D0=BD=D0=B8=D0=BC=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BD=D0=B0=20=D1=82=D0=B5=D0=BB=D0=B5=20(=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=D1=86=D1=8B=20=D0=B7=D0=BE=D0=BC=D0=B1=D0=B8=20?= =?UTF-8?q?=D0=B8=20=D1=82.=D0=BF.)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- HarmonySrc/NecroBody.cs | 108 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/HarmonySrc/NecroBody.cs b/HarmonySrc/NecroBody.cs index 1687c43..7ea5b54 100644 --- a/HarmonySrc/NecroBody.cs +++ b/HarmonySrc/NecroBody.cs @@ -3,6 +3,8 @@ using System.Globalization; using System.Text; using HarmonyLib; using UnityEngine; +using UnityEngine.Animations; +using UnityEngine.Playables; using UnityEngine.Scripting; namespace NecromancerTome @@ -31,6 +33,84 @@ namespace NecromancerTome static readonly List _frozenRot = new List(); static int _playerId = -1; + /// Любая загруженная анимация на теле (16.10: «Может и танцевать сможешь заставить?.. сильно бы помогло в + /// создании контента»): клип играется поверх контроллера через Playables, по кругу. Танцы есть у зомби (twitch_dance) - + /// их клипы в памяти, пока в мире есть зомби с таким аниматором. + static PlayableGraph _graph; + static AnimationClipPlayable _clip; + public static string Playing; + + public static List FindClips(string _filter) + { + var list = new List(); + var seen = new HashSet(); + foreach (AnimationClip c in Resources.FindObjectsOfTypeAll()) + { + if (c != null && (_filter.Length == 0 || c.name.IndexOf(_filter, System.StringComparison.OrdinalIgnoreCase) >= 0) && seen.Add(c.name)) + { + list.Add(c); + } + } + list.Sort((x, y) => string.CompareOrdinal(x.name, y.name)); + return list; + } + + public static string Play(string _name, float _speed) + { + Animator a = Anim(); + if (a == null) + { + return "нет аниматора тела"; + } + AnimationClip clip = null; + foreach (AnimationClip c in FindClips(_name)) + { + if (c.name == _name) + { + clip = c; + break; + } + clip = clip ?? c; + } + if (clip == null) + { + return "нет загруженной анимации «" + _name + "» (necrobody anims слово)"; + } + StopPlay(); + _graph = PlayableGraph.Create("NecroBodyPlay"); + _graph.SetTimeUpdateMode(DirectorUpdateMode.GameTime); + var output = AnimationPlayableOutput.Create(_graph, "body", a); + _clip = AnimationClipPlayable.Create(_graph, clip); + _clip.SetSpeed(_speed); + output.SetSourcePlayable(_clip); + _graph.Play(); + Playing = clip.name; + return "играет " + clip.name + " (" + NecroBody.F(clip.length) + " с" + (clip.isLooping ? ", петля" : "") + (clip.humanMotion ? ", гуманоид" : ", не гуманоид - может лечь криво") + "), скорость " + NecroBody.F(_speed); + } + + public static void StopPlay() + { + if (_graph.IsValid()) + { + _graph.Destroy(); + } + Playing = null; + } + + /// Петля для клипов без своей петли. + static void LoopPlay() + { + if (!_graph.IsValid()) + { + return; + } + AnimationClip c = _clip.GetAnimationClip(); + if (c != null && !c.isLooping && _clip.GetTime() >= c.length) + { + _clip.SetTime(0); + } + } + public static EntityPlayerLocal Player() => GameManager.Instance?.World?.GetPrimaryPlayer(); public static BodyAnimator Body() @@ -67,6 +147,7 @@ namespace NecromancerTome public static void Reset() { + StopPlay(); Params.Clear(); Pose.Clear(); Freeze = false; @@ -144,6 +225,7 @@ namespace NecromancerTome _bones.Clear(); return; } + LoopPlay(); if (!Active || Bone("") == null && _anim == null) { return; @@ -235,7 +317,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]|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|save|off"; static bool Num(List _p, int _i, out float _v) { @@ -425,6 +507,30 @@ namespace NecromancerTome Out(sb.ToString()); return; } + case "anims": + { + List l = NecroBody.FindClips(arg); + var sb = new StringBuilder("анимаций в памяти" + (arg.Length > 0 ? " со словом " + arg : "") + ": " + l.Count); + int shown = 0; + foreach (AnimationClip c in l) + { + if (shown++ >= 200) + { + sb.Append("\n ... (уточните слово)"); + break; + } + sb.Append("\n ").Append(c.name).Append(" ").Append(NecroBody.F(c.length)).Append(" с").Append(c.humanMotion ? "" : " [не гуманоид]"); + } + Out(sb.ToString()); + return; + } + case "play" when arg == "off": + NecroBody.StopPlay(); + Out("анимация снята"); + return; + case "play" when arg.Length > 0: + Out(NecroBody.Play(arg, Num(_params, 2, out float ps) ? ps : 1f)); + return; case "off": NecroBody.Reset(); Out("тело - как у игры");