using System.Collections.Generic; using System.Globalization; using UnityEngine; using UnityEngine.Scripting; namespace NecromancerTome { /// /// Зрение змеи (указание 28.09: «всё в более контрастном виде и в красных тонах», «как комикс»). /// Своих фильтров не заводим: у игры в Resources/ScreenEffects есть готовые, которыми она сама не /// пользуется (проверено по коду и XML): /// - "Vibrant" - цветокоррекция Code/ColorCorrection_Favale: _color, contrast, saturation, /// lows/mids/highs, whitepoint, _Gamma. Её материал перенастраиваем под красный и возвращаем /// исходные значения при выходе; /// - "Posterize" - ступени цвета (_steps), плоский «комиксный» вид. /// Что именно значит каждое свойство в шейдере Favale - видно только в игре, поэтому всё крутится /// командой `necrovision` на ходу; найденные значения потом переносятся в Default*. /// public static class SnakeVision { public const string GradeEffect = "Vibrant"; public const string PosterEffect = "Posterize"; /// Первая проверка 28.09 ночью (03:04): «слишком темно, ничего не разглядеть» - /// контраст и ступени загнали всё тёмное в чёрный. Под красное кладём игровое ночное /// зрение (осветляет), контраст обычный, ступеней больше. public const string NightEffect = "NightVision"; /// Проверка 28.09: «фильтр не пропускает другие цвета», «наложи в пол-силы, как бы /// полупрозрачным». Сила эффекта у игры - шейдерное свойство Intensity (ScreenEffect.Render: /// Material.SetFloat("Intensity", Clamp01)), а оно есть только у Vibrant и Bright. У /// NightVision и Posterize его нет - они всегда во всю силу, и ночное зрение делает картинку /// одноцветной. Поэтому по умолчанию они выключены, а оттенок и осветление - в половину. public static float DefaultNight = 0f; /// «Яркость можно и повысить» (28.09): эффект "Bright" - та же цветокоррекция Favale, /// настроенная игрой на осветление, и игрой не используемый. public const string BrightEffect = "Bright"; public static float DefaultBright = 0.5f; public static float DefaultStrength = 0.5f; /// Фиолетово-пурпурный оттенок, а не перекраска всего (указание 28.09: «эффект потустороннего, /// но чтобы был оттенок»): светлый пурпур и насыщенность 0.6 - свои цвета мира остаются. public static Color DefaultColor = new Color(0.8f, 0.55f, 1f, 1f); public static float DefaultContrast = 1f; public static float DefaultSaturation = 0.6f; public static int DefaultSteps = 8; static readonly string[] FloatProps = { "contrast", "saturation", "lows", "mids", "highs", "whitepoint", "_Gamma", "Intensity" }; static bool _on; static bool _posterize = false; static float? _strength; static Material _grade; static readonly Dictionary _savedFloats = new Dictionary(); static Color _savedColor; static readonly Dictionary _tuned = new Dictionary(); static Color? _tunedColor; static int _steps; static float? _night; static float? _bright; public static void On(EntityPlayerLocal _player) { ScreenEffects fx = _player?.ScreenEffectManager; if (fx == null || _on) { return; } _grade = Find(fx, GradeEffect)?.Material; if (_grade == null) { Debug.LogWarning("[NecromancerTome] SnakeVision: эффекта '" + GradeEffect + "' нет"); return; } _savedFloats.Clear(); foreach (string p in FloatProps) { if (_grade.HasProperty(p)) { _savedFloats[p] = _grade.GetFloat(p); } } _savedColor = _grade.GetColor("_color"); _on = true; Apply(fx); Debug.Log("[NecromancerTome] SnakeVision: включено"); } public static void Off(EntityPlayerLocal _player) { if (!_on) { return; } _on = false; ScreenEffects fx = _player?.ScreenEffectManager; fx?.SetScreenEffect(GradeEffect, 0f, 0f); fx?.SetScreenEffect(PosterEffect, 0f, 0f); fx?.SetScreenEffect(NightEffect, 0f, 0f); fx?.SetScreenEffect(BrightEffect, 0f, 0f); if (_grade != null) { foreach (KeyValuePair kv in _savedFloats) { _grade.SetFloat(kv.Key, kv.Value); } _grade.SetColor("_color", _savedColor); } Debug.Log("[NecromancerTome] SnakeVision: выключено"); } static void Apply(ScreenEffects _fx) { _grade.SetColor("_color", _tunedColor ?? DefaultColor); SetF("contrast", DefaultContrast); SetF("saturation", DefaultSaturation); foreach (KeyValuePair kv in _tuned) { _grade.SetFloat(kv.Key, kv.Value); } _fx.SetScreenEffect(GradeEffect, _strength ?? DefaultStrength, 0.3f); Material poster = Find(_fx, PosterEffect)?.Material; if (poster != null) { poster.SetFloat("_steps", _steps > 0 ? _steps : DefaultSteps); } _fx.SetScreenEffect(PosterEffect, _posterize ? 1f : 0f, 0.3f); _fx.SetScreenEffect(NightEffect, _night ?? DefaultNight, 0.3f); _fx.SetScreenEffect(BrightEffect, _bright ?? DefaultBright, 0.3f); } static void SetF(string _p, float _v) { if (_grade.HasProperty(_p)) { _grade.SetFloat(_p, _v); } } static ScreenEffects.ScreenEffect Find(ScreenEffects _fx, string _name) { foreach (ScreenEffects.ScreenEffect e in _fx.loadedEffects) { if (e != null && e.Name == _name) { return e; } } return null; } static string Queue(string _name) { ScreenEffects fx = GameManager.Instance?.World?.GetPrimaryPlayer()?.ScreenEffectManager; ScreenEffects.ScreenEffect e = fx != null ? Find(fx, _name) : null; return e?.Material != null ? e.Material.renderQueue.ToString() : "?"; } public static string Describe() { if (_grade == null) { return "necrovision: not initialised yet (enter snake mode or 'necrovision on')"; } string s = "necrovision: " + (_on ? "ON" : "off") + ", posterize " + (_posterize ? (_steps > 0 ? _steps : DefaultSteps).ToString() : "off") + ", nv " + (_night ?? DefaultNight).ToString("0.##", CultureInfo.InvariantCulture) + ", bright " + (_bright ?? DefaultBright).ToString("0.##", CultureInfo.InvariantCulture) + ", strength " + (_strength ?? DefaultStrength).ToString("0.##", CultureInfo.InvariantCulture) + ", queue grade/poster/nv " + Queue(GradeEffect) + "/" + Queue(PosterEffect) + "/" + Queue(NightEffect) + "\n color " + _grade.GetColor("_color").ToString("F2"); foreach (string p in FloatProps) { if (_grade.HasProperty(p)) { s += "\n " + p + " " + _grade.GetFloat(p).ToString("0.###", CultureInfo.InvariantCulture); } } return s; } /// Консоль: подстройка на ходу. Значения живут до конца сессии. public static string Command(List _params, EntityPlayerLocal _player) { if (_params.Count == 0) { return Describe(); } string cmd = _params[0].ToLowerInvariant(); if (cmd == "on") { On(_player); return Describe(); } if (cmd == "off") { Off(_player); return "necrovision: off"; } if (cmd == "reset") { _tuned.Clear(); _tunedColor = null; _steps = 0; _posterize = false; _night = null; _strength = null; _bright = null; } else if (cmd == "color" && _params.Count >= 4 && float.TryParse(_params[1], NumberStyles.Float, CultureInfo.InvariantCulture, out float r) && float.TryParse(_params[2], NumberStyles.Float, CultureInfo.InvariantCulture, out float g) && float.TryParse(_params[3], NumberStyles.Float, CultureInfo.InvariantCulture, out float b)) { _tunedColor = new Color(r, g, b, 1f); } else if (cmd == "nv" && _params.Count >= 2 && float.TryParse(_params[1], NumberStyles.Float, CultureInfo.InvariantCulture, out float nv)) { _night = nv; } else if (cmd == "strength" && _params.Count >= 2 && float.TryParse(_params[1], NumberStyles.Float, CultureInfo.InvariantCulture, out float st)) { _strength = st; } else if (cmd == "bright" && _params.Count >= 2 && float.TryParse(_params[1], NumberStyles.Float, CultureInfo.InvariantCulture, out float br)) { _bright = br; } else if (cmd == "posterize" && _params.Count >= 2) { if (_params[1] == "off") { _posterize = false; } else if (int.TryParse(_params[1], out int steps)) { _posterize = true; _steps = steps; } } else if (_params.Count >= 2 && float.TryParse(_params[1], NumberStyles.Float, CultureInfo.InvariantCulture, out float v)) { string prop = null; foreach (string p in FloatProps) { if (p.ToLowerInvariant() == cmd || p.TrimStart('_').ToLowerInvariant() == cmd) { prop = p; } } if (prop == null) { return "necrovision: unknown '" + cmd + "'. Try: help necrovision"; } _tuned[prop] = v; } else { return "necrovision: bad arguments. Try: help necrovision"; } if (_on && _player?.ScreenEffectManager != null) { Apply(_player.ScreenEffectManager); } return Describe(); } } [Preserve] public class ConsoleCmdNecroVision : ConsoleCmdAbstract { public override bool IsExecuteOnClient => true; public override bool AllowedInMainMenu => false; public override string[] getCommands() { return new string[] { "necrovision" }; } public override string getDescription() { return "Necromancer's Tome: tune the snake's vision filter live."; } public override string getHelp() { return "necrovision - show current values\n" + "necrovision on | off - switch the filter (snake mode switches it itself)\n" + "necrovision color R G B - tint, 0..1 each (default 0.8 0.55 1)\n" + "necrovision contrast V - also: saturation, lows, mids, highs, whitepoint, gamma, intensity\n" + "necrovision strength V - how strongly the tint lies over the picture, 0..1 (default 0.5)\n" + "necrovision posterize N|off - flat colour steps, always full strength (default off)\n" + "necrovision nv V - game night vision under the red, 0..1 (default 0 = off; full strength only, kills colour)\n" + "necrovision bright V - game 'Bright' grade, 0 = off 0..1 (default 0.5)\n" + "necrovision reset - back to defaults"; } public override void Execute(List _params, CommandSenderInfo _senderInfo) { EntityPlayerLocal player = GameManager.Instance.World?.GetPrimaryPlayer(); SingletonMonoBehaviour.Instance.Output(SnakeVision.Command(_params, player)); } } }