using HarmonyLib; using UnityEngine; namespace NecromancerTome { /// /// Одержимый медведь - танк (решено 09.10, BACKLOG «Медведь и лютоволк в одержимости»): /// - толстая шкура: пока в нём игрок, урон по нему вдвое меньше (вне одержимости медведь прежний); /// - рёв на ПКМ: зомби ближе RoarRange бросают свои цели и идут на медведя, спящие просыпаются - /// увести толпу от тела или базы. Та же провокация, что каждую секунду делает одержимость /// (Possession.Taunt), только шире, без «видит ли» и на 20 с; /// - удар лапой по блоку: ЛКМ по блоку под прицелом бьёт его на SmashDamage (дерево и двери за 2-3 /// удара, бетон дольше); /// - на дыбы на E (указание 09.10: «на E - действие»): враги ближе RearRange сбиты с ног на RearStun с /// и отброшены от медведя - передышка в толпе. Откат RearCooldown. /// public static class BearControl { public const string BearClass = "necroZombieBear"; public const float RoarRange = 15f; public const float RoarCooldown = 8f; public const int RoarTicks = 20 * 20; public const string RoarSound = "bearalert"; /// Урон блоку за удар лапой. Для сравнения: удар ванильного зомби-медведя по блокам - десятки. public const int SmashDamage = 300; /// Досягаемость лапы от головы медведя, м. public const float SmashReach = 2.6f; public const float SkinFactor = 0.5f; static float _nextRoar; public const float RearRange = 3.5f; public const float RearStun = 2f; public const float RearCooldown = 6f; /// Толчок от медведя, м за тик (как motion существ; гаснет сам). public const float RearPush = 0.5f; public const string RearSound = "bearattack"; static float _nextRear; public static bool Is(EntityAlive _e) { return _e != null && EntityClass.list.TryGetValue(_e.entityClass, out EntityClass cls) && cls != null && cls.entityClassName == BearClass; } public static void Roar(World _world, EntityAlive _bear) { if (Time.time < _nextRoar) { // Рёв в толпе не отличить от удара (тест 09.10) - пишем, сколько ждать и сколько пошло на медведя. Tip(string.Format(Localization.Get("necroBearRoarCooldown"), Mathf.CeilToInt(_nextRoar - Time.time))); return; } _nextRoar = Time.time + RoarCooldown; _bear.PlayOneShot(RoarSound); _bear.emodel?.avatarController?.StartAnimationAttack(); int n = 0; for (int i = 0; i < _world.EntityAlives.Count; i++) { EntityAlive e = _world.EntityAlives[i]; if (e == null || e == _bear || e.IsDead() || !(e is EntityZombie) || e.aiManager == null || PetFollowPatch.FindOwnerOfPet(e.entityId) != null || (e.position - _bear.position).sqrMagnitude > RoarRange * RoarRange) { continue; } if (e.IsSleeping) { e.ConditionalTriggerSleeperWakeUp(); } Possession.AllowZombieTarget(e, _bear); e.SetAttackTarget(_bear, RoarTicks); n++; } Debug.Log("[NecromancerTome] BearControl: рёв - на медведя идут " + n); Tip(string.Format(Localization.Get("necroBearRoarResult"), n)); } /// Сразу, мимо очереди подсказок: очередь игры (XUiC_PopupToolTip) отсеивает только одинаковый текст, /// и «через 7 с», «через 6 с»... при частых ПКМ копились бы и всплывали с опозданием. static void Tip(string _text) { if (Possession.Player != null) { GameManager.ShowTooltip(Possession.Player, _text, true); } } /// E: на дыбы - враги рядом падают (тот же приём, что у броска волка: BeginStun Prone) и /// отлетают от медведя. public static void RearUp(World _world, EntityAlive _bear) { if (Time.time < _nextRear) { Tip(string.Format(Localization.Get("necroBearRearCooldown"), Mathf.CeilToInt(_nextRear - Time.time))); return; } _nextRear = Time.time + RearCooldown; _bear.PlayOneShot(RearSound); _bear.emodel?.avatarController?.StartAnimationAttack(); int n = 0; for (int i = 0; i < _world.EntityAlives.Count; i++) { EntityAlive e = _world.EntityAlives[i]; if (!SnakeControl.IsFoe(e, _bear) || (e.position - _bear.position).sqrMagnitude > RearRange * RearRange) { continue; } Vector3 away = e.position - _bear.position; away.y = 0f; away = away.sqrMagnitude > 0.01f ? away.normalized : _bear.GetForwardVector(); e.motion += away * RearPush + Vector3.up * 0.15f; if (e.bodyDamage.CurrentStun == EnumEntityStunType.None && e.emodel?.avatarController != null) { e.emodel.avatarController.BeginStun(EnumEntityStunType.Prone, EnumBodyPartHit.Torso, Utils.EnumHitDirection.Back, false, 0.5f); e.SetStun(EnumEntityStunType.Prone); e.bodyDamage.StunDuration = RearStun; } n++; } Tip(string.Format(Localization.Get("necroBearRearResult"), n)); Debug.Log("[NecromancerTome] BearControl: на дыбы - сбиты с ног " + n); } /// ЛКМ: блок под прицелом в досягаемости лапы (не существо) - урон SmashDamage. public static void Smash(World _world, EntityPlayerLocal _player, EntityAlive _bear) { Camera cam = _player.playerCamera; if (cam == null) { return; } Vector3 head = _bear.getHeadPosition(); Vector3 from = cam.transform.position + Origin.position; Ray ray = new Ray(from, cam.transform.forward); // С существами: если первым на луче стоит существо - это удар по нему, а не по блоку. if (!Voxel.Raycast(_world, ray, Vector3.Distance(from, head) + SmashReach + 1f, -555528221, 69, 0f)) { return; } WorldRayHitInfo hit = Voxel.voxelRayHitInfo; if (hit.transform != null && GameUtils.GetHitRootTransform(hit.tag, hit.transform)?.GetComponent() != null) { return; } if ((hit.hit.pos - head).sqrMagnitude > SmashReach * SmashReach) { return; } Vector3i pos = hit.hit.blockPos; BlockValue bv = _world.GetBlock(pos); if (bv.isair || bv.Block == null) { return; } bv.Block.DamageBlock(_world, new BlockValueRef(pos), bv, SmashDamage, _bear.entityId); Debug.Log("[NecromancerTome] BearControl: лапой по " + bv.Block.GetBlockName() + " " + pos + " - " + SmashDamage + " (прочность " + (bv.Block.MaxDamage - bv.damage) + " из " + bv.Block.MaxDamage + ")"); } } /// Толстая шкура одержимого медведя - вдвое меньше урона. [HarmonyPatch(typeof(EntityAlive), nameof(EntityAlive.DamageEntity))] public static class Patch_EntityAlive_DamageEntity_BearSkin { public static void Prefix(EntityAlive __instance, ref int _strength) { if (_strength > 1 && Possession.IsPossessed(__instance) && BearControl.Is(__instance)) { _strength = Mathf.Max(1, Mathf.RoundToInt(_strength * BearControl.SkinFactor)); } } } }