ИИ питомцев переписан, добавлен Дух крысы, пороги разнесены по шкале

Работа по указаниям 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:
AlexCube
2026-09-18 20:51:42 +03:00
co-authored by Claude Opus 5
parent 15c59dd37a
commit d97f83e972
21 changed files with 2501 additions and 179 deletions
+235
View File
@@ -0,0 +1,235 @@
using UnityEngine;
namespace NecromancerTome
{
/// <summary>
/// СВЕТЯЩИЕСЯ ГЛАЗА у призванных зомбоживотных - указание 2026-09-18: "псу и волку сделай,
/// глаза светлофиолетовые светящиеся".
///
/// ПОЧЕМУ ТОЛЬКО ДВОЕ, И ЭТО НЕ ВЫБОР, А ОГРАНИЧЕНИЕ МОДЕЛЕЙ. Покрасить глаза отдельно можно
/// ровно тогда, когда они лежат ОТДЕЛЬНЫМ материалом. Проба шейдеров, которую печатает
/// GhostTraderPatch, показала по питомцам такую картину:
///
/// necroZombieDog - 5 рендереров: FeralEye, NormalEye, LOD0..LOD2 - ГОДИТСЯ
/// necroZombieWolf - 2 рендерера: DireWolf + DireWolfHair - НЕ ГОДИТСЯ
/// necroZombieBear - 1 рендерер - НЕ ГОДИТСЯ
/// necroZombieGriffin - 1 рендерер - НЕ ГОДИТСЯ
///
/// ПО ВОЛКУ ОТВЕТ ОКОНЧАТЕЛЬНЫЙ И ОТРИЦАТЕЛЬНЫЙ, по дампу 18.09:
///
/// DireWolf[0] shader='Game/Animal/Fur' texture=dire_wolf_df _Color=False
/// DireWolfHair[0] shader='Game/SDCS/Hair' texture=Afro_normal _Color=False
///
/// Глазного рендерера у него НЕТ ВООБЩЕ - только шкура и шерсть, глаза запечены прямо в
/// dire_wolf_df. Ни отдельного материала, ни даже _Color, чтобы хоть что-то подкрасить.
/// Единственный оставшийся путь - править саму текстуру по пикселям, зная координаты глаз на
/// развёртке. Поэтому LitEyes у Волка снят: пусть флаг не обещает того, чего нет.
///
/// У Медведя и Грифа материал один на всю модель, и отдельно глаза там не достать ничем,
/// кроме правки самой текстуры по пикселям. Поэтому их в списке нет - см. PetInfo.LitEyes.
///
/// КРАСИТСЯ СВЕЧЕНИЕ, А НЕ ЦВЕТ. У зомби глаза светятся сами (_EmissionMap у этого материала
/// уже назначена), и именно свечение читается как "горящие глаза". Поэтому ставится
/// _EmissionColor, а _Color правится следом только чтобы радужка не спорила с ним при ярком
/// дневном свете. Ключевое слово _EMISSION включается явно: материал, собранный без эмиссии,
/// игнорирует _EmissionColor, пока оно выключено, - ровно тот же класс ловушки, что с
/// _SrcBlend/_Mode у прозрачности (см. ApplyTransparency в GhostTraderPatch.cs).
///
/// ПОЧЕМУ ЭТО ТИК, А НЕ ХУК НА ПРИЗЫВ. Материалы появляются вместе с моделью, а она
/// собирается ПОЗЖЕ создания сущности - это уже дважды стоило нам ошибок: сначала
/// GhostTraderPatch пришлось перевести на опрос из-за трейдеров, потом
/// TryIgnoreCollisionWithOwner из-за того, что коллайдеров в момент CreateEntity ещё нет.
/// Здесь та же природа, поэтому сразу попытка с повтором: раз в секунду, до первого успеха,
/// из общего тика в PetFollowPatch.
/// </summary>
public static class PetEyeGlow
{
/// <summary>Фиолетовый, НАСЫЩЕННЫЙ. Первая версия была светлой (0.72, 0.55, 1.0) и в игре
/// дала ровно то, о чём сказал пользователь: "у собаки глаза просто белые".
///
/// Причина арифметическая: _EmissionColor читается как HDR, цвет умножается на яркость.
/// При множителе 6 светлый фиолетовый превращался в (4.3, 3.3, 6.0) - все три канала
/// далеко за единицей, и тонемаппинг сводил их в белое пятно. Оттенок выживает только
/// когда каналы РАСХОДЯТСЯ: отсюда низкий зелёный и умеренная яркость ниже.</summary>
public static readonly Color EyeColour = new Color(0.55f, 0.18f, 1f);
/// <summary>ПОДНЯТО С 2.4 ДО 6 после первой проверки: "у собаки они вообще никак не
/// светятся". Цвет сменился (это видно), а свечения нет - значит либо _EmissionColor у
/// этого шейдера читается как HDR и 2.4 для него ничто, либо свечение вообще идёт не
/// отсюда. Шесть - это попытка проверить первое предположение, а чтобы проверить второе,
/// рядом добавлен разовый дамп всех материалов питомца (DumpMaterialsOnce).</summary>
/// <summary>1.6, а не 6: см. комментарий к EyeColour - на шести глаза выгорали в белый.
/// Итоговое свечение (0.88, 0.29, 1.6): синий вдвое выше красного и вшестеро выше
/// зелёного, так что фиолетовый читается, а не тонет в пересвете.</summary>
public const float EmissionIntensity = 1.6f;
/// <summary>Классы, по которым дамп уже печатался: он нужен один раз на вид, а не на
/// каждого призванного.</summary>
public static readonly System.Collections.Generic.HashSet<string> Dumped =
new System.Collections.Generic.HashSet<string>();
/// <summary>ДИАГНОСТИКА, добавлена 2026-09-18. Первая попытка зажечь глаза дала половину
/// результата: у Пса цвет сменился, но свечения нет, а у Волка не изменилось ничего -
/// значит материала с "eye" в имени текстуры у него просто не нашлось. Гадать дальше
/// дороже, чем один раз посмотреть: этот дамп печатает по каждому виду все рендереры, их
/// материалы, шейдеры, имена текстур и наличие свойств свечения. По нему и будет видно,
/// за что хвататься - и надо ли вообще.</summary>
public static void DumpMaterialsOnce(Entity _pet, string _className)
{
if (_className == null || !Dumped.Add(_className))
{
return;
}
Renderer[] renderers = _pet.GetComponentsInChildren<Renderer>(true);
Debug.Log("[NecromancerTome] PetEyeGlow: ДАМП материалов " + _className + " - " +
(renderers != null ? renderers.Length : 0) + " рендерер(ов)");
if (renderers == null)
{
return;
}
foreach (Renderer renderer in renderers)
{
if (renderer == null)
{
continue;
}
Material[] mats = renderer.sharedMaterials;
for (int i = 0; mats != null && i < mats.Length; i++)
{
Material m = mats[i];
if (m == null)
{
continue;
}
string albedo = GhostTraderPatch.FindAlbedoProperty(m);
Texture tex = albedo != null ? m.GetTexture(albedo) : null;
Debug.Log("[NecromancerTome] PetEyeGlow: " + renderer.name + "[" + i + "] shader='" +
(m.shader != null ? m.shader.name : "null") + "' albedo=" + (albedo ?? "-") +
" texture=" + (tex != null ? tex.name : "-") +
" _EmissionColor=" + m.HasProperty("_EmissionColor") +
" _EmissionMap=" + (m.HasProperty("_EmissionMap") && m.GetTexture("_EmissionMap") != null) +
" _EmissionIntensity=" + m.HasProperty("_EmissionIntensity") +
" _Color=" + m.HasProperty("_Color"));
}
}
}
/// <summary>ПЕРЕПИСАНО 2026-09-18 ПО ДАМПУ. Первая версия искала материал по имени
/// ТЕКСТУРЫ ("eye"), нашла у собаки ровно один и зажгла его - а глаза всё равно не
/// светились. Дамп показал, почему:
///
/// FeralEye[0] shader='Game/Autodesk' texture=feral_eye _EmissionMap=True
/// NormalEye[0] shader='Standard' texture=HD_ZombieDog _EmissionMap=False
/// LOD0..LOD2 shader='Standard' texture=HD_ZombieDog
///
/// Глаз у собаки ДВА РАЗНЫХ, отдельными рендерерами. Светящийся feral - тот, что горит у
/// разъярённой собаки, и в обычном состоянии он не показывается; именно его мы и зажгли,
/// не увидев ничего. А обычный глаз - NormalEye - берёт текстуру ТУШКИ (HD_ZombieDog),
/// поэтому поиск по имени текстуры его пропускал.
///
/// Поэтому теперь глаз опознаётся по имени РЕНДЕРЕРА, а имя текстуры оставлено как
/// запасной признак. Материал у NormalEye свой собственный, так что правка цвета и
/// свечения на нём затрагивает только глаз, хотя текстура и общая с телом.</summary>
public static bool IsEyeRenderer(Renderer _renderer)
{
if (_renderer == null || _renderer is ParticleSystemRenderer)
{
return false;
}
if (_renderer.name != null && _renderer.name.ToLower().Contains("eye"))
{
return true;
}
Material[] shared = _renderer.sharedMaterials;
if (shared == null)
{
return false;
}
foreach (Material m in shared)
{
if (IsEyeMaterial(m))
{
return true;
}
}
return false;
}
/// <summary>Запасной признак: текстура с "eye" в имени (у собаки это feral_eye).</summary>
public static bool IsEyeMaterial(Material _material)
{
if (_material == null)
{
return false;
}
string albedo = GhostTraderPatch.FindAlbedoProperty(_material);
if (albedo == null)
{
return false;
}
Texture texture = _material.GetTexture(albedo);
return texture != null && texture.name != null &&
texture.name.ToLower().Contains("eye");
}
/// <summary>true, только если глаза действительно нашлись и были подожжены. Пока false,
/// тик повторяет попытку - модель может быть ещё не собрана.</summary>
public static bool TryLightEyes(Entity _pet)
{
if (_pet == null)
{
return false;
}
Renderer[] renderers = _pet.GetComponentsInChildren<Renderer>(true);
if (renderers == null || renderers.Length == 0)
{
return false;
}
int lit = 0;
foreach (Renderer renderer in renderers)
{
if (!IsEyeRenderer(renderer))
{
continue;
}
// Рендерер целиком глазной, поэтому красим ВСЕ его материалы: мешу глаза больше
// ничего не принадлежит, а вот у собаки их два (обычный и feral), и попасть надо
// в оба - какой из них покажется, решает сама игра.
Material[] instances = renderer.materials;
foreach (Material eye in instances)
{
if (eye == null)
{
continue;
}
if (eye.HasProperty("_EmissionColor"))
{
eye.EnableKeyword("_EMISSION");
eye.globalIlluminationFlags = MaterialGlobalIlluminationFlags.RealtimeEmissive;
eye.SetColor("_EmissionColor", EyeColour * EmissionIntensity);
if (eye.HasProperty("_EmissionIntensity"))
{
eye.SetFloat("_EmissionIntensity", EmissionIntensity);
}
lit++;
}
if (eye.HasProperty("_Color"))
{
Color tint = eye.GetColor("_Color");
eye.SetColor("_Color", new Color(EyeColour.r, EyeColour.g, EyeColour.b, tint.a));
}
}
Debug.Log("[NecromancerTome] PetEyeGlow: рендерер глаз '" + renderer.name + "', материалов " +
instances.Length);
}
if (lit > 0)
{
Debug.Log("[NecromancerTome] PetEyeGlow: pet " + _pet.entityId + " - " + lit +
" eye material(s) lit");
}
return lit > 0;
}
}
}