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