- 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>
716 lines
27 KiB
C#
716 lines
27 KiB
C#
using System.Collections.Generic;
|
|
using HarmonyLib;
|
|
using UnityEngine;
|
|
|
|
namespace NecromancerTome
|
|
{
|
|
/// <summary>
|
|
/// Новый оссуарий-котёл (#67, 22.10.2026): свечение модели и отладочная команда necroossuary.
|
|
/// Свечение - руны на котле, знаки на крышке, прах и кости внутри (карта _e, цвет - EmissionColor через MaterialPropertyBlock,
|
|
/// общий материал не трогается) и свет glow внутри котла (дочерний объект префаба, выключен) - пока оссуарий исправно работает
|
|
/// (22.10: «Когда оссуарий исправно работает, он должен подсвечиваться изнутри. Чтобы сразу было видно если что-то не так»;
|
|
/// «Пусть руны тоже загораются»). Сфера в держателе (sphere) видна, пока в топливе есть сфера или камень. Следит компонент
|
|
/// OssuaryVisual на модели - поэтому работает и у клиентов сервера. Выключатель - necroossuary off (модель как в префабе).
|
|
/// </summary>
|
|
public static class OssuaryGlow
|
|
{
|
|
public static bool Enabled = true;
|
|
/// <summary>Дикий дух-огонёк (#69): летает, пока оссуарий работает. Выключатель - necroossuary wisp off.</summary>
|
|
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;
|
|
|
|
public static GameObject WispPrefab()
|
|
{
|
|
if (!_wispTried)
|
|
{
|
|
_wispTried = true;
|
|
_wispPrefab = DataLoader.LoadAsset<GameObject>(WispPath);
|
|
Debug.Log("[NecromancerTome] OssuaryGlow: огонёк " + (_wispPrefab != null ? "загружен" : "НЕ загружен: " + WispPath));
|
|
}
|
|
return _wispPrefab;
|
|
}
|
|
|
|
/// <summary>Цвет свечения (HDR). 22.10: было (2, 0.24, 0.12) - «Руны нарисованы красным. Это не выглядит как свечение»:
|
|
/// днём смотрелось краской. Теперь ярче и теплее, как раскалённый уголь, с пульсацией. Подбор - necroossuary color / pulse.</summary>
|
|
public static Color GlowColor = new Color(6f, 1.4f, 0.35f, 1f);
|
|
/// <summary>Размах пульсации свечения (доля яркости) и её скорость (радиан в секунду).</summary>
|
|
public static float GlowPulse = 0.3f, GlowPulseSpeed = 1.6f;
|
|
static readonly MaterialPropertyBlock _block = new MaterialPropertyBlock();
|
|
static readonly int EmissionColorId = Shader.PropertyToID("_EmissionColor");
|
|
|
|
public static Transform Model(World _w, Vector3i _pos)
|
|
{
|
|
Chunk chunk = _w.GetChunkFromWorldPos(_pos) as Chunk;
|
|
BlockEntityData be = chunk?.GetBlockEntity(_pos);
|
|
return be != null && be.bHasTransform ? be.transform : null;
|
|
}
|
|
|
|
/// <summary>true - модель найдена и переключена.</summary>
|
|
public static bool Set(Transform _model, bool _on, bool _sphere = true)
|
|
{
|
|
if (_model == null)
|
|
{
|
|
return false;
|
|
}
|
|
foreach (string part in new[] { "body", "lid" })
|
|
{
|
|
Transform t = _model.Find(part);
|
|
Renderer r = t != null ? t.GetComponent<Renderer>() : null;
|
|
if (r == null)
|
|
{
|
|
continue;
|
|
}
|
|
r.GetPropertyBlock(_block);
|
|
_block.SetColor(EmissionColorId, _on ? GlowColor : Color.black);
|
|
r.SetPropertyBlock(_block);
|
|
}
|
|
Transform glow = _model.Find("glow");
|
|
if (glow != null)
|
|
{
|
|
glow.gameObject.SetActive(_on);
|
|
}
|
|
Transform sphere = _model.Find("sphere");
|
|
if (sphere != null)
|
|
{
|
|
sphere.gameObject.SetActive(_sphere);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// <summary>Пульсация: свечение рун и нутра - GlowColor с множителем _k (каждый кадр, пока светится).</summary>
|
|
public static void Pulse(Transform _model, float _k)
|
|
{
|
|
foreach (string part in new[] { "body", "lid" })
|
|
{
|
|
Transform t = _model.Find(part);
|
|
Renderer r = t != null ? t.GetComponent<Renderer>() : null;
|
|
if (r == null)
|
|
{
|
|
continue;
|
|
}
|
|
r.GetPropertyBlock(_block);
|
|
_block.SetColor(EmissionColorId, GlowColor * _k);
|
|
r.SetPropertyBlock(_block);
|
|
}
|
|
}
|
|
|
|
/// <summary>Оссуарии (главные блоки) в кубе вокруг точки.</summary>
|
|
public static List<Vector3i> Find(World _w, Vector3i _c, int _r, int _ry)
|
|
{
|
|
var res = new List<Vector3i>();
|
|
for (int x = _c.x - _r; x <= _c.x + _r; x++)
|
|
{
|
|
for (int z = _c.z - _r; z <= _c.z + _r; z++)
|
|
{
|
|
for (int y = Mathf.Max(1, _c.y - _ry); y <= Mathf.Min(254, _c.y + _ry); y++)
|
|
{
|
|
var p = new Vector3i(x, y, z);
|
|
BlockValue bv = _w.GetBlock(p);
|
|
if (!bv.ischild && GeoOssuary.Is(bv.Block))
|
|
{
|
|
res.Add(p);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
}
|
|
|
|
/// <summary>На модели оссуария: два раза в секунду смотрит на коллектор и переключает сферу и свечение, только при смене.
|
|
/// anyEnabled - ванильная проверка «есть рабочий выход»: учитывает топливо, заполненный выход, воду и пустые ячейки
|
|
/// модификаций (GeoOssuaryPatch.isDisabled).</summary>
|
|
public class OssuaryVisual : MonoBehaviour
|
|
{
|
|
public Vector3i Pos;
|
|
float _next;
|
|
int _state = -1;
|
|
readonly float _phase = Random.Range(0f, 6.28f); // соседние котлы пульсируют не в такт
|
|
|
|
void Update()
|
|
{
|
|
bool lit = _state > 0 && (_state & 1) != 0 && OssuaryGlow.Enabled; // -1 - ещё не смотрели
|
|
_wisp.Tick(transform, lit && OssuaryGlow.WispEnabled);
|
|
if (lit && OssuaryGlow.GlowPulse > 0f)
|
|
{
|
|
OssuaryGlow.Pulse(transform, 1f + OssuaryGlow.GlowPulse * Mathf.Sin(Time.time * OssuaryGlow.GlowPulseSpeed + _phase));
|
|
}
|
|
if (Time.time < _next)
|
|
{
|
|
return;
|
|
}
|
|
_next = Time.time + 0.5f;
|
|
World w = GameManager.Instance?.World;
|
|
if (w == null)
|
|
{
|
|
return;
|
|
}
|
|
int st; // бит 1 - светится, бит 2 - сфера видна; выключатель - как в префабе (2)
|
|
if (OssuaryGlow.Enabled)
|
|
{
|
|
st = 0;
|
|
if (w.GetTileEntity(Pos) is TileEntityCollector te)
|
|
{
|
|
bool fuel = false;
|
|
foreach (ItemStack s in te.FuelSlots)
|
|
{
|
|
if (s != null && !s.IsEmpty())
|
|
{
|
|
fuel = true;
|
|
break;
|
|
}
|
|
}
|
|
st = (fuel ? 2 : 0) + (fuel && te.anyEnabled() ? 1 : 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
st = 2;
|
|
}
|
|
if (st == _state)
|
|
{
|
|
return;
|
|
}
|
|
_state = st;
|
|
OssuaryGlow.Set(transform, (st & 1) != 0, (st & 2) != 0);
|
|
}
|
|
|
|
readonly OssuaryWisp _wisp = new OssuaryWisp();
|
|
|
|
void OnDestroy()
|
|
{
|
|
_wisp.Destroy();
|
|
}
|
|
}
|
|
|
|
/// <summary>Дикий дух оссуария (#69; 20.10: «Хватит одного огонька. Пусть светится сам. Производительность нам важна»):
|
|
/// чистая анимация, без сущностей (22.10: «периодически из окошка в котле вылетает дух и уходит вниз, увеличивая прозрачность
|
|
/// до полной, а через какое-то время с другой стороны снизу вылетает полностью прозрачный дух, плавно уменьшая прозрачность,
|
|
/// и влетает в противоположное окошко»). Один огонёк на котёл (бандл necrowisp), только пока оссуарий исправно работает
|
|
/// и игрок ближе WispViewRange. На добычу не влияет.</summary>
|
|
public class OssuaryWisp
|
|
{
|
|
public const float WispViewRange = 60f;
|
|
/// <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;
|
|
const float BelowWindow = 0.1f; // вертикаль кончается на столько ниже окошка, м; дальше дуга
|
|
const float Under = 0.35f; // насколько под землю, м
|
|
const float FlyTime = 2.4f;
|
|
static readonly int TintId = Shader.PropertyToID("_TintColor");
|
|
static readonly int ColorId = Shader.PropertyToID("_Color");
|
|
static readonly MaterialPropertyBlock _mpb = new MaterialPropertyBlock();
|
|
GameObject _go;
|
|
Renderer[] _rs;
|
|
Color[] _base;
|
|
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);
|
|
|
|
public void Tick(Transform _model, bool _work)
|
|
{
|
|
EntityPlayerLocal pl = GameManager.Instance?.World?.GetPrimaryPlayer();
|
|
bool near = pl != null && (pl.position - Origin.position - _model.position).sqrMagnitude < WispViewRange * WispViewRange;
|
|
float now = Time.time;
|
|
if (!_work || !near)
|
|
{
|
|
if (_phase >= 0)
|
|
{
|
|
Hide();
|
|
_phase = -1;
|
|
}
|
|
_until = Mathf.Max(_until, now + Random.Range(1f, 4f));
|
|
return;
|
|
}
|
|
if (_go == null && !Spawn(_model))
|
|
{
|
|
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)
|
|
{
|
|
case -1:
|
|
if (now < _until || !Places(_model))
|
|
{
|
|
return;
|
|
}
|
|
Show(_pathOut[0]);
|
|
if (OssuaryGlow.WispLog)
|
|
{
|
|
Debug.Log("[NecromancerTome] OssuaryWisp: вылет " + _pathOut[1]);
|
|
}
|
|
Next(0, FlyTime);
|
|
return;
|
|
case 0: // из окошка наружу и вниз, в землю; тает
|
|
Fly(_pathOut, k);
|
|
Alpha(1f - Ease(Mathf.InverseLerp(0.45f, 1f, k)));
|
|
if (k >= 1f)
|
|
{
|
|
Hide();
|
|
Next(1, Random.Range(12f, 20f));
|
|
}
|
|
return;
|
|
case 1: // под землёй
|
|
if (now >= _until)
|
|
{
|
|
Alpha(0f);
|
|
Show(_pathIn[0]);
|
|
if (OssuaryGlow.WispLog)
|
|
{
|
|
Debug.Log("[NecromancerTome] OssuaryWisp: влёт " + _pathIn[_pathIn.Length - 2]);
|
|
}
|
|
Next(2, FlyTime);
|
|
}
|
|
return;
|
|
case 2: // с другой стороны снизу вверх, проявляется, влетает в противоположное окошко
|
|
Fly(_pathIn, k);
|
|
Alpha(Ease(Mathf.InverseLerp(0f, 0.55f, k)));
|
|
if (k >= 1f)
|
|
{
|
|
Hide();
|
|
_phase = -1;
|
|
_until = now + Random.Range(3f, 6f);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Next(int _p, float _dur)
|
|
{
|
|
_phase = _p;
|
|
_t0 = Time.time;
|
|
_until = _t0 + _dur;
|
|
}
|
|
|
|
/// <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 = _s01 * 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)];
|
|
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>Окошко вылета - случайное из шести, влёта - напротив. Путь: нутро, центр окошка, точка дуги снаружи на высоте
|
|
/// окошка, вертикаль снаружи котла чуть ниже окошка, та же вертикаль под землёй (высота земли - лучом).</summary>
|
|
bool Places(Transform _model)
|
|
{
|
|
Transform lid = _model.Find("lid");
|
|
Transform body = _model.Find("body");
|
|
Renderer lr = lid != null ? lid.GetComponent<Renderer>() : null;
|
|
Renderer br = body != null ? body.GetComponent<Renderer>() : null;
|
|
if (lr == null)
|
|
{
|
|
return false;
|
|
}
|
|
Bounds b = lr.bounds;
|
|
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, 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;
|
|
}
|
|
|
|
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 // в земле
|
|
};
|
|
}
|
|
|
|
/// <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))
|
|
{
|
|
_p.y = hit.point.y;
|
|
}
|
|
else
|
|
{
|
|
_p.y -= 1f;
|
|
}
|
|
return _p;
|
|
}
|
|
|
|
bool Spawn(Transform _model)
|
|
{
|
|
GameObject prefab = OssuaryGlow.WispPrefab();
|
|
if (prefab == null)
|
|
{
|
|
return false;
|
|
}
|
|
_go = Object.Instantiate(prefab, _model, false);
|
|
_go.name = "NecroOssuaryWisp";
|
|
_trail = _go.GetComponent<TrailRenderer>();
|
|
_rs = _go.GetComponentsInChildren<Renderer>(true);
|
|
_base = new Color[_rs.Length];
|
|
for (int i = 0; i < _rs.Length; i++)
|
|
{
|
|
Material m = _rs[i].sharedMaterial;
|
|
_base[i] = m == null ? Color.white : m.HasProperty(TintId) ? m.GetColor(TintId) : m.HasProperty(ColorId) ? m.GetColor(ColorId) : Color.white;
|
|
}
|
|
_go.SetActive(false);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>Прозрачность - цветом материала через MaterialPropertyBlock (шейдер аддитивный: тусклее = прозрачнее),
|
|
/// общий материал не трогается.</summary>
|
|
void Alpha(float _a)
|
|
{
|
|
for (int i = 0; i < _rs.Length; i++)
|
|
{
|
|
Material m = _rs[i].sharedMaterial;
|
|
if (m == null)
|
|
{
|
|
continue;
|
|
}
|
|
Color c = _base[i] * _a;
|
|
_rs[i].GetPropertyBlock(_mpb);
|
|
if (m.HasProperty(TintId))
|
|
{
|
|
_mpb.SetColor(TintId, c);
|
|
}
|
|
if (m.HasProperty(ColorId))
|
|
{
|
|
_mpb.SetColor(ColorId, c);
|
|
}
|
|
_rs[i].SetPropertyBlock(_mpb);
|
|
}
|
|
}
|
|
|
|
void Show(Vector3 _at)
|
|
{
|
|
_go.transform.position = _at;
|
|
if (_trail != null)
|
|
{
|
|
_trail.Clear();
|
|
}
|
|
_go.SetActive(true);
|
|
if (_phase != 1)
|
|
{
|
|
Alpha(1f);
|
|
}
|
|
}
|
|
|
|
void Hide()
|
|
{
|
|
if (_go != null)
|
|
{
|
|
_go.SetActive(false);
|
|
}
|
|
}
|
|
|
|
public void Destroy()
|
|
{
|
|
if (_go != null)
|
|
{
|
|
Object.Destroy(_go);
|
|
_go = null;
|
|
}
|
|
foreach (LineRenderer l in new[] { _traceOut, _traceIn })
|
|
{
|
|
if (l != null)
|
|
{
|
|
Object.Destroy(l.gameObject);
|
|
}
|
|
}
|
|
_traceOut = _traceIn = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>Модель оссуария появилась в мире - повесить на неё OssuaryVisual.</summary>
|
|
[HarmonyPatch(typeof(BlockCollector), nameof(BlockCollector.OnBlockEntityTransformBeforeActivated))]
|
|
public static class Patch_BlockCollector_OnBlockEntityTransformBeforeActivated_OssuaryVisual
|
|
{
|
|
public static void Postfix(Vector3i _blockPos, BlockValue _blockValue, BlockEntityData _ebcd)
|
|
{
|
|
if (!GeoOssuary.Is(_blockValue.Block) || _ebcd?.transform == null || _ebcd.transform.Find("body") == null)
|
|
{
|
|
return;
|
|
}
|
|
OssuaryVisual v = _ebcd.transform.GetComponent<OssuaryVisual>() ?? _ebcd.transform.gameObject.AddComponent<OssuaryVisual>();
|
|
v.Pos = _blockPos;
|
|
}
|
|
}
|
|
|
|
public class ConsoleCmdNecroOssuary : ConsoleCmdAbstract
|
|
{
|
|
public override bool IsExecuteOnClient => true;
|
|
|
|
public override bool AllowedInMainMenu => false;
|
|
|
|
public override string[] getCommands()
|
|
{
|
|
return new string[] { "necroossuary" };
|
|
}
|
|
|
|
public override string getDescription()
|
|
{
|
|
return "Necromancer's Tome: cauldron ossuary - find, remove, sphere/glow switch.";
|
|
}
|
|
|
|
public override string getHelp()
|
|
{
|
|
return "necroossuary find [r] - ossuaries around the player (r blocks, default 30)\n" +
|
|
"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" +
|
|
"necroossuary pulse a [speed] - glow pulse amplitude (0 - steady) and speed";
|
|
}
|
|
|
|
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
|
|
{
|
|
SdtdConsole con = SingletonMonoBehaviour<SdtdConsole>.Instance;
|
|
World w = GameManager.Instance?.World;
|
|
EntityPlayerLocal pl = w?.GetPrimaryPlayer();
|
|
if (w == null || pl == null || _params.Count == 0)
|
|
{
|
|
con.Output(getHelp());
|
|
return;
|
|
}
|
|
Vector3i me = World.worldToBlockPos(pl.position);
|
|
switch (_params[0])
|
|
{
|
|
case "find":
|
|
{
|
|
int r = _params.Count > 1 ? int.Parse(_params[1]) : 30;
|
|
List<Vector3i> found = OssuaryGlow.Find(w, me, r, 20);
|
|
con.Output("necroossuary: найдено " + found.Count + (found.Count > 0 ? ": " + string.Join("; ", found) : ""));
|
|
break;
|
|
}
|
|
case "remove":
|
|
{
|
|
var p = new Vector3i(int.Parse(_params[1]), int.Parse(_params[2]), int.Parse(_params[3]));
|
|
BlockValue bv = w.GetBlock(p);
|
|
if (!GeoOssuary.Is(bv.Block))
|
|
{
|
|
con.Output("necroossuary: в " + p + " не оссуарий (" + bv.Block?.GetBlockName() + ") - не трогаю");
|
|
break;
|
|
}
|
|
if (bv.ischild)
|
|
{
|
|
p = p + bv.parent;
|
|
}
|
|
w.SetBlockRPC(p, BlockValue.Air);
|
|
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":
|
|
{
|
|
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;
|
|
}
|
|
if (!(w.GetTileEntity(p) is TileEntityCollector te) || !GeoOssuary.Is(te))
|
|
{
|
|
con.Output("necroossuary: в " + p + " не оссуарий");
|
|
break;
|
|
}
|
|
if (_params[0] == "put")
|
|
{
|
|
ItemStack[] slots = _params[4] == "fuel" ? te.FuelSlots : te.ModSlots;
|
|
int i = int.Parse(_params[5]) - 1;
|
|
bool none = _params[6] == "none";
|
|
ItemValue iv = none ? ItemValue.None : ItemClass.GetItem(_params[6]);
|
|
if (i < 0 || i >= slots.Length || iv == null || (!none && iv.IsEmpty()))
|
|
{
|
|
con.Output("necroossuary: нет ячейки " + (i + 1) + " или предмета " + _params[6]);
|
|
break;
|
|
}
|
|
slots[i] = none ? ItemStack.Empty.Clone() : new ItemStack(iv, _params.Count > 7 ? int.Parse(_params[7]) : 1);
|
|
te.SetModified();
|
|
}
|
|
string S(ItemStack[] _a)
|
|
{
|
|
var parts = new List<string>();
|
|
for (int i = 0; i < _a.Length; i++)
|
|
{
|
|
parts.Add(_a[i] == null || _a[i].IsEmpty() ? "-" : _a[i].itemValue.ItemClass?.Name + " x" + _a[i].count);
|
|
}
|
|
return string.Join(", ", parts);
|
|
}
|
|
con.Output("necroossuary " + p + ": образцы [" + S(te.ModSlots) + "]; топливо [" + S(te.FuelSlots) + "]; хранилище [" +
|
|
S(te.Items) + "]; работает " + te.anyEnabled());
|
|
break;
|
|
}
|
|
case "color":
|
|
if (_params.Count > 3)
|
|
{
|
|
OssuaryGlow.GlowColor = new Color(float.Parse(_params[1], System.Globalization.CultureInfo.InvariantCulture),
|
|
float.Parse(_params[2], System.Globalization.CultureInfo.InvariantCulture),
|
|
float.Parse(_params[3], System.Globalization.CultureInfo.InvariantCulture), 1f);
|
|
}
|
|
con.Output("necroossuary: цвет свечения " + OssuaryGlow.GlowColor);
|
|
break;
|
|
case "pulse":
|
|
if (_params.Count > 1)
|
|
{
|
|
OssuaryGlow.GlowPulse = float.Parse(_params[1], System.Globalization.CultureInfo.InvariantCulture);
|
|
}
|
|
if (_params.Count > 2)
|
|
{
|
|
OssuaryGlow.GlowPulseSpeed = float.Parse(_params[2], System.Globalization.CultureInfo.InvariantCulture);
|
|
}
|
|
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;
|
|
con.Output("necroossuary: лог огонька " + (OssuaryGlow.WispLog ? "включён" : "выключен"));
|
|
break;
|
|
}
|
|
if (_params.Count > 1)
|
|
{
|
|
OssuaryGlow.WispEnabled = _params[1] == "on";
|
|
}
|
|
con.Output("necroossuary: огонёк " + (OssuaryGlow.WispEnabled ? "летает" : "выключен"));
|
|
break;
|
|
case "on":
|
|
case "off":
|
|
OssuaryGlow.Enabled = _params[0] == "on";
|
|
con.Output("necroossuary: сфера и свечение по работе " + (OssuaryGlow.Enabled ? "включены" : "выключены (модель как в префабе)"));
|
|
break;
|
|
default:
|
|
con.Output(getHelp());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|