using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Scripting;
namespace NecromancerTome
{
///
/// Дух-работник урны - «джинн из урны» (решение 16.10): кончик хвоста уходит в горлышко, дух вырастает над урной,
/// без работы висит, чуть покачиваясь и медленно поворачиваясь. Этап 3 некротехнолога.
///
/// Не сущность игры, а видимая модель - дочерний объект модели блока урны, как каркас зоны (решение 16.10 вместо
/// плана 15.10 «сущность на префабе медсестры»): хитбокса, ИИ и сохранения у неё нет, поэтому «убить нельзя»,
/// «зомби не замечают», «сквозь стены» выходят сами, а сейвы дух не трогает - он появляется по черепу в урне
/// (TEFeatureCinerarium.HasSkull) и пропадает без него. Сдвиги Origin и выгрузку чанка переживает с моделью урны.
///
/// Модель - бандл necrotechnologist (NecroTechnologistSetup.cs): своя сетка на своём скелете NT_rig в позе покоя,
/// глаза - отдельные рендереры (в имени «eye»). Тело - светящаяся дымка (16.10: Fade давал пятна; сложение One/One,
/// холодный оттенок Tint, своё слабое свечение Glow); текстура уже серая (сделана под обесцвечивание). Глаза - свой
/// непрозрачный светящийся материал из бандла, очередь отрисовки 3100 - после тела.
///
public static class CinerariumGenie
{
public const string ModelPath = "#@modfolder(NecromancerTome):Resources/necrotechnologist?necroTechnologistPrefab.prefab";
/// Высота корня модели над низом блока урны, м. 16.10 в Blender по модели урны: -0.15 («духа чуть приподними»).
/// Подгоняется командой necrogenie.
public static float OffsetY = -0.15f;
/// У урны рабочего горлышко ниже (земля в чаше на 0.45 м против 0.53 у урны сборщика) - дух ниже на столько же.
public static float WorkerDrop = -0.08f;
/// Сдвиг вперёд в системе самого духа, м (16.10: «чуть вперёд, чтобы хвост не просвечивал сквозь урну сзади»
/// - загнутый назад кончик хвоста; в Blender 0.06 - ни одной вершины за стенкой, взято 0.07). Поворачивается вместе
/// с духом, поэтому хвост всегда в горлышке. Знак проверить в игре: necrogenie fwd.
public static float Forward = 0.07f;
/// Поворот без работы, градусов в секунду.
public static float TurnSpeed = 6f;
/// Покачивание: размах (м) и период (с).
public static float BobHeight = 0.04f;
public static float BobPeriod = 4f;
public static float Alpha = 0.3f;
/// Засыпание (16.10): игрок дальше SleepDistance - дух замирает дома, огоньки нитей стоят. Работа идёт.
public static float SleepDistance = 40f;
public static bool Hidden;
/// Далеко ли игрок от точки сцены (без Origin). Для засыпания духа и нитей.
public static bool PlayerFar(Vector3 _scenePos)
{
EntityPlayerLocal pl = GameManager.Instance?.World?.GetPrimaryPlayer();
if (pl == null)
{
return true;
}
return (pl.GetPosition() - Origin.position - _scenePos).sqrMagnitude > SleepDistance * SleepDistance;
}
/// Руки в жесте: плечо от вертикали вбок, локоть согнут вперёд, градусы. Покой - «смирно» (18.10: «Всё Смирно и
/// будет позой для бездельничающих некротехов») запечён в позу покоя модели (pose_attention.json при выгрузке FBX),
/// аниматора нет - без жеста кости не трогаются.
public static float ArmOut = 3f;
public static float Elbow = 0f;
static GameObject _prefab;
static bool _tried;
static readonly Dictionary _ghostMats = new Dictionary();
/// Все джинны на сцене - команда necrogenie правит их сразу.
public static readonly List Live = new List();
static bool Load()
{
if (!_tried)
{
_tried = true;
_prefab = DataLoader.LoadAsset(ModelPath);
Debug.Log("[NecromancerTome] CinerariumGenie: модель " + (_prefab != null ? "загружена" : "НЕ загружена: " + ModelPath));
}
return _prefab != null;
}
/// Дух логиста (18.10: «Пусть дух вылетает когда нужно что-то нести. Но уменьшим его в три раза»): размер.
public static float LogistScale = 1f / 3f;
/// Дом духа логиста в осях модели урны - корень модели духа внутри кадила, середина тела на высоте окон крышки
/// (по центру - окна на 0.63 м, у стены - 0.73 м, кадило в 0.22 м от стены, стена - -z).
public static Vector3 LogistHome = new Vector3(0f, 0.25f, 0f);
public static Vector3 LogistWallHome = new Vector3(0f, 0.35f, -0.22f);
/// Окна крышки - на расстоянии LogistExit от оси кадила (радиус крышки 0.21 м), по осям модели.
public static float LogistExit = 0.3f;
/// Лутер: сдвиг духа вперёд (подобран в Blender 18.10).
public static float LootForward = 0.08f;
/// _logist: 0 - обычный дух над горлышком, 1 - дух логиста в кадиле по центру, 2 - в кадиле у стены.
public static GameObject Spawn(Transform _urnModel, float _drop, int _logist = 0)
{
if (_urnModel == null || !Load())
{
return null;
}
GameObject g = Object.Instantiate(_prefab, _urnModel, false);
g.name = "NecroCinerariumGenie";
foreach (Renderer r in g.GetComponentsInChildren(true))
{
r.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
r.receiveShadows = false;
if (r is SkinnedMeshRenderer smr)
{
smr.updateWhenOffscreen = false; // 16.10: вне экрана скелет не считать (экономия); границы - от сетки
}
if (r.name.Contains("eye"))
{
continue;
}
Material[] mats = r.sharedMaterials;
for (int i = 0; i < mats.Length; i++)
{
mats[i] = Ghost(mats[i]);
}
r.sharedMaterials = mats;
}
foreach (Collider c in g.GetComponentsInChildren(true))
{
Object.Destroy(c);
}
CinerariumGenieMotion m = g.AddComponent();
m.Drop = _drop;
if (_logist > 0)
{
m.Small = true;
m.Wall = _logist == 2;
g.transform.localScale = Vector3.one * LogistScale;
}
m.Pose();
return g;
}
/// Полупрозрачная копия материала тела - одна на исходный материал на всю игру.
static Material Ghost(Material _src)
{
if (_src == null)
{
return null;
}
if (_ghostMats.TryGetValue(_src, out Material m) && m != null)
{
return m;
}
m = new Material(_src) { name = _src.name + "_ghost" };
Shader std = Shader.Find("Standard");
if (std != null && m.shader != std)
{
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.One);
m.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.One);
m.SetInt("_ZWrite", 0);
m.DisableKeyword("_ALPHATEST_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;
}
SetLook(m); // смешивание, глубина и очередь - тоже там (Solid)
_ghostMats[_src] = m;
return m;
}
/// Холодный бледный оттенок духа (умножается на серую текстуру).
public static Color Tint = new Color(0.55f, 0.75f, 1f, 1f);
/// Своё свечение тела, доля от Tint.
public static float Glow = 0.35f;
/// ВРЕМЕННО (17.10: «Временно убери им прозрачность. А то плохо видно» - пока подбирается поза рук): дух
/// непрозрачный, обычная заливка с записью глубины. Перед релизом вернуть false (BACKLOG). Консоль: necrogenie solid 0|1.
public static bool Solid = false; // 18.10: прозрачность вернули («Прозрачность верни сразу»)
static void SetLook(Material _m)
{
if (_m == null)
{
return;
}
_m.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
_m.SetInt("_DstBlend", (int)(Solid ? UnityEngine.Rendering.BlendMode.Zero : UnityEngine.Rendering.BlendMode.One));
_m.SetInt("_ZWrite", Solid ? 1 : 0);
_m.SetOverrideTag("RenderType", Solid ? "Opaque" : "Transparent");
_m.renderQueue = (int)(Solid ? UnityEngine.Rendering.RenderQueue.Geometry : UnityEngine.Rendering.RenderQueue.Transparent);
if (Solid)
{
if (_m.HasProperty("_Color"))
{
_m.color = new Color(Tint.r, Tint.g, Tint.b, 1f);
}
if (_m.HasProperty("_EmissionColor"))
{
_m.SetColor("_EmissionColor", new Color(Tint.r * Glow * 0.5f, Tint.g * Glow * 0.5f, Tint.b * Glow * 0.5f, 1f));
}
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));
}
}
public static void ApplyAlpha()
{
foreach (Material m in _ghostMats.Values)
{
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;
}
}
/// Покачивание и поворот джинна над урной. Только вид - никакой логики.
[Preserve]
public partial class CinerariumGenieMotion : MonoBehaviour
{
float _yaw;
float _phase;
/// Своя поправка высоты урны (CinerariumGenie.WorkerDrop у рабочего).
public float Drop;
/// Свой сдвиг вперёд (NaN - общий CinerariumGenie.Forward). Лутер (18.10, банка с крышкой-кадилом): 0.08 - хвост
/// по центру отверстия на макушке («Вот сейчас было идеально»).
public float ForwardOverride = float.NaN;
/// Полёт к месту работы и обратно (16.10: «возвращается задом», «делал действие не сразу, а хоть руками
/// пошевелив»): туда - лицом к месту, над местом - жест руками (дело делается в его середине, см. Visit),
/// обратно - развернувшись лицом к урне. Точка - в координатах модели урны; _t - время с начала полёта, -1 - дома.
struct Stop
{
public Vector3 Hover, Look; // в осях модели урны
public float Fly;
public bool Pass; // точка пути (окно кадила) - без жеста
}
/// Дух логиста (18.10): втрое меньше, дома (в кадиле) не виден, вылетает и влетает через окно крышки.
public bool Small;
/// Кадило у стены: дом сдвинут к кронштейну, окно в стену (-z) не используется.
public bool Wall;
readonly List _stops = new List();
float _backFly;
float _t = -1f;
/// Скорость полёта, м/с: время перелёта - не меньше OutSeconds/BackSeconds, дальше - по расстоянию (нити до 15 м).
public static float FlySpeed = 4f;
public static float OutSeconds = 1.3f;
public static float WorkSeconds = 1.6f;
public static float BackSeconds = 1.3f;
/// Жест: плечи вперёд до GestureFwd градусов, локти ходят на ±GestureWave вразнобой.
public static float GestureFwd = 55f;
public static float GestureWave = 30f;
bool _gesturing;
/// Почернел и замер (готовое класть некуда, решение 06.10).
bool _stuck;
/// Кости рук (плечо, предплечье, кисть по сторонам) и их поворот в позе покоя.
Transform[] _arm;
Quaternion[] _armRest;
static bool _armLogged;
/// Покой - руки в позе модели («смирно»).
public void Pose()
{
Pose(0f, CinerariumGenie.Elbow, CinerariumGenie.Elbow);
}
/// Жест долей _w от покоя: плечо вперёд на GestureFwd, направление плеча и предплечья задаётся в осях модели
/// (вперёд - +Z), поворот - от текущего направления кости к нужному, так что оси самих костей значения не имеют.
void Pose(float _w, float _elbowL, float _elbowR)
{
if (_arm == null)
{
string[] names = { "LeftArm", "LeftForeArm", "LeftHand", "RightArm", "RightForeArm", "RightHand" };
_arm = new Transform[names.Length];
_armRest = new Quaternion[names.Length];
foreach (Transform t in GetComponentsInChildren(true))
{
for (int i = 0; i < names.Length; i++)
{
if (_arm[i] == null && t.name.EndsWith(names[i]))
{
_arm[i] = t;
_armRest[i] = t.localRotation;
}
}
}
if (!_armLogged)
{
_armLogged = true;
System.Text.StringBuilder sb = new System.Text.StringBuilder();
for (int i = 0; i < names.Length; i++)
{
sb.Append(names[i]).Append('=').Append(_arm[i] != null ? "ok" : "НЕТ").Append(' ');
}
Debug.Log("[NecromancerTome] CinerariumGenie: кости рук " + sb);
}
}
for (int i = 0; i < _arm.Length; i++)
{
if (_arm[i] != null)
{
_arm[i].localRotation = _armRest[i];
}
}
if (_w <= 0f)
{
return;
}
for (int side = 0; side < 2; side++)
{
Transform up = _arm[side * 3], fore = _arm[side * 3 + 1], hand = _arm[side * 3 + 2];
if (up == null || fore == null || hand == null)
{
continue;
}
float sign = Mathf.Sign(transform.InverseTransformPoint(up.position).x);
float o = CinerariumGenie.ArmOut * Mathf.Deg2Rad, f = GestureFwd * Mathf.Deg2Rad;
Vector3 upDir = new Vector3(sign * Mathf.Sin(o), -Mathf.Cos(o) * Mathf.Cos(f), Mathf.Sin(f)).normalized;
Vector3 foreDir = Vector3.RotateTowards(upDir, Vector3.forward, (side == 0 ? _elbowL : _elbowR) * Mathf.Deg2Rad, 0f);
up.rotation = Quaternion.Slerp(Quaternion.identity, Quaternion.FromToRotation(fore.position - up.position, transform.TransformDirection(upDir)), _w) * up.rotation;
fore.rotation = Quaternion.Slerp(Quaternion.identity, Quaternion.FromToRotation(hand.position - fore.position, transform.TransformDirection(foreDir)), _w) * fore.rotation;
}
}
/// Светящиеся материалы рендереров - вернуть после почернения.
readonly Dictionary _glowing = new Dictionary();
/// Облёт мест работы по порядку (у перевозки по нити - ящик «откуда», потом «куда»), у каждого - жест, затем
/// назад к урне. Точки - в координатах сцены (без Origin): stand - где встать (низ клетки), look - низ клетки места,
/// beside - встал сбоку, а не над местом. Возвращает, через сколько секунд дело (середина последнего жеста).
public float Visit(List<(Vector3 stand, Vector3 look, bool beside)> _list)
{
if (transform.parent == null || _list == null || _list.Count == 0)
{
return 0f;
}
_stops.Clear();
Vector3 home = Home();
Vector3 prev = home;
float delay = 0f;
Vector3 exit = home;
if (Small)
{
// Сначала - к окну крышки в сторону первого места (у стены окно в стену не годится), оттуда - к месту.
exit = Window(home, transform.parent.InverseTransformPoint(_list[0].stand) - home);
_stops.Add(new Stop { Hover = exit, Look = exit + (exit - home), Fly = 0.35f, Pass = true });
delay += 0.35f;
prev = exit;
}
foreach ((Vector3 stand, Vector3 look, bool beside) in _list)
{
// Высота корня духа над низом клетки места: обычный дух - над станцией 0.8 (сбоку 0.15) с поправкой урны;
// маленький - над верхом клетки (1.05) или у её середины сбоку (0.4).
float up = Small ? (beside ? 0.4f : 1.05f) : CinerariumGenie.OffsetY + Drop + (beside ? 0.15f : 0.8f);
Stop st = new Stop
{
Hover = transform.parent.InverseTransformPoint(stand) + new Vector3(0f, up, 0f),
Look = transform.parent.InverseTransformPoint(look)
};
st.Fly = Mathf.Max(OutSeconds, Vector3.Distance(prev, st.Hover) / FlySpeed);
delay += st.Fly + WorkSeconds;
prev = st.Hover;
_stops.Add(st);
}
if (Small)
{
// Назад - тем же окном.
_stops.Add(new Stop { Hover = exit, Look = home, Fly = Mathf.Max(BackSeconds, Vector3.Distance(prev, exit) / FlySpeed), Pass = true });
prev = exit;
}
_backFly = Small ? 0.35f : Mathf.Max(BackSeconds, Vector3.Distance(prev, home) / FlySpeed);
_t = 0f;
return delay - WorkSeconds * 0.5f;
}
/// Окно крышки кадила по направлению _dir (оси модели): одно из четырёх по осям x/z, у стены - не в стену.
Vector3 Window(Vector3 _home, Vector3 _dir)
{
Vector3 best = Vector3.forward;
float bestDot = float.MinValue;
foreach (Vector3 w in new[] { Vector3.forward, Vector3.back, Vector3.left, Vector3.right })
{
if (Wall && w == Vector3.back)
{
continue;
}
float d = Vector3.Dot(w, new Vector3(_dir.x, 0f, _dir.z));
if (d > bestDot)
{
bestDot = d;
best = w;
}
}
return _home + best * CinerariumGenie.LogistExit;
}
Vector3 Home()
{
if (Small)
{
return Wall ? CinerariumGenie.LogistWallHome : CinerariumGenie.LogistHome;
}
float fwd = float.IsNaN(ForwardOverride) ? CinerariumGenie.Forward : ForwardOverride;
return new Vector3(0f, CinerariumGenie.OffsetY + Drop, 0f) + Quaternion.Euler(0f, _yaw, 0f) * new Vector3(0f, 0f, fwd);
}
/// Повернуться лицом по направлению _dir (горизонтально), не быстрее 300°/с.
void Face(Vector3 _dir)
{
if (_dir.x * _dir.x + _dir.z * _dir.z > 0.01f)
{
_yaw = Mathf.MoveTowardsAngle(_yaw, Mathf.Atan2(_dir.x, _dir.z) * Mathf.Rad2Deg, 300f * Time.deltaTime) % 360f;
}
}
void Gesture(float _w)
{
if (_w > 0f)
{
float wave = Mathf.Sin(_t * Mathf.PI * 2f * 1.5f) * GestureWave;
Pose(_w, CinerariumGenie.Elbow + 15f + wave, CinerariumGenie.Elbow + 15f - wave);
_gesturing = true;
}
else if (_gesturing)
{
Pose();
_gesturing = false;
}
}
public void SetStuck(bool _on)
{
if (_stuck == _on)
{
return;
}
_stuck = _on;
foreach (Renderer r in GetComponentsInChildren(true))
{
if (r.name.Contains("eye"))
{
continue;
}
if (!_glowing.ContainsKey(r))
{
_glowing[r] = r.sharedMaterials;
}
Material[] glow = _glowing[r];
if (_on)
{
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.sharedMaterials = glow;
}
}
}
void OnEnable()
{
CinerariumGenie.Live.Add(this);
_phase = Random.value * 10f;
_yaw = Random.value * 360f;
}
void OnDisable()
{
CinerariumGenie.Live.Remove(this);
}
float _nextSleepCheck;
bool _asleep;
bool _hiddenApplied;
void Update()
{
if (Time.time >= _nextSleepCheck)
{
_nextSleepCheck = Time.time + 0.5f;
// Уснул - игрок далеко или дух не среди ближайших (CinerariumLoad.AnimateNearest): замирает дома, работа идёт.
bool asleep = CinerariumGenie.PlayerFar(transform.position) || !CinerariumLoad.Animated(this);
if (asleep && !_asleep && !Digging) // старатель под землёй - не возвращать домой
{
_t = -1f; // полёт прерван - дело всё равно выполнится
Gesture(0f);
transform.localPosition = Home();
}
_asleep = asleep;
}
// Виден ли: не скрыт командой; дух логиста - только в полёте (дома сидит в кадиле). Рендереры - только при смене
// (16.10, проверка на утечки: раньше список рендереров собирался каждые 0.5 с - мусор для GC).
bool show = !CinerariumGenie.Hidden && (!Small || _t >= 0f) && !DigHidden;
if (_hiddenApplied != !show)
{
_hiddenApplied = !show;
foreach (Renderer r in GetComponentsInChildren(true))
{
r.enabled = show;
}
}
if (DigUpdate(_asleep))
{
return; // старатель: спуск, под землёй, подъём (CinerariumProspector.cs)
}
if (_stuck || _asleep)
{
return; // замер как был
}
float yawTarget = _yaw + CinerariumGenie.TurnSpeed * Time.deltaTime;
Vector3 home = Home();
if (_t >= 0f && _stops.Count > 0)
{
_t += Time.deltaTime;
float acc = 0f;
Vector3 from = home;
foreach (Stop st in _stops)
{
if (_t < acc + st.Fly)
{
transform.localPosition = Vector3.Lerp(from, st.Hover, Mathf.SmoothStep(0f, 1f, (_t - acc) / st.Fly));
Face(st.Hover - from);
Gesture(0f);
transform.localRotation = Quaternion.Euler(0f, _yaw, 0f);
return;
}
acc += st.Fly;
if (!st.Pass && _t < acc + WorkSeconds)
{
transform.localPosition = st.Hover;
Face(st.Look - st.Hover); // за работой - лицом к месту
Gesture(Mathf.Sin((_t - acc) / WorkSeconds * Mathf.PI));
transform.localRotation = Quaternion.Euler(0f, _yaw, 0f);
return;
}
if (!st.Pass)
{
acc += WorkSeconds;
}
from = st.Hover;
}
if (_t < acc + _backFly)
{
transform.localPosition = Vector3.Lerp(from, home, Mathf.SmoothStep(0f, 1f, (_t - acc) / _backFly));
Face(home - from); // назад - лицом к урне
Gesture(0f);
transform.localRotation = Quaternion.Euler(0f, _yaw, 0f);
return;
}
_t = -1f;
Gesture(0f);
}
if (Small)
{
transform.localPosition = home; // в кадиле, не виден
return;
}
_yaw = yawTarget % 360f;
float bob = CinerariumGenie.BobPeriod > 0.01f
? Mathf.Sin((Time.time + _phase) * Mathf.PI * 2f / CinerariumGenie.BobPeriod) * CinerariumGenie.BobHeight
: 0f;
transform.localPosition = home + new Vector3(0f, bob, 0f);
transform.localRotation = Quaternion.Euler(0f, _yaw, 0f);
}
}
/// Подгонка джинна в игре: высота над урной, скорость поворота, покачивание, прозрачность. Не сохраняется -
/// найденные значения переносятся в константы CinerariumGenie.
[Preserve]
public class ConsoleCmdNecroGenie : ConsoleCmdAbstract
{
public override bool IsExecuteOnClient => true;
public override bool AllowedInMainMenu => false;
public override string[] getCommands()
{
return new string[] { "necrogenie" };
}
public override string getDescription()
{
return "Necromancer's Tome: tune the cinerarium spirit (height over the urn, turning, bobbing, transparency).";
}
public override string getHelp()
{
return "necrogenie - show the current values and how many spirits are on screen\n" +
"necrogenie y - model root height above the urn block's bottom (tail tip is 0.47 m above the root)\n" +
"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 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\n" +
"necrogenie arms [elbow] - gesture arms, degrees: upper arm from vertical sideways, elbow bend (rest pose 'attention' is the model's own)\n" +
"necrogenie solid <0|1> - TEMPORARY: opaque body (1) to see the pose, or the ghostly glow (0)\n" +
"necrogenie sleep - spirits and threads freeze when the player is farther (work goes on)\n" +
"necrogenie hide|show - hide all spirits (weak PCs, video), work goes on";
}
public override void Execute(List _params, CommandSenderInfo _senderInfo)
{
SdtdConsole con = SingletonMonoBehaviour.Instance;
if (_params.Count == 1 && (_params[0] == "hide" || _params[0] == "show"))
{
CinerariumGenie.Hidden = _params[0] == "hide";
}
if (_params.Count >= 2 && float.TryParse(_params[1], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float v))
{
switch (_params[0].ToLowerInvariant())
{
case "y":
CinerariumGenie.OffsetY = v;
break;
case "fwd":
CinerariumGenie.Forward = v;
break;
case "turn":
CinerariumGenie.TurnSpeed = v;
break;
case "bob":
CinerariumGenie.BobHeight = v;
if (_params.Count >= 3 && float.TryParse(_params[2], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float per))
{
CinerariumGenie.BobPeriod = per;
}
break;
case "alpha":
CinerariumGenie.Alpha = Mathf.Clamp01(v);
CinerariumGenie.ApplyAlpha();
break;
case "glow":
CinerariumGenie.Glow = Mathf.Clamp01(v);
CinerariumGenie.ApplyAlpha();
break;
case "sleep":
CinerariumGenie.SleepDistance = Mathf.Max(5f, v);
break;
case "arms":
CinerariumGenie.ArmOut = v;
if (_params.Count >= 3 && float.TryParse(_params[2], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float ae))
{
CinerariumGenie.Elbow = ae;
}
break;
case "solid":
CinerariumGenie.Solid = v != 0f;
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;
}
}
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") + ", glow " + CinerariumGenie.Glow.ToString("F2") + ", tint " +
CinerariumGenie.Tint.r.ToString("F2") + " " + CinerariumGenie.Tint.g.ToString("F2") + " " + CinerariumGenie.Tint.b.ToString("F2") + ", arms " + CinerariumGenie.ArmOut.ToString("F0") + " " +
CinerariumGenie.Elbow.ToString("F0") + ", sleep " + CinerariumGenie.SleepDistance.ToString("F0") + " m" + (CinerariumGenie.Hidden ? ", HIDDEN" : "") + "; spirits on screen: " + CinerariumGenie.Live.Count);
}
}
}