using System.Collections.Generic; using UnityEngine; namespace NecromancerTome { /// /// Одержимый лютоволк - охотник (решено 09.10, BACKLOG «Медведь и лютоволк в одержимости»): /// - бросок на ПКМ: враг под прицелом не дальше LeapRange - волк разворачивается и прыгает прямо на /// него (прыжок самой игры: SetJumpDistance на точную дальность и перепад высоты); приземлился рядом - /// цель падает на LeapStun секунд; /// - след крови: раненые враги (здоровье ниже BloodHealth) отмечены на компасе и карте до BloodRange, /// пока не умрут или не залечатся, - отличие от нюха собаки: та чует всех рядом, волк - подранков издалека. /// 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 _blood = new HashSet(); 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; } /// Каждый кадр одержимости волком. 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 + " сбит с ног"); } /// Враг ближе всех к лучу прицела: не дальше LeapRange от волка и в конусе LeapConeDeg. 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); } /// Выход из волка - снять метки, забыть бросок. 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; } } }