ИИ питомцев переписан, добавлен Дух крысы, пороги разнесены по шкале
Работа по указаниям 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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
15c59dd37a
commit
d97f83e972
@@ -0,0 +1,92 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user