- Могильная крыса: своя модель без волчьей шерсти; стоит на земле (центр капсулы); E - осмотр контейнеров без права взять (окно игры, рюкзак скрыт, отказ «инвентарь заполнен», контейнер не разрушается); ПКМ - проход сквозь постройки, полупрозрачная. - Стервятник: курица в когтях - слепок с обвисшими крыльями и шеей, без хитбокса. - Зомбособака: нюх - все существа рядом на компасе и карте. - Медведь: толстая шкура (вдвое меньше урона), рёв-провокация, удар лапой по блокам. - Лютоволк: бросок на цель с опрокидыванием, след крови раненых, сумка на 3 ячейки (лут, выгрузка у тела, выпадает мешком при гибели и отзыве; отдельный файл сейва). - Череп (останки): 2% при обычном обыске красного трупа вместо браслета. - Прицел книги не рисуется в одержимости; подсказки вселения на 13 языках. - Замер EntityLeakProbe: удалённые, но живые в памяти сущности - в лог. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
114 lines
4.8 KiB
C#
114 lines
4.8 KiB
C#
using HarmonyLib;
|
|
using UnityEngine;
|
|
|
|
namespace NecromancerTome
|
|
{
|
|
/// <summary>
|
|
/// Одержимый медведь - танк (решено 09.10, BACKLOG «Медведь и лютоволк в одержимости»):
|
|
/// - толстая шкура: пока в нём игрок, урон по нему вдвое меньше (вне одержимости медведь прежний);
|
|
/// - рёв на ПКМ: зомби ближе RoarRange бросают свои цели и идут на медведя, спящие просыпаются -
|
|
/// увести толпу от тела или базы. Та же провокация, что каждую секунду делает одержимость
|
|
/// (Possession.Taunt), только шире, без «видит ли» и на 20 с;
|
|
/// - удар лапой по блоку: ЛКМ по блоку под прицелом бьёт его на SmashDamage (дерево и двери за 2-3
|
|
/// удара, бетон дольше).
|
|
/// </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 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)
|
|
{
|
|
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);
|
|
}
|
|
|
|
/// <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));
|
|
}
|
|
}
|
|
}
|
|
}
|