Нити цинерария: нож «хватом для нитей» (R), нити урна-ящик как у стриппера, урна-логист перевозит по образцу, зелёные глазницы

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlexCube
2026-10-16 20:29:50 +03:00
co-authored by Claude Opus 5.5
parent fad57a3756
commit d8e7c8c66a
10 changed files with 885 additions and 63 deletions
+244 -6
View File
@@ -36,10 +36,15 @@ namespace NecromancerTome
public const int Below = 1;
public const int Above = 4;
public const float KillsPerUrn = 500f;
const byte SaveVersion = 1;
const byte SaveVersion = 2; // 2 - нити (16.10)
public bool HasSkull;
public bool ZoneShown;
/// <summary>Нити (CinerariumThreads.cs): ящики «откуда» и ящик «куда», главные клетки блоков. Есть нити - урна-логист.</summary>
public readonly List<Vector3i> ThreadFrom = new List<Vector3i>();
public bool HasThreadTo;
public Vector3i ThreadTo;
public bool IsLogist => HasThreadTo || ThreadFrom.Count > 0;
public override void CopyFromInternal(TileEntityComposite _other)
{
@@ -47,6 +52,11 @@ namespace NecromancerTome
{
HasSkull = other.HasSkull;
ZoneShown = other.ZoneShown;
ThreadFrom.Clear();
ThreadFrom.AddRange(other.ThreadFrom);
HasThreadTo = other.HasThreadTo;
ThreadTo = other.ThreadTo;
_threadsDirty = true;
}
}
@@ -69,6 +79,7 @@ namespace NecromancerTome
{
base.OnRemove(_world);
RemoveBounds();
RemoveThreadView();
RemoveGenie(false);
CinerariumContents.CloseIfShowing(this);
if (!ConnectionManager.Instance.IsServer)
@@ -95,6 +106,8 @@ namespace NecromancerTome
{
base.OnUnload(_world);
RemoveBounds();
RemoveThreadView();
_threadsDirty = true; // чанк вернётся - нити построить заново
RemoveGenie(false);
CinerariumContents.CloseIfShowing(this);
}
@@ -103,6 +116,14 @@ namespace NecromancerTome
{
base.Write(_bw, _eStreamMode);
CinerariumWriteHelper.Write(_bw, SaveVersion, HasSkull, ZoneShown);
List<int> t = new List<int> { ThreadFrom.Count };
foreach (Vector3i p in ThreadFrom)
{
t.Add(p.x); t.Add(p.y); t.Add(p.z);
}
t.Add(HasThreadTo ? 1 : 0);
t.Add(ThreadTo.x); t.Add(ThreadTo.y); t.Add(ThreadTo.z);
CinerariumWriteHelper.WriteInts(_bw, t.ToArray());
}
public override void Read(PooledBinaryReader _br, StreamModeRead _eStreamMode)
@@ -111,6 +132,29 @@ namespace NecromancerTome
byte version = _br.ReadByte();
HasSkull = _br.ReadBoolean();
ZoneShown = _br.ReadBoolean();
ThreadFrom.Clear();
HasThreadTo = false;
if (version >= 2)
{
int n = _br.ReadInt32();
int[] v = new int[n];
for (int i = 0; i < n; i++)
{
v[i] = _br.ReadInt32();
}
int k = 0;
int from = n > 0 ? v[k++] : 0;
for (int i = 0; i < from && k + 2 < n; i++, k += 3)
{
ThreadFrom.Add(new Vector3i(v[k], v[k + 1], v[k + 2]));
}
if (k + 3 < n)
{
HasThreadTo = v[k] != 0;
ThreadTo = new Vector3i(v[k + 1], v[k + 2], v[k + 3]);
}
}
_threadsDirty = true;
if (version > SaveVersion)
{
Debug.LogWarning("[NecromancerTome] Cinerarium: урна из более новой версии мода (" + version + ") - прочитано только известное");
@@ -148,9 +192,148 @@ namespace NecromancerTome
SpawnBounds();
}
SyncGenie(false);
SyncThreads(_world);
Work(_world);
}
// ------------------------------------------------------------------
// Нити (CinerariumThreads.cs).
// ------------------------------------------------------------------
bool _threadsDirty = true;
GameObject _threadView;
float _nextThreadCheck;
public bool AddThreadFrom(Vector3i _box)
{
if (ThreadFrom.Contains(_box))
{
return true;
}
if (ThreadFrom.Count >= CinerariumThreads.MaxFrom)
{
return false;
}
ThreadFrom.Add(_box);
ThreadsChanged();
return true;
}
public void SetThreadTo(Vector3i _box)
{
HasThreadTo = true;
ThreadTo = _box;
ThreadsChanged();
}
public int ClearThreads()
{
int n = ThreadFrom.Count + (HasThreadTo ? 1 : 0);
ThreadFrom.Clear();
HasThreadTo = false;
if (n > 0)
{
ThreadsChanged();
}
return n;
}
public int RemoveThreadsTo(Vector3i _box)
{
int n = ThreadFrom.RemoveAll(p => p == _box);
if (HasThreadTo && ThreadTo == _box)
{
HasThreadTo = false;
n++;
}
if (n > 0)
{
ThreadsChanged();
}
return n;
}
void ThreadsChanged()
{
_threadsDirty = true;
_glowState = -1; // логист - другие глаза
SetModified();
Debug.Log("[NecromancerTome] Cinerarium: нити урны " + ToWorldPos() + " - откуда " + ThreadFrom.Count + ", куда " + (HasThreadTo ? ThreadTo.ToString() : "нет"));
}
/// <summary>Нити к убранным ящикам снимаются (раз в 2 с); вид перестраивается после изменений.</summary>
void SyncThreads(World _world)
{
if (_world != null && ConnectionManager.Instance.IsServer && IsLogist && Time.time >= _nextThreadCheck)
{
_nextThreadCheck = Time.time + 2f;
bool gone = false;
foreach (Vector3i p in ThreadFrom.ToArray())
{
if (!CinerariumThreads.Resolve(_world, p, out Vector3i _, out TEFeatureCinerarium u) || u != null)
{
if (_world.GetChunkFromWorldPos(p) != null) // чанк не загружен - не снимать
{
ThreadFrom.Remove(p);
gone = true;
}
}
}
if (HasThreadTo && (!CinerariumThreads.Resolve(_world, ThreadTo, out Vector3i _, out TEFeatureCinerarium u2) || u2 != null) &&
_world.GetChunkFromWorldPos(ThreadTo) != null)
{
HasThreadTo = false;
gone = true;
}
if (gone)
{
ThreadsChanged();
}
}
if (!_threadsDirty && (_threadView != null || !IsLogist))
{
return;
}
Transform t = ModelTransform();
if (t == null)
{
return;
}
_threadsDirty = false;
SyncGlow();
if (!IsLogist)
{
RemoveThreadView();
return;
}
if (_threadView == null)
{
_threadView = new GameObject("NecroCinerariumThreads");
_threadView.transform.SetParent(t, false);
_threadView.AddComponent<CinerariumThreadView>();
}
Vector3i me = ToWorldPos();
List<(Vector3 a, Vector3 b)> pairs = new List<(Vector3, Vector3)>();
foreach (Vector3i p in ThreadFrom)
{
pairs.Add((CinerariumThreads.BoxPoint(p, me), CinerariumThreads.UrnPoint(me)));
}
if (HasThreadTo)
{
pairs.Add((CinerariumThreads.UrnPoint(me), CinerariumThreads.BoxPoint(ThreadTo, me)));
}
_threadView.GetComponent<CinerariumThreadView>().Build(pairs);
}
void RemoveThreadView()
{
if (_threadView != null)
{
UnityEngine.Object.Destroy(_threadView);
_threadView = null;
}
}
float _nextWork;
public bool Stuck;
/// <summary>Дело найдено пробным шагом, дух полетел; выполнить в _actAt (16.10: «делал действие не сразу»).</summary>
@@ -197,11 +380,17 @@ namespace NecromancerTome
if (at.HasValue)
{
_pending = true;
_actAt = Time.time + (motion != null ? CinerariumGenieMotion.ActDelay : 0f);
_actAt = Time.time;
if (motion != null)
{
Vector3 look = World.blockToTransformPos(at.Value) - Origin.position;
motion.Visit(Stand(_world, at.Value, out bool beside) - Origin.position, look, beside);
Vector3i? from = CinerariumWork.LastFrom;
List<(Vector3, Vector3, bool)> stops = new List<(Vector3, Vector3, bool)>();
if (from.HasValue)
{
stops.Add((Stand(_world, from.Value, out bool b0) - Origin.position, World.blockToTransformPos(from.Value) - Origin.position, b0));
}
stops.Add((Stand(_world, at.Value, out bool beside) - Origin.position, World.blockToTransformPos(at.Value) - Origin.position, beside));
_actAt = Time.time + motion.Visit(stops);
}
}
}
@@ -330,23 +519,67 @@ namespace NecromancerTome
void SyncGlow()
{
Transform t = ModelTransform();
int want = HasSkull ? 1 : 0;
int want = HasSkull ? (IsLogist ? 2 : 1) : 0;
Texture logist = want == 2 ? LogistEmission() : null;
if (t == null || (_glowState == want && _glowModel == t))
{
return;
}
foreach (Renderer r in t.GetComponentsInChildren<Renderer>(true))
{
if (_genie != null && r.transform.IsChildOf(_genie.transform) || _bounds != null && r.transform.IsChildOf(_bounds.transform))
if (_genie != null && r.transform.IsChildOf(_genie.transform) || _bounds != null && r.transform.IsChildOf(_bounds.transform) ||
_threadView != null && r.transform.IsChildOf(_threadView.transform))
{
continue;
}
r.GetPropertyBlock(_glowBlock);
_glowBlock.SetColor(EmissionColorId, HasSkull ? GlowColor : Color.black);
if (logist != null)
{
_glowBlock.SetTexture(EmissionMapId, logist);
}
else if (_hadLogistTex)
{
_glowBlock.Clear(); // вернуть текстуру материала
_glowBlock.SetColor(EmissionColorId, HasSkull ? GlowColor : Color.black);
}
r.SetPropertyBlock(_glowBlock);
}
_glowState = want;
_glowModel = t;
_hadLogistTex = logist != null;
}
bool _hadLogistTex;
static readonly int EmissionMapId = Shader.PropertyToID("_EmissionMap");
static Texture2D _logistTex;
static bool _logistTried;
public const string LogistTextureFile = "necroCinerarium_e_logist.png";
/// <summary>Свечение урны-логиста (16.10: «пусть глаза черепов светятся другим цветом. Зелёным»): копия запечённой
/// necroCinerarium_e с зелёными глазницами (руны - те же), из папки Resources мода - бандл не пересобирается.</summary>
static Texture2D LogistEmission()
{
if (!_logistTried)
{
_logistTried = true;
try
{
string path = Path.Combine(ModEntry.Instance.Path, "Resources", LogistTextureFile);
Texture2D tex = new Texture2D(2, 2, TextureFormat.RGBA32, true);
if (tex.LoadImage(File.ReadAllBytes(path)))
{
tex.name = "necroCinerarium_e_logist";
tex.anisoLevel = 4;
_logistTex = tex;
}
}
catch (Exception e)
{
Debug.LogWarning("[NecromancerTome] Cinerarium: свечение логиста не загружено (" + e.Message + ")");
}
}
return _logistTex;
}
Transform _glowModel;
@@ -563,6 +796,11 @@ namespace NecromancerTome
return n;
}
public static List<Vector3i> All()
{
return new List<Vector3i>(Urns().Keys);
}
/// <summary>Пересекутся ли зоны урны в _pos и уже стоящей: по горизонтали ближе 5 блоков по обеим осям,
/// по высоте ближе 6 (зона от -1 до +4).</summary>
public static bool Overlaps(Vector3i _pos)