using UnityEngine;
namespace NecromancerTome
{
///
/// СВЕТЯЩИЕСЯ ГЛАЗА у призванных зомбоживотных - указание 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.
///
public static class PetEyeGlow
{
/// Фиолетовый, НАСЫЩЕННЫЙ. Первая версия была светлой (0.72, 0.55, 1.0) и в игре
/// дала ровно то, о чём сказал пользователь: "у собаки глаза просто белые".
///
/// Причина арифметическая: _EmissionColor читается как HDR, цвет умножается на яркость.
/// При множителе 6 светлый фиолетовый превращался в (4.3, 3.3, 6.0) - все три канала
/// далеко за единицей, и тонемаппинг сводил их в белое пятно. Оттенок выживает только
/// когда каналы РАСХОДЯТСЯ: отсюда низкий зелёный и умеренная яркость ниже.
public static readonly Color EyeColour = new Color(0.55f, 0.18f, 1f);
/// ПОДНЯТО С 2.4 ДО 6 после первой проверки: "у собаки они вообще никак не
/// светятся". Цвет сменился (это видно), а свечения нет - значит либо _EmissionColor у
/// этого шейдера читается как HDR и 2.4 для него ничто, либо свечение вообще идёт не
/// отсюда. Шесть - это попытка проверить первое предположение, а чтобы проверить второе,
/// рядом добавлен разовый дамп всех материалов питомца (DumpMaterialsOnce).
/// 1.6, а не 6: см. комментарий к EyeColour - на шести глаза выгорали в белый.
/// Итоговое свечение (0.88, 0.29, 1.6): синий вдвое выше красного и вшестеро выше
/// зелёного, так что фиолетовый читается, а не тонет в пересвете.
public const float EmissionIntensity = 1.6f;
/// Классы, по которым дамп уже печатался: он нужен один раз на вид, а не на
/// каждого призванного.
public static readonly System.Collections.Generic.HashSet Dumped =
new System.Collections.Generic.HashSet();
/// ДИАГНОСТИКА, добавлена 2026-09-18. Первая попытка зажечь глаза дала половину
/// результата: у Пса цвет сменился, но свечения нет, а у Волка не изменилось ничего -
/// значит материала с "eye" в имени текстуры у него просто не нашлось. Гадать дальше
/// дороже, чем один раз посмотреть: этот дамп печатает по каждому виду все рендереры, их
/// материалы, шейдеры, имена текстур и наличие свойств свечения. По нему и будет видно,
/// за что хвататься - и надо ли вообще.
public static void DumpMaterialsOnce(Entity _pet, string _className)
{
if (_className == null || !Dumped.Add(_className))
{
return;
}
Renderer[] renderers = _pet.GetComponentsInChildren(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"));
}
}
}
/// ПЕРЕПИСАНО 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 свой собственный, так что правка цвета и
/// свечения на нём затрагивает только глаз, хотя текстура и общая с телом.
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;
}
/// Запасной признак: текстура с "eye" в имени (у собаки это feral_eye).
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");
}
/// true, только если глаза действительно нашлись и были подожжены. Пока false,
/// тик повторяет попытку - модель может быть ещё не собрана.
public static bool TryLightEyes(Entity _pet)
{
if (_pet == null)
{
return false;
}
Renderer[] renderers = _pet.GetComponentsInChildren(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;
}
}
}