Files
necromants-tome-7d2d-3-2/HarmonySrc/BearControl.cs
T
AlexCubeandClaude Opus 5.5 e94e967b8e Одержимость: прицел-перекрестие с покраснением, кнопки E/F, лут мешков и багажник волка, медведь на дыбы
- Прицел всех одержимых - всегда перекрестие (свой guiDrawCrosshair: точка бралась из проверки
  камеры от 3-го лица у тела); краснеет на враге: у стервятника - цель рывка, у наземных - враг
  на луче прицела до 30 м, не за стеной
- Падаль: отхил вдвое меньше (EatHealFrac 0.125); ест на F стервятник, медведь, собака, волк и
  одержимые зомби
- E - действие: стервятник - клич (был F), крыса/волк - обыск, медведь - на дыбы (враги до 3.5 м
  сбиты с ног и отброшены, откат 6 с)
- Рёв медведя: надписи «на медведя идут N» и откат, мимо очереди подсказок
- Крыса и волк обыскивают мешки с зомби (EntityLootContainer), волк берёт в сумку
- Багажник волка: окно сумки как у браслета, без рюкзака; выброс - у волка
- Отзыв волка: мешок на месте и метка «Сумка волка» на карте, снимается с мешком
- Тексты на 13 языках

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

172 lines
7.7 KiB
C#
Raw Permalink 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 HarmonyLib;
using UnityEngine;
namespace NecromancerTome
{
/// <summary>
/// Одержимый медведь - танк (решено 09.10, BACKLOG «Медведь и лютоволк в одержимости»):
/// - толстая шкура: пока в нём игрок, урон по нему вдвое меньше (вне одержимости медведь прежний);
/// - рёв на ПКМ: зомби ближе RoarRange бросают свои цели и идут на медведя, спящие просыпаются -
/// увести толпу от тела или базы. Та же провокация, что каждую секунду делает одержимость
/// (Possession.Taunt), только шире, без «видит ли» и на 20 с;
/// - удар лапой по блоку: ЛКМ по блоку под прицелом бьёт его на SmashDamage (дерево и двери за 2-3
/// удара, бетон дольше);
/// - на дыбы на E (указание 09.10: «на E - действие»): враги ближе RearRange сбиты с ног на RearStun с
/// и отброшены от медведя - передышка в толпе. Откат RearCooldown.
/// </summary>
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";
/// <summary>Урон блоку за удар лапой. Для сравнения: удар ванильного зомби-медведя по блокам - десятки.</summary>
public const int SmashDamage = 300;
/// <summary>Досягаемость лапы от головы медведя, м.</summary>
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;
/// <summary>Толчок от медведя, м за тик (как motion существ; гаснет сам).</summary>
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));
}
/// <summary>Сразу, мимо очереди подсказок: очередь игры (XUiC_PopupToolTip) отсеивает только одинаковый текст,
/// и «через 7 с», «через 6 с»... при частых ПКМ копились бы и всплывали с опозданием.</summary>
static void Tip(string _text)
{
if (Possession.Player != null)
{
GameManager.ShowTooltip(Possession.Player, _text, true);
}
}
/// <summary>E: на дыбы - враги рядом падают (тот же приём, что у броска волка: BeginStun Prone) и
/// отлетают от медведя.</summary>
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);
}
/// <summary>ЛКМ: блок под прицелом в досягаемости лапы (не существо) - урон SmashDamage.</summary>
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<Entity>() != 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 + ")");
}
}
/// <summary>Толстая шкура одержимого медведя - вдвое меньше урона.</summary>
[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));
}
}
}
}