Files
necromants-tome-7d2d-3-2/HarmonySrc/WolfControl.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

173 lines
6.0 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>
/// Одержимый лютоволк - охотник (решено 09.10, BACKLOG «Медведь и лютоволк в одержимости»):
/// - бросок на ПКМ: враг под прицелом не дальше LeapRange - волк разворачивается и прыгает прямо на
/// него (прыжок самой игры: SetJumpDistance на точную дальность и перепад высоты); приземлился рядом -
/// цель падает на LeapStun секунд;
/// - след крови: раненые враги (здоровье ниже BloodHealth) отмечены на компасе и карте до BloodRange,
/// пока не умрут или не залечатся, - отличие от нюха собаки: та чует всех рядом, волк - подранков издалека.
/// </summary>
public static class WolfControl
{
public const string WolfClass = "necroZombieWolf";
public const float LeapRange = 10f;
public const float LeapMin = 2f;
public const float LeapConeDeg = 10f;
public const float LeapCooldown = 3f;
public const float LeapHit = 2.5f;
public const float LeapStun = 2.5f;
public const float BloodHealth = 0.5f;
public const float BloodRange = 150f;
public const float BloodDropRange = 200f;
static EntityAlive _leapTarget;
static float _leapUntil, _leapStart, _nextLeap;
static readonly HashSet<int> _blood = new HashSet<int>();
static float _bloodCheck;
public static bool Is(EntityAlive _e)
{
return _e != null && EntityClass.list.TryGetValue(_e.entityClass, out EntityClass cls) && cls != null &&
cls.entityClassName == WolfClass;
}
/// <summary>Каждый кадр одержимости волком.</summary>
public static void Tick(World _world, EntityPlayerLocal _player, EntityAlive _wolf, bool _leap, ref float _bodyYaw)
{
if (_leap && _leapTarget == null && Time.time >= _nextLeap && _wolf.onGround && !_wolf.Jumping)
{
EntityAlive t = Target(_world, _player, _wolf);
if (t != null)
{
Vector3 to = t.position - _wolf.position;
Vector3 flat = new Vector3(to.x, 0f, to.z);
_bodyYaw = Mathf.Atan2(flat.x, flat.z) * Mathf.Rad2Deg;
_wolf.SetRotation(new Vector3(0f, _bodyYaw, 0f));
_wolf.Jumping = true;
_wolf.SetJumpDistance(flat.magnitude, Mathf.Max(0.5f, to.y + 0.5f));
_leapTarget = t;
_leapStart = Time.time;
_leapUntil = Time.time + 2f;
_nextLeap = Time.time + LeapCooldown;
Debug.Log("[NecromancerTome] WolfControl: бросок на " + t.EntityName + " (" + t.entityId + "), " + flat.magnitude.ToString("F1") + " м");
}
}
if (_leapTarget != null)
{
bool landed = Time.time - _leapStart > 0.3f && _wolf.onGround && !_wolf.Jumping;
if (_leapTarget.IsDead() || Time.time > _leapUntil)
{
_leapTarget = null;
}
else if (landed || (_leapTarget.getChestPosition() - _wolf.getHeadPosition()).sqrMagnitude < LeapHit * LeapHit * 0.5f)
{
if ((_leapTarget.position - _wolf.position).sqrMagnitude < LeapHit * LeapHit)
{
KnockDown(_leapTarget, _wolf);
}
_leapTarget = null;
}
}
BloodTick(_world, _wolf);
}
static void KnockDown(EntityAlive _t, EntityAlive _wolf)
{
_wolf.emodel?.avatarController?.StartAnimationAttack();
if (_t.bodyDamage.CurrentStun == EnumEntityStunType.None && _t.emodel?.avatarController != null)
{
_t.emodel.avatarController.BeginStun(EnumEntityStunType.Prone, EnumBodyPartHit.Torso, Utils.EnumHitDirection.Front, false, 0.5f);
_t.SetStun(EnumEntityStunType.Prone);
_t.bodyDamage.StunDuration = LeapStun;
}
Debug.Log("[NecromancerTome] WolfControl: " + _t.EntityName + " сбит с ног");
}
/// <summary>Враг ближе всех к лучу прицела: не дальше LeapRange от волка и в конусе LeapConeDeg.</summary>
static EntityAlive Target(World _world, EntityPlayerLocal _player, EntityAlive _wolf)
{
Transform cam = _player.cameraTransform;
if (cam == null)
{
return null;
}
Vector3 eye = cam.position + Origin.position;
EntityAlive best = null;
float bestAngle = LeapConeDeg;
for (int i = 0; i < _world.EntityAlives.Count; i++)
{
EntityAlive e = _world.EntityAlives[i];
if (!SnakeControl.IsFoe(e, _wolf))
{
continue;
}
float d = (e.position - _wolf.position).magnitude;
if (d > LeapRange || d < LeapMin)
{
continue;
}
float angle = Vector3.Angle(cam.forward, e.getChestPosition() - eye);
if (angle < bestAngle)
{
bestAngle = angle;
best = e;
}
}
return best;
}
static void BloodTick(World _world, EntityAlive _wolf)
{
_bloodCheck -= Time.deltaTime;
if (_bloodCheck > 0f)
{
return;
}
_bloodCheck = 1f;
for (int i = 0; i < _world.EntityAlives.Count; i++)
{
EntityAlive e = _world.EntityAlives[i];
if (e == null)
{
continue;
}
float d2 = (e.position - _wolf.position).sqrMagnitude;
bool bleeding = SnakeControl.IsFoe(e, _wolf) && e.Health < e.GetMaxHealth() * BloodHealth;
if (bleeding && d2 < BloodRange * BloodRange)
{
if (_blood.Add(e.entityId))
{
e.AddNavObject(SnakeControl.SenseNavObject, null, null);
}
}
else if (_blood.Contains(e.entityId) && (!bleeding || d2 > BloodDropRange * BloodDropRange))
{
e.RemoveNavObject(SnakeControl.SenseNavObject);
_blood.Remove(e.entityId);
}
}
_blood.RemoveWhere(id => _world.GetEntity(id) == null);
}
/// <summary>Выход из волка - снять метки, забыть бросок.</summary>
public static void Clear()
{
World world = GameManager.Instance?.World;
if (world != null)
{
foreach (int id in _blood)
{
(world.GetEntity(id) as EntityAlive)?.RemoveNavObject(SnakeControl.SenseNavObject);
}
}
_blood.Clear();
_bloodCheck = 0f;
_leapTarget = null;
}
}
}