Files
necromants-tome-7d2d-3-2/HarmonySrc/CarryVisual.cs
T
AlexCubeandClaude Opus 5.5 31aa99860c Одержимость питомцев: роли медведя, волка, собаки и крысы; ноша стервятника; череп при обыске
- Могильная крыса: своя модель без волчьей шерсти; стоит на земле (центр капсулы);
  E - осмотр контейнеров без права взять (окно игры, рюкзак скрыт, отказ «инвентарь
  заполнен», контейнер не разрушается); ПКМ - проход сквозь постройки, полупрозрачная.
- Стервятник: курица в когтях - слепок с обвисшими крыльями и шеей, без хитбокса.
- Зомбособака: нюх - все существа рядом на компасе и карте.
- Медведь: толстая шкура (вдвое меньше урона), рёв-провокация, удар лапой по блокам.
- Лютоволк: бросок на цель с опрокидыванием, след крови раненых, сумка на 3 ячейки
  (лут, выгрузка у тела, выпадает мешком при гибели и отзыве; отдельный файл сейва).
- Череп (останки): 2% при обычном обыске красного трупа вместо браслета.
- Прицел книги не рисуется в одержимости; подсказки вселения на 13 языках.
- Замер EntityLeakProbe: удалённые, но живые в памяти сущности - в лог.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 00:58:40 +03:00

340 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using UnityEngine;
namespace NecromancerTome
{
/// <summary>
/// Ноша в когтях одержимого стервятника (указание 08.10: «сделать курицу в когтях стервятника без хитбокса
/// и отдельным элементом, чтобы крылья и шея свисали вниз»).
///
/// Раньше под лапы каждый кадр переставлялась сама курица, и было две беды. Рывки: модель сущности
/// догоняет её позицию с задержкой (Entity.updateTransform - Lerp), а позиция стервятника меняется
/// 20 раз в секунду - курица отставала и прыгала. Твёрдость: её коллайдеры мешали спускаться, и
/// стервятник садился на неё.
///
/// Теперь при захвате с курицы снимается слепок: кости крыльев и шеи развёрнуты вниз, сетки запечены
/// (BakeMesh) в неподвижные MeshRenderer без коллайдеров. Слепок висит под стервятником по его
/// видимой (сглаженной) позиции. Сама курица спрятана (forceRenderingOff - его не трогает
/// avatarController.SetVisible), коллайдеры выключены, ИИ стоит; её позиция идёт за слепком,
/// и когда стервятник её отпускает, она появляется ровно там, где висел слепок.
/// </summary>
public static class CarryVisual
{
static GameObject _holder;
static readonly List<Mesh> _meshes = new List<Mesh>();
static readonly List<Renderer> _hidden = new List<Renderer>();
static readonly List<Collider> _off = new List<Collider>();
/// <summary>Верх и низ слепка относительно его опоры, м.</summary>
static float _top, _bottom;
/// <summary>Сколько крыло отводится в сторону от вертикали и шея - вперёд.</summary>
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<Collider>(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<Renderer>(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<MeshFilter>().sharedMesh = mesh;
MeshRenderer mr = part.AddComponent<MeshRenderer>();
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<Renderer>(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);
}
/// <summary>Каждый кадр: слепок под лапами стервятника по его видимой позиции; у земли - стоит на ней.</summary>
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<Transform> _claws = new List<Transform>();
/// <summary>Точка хвата - середина между лапами стервятника (кости с «toe»/«foot» в имени, а без них - «leg»),
/// по анимации, каждый кадр (тест 08.10: «курица висит под стервятником, а не в его когтях»). Костей нет -
/// низ его модели.</summary>
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<string> all = new List<string>();
foreach (string key in new[] { "toe", "foot", "leg" })
{
foreach (Transform t in model.GetComponentsInChildren<Transform>(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<Transform>(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;
}
/// <summary>Отпустить: курица снова видна и тверда там, где висел слепок.</summary>
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();
}
/// <summary>Убрать слепок, ничего не возвращая (выход из мира).</summary>
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();
}
/// <summary>Обвисшая поза: каждая кость крыла и шеи (от корня к концу) разворачивается так, чтобы её
/// дочерняя кость оказалась под ней - крылья свисают чуть в стороны, шея с головой - чуть вперёд.
/// Аниматор следующим кадром позу вернёт, но слепок уже запечён, а сама курица спрятана.</summary>
static void Limp(EntityAlive _c, Transform _model)
{
HashSet<Transform> bones = new HashSet<Transform>();
foreach (SkinnedMeshRenderer smr in _model.GetComponentsInChildren<SkinnedMeshRenderer>(true))
{
if (smr.bones == null)
{
continue;
}
foreach (Transform b in smr.bones)
{
if (b != null)
{
bones.Add(b);
}
}
}
List<Transform> order = new List<Transform>(bones);
order.Sort((a, b) => Depth(a).CompareTo(Depth(b)));
if (!_loggedBones)
{
_loggedBones = true;
List<string> names = new List<string>();
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;
}
}
}