diff --git a/HarmonySrc/OssuaryGlow.cs b/HarmonySrc/OssuaryGlow.cs index c7b8e2f..38607be 100644 --- a/HarmonySrc/OssuaryGlow.cs +++ b/HarmonySrc/OssuaryGlow.cs @@ -17,6 +17,8 @@ namespace NecromancerTome public static bool Enabled = true; /// Дикий дух-огонёк (#69): летает, пока оссуарий работает. Выключатель - necroossuary wisp off. public static bool WispEnabled = true; + /// necroossuary wisp log - писать в лог вылет и влёт (чтобы успеть снять). + public static bool WispLog; public const string WispPath = "#@modfolder(NecromancerTome):Resources/necrowisp?necroWispPrefab.prefab"; static GameObject _wispPrefab; static bool _wispTried; @@ -196,7 +198,10 @@ namespace NecromancerTome /// Окошки крышки (модель #67): шесть, между швами, через 60°; по высоте - на WindowUp от низа крышки, /// от оси - на WindowR (доли полуширины крышки; в модели 0.14 м и 0.37 м при полуширине 0.418). const float WindowUp = 0.14f / 0.836f, WindowR = 0.37f / 0.418f; - const float Out = 0.9f; // насколько дальше от оси котла дух уходит в землю, м + /// Подъём и спуск - вертикально снаружи, на Clear дальше самого широкого места котла (22.10: «Огонёк летит сквозь + /// котёл. А надо чтобы поднимался почти на уровень окошка, и по дуге летел прямо через его центр»). + const float Clear = 0.25f; + const float BelowWindow = 0.1f; // вертикаль кончается на столько ниже окошка, м; дальше дуга const float Under = 0.35f; // насколько под землю, м const float FlyTime = 2.4f; static readonly int TintId = Shader.PropertyToID("_TintColor"); @@ -208,7 +213,7 @@ namespace NecromancerTome TrailRenderer _trail; int _phase = -1; // -1 дома, 0 вылет и уход в землю, 1 под землёй, 2 подъём и влёт float _t0, _until; - Vector3 _winOut, _winIn, _inOut, _inIn, _down, _up; + Vector3[] _pathOut, _pathIn; // вылет: нутро -> окошко -> дуга -> вниз в землю; влёт - обратный порядок с другой стороны static float Ease(float _k) => _k * _k * (3f - 2f * _k); @@ -239,12 +244,16 @@ namespace NecromancerTome { return; } - Show(_inOut); + Show(_pathOut[0]); + if (OssuaryGlow.WispLog) + { + Debug.Log("[NecromancerTome] OssuaryWisp: вылет " + _pathOut[1]); + } Next(0, FlyTime); return; case 0: // из окошка наружу и вниз, в землю; тает - Fly(_inOut, _winOut, _down, k); - Alpha(1f - Ease(Mathf.InverseLerp(0.25f, 1f, k))); + Fly(_pathOut, k); + Alpha(1f - Ease(Mathf.InverseLerp(0.45f, 1f, k))); if (k >= 1f) { Hide(); @@ -255,13 +264,17 @@ namespace NecromancerTome if (now >= _until) { Alpha(0f); - Show(_up); + Show(_pathIn[0]); + if (OssuaryGlow.WispLog) + { + Debug.Log("[NecromancerTome] OssuaryWisp: влёт " + _pathIn[_pathIn.Length - 2]); + } Next(2, FlyTime); } return; case 2: // с другой стороны снизу вверх, проявляется, влетает в противоположное окошко - Fly(_up, _winIn, _inIn, k); - Alpha(Ease(Mathf.InverseLerp(0f, 0.75f, k))); + Fly(_pathIn, k); + Alpha(Ease(Mathf.InverseLerp(0f, 0.55f, k))); if (k >= 1f) { Hide(); @@ -279,41 +292,77 @@ namespace NecromancerTome _until = _t0 + _dur; } - /// Квадратичная кривая _a -> _b -> _c: проходит окошко _b по касательной. - void Fly(Vector3 _a, Vector3 _b, Vector3 _c, float _k) + /// Гладкая кривая (Catmull-Rom) точно через точки пути; скорость по пути ровная (время - по длинам отрезков). + void Fly(Vector3[] _path, float _k) { - float k = Ease(_k); - Vector3 p = (1f - k) * (1f - k) * _a + 2f * k * (1f - k) * _b + k * k * _c; - p.y += 0.02f * Mathf.Sin(Time.time * 7f); // лёгкое дрожание огонька + float total = 0f; + for (int i = 1; i < _path.Length; i++) + { + total += Vector3.Distance(_path[i - 1], _path[i]); + } + float s = Ease(_k) * total; + int seg = 0; + float len = 0f; + for (; seg < _path.Length - 2; seg++) + { + len = Vector3.Distance(_path[seg], _path[seg + 1]); + if (s <= len) + { + break; + } + s -= len; + } + len = Vector3.Distance(_path[seg], _path[seg + 1]); + float u = len > 0.0001f ? Mathf.Clamp01(s / len) : 1f; + Vector3 p0 = _path[Mathf.Max(0, seg - 1)], p1 = _path[seg], p2 = _path[seg + 1], p3 = _path[Mathf.Min(_path.Length - 1, seg + 2)]; + Vector3 p = 0.5f * (2f * p1 + (p2 - p0) * u + (2f * p0 - 5f * p1 + 4f * p2 - p3) * u * u + (3f * p1 - p0 - 3f * p2 + p3) * u * u * u); + p.y += 0.015f * Mathf.Sin(Time.time * 7f); // лёгкое дрожание огонька _go.transform.position = p; } - /// Окошко вылета - случайное из шести, влёта - напротив; точки в земле - снаружи от них, высота земли - лучом. + /// Окошко вылета - случайное из шести, влёта - напротив. Путь: нутро, центр окошка, точка дуги снаружи на высоте + /// окошка, вертикаль снаружи котла чуть ниже окошка, та же вертикаль под землёй (высота земли - лучом). bool Places(Transform _model) { Transform lid = _model.Find("lid"); + Transform body = _model.Find("body"); Renderer lr = lid != null ? lid.GetComponent() : null; + Renderer br = body != null ? body.GetComponent() : null; if (lr == null) { return false; } Bounds b = lr.bounds; - Vector3 axis = new Vector3(b.center.x, b.min.y + WindowUp * (b.size.x + b.size.z) * 0.5f, b.center.z); float half = (b.size.x + b.size.z) * 0.25f; + float bodyHalf = br != null ? (br.bounds.size.x + br.bounds.size.z) * 0.25f : half * 1.1f; + float winY = b.min.y + WindowUp * half * 2f; + Vector3 axis = new Vector3(b.center.x, winY, b.center.z); int w = Random.Range(0, 6); Vector3 dOut = lid.rotation * Quaternion.Euler(0f, w * 60f, 0f) * Vector3.right; dOut.y = 0f; dOut.Normalize(); - Vector3 dIn = -dOut; - _winOut = axis + dOut * (half * WindowR); - _winIn = axis + dIn * (half * WindowR); - _inOut = axis + dOut * (half * WindowR * 0.4f); - _inIn = axis + dIn * (half * WindowR * 0.4f); - _down = Ground(axis + dOut * (half + Out)) + Vector3.down * Under; - _up = Ground(axis + dIn * (half + Out)) + Vector3.down * Under; + _pathOut = Path(axis, dOut, half, Mathf.Max(bodyHalf, half) + Clear); + _pathIn = Path(axis, -dOut, half, Mathf.Max(bodyHalf, half) + Clear); + System.Array.Reverse(_pathIn); return true; } + Vector3[] Path(Vector3 _axis, Vector3 _dir, float _half, float _rOut) + { + float rWin = _half * WindowR; + Vector3 low = _axis + _dir * _rOut + Vector3.down * BelowWindow; + Vector3 under = Ground(_axis + _dir * _rOut) + Vector3.down * Under; + return new[] + { + _axis + _dir * (rWin * 0.45f), // внутри котла + _axis + _dir * rWin, // центр окошка + _axis + _dir * (rWin + (_rOut - rWin) * 0.55f), // дуга: снаружи на высоте окошка + low, // вертикаль снаружи, чуть ниже окошка + new Vector3(low.x, Mathf.Lerp(low.y, under.y, 0.5f), low.z), + under // в земле + }; + } + Vector3 Ground(Vector3 _p) { if (Physics.Raycast(_p + Vector3.up * 1.5f, Vector3.down, out RaycastHit hit, 8f, SnakeControl.WallMask, QueryTriggerInteraction.Ignore)) @@ -547,6 +596,12 @@ namespace NecromancerTome con.Output("necroossuary: пульсация " + OssuaryGlow.GlowPulse + ", скорость " + OssuaryGlow.GlowPulseSpeed); break; case "wisp": + if (_params.Count > 1 && _params[1] == "log") + { + OssuaryGlow.WispLog = !OssuaryGlow.WispLog; + con.Output("necroossuary: лог огонька " + (OssuaryGlow.WispLog ? "включён" : "выключен")); + break; + } if (_params.Count > 1) { OssuaryGlow.WispEnabled = _params[1] == "on"; diff --git a/NecromancerHarmony.dll b/NecromancerHarmony.dll index e344fd4..26cb5cc 100644 Binary files a/NecromancerHarmony.dll and b/NecromancerHarmony.dll differ diff --git a/NecromancerHarmony.pdb b/NecromancerHarmony.pdb index 9be68c6..dbdee98 100644 Binary files a/NecromancerHarmony.pdb and b/NecromancerHarmony.pdb differ