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"; /// Высота корня модели над низом блока урны, м (кончик хвоста в модели - на 0.45 м). Подгоняется командой necrogenie. public static float OffsetY = -0.25f; /// Поворот без работы, градусов в секунду. 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; void OnEnable() { CinerariumGenie.Live.Add(this); _phase = Random.value * 10f; _yaw = Random.value * 360f; } void OnDisable() { CinerariumGenie.Live.Remove(this); } void Update() { _yaw = (_yaw + CinerariumGenie.TurnSpeed * Time.deltaTime) % 360f; float bob = CinerariumGenie.BobPeriod > 0.01f ? Mathf.Sin((Time.time + _phase) * Mathf.PI * 2f / CinerariumGenie.BobPeriod) * CinerariumGenie.BobHeight : 0f; transform.localPosition = new Vector3(0f, CinerariumGenie.OffsetY + 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.45 m above the root)\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 "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") + ", 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); } } }