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; } } }