From 49c7afe273b14829ff1319834a71bc8a46fa6bb4 Mon Sep 17 00:00:00 2001 From: AlexCube Date: Fri, 16 Oct 2026 19:49:20 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A3=D1=80=D0=BD=D0=B0:=20=D0=B4=D1=83=D1=85?= =?UTF-8?q?=20-=20=D1=81=D0=B2=D0=B5=D1=82=D1=8F=D1=89=D0=B0=D1=8F=D1=81?= =?UTF-8?q?=D1=8F=20=D0=B4=D1=8B=D0=BC=D0=BA=D0=B0=20(=D1=81=D0=BB=D0=BE?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20One/One)=20=D0=B2=D0=BC=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D0=BE=20Fade=20=D1=81=20=D0=BF=D1=8F=D1=82=D0=BD?= =?UTF-8?q?=D0=B0=D0=BC=D0=B8;=20necrogenie=20glow/tint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- HarmonySrc/CinerariumGenie.cs | 141 +++++++++++++++++++++++++++++----- 1 file changed, 121 insertions(+), 20 deletions(-) diff --git a/HarmonySrc/CinerariumGenie.cs b/HarmonySrc/CinerariumGenie.cs index 83124f7..1648402 100644 --- a/HarmonySrc/CinerariumGenie.cs +++ b/HarmonySrc/CinerariumGenie.cs @@ -14,8 +14,8 @@ namespace NecromancerTome /// (TEFeatureCinerarium.HasSkull) и пропадает без него. Сдвиги Origin и выгрузку чанка переживает с моделью урны. /// /// Модель - бандл necrotechnologist (NecroTechnologistSetup.cs): своя сетка на своём скелете NT_rig в позе покоя, - /// глаза - отдельные рендереры (в имени «eye»). Тело - полупрозрачное, как у торговцев-призраков (alpha 0.3, Standard - /// в режиме Fade, как у крысы при проходе); текстура уже серая (сделана под обесцвечивание). Глаза - свой + /// глаза - отдельные рендереры (в имени «eye»). Тело - светящаяся дымка (16.10: Fade давал пятна; сложение One/One, + /// холодный оттенок Tint, своё слабое свечение Glow); текстура уже серая (сделана под обесцвечивание). Глаза - свой /// непрозрачный светящийся материал из бандла, очередь отрисовки 3100 - после тела. /// public static class CinerariumGenie @@ -105,27 +105,55 @@ namespace NecromancerTome { m.shader = std; } + // 16.10: Fade (SrcAlpha/OneMinusSrcAlpha) давал «прозрачный, но какими-то пятнами» и «не выглядит бесплотным + // духом»: без записи глубины треугольники одной сетки ложатся в случайном порядке. Свечение (One/One) от порядка + // не зависит: дух только добавляет свет к тому, что за ним, - дымка. Альфа здесь не нужна, яркость - цветом. m.SetFloat("_Mode", 2f); m.SetOverrideTag("RenderType", "Transparent"); - m.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); - m.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); + m.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One); + m.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.One); m.SetInt("_ZWrite", 0); m.DisableKeyword("_ALPHATEST_ON"); - m.EnableKeyword("_ALPHABLEND_ON"); + m.DisableKeyword("_ALPHABLEND_ON"); m.DisableKeyword("_ALPHAPREMULTIPLY_ON"); + if (m.HasProperty("_Glossiness")) + { + m.SetFloat("_Glossiness", 0f); // без бликов: блик при сложении - белое пятно + } + if (m.HasProperty("_Metallic")) + { + m.SetFloat("_Metallic", 0f); + } + if (m.HasProperty("_EmissionMap") && m.HasProperty("_MainTex")) + { + m.SetTexture("_EmissionMap", m.GetTexture("_MainTex")); // своё свечение - чтобы ночью не пропадал + m.EnableKeyword("_EMISSION"); + m.globalIlluminationFlags = MaterialGlobalIlluminationFlags.None; + } m.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent; - SetAlpha(m, Alpha); + SetLook(m); _ghostMats[_src] = m; return m; } - static void SetAlpha(Material _m, float _a) + /// Холодный бледный оттенок духа (умножается на серую текстуру). + public static Color Tint = new Color(0.55f, 0.75f, 1f, 1f); + /// Своё свечение тела, доля от Tint. + public static float Glow = 0.35f; + + static void SetLook(Material _m) { - if (_m != null && _m.HasProperty("_Color")) + if (_m == null) { - Color c = _m.color; - c.a = _a; - _m.color = c; + return; + } + if (_m.HasProperty("_Color")) + { + _m.color = new Color(Tint.r * Alpha, Tint.g * Alpha, Tint.b * Alpha, 1f); + } + if (_m.HasProperty("_EmissionColor")) + { + _m.SetColor("_EmissionColor", new Color(Tint.r * Glow, Tint.g * Glow, Tint.b * Glow, 1f)); } } @@ -133,9 +161,58 @@ namespace NecromancerTome { foreach (Material m in _ghostMats.Values) { - SetAlpha(m, Alpha); + SetLook(m); } } + + static readonly Dictionary _stuckMats = new Dictionary(); + + /// Почерневший дух (класть некуда): свечением чёрное не нарисовать, поэтому обычная Fade-копия, тёмная. + public static Material Stuck(Material _src) + { + if (_src == null) + { + return null; + } + if (_stuckMats.TryGetValue(_src, out Material m) && m != null) + { + return m; + } + m = new Material(_src) { name = _src.name + "_stuck" }; + Shader std = Shader.Find("Standard"); + if (std != null && m.shader != std) + { + m.shader = std; + } + m.SetFloat("_Mode", 2f); + m.SetOverrideTag("RenderType", "Transparent"); + m.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); + m.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); + m.SetInt("_ZWrite", 1); // с глубиной: пятен нет, тёмный и почти плотный + m.DisableKeyword("_ALPHATEST_ON"); + m.EnableKeyword("_ALPHABLEND_ON"); + m.DisableKeyword("_ALPHAPREMULTIPLY_ON"); + m.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent; + if (m.HasProperty("_Color")) + { + m.color = new Color(0.04f, 0.035f, 0.045f, 0.8f); + } + _stuckMats[_src] = m; + return m; + } + + /// Исходный материал тела по его светящейся копии. + public static Material Source(Material _ghost) + { + foreach (KeyValuePair p in _ghostMats) + { + if (p.Value == _ghost) + { + return p.Key; + } + } + return _ghost; + } } /// Покачивание и поворот джинна над урной. Только вид - никакой логики. @@ -150,8 +227,8 @@ namespace NecromancerTome public const float VisitSeconds = 2.2f; /// Почернел и замер (готовое класть некуда, решение 06.10). bool _stuck; - static readonly MaterialPropertyBlock _mpb = new MaterialPropertyBlock(); - static readonly int ColorId = Shader.PropertyToID("_Color"); + /// Светящиеся материалы рендереров - вернуть после почернения. + readonly Dictionary _glowing = new Dictionary(); /// _worldUnity - точка в координатах сцены (без Origin): низ блока, где дух работал. public void Visit(Vector3 _worldUnity) @@ -177,15 +254,23 @@ namespace NecromancerTome { continue; } + if (!_glowing.ContainsKey(r)) + { + _glowing[r] = r.sharedMaterials; + } + Material[] glow = _glowing[r]; if (_on) { - r.GetPropertyBlock(_mpb); - _mpb.SetColor(ColorId, new Color(0.04f, 0.035f, 0.045f, Mathf.Max(CinerariumGenie.Alpha, 0.6f))); - r.SetPropertyBlock(_mpb); + Material[] dark = new Material[glow.Length]; + for (int i = 0; i < glow.Length; i++) + { + dark[i] = CinerariumGenie.Stuck(CinerariumGenie.Source(glow[i])); + } + r.sharedMaterials = dark; } else { - r.SetPropertyBlock(null); + r.sharedMaterials = glow; } } } @@ -261,7 +346,9 @@ namespace NecromancerTome "necrogenie fwd - shift along the spirit's own forward (keeps the curled tail inside the neck)\n" + "necrogenie turn - idle turning speed\n" + "necrogenie bob - bobbing height and period\n" + - "necrogenie alpha <0-1> - body opacity"; + "necrogenie alpha <0-1> - body brightness (the body adds light, it is not alpha-blended)\n" + + "necrogenie glow <0-1> - body's own glow (visible at night)\n" + + "necrogenie tint - body colour"; } public override void Execute(List _params, CommandSenderInfo _senderInfo) @@ -291,6 +378,19 @@ namespace NecromancerTome CinerariumGenie.Alpha = Mathf.Clamp01(v); CinerariumGenie.ApplyAlpha(); break; + case "glow": + CinerariumGenie.Glow = Mathf.Clamp01(v); + CinerariumGenie.ApplyAlpha(); + break; + case "tint": + if (_params.Count >= 4 && + float.TryParse(_params[2], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float g) && + float.TryParse(_params[3], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float b)) + { + CinerariumGenie.Tint = new Color(v, g, b, 1f); + CinerariumGenie.ApplyAlpha(); + } + break; default: con.Output("necrogenie: unknown '" + _params[0] + "' - see 'help necrogenie'"); return; @@ -298,7 +398,8 @@ namespace NecromancerTome } con.Output("necrogenie: y " + CinerariumGenie.OffsetY.ToString("F2") + ", fwd " + CinerariumGenie.Forward.ToString("F2") + ", turn " + CinerariumGenie.TurnSpeed.ToString("F1") + ", bob " + CinerariumGenie.BobHeight.ToString("F2") + " / " + CinerariumGenie.BobPeriod.ToString("F1") + " s, alpha " + - CinerariumGenie.Alpha.ToString("F2") + "; spirits on screen: " + CinerariumGenie.Live.Count); + CinerariumGenie.Alpha.ToString("F2") + ", glow " + CinerariumGenie.Glow.ToString("F2") + ", tint " + + CinerariumGenie.Tint.r.ToString("F2") + " " + CinerariumGenie.Tint.g.ToString("F2") + " " + CinerariumGenie.Tint.b.ToString("F2") + "; spirits on screen: " + CinerariumGenie.Live.Count); } } }