Files
necromants-tome-7d2d-3-2/HarmonySrc/SnakeVision.cs
T
AlexCubeandClaude Opus 5.5 6ff76d174e Режим змеи для Ножа некроманта и чёрный разрыв
Ночью по R с ножом - дух змеи: тело падает (-95% HP), игрок управляет
призрачной змеёй от первого лица. Свободный ход по миру (свой наблюдатель
чанков, отрисовка, звук, спавн и спящие считаются от змеи), прыжок,
атака на ЛКМ - рывок сквозь препятствия и чёрный разрыв, ПКМ - выбраться
из текстур. Тёмное чутьё, тревога у тела, отсчёт в радиации края карты,
пурпурный фильтр зрения (necrovision). Утром змея развеивается.

Чёрный разрыв (NecroBurst.cs): клон частиц confetti в чёрном, команда
necroburst. Проверено в игре 28.09.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 15:31:37 +03:00

306 lines
12 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using System.Globalization;
using UnityEngine;
using UnityEngine.Scripting;
namespace NecromancerTome
{
/// <summary>
/// Зрение змеи (указание 28.09: «всё в более контрастном виде и в красных тонах», «как комикс»).
/// Своих фильтров не заводим: у игры в Resources/ScreenEffects есть готовые, которыми она сама не
/// пользуется (проверено по коду и XML):
/// - "Vibrant" - цветокоррекция Code/ColorCorrection_Favale: _color, contrast, saturation,
/// lows/mids/highs, whitepoint, _Gamma. Её материал перенастраиваем под красный и возвращаем
/// исходные значения при выходе;
/// - "Posterize" - ступени цвета (_steps), плоский «комиксный» вид.
/// Что именно значит каждое свойство в шейдере Favale - видно только в игре, поэтому всё крутится
/// командой `necrovision` на ходу; найденные значения потом переносятся в Default*.
/// </summary>
public static class SnakeVision
{
public const string GradeEffect = "Vibrant";
public const string PosterEffect = "Posterize";
/// <summary>Первая проверка 28.09 ночью (03:04): «слишком темно, ничего не разглядеть» -
/// контраст и ступени загнали всё тёмное в чёрный. Под красное кладём игровое ночное
/// зрение (осветляет), контраст обычный, ступеней больше.</summary>
public const string NightEffect = "NightVision";
/// Проверка 28.09: «фильтр не пропускает другие цвета», «наложи в пол-силы, как бы
/// полупрозрачным». Сила эффекта у игры - шейдерное свойство Intensity (ScreenEffect.Render:
/// Material.SetFloat("Intensity", Clamp01)), а оно есть только у Vibrant и Bright. У
/// NightVision и Posterize его нет - они всегда во всю силу, и ночное зрение делает картинку
/// одноцветной. Поэтому по умолчанию они выключены, а оттенок и осветление - в половину.
public static float DefaultNight = 0f;
/// <summary>«Яркость можно и повысить» (28.09): эффект "Bright" - та же цветокоррекция Favale,
/// настроенная игрой на осветление, и игрой не используемый.</summary>
public const string BrightEffect = "Bright";
public static float DefaultBright = 0.5f;
public static float DefaultStrength = 0.5f;
/// <summary>Фиолетово-пурпурный оттенок, а не перекраска всего (указание 28.09: «эффект потустороннего,
/// но чтобы был оттенок»): светлый пурпур и насыщенность 0.6 - свои цвета мира остаются.</summary>
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<string, float> _savedFloats = new Dictionary<string, float>();
static Color _savedColor;
static readonly Dictionary<string, float> _tuned = new Dictionary<string, float>();
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<string, float> 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<string, float> 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;
}
/// <summary>Консоль: подстройка на ходу. Значения живут до конца сессии.</summary>
public static string Command(List<string> _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<string> _params, CommandSenderInfo _senderInfo)
{
EntityPlayerLocal player = GameManager.Instance.World?.GetPrimaryPlayer();
SingletonMonoBehaviour<SdtdConsole>.Instance.Output(SnakeVision.Command(_params, player));
}
}
}