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»). Тело - полупрозрачное, как у торговцев-призраков (alpha 0.3, Standard
/// в режиме Fade, как у крысы при проходе); текстура уже серая (сделана под обесцвечивание). Глаза - свой
/// непрозрачный светящийся материал из бандла, очередь отрисовки 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;
/// Сдвиг вперёд в системе самого духа, м (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;
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;
}
public static GameObject Spawn(Transform _urnModel)
{
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 = true; // скелет свой и неподвижный - границы не пересчитываются, не пропадать с краю экрана
}
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);
}
g.AddComponent();
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;
}
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", 0);
m.DisableKeyword("_ALPHATEST_ON");
m.EnableKeyword("_ALPHABLEND_ON");
m.DisableKeyword("_ALPHAPREMULTIPLY_ON");
m.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent;
SetAlpha(m, Alpha);
_ghostMats[_src] = m;
return m;
}
static void SetAlpha(Material _m, float _a)
{
if (_m != null && _m.HasProperty("_Color"))
{
Color c = _m.color;
c.a = _a;
_m.color = c;
}
}
public static void ApplyAlpha()
{
foreach (Material m in _ghostMats.Values)
{
SetAlpha(m, Alpha);
}
}
}
/// Покачивание и поворот джинна над урной. Только вид - никакой логики.
[Preserve]
public class CinerariumGenieMotion : MonoBehaviour
{
float _yaw;
float _phase;
/// Полёт к месту работы и обратно: точка в координатах модели урны и сколько осталось.
Vector3 _visit;
float _visitLeft;
public const float VisitSeconds = 2.2f;
/// Почернел и замер (готовое класть некуда, решение 06.10).
bool _stuck;
static readonly MaterialPropertyBlock _mpb = new MaterialPropertyBlock();
static readonly int ColorId = Shader.PropertyToID("_Color");
/// _worldUnity - точка в координатах сцены (без Origin): низ блока, где дух работал.
public void Visit(Vector3 _worldUnity)
{
if (transform.parent == null)
{
return;
}
_visit = transform.parent.InverseTransformPoint(_worldUnity);
_visitLeft = VisitSeconds;
}
public void SetStuck(bool _on)
{
if (_stuck == _on)
{
return;
}
_stuck = _on;
foreach (Renderer r in GetComponentsInChildren(true))
{
if (r.name.Contains("eye"))
{
continue;
}
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);
}
else
{
r.SetPropertyBlock(null);
}
}
}
void OnEnable()
{
CinerariumGenie.Live.Add(this);
_phase = Random.value * 10f;
_yaw = Random.value * 360f;
}
void OnDisable()
{
CinerariumGenie.Live.Remove(this);
}
void Update()
{
if (_stuck)
{
return; // замер как был
}
float yawTarget = _yaw + CinerariumGenie.TurnSpeed * Time.deltaTime;
Vector3 home = new Vector3(0f, CinerariumGenie.OffsetY, 0f) + Quaternion.Euler(0f, _yaw, 0f) * new Vector3(0f, 0f, CinerariumGenie.Forward);
if (_visitLeft > 0f)
{
// Туда и обратно по синусу: на полпути - над местом работы, лицом к нему.
_visitLeft -= Time.deltaTime;
float k = Mathf.Sin(Mathf.Clamp01(1f - _visitLeft / VisitSeconds) * Mathf.PI);
Vector3 over = _visit + new Vector3(0f, CinerariumGenie.OffsetY + 0.6f, 0f) + Quaternion.Euler(0f, _yaw, 0f) * new Vector3(0f, 0f, CinerariumGenie.Forward);
transform.localPosition = Vector3.Lerp(home, over, k);
Vector3 d = _visit - home;
if (d.x * d.x + d.z * d.z > 0.01f)
{
yawTarget = Mathf.LerpAngle(_yaw, Mathf.Atan2(d.x, d.z) * Mathf.Rad2Deg, Time.deltaTime * 4f);
}
_yaw = yawTarget % 360f;
transform.localRotation = Quaternion.Euler(0f, _yaw, 0f);
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 opacity";
}
public override void Execute(List _params, CommandSenderInfo _senderInfo)
{
SdtdConsole con = SingletonMonoBehaviour.Instance;
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;
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") + "; spirits on screen: " + CinerariumGenie.Live.Count);
}
}
}