Оссуарий: три образца - столбец хранилища под каждым; одна ячейка топлива; огонёк в окошки; трассер (#66, #69)

- 22.10: «Пусть можно будет положить 3 разных образца, и тогда в ячейки под
  ними пойдут такие же ресурсы»: выходы os1/os4 - образец 1, os2/os5 - 2,
  os3/os6 - 3. Описание на 13 языках поправлено.
- Лишние ячейки топлива снова показывались: сетка топлива каждый кадр
  выставляет видимость по высоте - прятать в Update. Видна одна.
- Огонёк: окошки на 30°, 90°... (на 0°, 60°... - полосы металла; 22.10
  «Светлячок заходит в полосы металла, и игнорирует окошки»).
- necroossuary wisp trace - линии путей огонька; necroossuary open - окно
  как по E.
Проверено в test8: окно (3 образца, 1 топливо, хранилище 2x3), уголь во
втором образце -> стак угля в 5-й ячейке; трассер - путь через центр окошка.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlexCube
2026-10-22 23:41:25 +03:00
co-authored by Claude Opus 5.5
parent ead6823c8a
commit be7c203abe
6 changed files with 159 additions and 28 deletions
+101 -8
View File
@@ -19,6 +19,9 @@ namespace NecromancerTome
public static bool WispEnabled = true;
/// <summary>necroossuary wisp log - писать в лог вылет и влёт (чтобы успеть снять).</summary>
public static bool WispLog;
/// <summary>necroossuary wisp trace - трассер: линии обоих путей огонька (вылет и влёт) у каждого котла (22.10: «А не проще
/// для отладки нарисовать трассер, по которому будет лететь огонёк?»).</summary>
public static bool WispTrace;
public const string WispPath = "#@modfolder(NecromancerTome):Resources/necrowisp?necroWispPrefab.prefab";
static GameObject _wispPrefab;
static bool _wispTried;
@@ -195,9 +198,12 @@ namespace NecromancerTome
public class OssuaryWisp
{
public const float WispViewRange = 60f;
/// <summary>Окошки крышки (модель #67): шесть, между швами, через 60°; по высоте - на WindowUp от низа крышки,
/// от оси - на WindowR (доли полуширины крышки; в модели 0.14 м и 0.37 м при полуширине 0.418).</summary>
const float WindowUp = 0.14f / 0.836f, WindowR = 0.37f / 0.418f;
/// <summary>Окошки крышки (модель #67): шесть, через 60°, со сдвигом WindowAngle от оси X модели (рамки окошек в сетке -
/// на 30°, 90°...; на 0°, 60°... - металлические полосы: 22.10 «Светлячок заходит в полосы металла, и игнорирует окошки»).
/// По высоте - на WindowUp от низа крышки, от оси - на WindowR (доли размеров крышки; в модели окошко 0.46-0.56 м
/// при низе крышки 0.382, радиус 0.37 при полуширине 0.418).</summary>
const float WindowAngle = 30f;
const float WindowUp = 0.13f / 0.836f, WindowR = 0.37f / 0.418f;
/// <summary>Подъём и спуск - вертикально снаружи, на Clear дальше самого широкого места котла (22.10: «Огонёк летит сквозь
/// котёл. А надо чтобы поднимался почти на уровень окошка, и по дуге летел прямо через его центр»).</summary>
const float Clear = 0.25f;
@@ -213,6 +219,7 @@ namespace NecromancerTome
TrailRenderer _trail;
int _phase = -1; // -1 дома, 0 вылет и уход в землю, 1 под землёй, 2 подъём и влёт
float _t0, _until;
LineRenderer _traceOut, _traceIn;
Vector3[] _pathOut, _pathIn; // вылет: нутро -> окошко -> дуга -> вниз в землю; влёт - обратный порядок с другой стороны
static float Ease(float _k) => _k * _k * (3f - 2f * _k);
@@ -236,6 +243,14 @@ namespace NecromancerTome
{
return;
}
if (!OssuaryGlow.WispTrace && _traceOut != null && _traceOut.enabled)
{
_traceOut.enabled = _traceIn.enabled = false;
}
else if (OssuaryGlow.WispTrace && _pathOut == null)
{
Places(_model);
}
float k = Mathf.Clamp01((now - _t0) / Mathf.Max(0.01f, _until - _t0));
switch (_phase)
{
@@ -294,13 +309,21 @@ namespace NecromancerTome
/// <summary>Гладкая кривая (Catmull-Rom) точно через точки пути; скорость по пути ровная (время - по длинам отрезков).</summary>
void Fly(Vector3[] _path, float _k)
{
Vector3 p = At(_path, Ease(_k));
p.y += 0.015f * Mathf.Sin(Time.time * 7f); // лёгкое дрожание огонька
_go.transform.position = p;
}
/// <summary>Точка пути на доле длины _s (0..1).</summary>
static Vector3 At(Vector3[] _path, float _s01)
{
float total = 0f;
for (int i = 1; i < _path.Length; i++)
{
total += Vector3.Distance(_path[i - 1], _path[i]);
}
float s = Ease(_k) * total;
float s = _s01 * total;
int seg = 0;
float len = 0f;
for (; seg < _path.Length - 2; seg++)
@@ -315,9 +338,7 @@ namespace NecromancerTome
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;
return 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);
}
/// <summary>Окошко вылета - случайное из шести, влёта - напротив. Путь: нутро, центр окошка, точка дуги снаружи на высоте
@@ -338,12 +359,13 @@ namespace NecromancerTome
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;
Vector3 dOut = lid.rotation * Quaternion.Euler(0f, WindowAngle + w * 60f, 0f) * Vector3.right;
dOut.y = 0f;
dOut.Normalize();
_pathOut = Path(axis, dOut, half, Mathf.Max(bodyHalf, half) + Clear);
_pathIn = Path(axis, -dOut, half, Mathf.Max(bodyHalf, half) + Clear);
System.Array.Reverse(_pathIn);
Trace(_model);
return true;
}
@@ -363,6 +385,49 @@ namespace NecromancerTome
};
}
/// <summary>Трассер: путь вылета и путь влёта линиями, точками той же кривой, по которой летит огонёк.</summary>
void Trace(Transform _model)
{
if (!OssuaryGlow.WispTrace || _trail == null)
{
return;
}
if (_traceOut == null)
{
_traceOut = Line(_model, "NecroWispTraceOut", new Color(1f, 0.6f, 0.1f));
_traceIn = Line(_model, "NecroWispTraceIn", new Color(0.2f, 0.8f, 1f));
}
Fill(_traceOut, _pathOut);
Fill(_traceIn, _pathIn);
}
LineRenderer Line(Transform _model, string _name, Color _c)
{
var g = new GameObject(_name);
g.transform.SetParent(_model, false);
LineRenderer l = g.AddComponent<LineRenderer>();
l.sharedMaterial = _trail.sharedMaterial;
l.useWorldSpace = true;
l.widthMultiplier = 0.015f;
l.startColor = l.endColor = _c;
l.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
l.receiveShadows = false;
return l;
}
void Fill(LineRenderer _l, Vector3[] _path)
{
const int n = 60;
var pts = new Vector3[n + 1];
for (int i = 0; i <= n; i++)
{
pts[i] = At(_path, (float)i / n);
}
_l.positionCount = pts.Length;
_l.SetPositions(pts);
_l.enabled = true;
}
Vector3 Ground(Vector3 _p)
{
if (Physics.Raycast(_p + Vector3.up * 1.5f, Vector3.down, out RaycastHit hit, 8f, SnakeControl.WallMask, QueryTriggerInteraction.Ignore))
@@ -451,6 +516,14 @@ namespace NecromancerTome
Object.Destroy(_go);
_go = null;
}
foreach (LineRenderer l in new[] { _traceOut, _traceIn })
{
if (l != null)
{
Object.Destroy(l.gameObject);
}
}
_traceOut = _traceIn = null;
}
}
@@ -491,6 +564,8 @@ namespace NecromancerTome
"necroossuary remove x y z - remove the ossuary at x y z (only if it is an ossuary)\n" +
"necroossuary on|off - off: sphere and glow no longer follow the ossuary state\n" +
"necroossuary wisp on|off - wild spirit wisp flying while the ossuary works\n" +
"necroossuary wisp trace|log - dev: draw the wisp paths / log take-off and return\n" +
"necroossuary open x y z - dev: open the ossuary window as with E\n" +
"necroossuary info x y z - slots of the ossuary (samples, fuel, storage) and whether it works\n" +
"necroossuary put x y z mod|fuel <slot 1..> <item|none> [count] - dev: put an item into a slot (none - empty it)\n" +
"necroossuary color r g b - glow color (HDR, e.g. 6 1.4 0.35)\n" +
@@ -534,6 +609,18 @@ namespace NecromancerTome
con.Output("necroossuary: убран оссуарий в " + p);
break;
}
case "open":
{
var p = new Vector3i(int.Parse(_params[1]), int.Parse(_params[2]), int.Parse(_params[3]));
BlockValue bv = w.GetBlock(p);
if (bv.ischild)
{
p = p + bv.parent;
bv = w.GetBlock(p);
}
con.Output("necroossuary: открыть " + p + " - " + (GeoOssuary.Is(bv.Block) && bv.Block.OnBlockActivated(w, p, bv, pl)));
break;
}
case "info":
case "put":
{
@@ -596,6 +683,12 @@ namespace NecromancerTome
con.Output("necroossuary: пульсация " + OssuaryGlow.GlowPulse + ", скорость " + OssuaryGlow.GlowPulseSpeed);
break;
case "wisp":
if (_params.Count > 1 && _params[1] == "trace")
{
OssuaryGlow.WispTrace = !OssuaryGlow.WispTrace;
con.Output("necroossuary: трассер огонька " + (OssuaryGlow.WispTrace ? "включён (оранжевый - вылет, голубой - влёт)" : "выключен"));
break;
}
if (_params.Count > 1 && _params[1] == "log")
{
OssuaryGlow.WispLog = !OssuaryGlow.WispLog;