695 lines
23 KiB
C#
695 lines
23 KiB
C#
using System.Collections.Generic;
|
||
using System.Globalization;
|
||
using System.Text;
|
||
using HarmonyLib;
|
||
using UnityEngine;
|
||
using UnityEngine.Animations;
|
||
using UnityEngine.Playables;
|
||
using UnityEngine.Scripting;
|
||
|
||
namespace NecromancerTome
|
||
{
|
||
/// <summary>
|
||
/// Правка аниматора ТЕЛА игрока (вид от третьего лица, его же видят другие) прямо в игре (16.10: «Добавь все необходимые
|
||
/// команды для аниматора тела, чтобы можно было править прямо в игре»). Пара к necropose/necrobones, которые - для рук
|
||
/// первого лица. Всё держится до necrobody off или выхода из мира; итог переносится в код (necrobody save).
|
||
/// Параметры ставятся после Update аватара (иначе игра перезапишет их до расчёта аниматора), кости - в LateUpdate.
|
||
/// </summary>
|
||
public static class NecroBody
|
||
{
|
||
/// <summary>Параметры аниматора тела, которые держим своими (имя -> значение; bool - 0/1).</summary>
|
||
public static readonly Dictionary<string, float> Params = new Dictionary<string, float>();
|
||
/// <summary>Локальные повороты костей тела поверх анимации.</summary>
|
||
public static readonly Dictionary<string, Vector3> Pose = new Dictionary<string, Vector3>();
|
||
public static bool Freeze;
|
||
public static float Speed = 1f;
|
||
/// <summary>Место предмета в руке тела (поверх смещения игры по хвату), если задано.</summary>
|
||
public static Vector3? ItemPos, ItemRot;
|
||
|
||
static Animator _anim;
|
||
static readonly Dictionary<string, Transform> _bones = new Dictionary<string, Transform>();
|
||
static readonly List<Transform> _frozen = new List<Transform>();
|
||
static readonly List<Vector3> _frozenPos = new List<Vector3>();
|
||
static readonly List<Quaternion> _frozenRot = new List<Quaternion>();
|
||
static int _playerId = -1;
|
||
|
||
/// <summary>Любая загруженная анимация на теле (16.10: «Может и танцевать сможешь заставить?.. сильно бы помогло в
|
||
/// создании контента»): клип играется поверх контроллера через Playables, по кругу. Танцы есть у зомби (twitch_dance) -
|
||
/// их клипы в памяти, пока в мире есть зомби с таким аниматором.</summary>
|
||
static PlayableGraph _graph;
|
||
static AnimationClipPlayable _clip;
|
||
public static string Playing;
|
||
|
||
public static List<AnimationClip> FindClips(string _filter)
|
||
{
|
||
var list = new List<AnimationClip>();
|
||
var seen = new HashSet<string>();
|
||
foreach (AnimationClip c in Resources.FindObjectsOfTypeAll<AnimationClip>())
|
||
{
|
||
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);
|
||
}
|
||
|
||
/// <summary>Тело повторяет движения другого существа (16.10: танец зомби не лёг на тело игрока через Playables - клип
|
||
/// гуманоидный, а тело игрока - нет). Каждый кадр локальные повороты костей источника копируются на кости тела с теми же
|
||
/// именами (Hips, Spine, LeftArm...); у бёдер - ещё и сдвиг. Источник - зомби, которого necrobody zdance заставляет танцевать.</summary>
|
||
static EntityAlive _mimic;
|
||
static readonly List<Transform> _mimicSrc = new List<Transform>();
|
||
static readonly List<Transform> _mimicDst = new List<Transform>();
|
||
|
||
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<string, Transform>();
|
||
foreach (Transform t in src.GetComponentsInChildren<Transform>(true))
|
||
{
|
||
if (!byName.ContainsKey(t.name))
|
||
{
|
||
byName[t.name] = t;
|
||
}
|
||
}
|
||
foreach (Transform t in a.GetComponentsInChildren<Transform>(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())
|
||
{
|
||
_graph.Destroy();
|
||
}
|
||
Playing = null;
|
||
}
|
||
|
||
/// <summary>Петля для клипов без своей петли.</summary>
|
||
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()
|
||
{
|
||
return (Player()?.emodel?.avatarController as AvatarCharacterController)?.CharacterBody;
|
||
}
|
||
|
||
public static Animator Anim() => Body()?.Animator;
|
||
|
||
public static bool Active => Params.Count > 0 || Pose.Count > 0 || Freeze || Speed != 1f || ItemPos.HasValue || ItemRot.HasValue;
|
||
|
||
public static Transform Bone(string _name)
|
||
{
|
||
Animator a = Anim();
|
||
if (a == null)
|
||
{
|
||
return null;
|
||
}
|
||
if (a != _anim)
|
||
{
|
||
_anim = a;
|
||
_bones.Clear();
|
||
_frozen.Clear();
|
||
foreach (Transform t in a.GetComponentsInChildren<Transform>(true))
|
||
{
|
||
if (!_bones.ContainsKey(t.name))
|
||
{
|
||
_bones[t.name] = t;
|
||
}
|
||
}
|
||
}
|
||
return _bones.TryGetValue(_name, out Transform b) ? b : null;
|
||
}
|
||
|
||
public static void Reset()
|
||
{
|
||
StopPlay();
|
||
StopMimic();
|
||
Params.Clear();
|
||
Pose.Clear();
|
||
Freeze = false;
|
||
_frozen.Clear();
|
||
ItemPos = null;
|
||
ItemRot = null;
|
||
if (Speed != 1f)
|
||
{
|
||
Speed = 1f;
|
||
Animator a = Anim();
|
||
if (a != null)
|
||
{
|
||
a.speed = 1f;
|
||
}
|
||
}
|
||
}
|
||
|
||
public static void Unfreeze()
|
||
{
|
||
_frozen.Clear();
|
||
_frozenPos.Clear();
|
||
_frozenRot.Clear();
|
||
}
|
||
|
||
/// <summary>Параметр по имени: тип берётся из аниматора. false - нет такого.</summary>
|
||
public static bool SetParam(Animator _a, string _name, float _v)
|
||
{
|
||
foreach (AnimatorControllerParameter p in _a.parameters)
|
||
{
|
||
if (p.name != _name)
|
||
{
|
||
continue;
|
||
}
|
||
switch (p.type)
|
||
{
|
||
case AnimatorControllerParameterType.Float: _a.SetFloat(p.nameHash, _v); break;
|
||
case AnimatorControllerParameterType.Int: _a.SetInteger(p.nameHash, (int)_v); break;
|
||
case AnimatorControllerParameterType.Bool: _a.SetBool(p.nameHash, _v != 0f); break;
|
||
case AnimatorControllerParameterType.Trigger: _a.SetTrigger(p.nameHash); break;
|
||
}
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/// <summary>После Update аватара (заплатка ниже) - до расчёта аниматора в этом кадре.</summary>
|
||
public static void ApplyParams()
|
||
{
|
||
if (Params.Count == 0 && Speed == 1f)
|
||
{
|
||
return;
|
||
}
|
||
Animator a = Anim();
|
||
if (a == null)
|
||
{
|
||
return;
|
||
}
|
||
foreach (KeyValuePair<string, float> kv in Params)
|
||
{
|
||
SetParam(a, kv.Key, kv.Value);
|
||
}
|
||
a.speed = Speed;
|
||
}
|
||
|
||
/// <summary>Из HandPoseDriver.LateUpdate.</summary>
|
||
public static void LateUpdate()
|
||
{
|
||
EntityPlayerLocal pl = Player();
|
||
int id = pl != null ? pl.entityId : -1;
|
||
if (id != _playerId)
|
||
{
|
||
_playerId = id;
|
||
Reset();
|
||
_anim = null;
|
||
_bones.Clear();
|
||
return;
|
||
}
|
||
LoopPlay();
|
||
ApplyMimic();
|
||
if (!Active || Bone("") == null && _anim == null)
|
||
{
|
||
return;
|
||
}
|
||
if (Freeze)
|
||
{
|
||
if (_frozen.Count == 0 || _frozen[0] == null)
|
||
{
|
||
Unfreeze();
|
||
foreach (Transform t in _anim.GetComponentsInChildren<Transform>(true))
|
||
{
|
||
_frozen.Add(t);
|
||
_frozenPos.Add(t.localPosition);
|
||
_frozenRot.Add(t.localRotation);
|
||
}
|
||
Debug.Log("[NecromancerTome] necrobody: заморожено костей " + _frozen.Count);
|
||
}
|
||
else
|
||
{
|
||
for (int i = 0; i < _frozen.Count; i++)
|
||
{
|
||
if (_frozen[i] != null)
|
||
{
|
||
_frozen[i].localPosition = _frozenPos[i];
|
||
_frozen[i].localRotation = _frozenRot[i];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
foreach (KeyValuePair<string, Vector3> kv in Pose)
|
||
{
|
||
Transform b = Bone(kv.Key);
|
||
if (b != null)
|
||
{
|
||
b.localRotation = Quaternion.Euler(kv.Value);
|
||
}
|
||
}
|
||
if ((ItemPos.HasValue || ItemRot.HasValue) && pl.emodel != null && !pl.emodel.IsFPV)
|
||
{
|
||
Transform item = (pl.emodel.avatarController as AvatarMultiBodyController)?.heldItemTransform;
|
||
if (item != null)
|
||
{
|
||
if (ItemPos.HasValue)
|
||
{
|
||
item.localPosition = ItemPos.Value;
|
||
}
|
||
if (ItemRot.HasValue)
|
||
{
|
||
item.localEulerAngles = ItemRot.Value;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
[HarmonyPatch(typeof(AvatarMultiBodyController), "Update")]
|
||
public static class Patch_AvatarMultiBodyController_Update_NecroBody
|
||
{
|
||
public static void Postfix(AvatarMultiBodyController __instance)
|
||
{
|
||
if (NecroBody.Params.Count > 0 || NecroBody.Speed != 1f)
|
||
{
|
||
EntityPlayerLocal pl = NecroBody.Player();
|
||
if (pl != null && __instance.Entity == pl)
|
||
{
|
||
NecroBody.ApplyParams();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// necrobody - аниматор тела игрока (вид от третьего лица):
|
||
/// params [фильтр] - параметры аниматора и значения
|
||
/// clips - что играет в каждом слое (клип, вес слоя)
|
||
/// set <параметр> <число> - держать значение (bool 0/1); unset <параметр>; trigger <параметр>
|
||
/// speed <k> - скорость аниматора (0 - пауза)
|
||
/// freeze [off] - замереть в текущем кадре (все кости)
|
||
/// bones [фильтр] - кости тела с локальными поворотами в лог
|
||
/// show <кость>; pose <кость> x y z; add <кость> dx dy dz; unpose <кость>
|
||
/// item pos x y z (см) | rot x y z | off | show - место предмета в руке тела
|
||
/// holdoffset [N] - смещение игры для хвата N (по умолчанию - предмета в руке)
|
||
/// save - всё заданное строками в лог; off - всё снять
|
||
/// </summary>
|
||
[Preserve]
|
||
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|zdance [off]|mimic [id]|mimic off|save|off";
|
||
|
||
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, int _i, out Vector3 _v)
|
||
{
|
||
_v = Vector3.zero;
|
||
if (Num(_p, _i, out float x) && Num(_p, _i + 1, out float y) && Num(_p, _i + 2, out float z))
|
||
{
|
||
_v = new Vector3(x, y, z);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
static void Out(string _s)
|
||
{
|
||
SdtdConsole.Instance.Output(_s);
|
||
foreach (string line in _s.Split('\n'))
|
||
{
|
||
Debug.Log("[NecromancerTome] necrobody: " + line);
|
||
}
|
||
}
|
||
|
||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
|
||
{
|
||
Animator a = NecroBody.Anim();
|
||
if (a == null)
|
||
{
|
||
Out("нет аниматора тела (игрок не в мире?)");
|
||
return;
|
||
}
|
||
string cmd = _params.Count > 0 ? _params[0] : "";
|
||
string arg = _params.Count > 1 ? _params[1] : "";
|
||
switch (cmd)
|
||
{
|
||
case "params":
|
||
{
|
||
var sb = new StringBuilder("аниматор " + a.name + ", контроллер " + (a.runtimeAnimatorController != null ? a.runtimeAnimatorController.name : "нет"));
|
||
foreach (AnimatorControllerParameter p in a.parameters)
|
||
{
|
||
if (arg.Length > 0 && p.name.IndexOf(arg, System.StringComparison.OrdinalIgnoreCase) < 0)
|
||
{
|
||
continue;
|
||
}
|
||
string v = p.type == AnimatorControllerParameterType.Float ? NecroBody.F(a.GetFloat(p.nameHash))
|
||
: p.type == AnimatorControllerParameterType.Int ? a.GetInteger(p.nameHash).ToString()
|
||
: p.type == AnimatorControllerParameterType.Bool ? (a.GetBool(p.nameHash) ? "1" : "0") : "trigger";
|
||
sb.Append("\n ").Append(p.name).Append(" (").Append(p.type).Append(") = ").Append(v)
|
||
.Append(NecroBody.Params.ContainsKey(p.name) ? " [держим]" : "");
|
||
}
|
||
Out(sb.ToString());
|
||
return;
|
||
}
|
||
case "clips":
|
||
{
|
||
var sb = new StringBuilder("слоёв " + a.layerCount);
|
||
for (int l = 0; l < a.layerCount; l++)
|
||
{
|
||
string names = "";
|
||
foreach (AnimatorClipInfo c in a.GetCurrentAnimatorClipInfo(l))
|
||
{
|
||
names += (names.Length > 0 ? ", " : "") + (c.clip != null ? c.clip.name : "?") + " x" + c.weight.ToString("0.##");
|
||
}
|
||
sb.Append("\n ").Append(l).Append(' ').Append(a.GetLayerName(l)).Append(" вес ").Append(a.GetLayerWeight(l).ToString("0.##")).Append(": ").Append(names);
|
||
}
|
||
Out(sb.ToString());
|
||
return;
|
||
}
|
||
case "set" when Num(_params, 2, out float v):
|
||
if (!NecroBody.SetParam(a, arg, v))
|
||
{
|
||
Out("нет параметра " + arg + " (necrobody params)");
|
||
return;
|
||
}
|
||
NecroBody.Params[arg] = v;
|
||
Out(arg + " = " + NecroBody.F(v) + " (держим; unset - отпустить)");
|
||
return;
|
||
case "unset":
|
||
Out(NecroBody.Params.Remove(arg) ? arg + " отпущен" : arg + " не был задан");
|
||
return;
|
||
case "trigger":
|
||
Out(NecroBody.SetParam(a, arg, 1f) ? "сработал " + arg : "нет параметра " + arg);
|
||
return;
|
||
case "speed" when Num(_params, 1, out float k):
|
||
NecroBody.Speed = k;
|
||
a.speed = k;
|
||
Out("скорость аниматора тела " + NecroBody.F(k));
|
||
return;
|
||
case "freeze":
|
||
NecroBody.Freeze = arg != "off";
|
||
NecroBody.Unfreeze();
|
||
Out("заморозка тела " + (NecroBody.Freeze ? "включена (снимок - в этом кадре)" : "выключена"));
|
||
return;
|
||
case "bones":
|
||
{
|
||
var sb = new StringBuilder();
|
||
int n = 0;
|
||
Dump(a.transform, 0, arg, sb, ref n);
|
||
Out("костей " + n + (arg.Length > 0 ? " (фильтр " + arg + ")" : "") + sb);
|
||
return;
|
||
}
|
||
case "show":
|
||
{
|
||
Transform b = NecroBody.Bone(arg);
|
||
Out(b == null ? "нет кости " + arg : arg + " = " + NecroBody.F(b.localEulerAngles) + (NecroBody.Pose.ContainsKey(arg) ? " (задана)" : " (от аниматора)") + ", поз. " + NecroBody.F(b.localPosition));
|
||
return;
|
||
}
|
||
case "pose" when Vec(_params, 2, out Vector3 r):
|
||
if (NecroBody.Bone(arg) == null)
|
||
{
|
||
Out("нет кости " + arg);
|
||
return;
|
||
}
|
||
NecroBody.Pose[arg] = r;
|
||
Out(arg + " = " + NecroBody.F(r));
|
||
return;
|
||
case "add" when Vec(_params, 2, out Vector3 d):
|
||
{
|
||
Transform b = NecroBody.Bone(arg);
|
||
if (b == null)
|
||
{
|
||
Out("нет кости " + arg);
|
||
return;
|
||
}
|
||
Vector3 cur = NecroBody.Pose.TryGetValue(arg, out Vector3 was) ? was : b.localEulerAngles;
|
||
NecroBody.Pose[arg] = cur + d;
|
||
Out(arg + " = " + NecroBody.F(NecroBody.Pose[arg]));
|
||
return;
|
||
}
|
||
case "unpose":
|
||
Out(NecroBody.Pose.Remove(arg) ? arg + " отпущена" : arg + " не была задана");
|
||
return;
|
||
case "item":
|
||
{
|
||
Transform it = (NecroBody.Player()?.emodel?.avatarController as AvatarMultiBodyController)?.heldItemTransform;
|
||
if (arg == "pos" && Vec(_params, 2, out Vector3 p))
|
||
{
|
||
NecroBody.ItemPos = p / 100f;
|
||
}
|
||
else if (arg == "rot" && Vec(_params, 2, out Vector3 r2))
|
||
{
|
||
NecroBody.ItemRot = r2;
|
||
}
|
||
else if (arg == "off")
|
||
{
|
||
NecroBody.ItemPos = null;
|
||
NecroBody.ItemRot = null;
|
||
Out("место предмета - как у игры (при следующем доставании)");
|
||
return;
|
||
}
|
||
Out("предмет в руке тела " + (it != null ? it.name + ": поз. (см) " + NecroBody.F(it.localPosition * 100f) + ", пов. " + NecroBody.F(it.localEulerAngles) : "нет") +
|
||
(NecroBody.ItemPos.HasValue || NecroBody.ItemRot.HasValue ? " [держим]" : ""));
|
||
return;
|
||
}
|
||
case "holdoffset":
|
||
{
|
||
int h = Num(_params, 1, out float hv) ? (int)hv : NecroBody.Player()?.inventory?.holdingItem?.HoldType.Value ?? 0;
|
||
var o = AnimationGunjointOffsetData.AnimationGunjointOffset[h];
|
||
Out("хват " + h + ": смещение игры поз. (см) " + NecroBody.F(o.position * 100f) + ", пов. " + NecroBody.F(o.rotation));
|
||
return;
|
||
}
|
||
case "save":
|
||
{
|
||
var sb = new StringBuilder("заданное:");
|
||
foreach (var kv in NecroBody.Params)
|
||
{
|
||
sb.Append("\n param ").Append(kv.Key).Append(" = ").Append(NecroBody.F(kv.Value));
|
||
}
|
||
foreach (var kv in NecroBody.Pose)
|
||
{
|
||
sb.Append("\n Pose[\"").Append(kv.Key).Append("\"] = new Vector3(").Append(NecroBody.F(kv.Value.x)).Append("f, ")
|
||
.Append(NecroBody.F(kv.Value.y)).Append("f, ").Append(NecroBody.F(kv.Value.z)).Append("f);");
|
||
}
|
||
if (NecroBody.ItemPos.HasValue)
|
||
{
|
||
sb.Append("\n item pos (см) ").Append(NecroBody.F(NecroBody.ItemPos.Value * 100f));
|
||
}
|
||
if (NecroBody.ItemRot.HasValue)
|
||
{
|
||
sb.Append("\n item rot ").Append(NecroBody.F(NecroBody.ItemRot.Value));
|
||
}
|
||
sb.Append("\n speed ").Append(NecroBody.F(NecroBody.Speed)).Append(", freeze ").Append(NecroBody.Freeze);
|
||
Out(sb.ToString());
|
||
return;
|
||
}
|
||
case "anims":
|
||
{
|
||
List<AnimationClip> 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 "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("анимация снята");
|
||
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("тело - как у игры");
|
||
return;
|
||
}
|
||
Out(getDescription());
|
||
}
|
||
|
||
static void Dump(Transform _t, int _depth, string _filter, StringBuilder _sb, ref int _n)
|
||
{
|
||
if (_filter.Length == 0 || _t.name.IndexOf(_filter, System.StringComparison.OrdinalIgnoreCase) >= 0)
|
||
{
|
||
_n++;
|
||
_sb.Append('\n').Append(new string(' ', _depth * 2)).Append(_t.name).Append(_t.gameObject.activeSelf ? "" : " [выкл]")
|
||
.Append(" пов. ").Append(NecroBody.F(_t.localEulerAngles)).Append(" поз. ").Append(NecroBody.F(_t.localPosition));
|
||
}
|
||
for (int i = 0; i < _t.childCount; i++)
|
||
{
|
||
Dump(_t.GetChild(i), _depth + 1, _filter, _sb, ref _n);
|
||
}
|
||
}
|
||
}
|
||
}
|