Files
necromants-tome-7d2d-3-2/HarmonySrc/PetCommandPatch.cs
T
AlexCubeandClaude Opus 5 d97f83e972 ИИ питомцев переписан, добавлен Дух крысы, пороги разнесены по шкале
Работа по указаниям 2026-09-18. В релиз пока не выходит.

НОВЫЙ ПИТОМЕЦ "Дух крысы" (necroRatSpirit), Некромантия 3 (60 убийств):
модель зомбоволка в масштабе 0.28, урон 5, кроличьи звуки. Сам не нападает
никогда, держится справа-сзади в двух блоках, упёршись в препятствие проходит
СКВОЗЬ него и блоков не ломает. Укус замедляет, оставляет метку духа (видна на
компасе, +25% получаемого урона) и рвёт жилу. Обгрызая труп, лечится.

СВОЯ ЗАДАЧА ИИ. Задачи "иди за сущностью" в игре нет вовсе - проверены все 32
типа EAI*. Написана NecroFollowOwnerTask по образцу EAIApproachSpot: сектор
"3-6 часов" от хозяина, FindPath с canBreak:false, проход сквозь препятствие
через Entity.IsStuck. Вешается в рантайме, минуя Type.GetType.

ПРИКАЗ АТАКОВАТЬ. Повторное применение свитка при живом питомце натравливает
его на цель под прицелом: EntityPlayerLocal.HitInfo + ItemActionAttack.
GetEntityFromHit. Торговцы и игроки отсеяны. Нет цели - "Нет цели для атаки".

ЗОМБОЖИВОТНЫЕ ПРИВЕДЕНЫ К ТОМУ ЖЕ ОБРАЗЦУ. Сняты BreakBlock, Territorial,
ApproachSpot, Wander, BlockingTargetTask и поедание трупов; цель они больше не
выбирают сами. Лестница урона 20/35/45/60 плюс расчленение у Пса, Медведя и
Волка. Кровотечение всем, метка и ослабление - только у крысы.

УБИЙСТВА ПИТОМЦЕМ ЗАСЧИТЫВАЮТСЯ ВЛАДЕЛЬЦУ, включая добивание кровотечением.
Префикс на AwardKillXPServer подменяет убийцу владельцем; для смерти от баффа
заведена память укусов, потому что в DamageSource от баффа нет того, кто его
наложил. Зомби под Камнем духов это не задело - решение от 17.09 в силе.

ГРИФ откачен на летающую ветку EntityVulture и переименован в Могильного
стервятника: модель наконец соответствует имени. Держится у игрока сам, через
собственный механизм "дома" (setHomeArea), на время погони дом отвязывается.
Попытка натянуть птичий префаб на наземный класс провалилась и записана - так
делать нельзя.

ПРОЧЕЕ: призрачный вид распространён с торговцев на питомцев (у Пса, Медведя и
Волка выключен по указанию), у Пса светятся фиолетовые глаза, белая иконка
книги снята со всех свитков призыва, "Жуки Властелина" переименованы в "Рой
фараона" на всех 13 языках.

ПОРОГИ: крыса 60, стервятник 500, пёс 1300, медведь 2000, волк 4000.

ИСПРАВЛЕНО ПО ХОДУ ИГРОВЫХ ПРОВЕРОК: питомцы не призывались обычным кликом
(AnimWait требовал удержания), отзыв срабатывал не с первого раза (автомат
состояний ItemActionSpawnEntity), крыса проваливалась сквозь мир (IsStuck
отключает и пол), питомец подбрасывал хозяина (коллайдеры разводились до
появления модели).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 20:51:42 +03:00

93 lines
5.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 UnityEngine;
namespace NecromancerTome
{
/// <summary>
/// КОМАНДА "АТАКУЙ ТО, НА ЧТО Я СМОТРЮ" - второй режим свитка Духа крысы, указание
/// 2026-09-18: "у свитка призыва есть два режима (призыв и отзыв). Если крыса уже призвана,
/// то призыв становится атакой. И крыса берёт целью атаки того, на кого указал прицел
/// игрока. Если в прицеле нету целей для атаки, то пишется надпись "Нет цели для атаки"".
///
/// Это не Harmony-заплатка, а helper: точка входа одна и та же для всех питомцев -
/// префикс на ItemActionSpawnEntity.Spawn в SummonPatch.cs. Второй патч на тот же метод
/// только запутал бы порядок исполнения.
///
/// ОТКУДА БЕРЁТСЯ ЦЕЛЬ ПОД ПРИЦЕЛОМ. EntityPlayerLocal.HitInfo - публичное поле типа
/// WorldRayHitInfo, которое PlayerMoveController перезаписывает КАЖДЫЙ КАДР результатом луча
/// из прицела (Voxel.Raycast по GetLookRay, длина Utils.FastMax(cDigAndBuildDistance,
/// cCollectItemDistance, 30f), то есть не меньше 30 метров). Ничего своего лучить не надо -
/// игра уже посчитала это для интерфейса. Из попадания сущность достаётся собственным
/// публичным методом игры ItemActionAttack.GetEntityFromHit(hitInfo), который внутри зовёт
/// GameUtils.GetHitRootEntity(tag, transform) - то же самое, чем пользуется обычный удар
/// оружием, а значит и попадание по конечности/голове разрешится в саму сущность.
///
/// Луч упирается в блоки - и это правильно: натравить крысу сквозь стену на то, чего игрок
/// не видит, нельзя.
///
/// КОГО НЕЛЬЗЯ НАЗНАЧИТЬ ЦЕЛЬЮ (указание "пусть атакует всех существ кроме NPC торговцев"):
/// - EntityTrader - прямо по указанию;
/// - EntityPlayer - сам хозяин и вообще игроки: крыса не оружие против людей;
/// - собственный хозяин и она сама - на всякий случай, отдельной проверкой;
/// - мёртвые.
/// Отсев сделан ЗДЕСЬ, а не в entityclasses.xml, потому что targetClasses у
/// EAIApproachAndAttackTarget умеет только РАЗРЕШАТЬ тип, запретить им нельзя. В XML у крысы
/// стоит class=EntityAlive - это разрешение задаче подхватить любую цель, которую ей дали;
/// решение "кого дать" целиком принимается тут.
/// </summary>
public static class PetAttackCommand
{
/// <summary>Сколько тиков держится приказ. attackTargetTime уменьшается на 1 за тик ИИ и
/// на нуле сбрасывает цель (EntityAlive: "if (attackTargetTime > 0) { attackTargetTime--;
/// ... }"), так что это по сути "пока не убьёт". 6000 - заведомо больше любой драки;
/// смысл не в таймере, а в том, чтобы приказ не висел вечно, если цель уйдёт из мира.</summary>
public const int OrderTicks = 6000;
/// <summary>Отдаёт питомцу приказ атаковать то, на что смотрит игрок. false - приказа не
/// вышло, и вызывающая сторона показывает "Нет цели для атаки".</summary>
public static bool TryOrderAttack(EntityAlive _owner, EntityAlive _pet)
{
if (_owner == null || _pet == null || _pet.IsDead())
{
return false;
}
EntityPlayerLocal local = _owner as EntityPlayerLocal;
if (local == null)
{
// Не локальный игрок - HitInfo просто неоткуда взять (у мода нет сетевого слоя
// нигде, см. SummonPatch.cs). Молча отказываем, как будто цели нет.
Debug.Log("[NecromancerTome] PetAttackCommand: owner is not the local player, no crosshair to read");
return false;
}
WorldRayHitInfo hit = local.HitInfo;
if (hit == null || !hit.bHitValid || hit.transform == null)
{
return false;
}
Entity hitEntity = ItemActionAttack.GetEntityFromHit(hit);
EntityAlive target = hitEntity as EntityAlive;
if (target == null || target.IsDead())
{
return false;
}
if (target == _pet || target == _owner)
{
return false;
}
if (target is EntityPlayer || target is EntityTrader)
{
return false;
}
// Месть сбрасывается вместе с приказом: иначе задача SetAsTargetIfHurt могла бы на
// следующем тике перебить наш приказ тем, кто ударил крысу последним.
_pet.SetRevengeTarget(null);
_pet.SetAttackTarget(target, OrderTicks);
Debug.Log("[NecromancerTome] PetAttackCommand: pet " + _pet.entityId + " ordered to attack " +
target.entityId + " (" + target.EntityClass.entityClassName + ")");
return true;
}
}
}