using System.Collections.Generic;
using UnityEngine;
namespace NecromancerTome
{
///
/// Ноша в когтях одержимого стервятника (указание 08.10: «сделать курицу в когтях стервятника без хитбокса
/// и отдельным элементом, чтобы крылья и шея свисали вниз»).
///
/// Раньше под лапы каждый кадр переставлялась сама курица, и было две беды. Рывки: модель сущности
/// догоняет её позицию с задержкой (Entity.updateTransform - Lerp), а позиция стервятника меняется
/// 20 раз в секунду - курица отставала и прыгала. Твёрдость: её коллайдеры мешали спускаться, и
/// стервятник садился на неё.
///
/// Теперь при захвате с курицы снимается слепок: кости крыльев и шеи развёрнуты вниз, сетки запечены
/// (BakeMesh) в неподвижные MeshRenderer без коллайдеров. Слепок висит под стервятником по его
/// видимой (сглаженной) позиции. Сама курица спрятана (forceRenderingOff - его не трогает
/// avatarController.SetVisible), коллайдеры выключены, ИИ стоит; её позиция идёт за слепком,
/// и когда стервятник её отпускает, она появляется ровно там, где висел слепок.
///
public static class CarryVisual
{
static GameObject _holder;
static readonly List _meshes = new List();
static readonly List _hidden = new List();
static readonly List _off = new List();
/// Верх и низ слепка относительно его опоры, м.
static float _top, _bottom;
/// Сколько крыло отводится в сторону от вертикали и шея - вперёд.
const float WingOut = 0.35f;
const float NeckForward = 0.35f;
static bool _loggedBones;
public static void Begin(EntityAlive _c)
{
Clear();
Transform model = _c.emodel != null ? _c.emodel.GetModelTransform() : null;
if (model == null)
{
Debug.LogWarning("[NecromancerTome] CarryVisual: у " + _c.EntityName + " нет модели - несём как есть");
return;
}
// Не только свои: физическое тело для толкания (physicsRBT) игра выносит из иерархии сущности
// (Entity.PhysicsInit - SetParent наружу), и на нём стервятник «сидел» в воздухе (тест 09.10).
foreach (Transform root in new[] { _c.transform, _c.PhysicsTransform, _c.physicsRBT })
{
if (root == null)
{
continue;
}
foreach (Collider col in root.GetComponentsInChildren(true))
{
if (col != null && col.enabled)
{
col.enabled = false;
_off.Add(col);
}
}
}
Limp(_c, model);
_holder = new GameObject("necroCarry_" + _c.entityId);
_holder.transform.SetPositionAndRotation(_c.transform.position, Quaternion.Euler(0f, _c.transform.eulerAngles.y, 0f));
bool any = false;
_top = float.MinValue;
_bottom = float.MaxValue;
foreach (Renderer r in model.GetComponentsInChildren(false))
{
if (r == null || !r.enabled || r.forceRenderingOff)
{
continue;
}
Mesh mesh;
if (r is SkinnedMeshRenderer smr)
{
if (smr.sharedMesh == null)
{
continue;
}
mesh = new Mesh();
smr.BakeMesh(mesh, true);
_meshes.Add(mesh);
}
else if (r is MeshRenderer && r.TryGetComponent(out MeshFilter mf) && mf.sharedMesh != null)
{
mesh = mf.sharedMesh;
}
else
{
continue;
}
GameObject part = new GameObject(r.name);
part.transform.SetPositionAndRotation(r.transform.position, r.transform.rotation);
part.transform.SetParent(_holder.transform, true);
if (!(r is SkinnedMeshRenderer))
{
part.transform.localScale = r.transform.lossyScale;
}
part.AddComponent().sharedMesh = mesh;
MeshRenderer mr = part.AddComponent();
mr.sharedMaterials = r.sharedMaterials;
mr.shadowCastingMode = r.shadowCastingMode;
// Высота - по самой запечённой сетке: r.bounds у SkinnedMeshRenderer - рамка с запасом, по ней
// курица висела далеко под лапами (тест 09.10, «высота 0.99 м»).
foreach (Vector3 v in mesh.vertices)
{
float y = part.transform.TransformPoint(v).y - _holder.transform.position.y;
_top = Mathf.Max(_top, y);
_bottom = Mathf.Min(_bottom, y);
}
any = true;
}
if (!any)
{
_top = 0.3f;
_bottom = 0f;
}
foreach (Renderer r in _c.GetComponentsInChildren(true))
{
if (r != null && !r.forceRenderingOff)
{
r.forceRenderingOff = true;
_hidden.Add(r);
}
}
Debug.Log("[NecromancerTome] CarryVisual: слепок " + _c.EntityName + " - частей " + _holder.transform.childCount +
", высота " + (_top - _bottom).ToString("F2") + " м, выключено коллайдеров " + _off.Count);
}
/// Каждый кадр: слепок под лапами стервятника по его видимой позиции; у земли - стоит на ней.
public static void Place(EntityAlive _v, EntityAlive _c, float _gap)
{
Vector3 feet = Claws(_v);
Quaternion yaw = Quaternion.Euler(0f, _v.transform.eulerAngles.y, 0f);
float y = feet.y - _top - _gap;
if (Physics.Raycast(feet + Vector3.up * 0.1f, Vector3.down, out RaycastHit hit, _top - _bottom + _gap + 0.1f,
SnakeControl.WallMask, QueryTriggerInteraction.Ignore) && hit.point.y > y + _bottom)
{
y = hit.point.y - _bottom;
}
Vector3 at = new Vector3(feet.x, y, feet.z);
if (_holder != null)
{
_holder.transform.SetPositionAndRotation(at, yaw);
}
_c.SetPosition(at + Origin.position);
}
static int _clawsOf = -1;
static readonly List _claws = new List();
/// Точка хвата - середина между лапами стервятника (кости с «toe»/«foot» в имени, а без них - «leg»),
/// по анимации, каждый кадр (тест 08.10: «курица висит под стервятником, а не в его когтях»). Костей нет -
/// низ его модели.
static Vector3 Claws(EntityAlive _v)
{
if (_clawsOf != _v.entityId)
{
_clawsOf = _v.entityId;
_claws.Clear();
Transform model = _v.emodel != null ? _v.emodel.GetModelTransform() : null;
if (model != null)
{
List all = new List();
foreach (string key in new[] { "toe", "foot", "leg" })
{
foreach (Transform t in model.GetComponentsInChildren(true))
{
string n = t.name.ToLowerInvariant();
if (key == "toe" || key == "foot" ? n.Contains(key) : n.Contains(key) && !n.Contains("up") && !n.Contains("thigh"))
{
_claws.Add(t);
}
}
if (_claws.Count > 0)
{
break;
}
}
foreach (Transform t in model.GetComponentsInChildren(true))
{
all.Add(t.name);
}
Debug.Log("[NecromancerTome] CarryVisual: лапы " + _v.EntityName + ": " +
(_claws.Count > 0 ? string.Join(", ", _claws.ConvertAll(t => t.name)) : "не найдены") +
"; все кости: " + string.Join(", ", all));
}
}
if (_claws.Count == 0)
{
return _v.transform.position;
}
Vector3 sum = Vector3.zero;
float low = float.MaxValue;
foreach (Transform t in _claws)
{
sum += t.position;
low = Mathf.Min(low, t.position.y);
}
Vector3 c = sum / _claws.Count;
c.y = low;
return c;
}
/// Отпустить: курица снова видна и тверда там, где висел слепок.
public static void End(EntityAlive _c)
{
if (_c != null && _holder != null && !_c.IsDead())
{
Vector3 at = _holder.transform.position;
_c.SetPosition(at + Origin.position);
_c.transform.position = at;
}
foreach (Renderer r in _hidden)
{
if (r != null)
{
r.forceRenderingOff = false;
}
}
foreach (Collider col in _off)
{
if (col != null)
{
col.enabled = true;
}
}
_hidden.Clear();
_off.Clear();
Clear();
}
/// Убрать слепок, ничего не возвращая (выход из мира).
public static void Clear()
{
if (_holder != null)
{
Object.Destroy(_holder);
}
_holder = null;
foreach (Mesh m in _meshes)
{
if (m != null)
{
Object.Destroy(m);
}
}
_meshes.Clear();
}
/// Обвисшая поза: каждая кость крыла и шеи (от корня к концу) разворачивается так, чтобы её
/// дочерняя кость оказалась под ней - крылья свисают чуть в стороны, шея с головой - чуть вперёд.
/// Аниматор следующим кадром позу вернёт, но слепок уже запечён, а сама курица спрятана.
static void Limp(EntityAlive _c, Transform _model)
{
HashSet bones = new HashSet();
foreach (SkinnedMeshRenderer smr in _model.GetComponentsInChildren(true))
{
if (smr.bones == null)
{
continue;
}
foreach (Transform b in smr.bones)
{
if (b != null)
{
bones.Add(b);
}
}
}
List order = new List(bones);
order.Sort((a, b) => Depth(a).CompareTo(Depth(b)));
if (!_loggedBones)
{
_loggedBones = true;
List names = new List();
foreach (Transform b in order)
{
names.Add(b.name);
}
Debug.Log("[NecromancerTome] CarryVisual: кости " + _c.EntityName + ": " + string.Join(", ", names));
}
Vector3 fwd = _c.transform.forward;
Vector3 right = _c.transform.right;
Vector3 center = _c.transform.position;
int wings = 0, necks = 0;
foreach (Transform b in order)
{
string n = b.name.ToLowerInvariant();
Vector3 want;
if (n.Contains("wing"))
{
float side = Mathf.Sign(Vector3.Dot(b.position - center, right));
want = (Vector3.down + right * side * WingOut).normalized;
wings++;
}
else if (n.Contains("neck") || n.Contains("head"))
{
want = (Vector3.down + fwd * NeckForward).normalized;
necks++;
}
else
{
continue;
}
Transform child = null;
for (int i = 0; i < b.childCount; i++)
{
if (bones.Contains(b.GetChild(i)))
{
child = b.GetChild(i);
break;
}
}
if (child == null)
{
continue;
}
Vector3 dir = child.position - b.position;
if (dir.sqrMagnitude < 1e-6f)
{
continue;
}
b.rotation = Quaternion.FromToRotation(dir, want) * b.rotation;
}
Debug.Log("[NecromancerTome] CarryVisual: обвисли костей крыльев " + wings + ", шеи/головы " + necks);
}
static int Depth(Transform _t)
{
int d = 0;
for (Transform p = _t.parent; p != null; p = p.parent)
{
d++;
}
return d;
}
}
}