Урна рабочего: изготовление по образцу на станциях по нитям (CinerariumCraft), образец во второй ячейке, нити к станциям, топливо и огонь
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a5f166c161
commit
eba17606d5
+135
-21
@@ -41,12 +41,26 @@ namespace NecromancerTome
|
||||
string n = _b?.GetBlockName();
|
||||
return n == LogistBlockName || n == LogistSkullBlockName;
|
||||
}
|
||||
/// <summary>Урна рабочего (17.10, BACKLOG «Урна рабочего»): изготавливает предмет из ячейки образца на станциях, к которым
|
||||
/// от неё тянутся нити; ингредиенты - из ящиков с нитями к урне, готовое - в ящики с нитями от урны. Без зоны и вне лимита,
|
||||
/// как логист.</summary>
|
||||
public const string WorkerBlockName = "necroCinerariumWorker";
|
||||
public const string WorkerSkullBlockName = "necroCinerariumWorkerSkull";
|
||||
|
||||
public static bool IsWorkerBlock(Block _b)
|
||||
{
|
||||
string n = _b?.GetBlockName();
|
||||
return n == WorkerBlockName || n == WorkerSkullBlockName;
|
||||
}
|
||||
|
||||
/// <summary>Урна без зоны (логист, рабочий): ставится вплотную к любым урнам, в лимит не входит, работает по нитям.</summary>
|
||||
public static bool IsZonelessBlock(Block _b) => IsLogistBlock(_b) || IsWorkerBlock(_b);
|
||||
public const string SkullItemName = "necroSkull";
|
||||
public const int HalfWidth = 2;
|
||||
public const int Below = 1;
|
||||
public const int Above = 4;
|
||||
public const float KillsPerUrn = 500f;
|
||||
const byte SaveVersion = 3; // 2 - нити, 3 - фильтр логиста (16.10)
|
||||
const byte SaveVersion = 4; // 2 - нити, 3 - фильтр логиста (16.10), 4 - нити рабочего «куда» и образец (18.10)
|
||||
|
||||
public bool HasSkull;
|
||||
public bool ZoneShown;
|
||||
@@ -54,7 +68,23 @@ namespace NecromancerTome
|
||||
public readonly List<Vector3i> ThreadFrom = new List<Vector3i>();
|
||||
public bool HasThreadTo;
|
||||
public Vector3i ThreadTo;
|
||||
public bool HasThreads => HasThreadTo || ThreadFrom.Count > 0;
|
||||
public bool HasThreads => HasThreadTo || ThreadFrom.Count > 0 || ThreadOut.Count > 0;
|
||||
/// <summary>Нити рабочего «от урны»: станции (где изготавливать) и ящики (куда класть готовое), главные клетки блоков.</summary>
|
||||
public readonly List<Vector3i> ThreadOut = new List<Vector3i>();
|
||||
/// <summary>Образец рабочего: что изготавливать; число в стопке - сколько держать в запасе (ящики «куда» и выход станций).
|
||||
/// Лежит в «Содержимом» урны второй ячейкой.</summary>
|
||||
public ItemStack Sample = ItemStack.Empty.Clone();
|
||||
/// <summary>Урна рабочего - по блоку, как логист.</summary>
|
||||
public bool IsWorker
|
||||
{
|
||||
get
|
||||
{
|
||||
_ = IsLogist; // узнаёт тип блока
|
||||
return _worker;
|
||||
}
|
||||
}
|
||||
bool _worker;
|
||||
public bool IsZoneless => IsLogist || IsWorker;
|
||||
/// <summary>Урна-логист - по блоку (necroCinerariumLogist / ...Skull); узнаётся при постановке или по миру.</summary>
|
||||
public bool IsLogist
|
||||
{
|
||||
@@ -67,6 +97,7 @@ namespace NecromancerTome
|
||||
if (IsUrn(bv.Block))
|
||||
{
|
||||
_logist = IsLogistBlock(bv.Block);
|
||||
_worker = IsWorkerBlock(bv.Block);
|
||||
_logistKnown = true;
|
||||
}
|
||||
}
|
||||
@@ -96,6 +127,9 @@ namespace NecromancerTome
|
||||
ThreadFrom.AddRange(other.ThreadFrom);
|
||||
HasThreadTo = other.HasThreadTo;
|
||||
ThreadTo = other.ThreadTo;
|
||||
ThreadOut.Clear();
|
||||
ThreadOut.AddRange(other.ThreadOut);
|
||||
Sample = other.Sample.Clone();
|
||||
GroupMask = other.GroupMask;
|
||||
_threadsDirty = true;
|
||||
}
|
||||
@@ -106,15 +140,16 @@ namespace NecromancerTome
|
||||
{
|
||||
base.OnAdded(_blockPos, _blockValue);
|
||||
_logist = IsLogistBlock(_blockValue.Block);
|
||||
_worker = IsWorkerBlock(_blockValue.Block);
|
||||
_logistKnown = true;
|
||||
string placed = _blockValue.Block?.GetBlockName();
|
||||
if (placed == SkullBlockName || placed == LogistSkullBlockName)
|
||||
if (placed == SkullBlockName || placed == LogistSkullBlockName || placed == WorkerSkullBlockName)
|
||||
{
|
||||
HasSkull = true; // поставили урну, поднятую с черепом
|
||||
}
|
||||
if (ConnectionManager.Instance.IsServer)
|
||||
{
|
||||
CinerariumRegistry.Add(_blockPos, Parent?.Owner, _logist);
|
||||
CinerariumRegistry.Add(_blockPos, Parent?.Owner, _logist || _worker);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,6 +179,12 @@ namespace NecromancerTome
|
||||
Debug.Log("[NecromancerTome] Cinerarium: урна " + pos + " убрана - череп выпал");
|
||||
}
|
||||
}
|
||||
if (Sample != null && !Sample.IsEmpty() && _world != null)
|
||||
{
|
||||
_world.gameManager.ItemDropServer(Sample.Clone(), World.blockToTransformPos(pos) + Vector3.up * 0.7f, Vector3.zero);
|
||||
Sample = ItemStack.Empty.Clone();
|
||||
Debug.Log("[NecromancerTome] Cinerarium: урна " + pos + " убрана - образец выпал");
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnUnload(World _world)
|
||||
@@ -170,6 +211,13 @@ namespace NecromancerTome
|
||||
CinerariumWriteHelper.WriteInts(_bw, t.ToArray());
|
||||
// Второе число - сколько групп было при записи: новые группы (в конец) у старых урн включаются по правилу ниже.
|
||||
CinerariumWriteHelper.WriteInts(_bw, new[] { unchecked((int)GroupMask), CinerariumGroups.Count });
|
||||
List<int> o = new List<int> { ThreadOut.Count };
|
||||
foreach (Vector3i p in ThreadOut)
|
||||
{
|
||||
o.Add(p.x); o.Add(p.y); o.Add(p.z);
|
||||
}
|
||||
CinerariumWriteHelper.WriteInts(_bw, o.ToArray());
|
||||
(Sample ?? ItemStack.Empty).Clone().Write(_bw);
|
||||
}
|
||||
|
||||
public override void Read(PooledBinaryReader _br, StreamModeRead _eStreamMode)
|
||||
@@ -224,6 +272,23 @@ namespace NecromancerTome
|
||||
GroupMask = on ? GroupMask | (1u << g) : GroupMask & ~(1u << g);
|
||||
}
|
||||
}
|
||||
ThreadOut.Clear();
|
||||
Sample = ItemStack.Empty.Clone();
|
||||
if (version >= 4)
|
||||
{
|
||||
int n = _br.ReadInt32();
|
||||
int[] v = new int[n];
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
v[i] = _br.ReadInt32();
|
||||
}
|
||||
int cnt = n > 0 ? v[0] : 0;
|
||||
for (int i = 0, k = 1; i < cnt && k + 2 < n; i++, k += 3)
|
||||
{
|
||||
ThreadOut.Add(new Vector3i(v[k], v[k + 1], v[k + 2]));
|
||||
}
|
||||
Sample = ItemStack.Empty.Clone().Read(_br);
|
||||
}
|
||||
_threadsDirty = true;
|
||||
if (version > SaveVersion)
|
||||
{
|
||||
@@ -296,10 +361,26 @@ namespace NecromancerTome
|
||||
ThreadsChanged();
|
||||
}
|
||||
|
||||
public bool AddThreadOut(Vector3i _target)
|
||||
{
|
||||
if (ThreadOut.Contains(_target))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (ThreadOut.Count >= CinerariumThreads.MaxFrom)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ThreadOut.Add(_target);
|
||||
ThreadsChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
public int ClearThreads()
|
||||
{
|
||||
int n = ThreadFrom.Count + (HasThreadTo ? 1 : 0);
|
||||
int n = ThreadFrom.Count + (HasThreadTo ? 1 : 0) + ThreadOut.Count;
|
||||
ThreadFrom.Clear();
|
||||
ThreadOut.Clear();
|
||||
HasThreadTo = false;
|
||||
if (n > 0)
|
||||
{
|
||||
@@ -310,7 +391,7 @@ namespace NecromancerTome
|
||||
|
||||
public int RemoveThreadsTo(Vector3i _box)
|
||||
{
|
||||
int n = ThreadFrom.RemoveAll(p => p == _box);
|
||||
int n = ThreadFrom.RemoveAll(p => p == _box) + ThreadOut.RemoveAll(p => p == _box);
|
||||
if (HasThreadTo && ThreadTo == _box)
|
||||
{
|
||||
HasThreadTo = false;
|
||||
@@ -328,16 +409,17 @@ namespace NecromancerTome
|
||||
_threadsDirty = true;
|
||||
_glowState = -1; // логист - другие глаза
|
||||
SetModified();
|
||||
Debug.Log("[NecromancerTome] Cinerarium: нити урны " + ToWorldPos() + " - откуда " + ThreadFrom.Count + ", куда " + (HasThreadTo ? ThreadTo.ToString() : "нет"));
|
||||
Debug.Log("[NecromancerTome] Cinerarium: нити урны " + ToWorldPos() + " - откуда " + ThreadFrom.Count + ", куда " + (HasThreadTo ? ThreadTo.ToString() : "нет") + (ThreadOut.Count > 0 ? ", рабочему " + ThreadOut.Count : ""));
|
||||
}
|
||||
|
||||
/// <summary>Нити к убранным ящикам снимаются (раз в 2 с); вид перестраивается после изменений.</summary>
|
||||
void SyncThreads(World _world)
|
||||
{
|
||||
if (_world != null && ConnectionManager.Instance.IsServer && HasThreads && !IsLogist && _logistKnown)
|
||||
if (_world != null && ConnectionManager.Instance.IsServer && HasThreads && !IsZoneless && _logistKnown)
|
||||
{
|
||||
Debug.Log("[NecromancerTome] Cinerarium: нити у обычной урны " + ToWorldPos() + " сняты - нити только у урны-логиста");
|
||||
Debug.Log("[NecromancerTome] Cinerarium: нити у обычной урны " + ToWorldPos() + " сняты - нити только у логиста и рабочего");
|
||||
ThreadFrom.Clear();
|
||||
ThreadOut.Clear();
|
||||
HasThreadTo = false;
|
||||
ThreadsChanged();
|
||||
}
|
||||
@@ -362,6 +444,15 @@ namespace NecromancerTome
|
||||
HasThreadTo = false;
|
||||
gone = true;
|
||||
}
|
||||
foreach (Vector3i p in ThreadOut.ToArray())
|
||||
{
|
||||
bool box = CinerariumThreads.Resolve(_world, p, out Vector3i _, out TEFeatureCinerarium u3) && u3 == null;
|
||||
if (!box && CinerariumThreads.StationAt(_world, p) == null && _world.GetChunkFromWorldPos(p) != null)
|
||||
{
|
||||
ThreadOut.Remove(p);
|
||||
gone = true;
|
||||
}
|
||||
}
|
||||
if (gone)
|
||||
{
|
||||
ThreadsChanged();
|
||||
@@ -399,6 +490,10 @@ namespace NecromancerTome
|
||||
{
|
||||
pairs.Add((CinerariumThreads.UrnPoint(me), CinerariumThreads.BoxPoint(ThreadTo, me)));
|
||||
}
|
||||
foreach (Vector3i p in ThreadOut)
|
||||
{
|
||||
pairs.Add((CinerariumThreads.UrnPoint(me), CinerariumThreads.BoxPoint(p, me)));
|
||||
}
|
||||
_threadView.GetComponent<CinerariumThreadView>().Build(pairs);
|
||||
}
|
||||
|
||||
@@ -522,6 +617,20 @@ namespace NecromancerTome
|
||||
Debug.Log("[NecromancerTome] Cinerarium: урна " + ToWorldPos() + (_has ? " - череп положен" : " - череп вынут"));
|
||||
}
|
||||
|
||||
/// <summary>Образец рабочего положен, изменён или вынут.</summary>
|
||||
public void SetSample(ItemStack _s)
|
||||
{
|
||||
ItemStack was = Sample ?? ItemStack.Empty;
|
||||
if (was.itemValue.type == _s.itemValue.type && was.count == _s.count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Sample = _s;
|
||||
SetModified();
|
||||
Debug.Log("[NecromancerTome] Cinerarium: образец урны " + ToWorldPos() + " - " +
|
||||
(_s.IsEmpty() ? "вынут" : _s.itemValue.ItemClass?.GetItemName() + " x" + _s.count));
|
||||
}
|
||||
|
||||
/// <summary>«Починить», как у транспорта: целиком, за RepairBones костей из рюкзака (сколько берёт и молоток -
|
||||
/// RepairItems в blocks.xml).</summary>
|
||||
public const string RepairItemName = "resourceBone";
|
||||
@@ -574,7 +683,7 @@ namespace NecromancerTome
|
||||
SyncGlow();
|
||||
if (HasSkull && _genie == null)
|
||||
{
|
||||
_genie = CinerariumGenie.Spawn(ModelTransform());
|
||||
_genie = CinerariumGenie.Spawn(ModelTransform(), IsWorker ? CinerariumGenie.WorkerDrop : 0f);
|
||||
if (_genie != null && _sound)
|
||||
{
|
||||
Audio.Manager.BroadcastPlay(World.blockToTransformPos(ToWorldPos()), GenieSound);
|
||||
@@ -754,7 +863,8 @@ namespace NecromancerTome
|
||||
public static bool IsUrn(Block _b)
|
||||
{
|
||||
string n = _b?.GetBlockName();
|
||||
return n == BlockName || n == SkullBlockName || n == LogistBlockName || n == LogistSkullBlockName;
|
||||
return n == BlockName || n == SkullBlockName || n == LogistBlockName || n == LogistSkullBlockName ||
|
||||
n == WorkerBlockName || n == WorkerSkullBlockName;
|
||||
}
|
||||
|
||||
public static int UrnLimit(EntityPlayer _player)
|
||||
@@ -766,13 +876,13 @@ namespace NecromancerTome
|
||||
/// <summary>Ключ подсказки, почему урну сюда нельзя, или null.</summary>
|
||||
public static string PlacementRefusal(Vector3i _pos, Block _placing)
|
||||
{
|
||||
if (!IsLogistBlock(_placing) && CinerariumRegistry.Overlaps(_pos))
|
||||
if (!IsZonelessBlock(_placing) && CinerariumRegistry.Overlaps(_pos))
|
||||
{
|
||||
return "necroCinerariumOverlap";
|
||||
}
|
||||
if (IsLogistBlock(_placing))
|
||||
if (IsZonelessBlock(_placing))
|
||||
{
|
||||
return null; // логисты в лимит не входят (16.10, пользователь)
|
||||
return null; // логисты и рабочие в лимит не входят (16.10, 17.10 - пользователь)
|
||||
}
|
||||
World world = GameManager.Instance?.World;
|
||||
EntityPlayerLocal player = world?.GetPrimaryPlayer();
|
||||
@@ -892,7 +1002,7 @@ namespace NecromancerTome
|
||||
{
|
||||
// Логисты не считаются; записанные до метки «L» узнаются по блоку в мире (чанк рядом загружен).
|
||||
if (kv.Value == id && !_logists.Contains(kv.Key) &&
|
||||
!(world != null && TEFeatureCinerarium.IsLogistBlock(world.GetBlock(kv.Key).Block)))
|
||||
!(world != null && TEFeatureCinerarium.IsZonelessBlock(world.GetBlock(kv.Key).Block)))
|
||||
{
|
||||
n++;
|
||||
}
|
||||
@@ -914,9 +1024,9 @@ namespace NecromancerTome
|
||||
World world = GameManager.Instance?.World;
|
||||
foreach (Vector3i p in Urns().Keys)
|
||||
{
|
||||
if (world != null && TEFeatureCinerarium.IsLogistBlock(world.GetBlock(p).Block))
|
||||
if (world != null && TEFeatureCinerarium.IsZonelessBlock(world.GetBlock(p).Block))
|
||||
{
|
||||
continue; // у логиста зоны нет
|
||||
continue; // у логиста и рабочего зоны нет
|
||||
}
|
||||
if (p != _pos && Math.Abs(p.x - _pos.x) <= w && Math.Abs(p.z - _pos.z) <= w && Math.Abs(p.y - _pos.y) <= h)
|
||||
{
|
||||
@@ -969,11 +1079,11 @@ namespace NecromancerTome
|
||||
}
|
||||
else if (text.EndsWith("bounds_show"))
|
||||
{
|
||||
_commands[i].enabled = !urn.ZoneShown && !urn.IsLogist;
|
||||
_commands[i].enabled = !urn.ZoneShown && !urn.IsZoneless;
|
||||
}
|
||||
else if (text.EndsWith("bounds_hide"))
|
||||
{
|
||||
_commands[i].enabled = urn.ZoneShown && !urn.IsLogist;
|
||||
_commands[i].enabled = urn.ZoneShown && !urn.IsZoneless;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1107,15 +1217,19 @@ namespace NecromancerTome
|
||||
GameManager.ShowTooltip(player, Localization.Get("ttCantPickupInUse"), string.Empty, "ui_denied");
|
||||
return false;
|
||||
}
|
||||
if (composite.TryGetSelfOrFeature<TEFeatureStorage>(out TEFeatureStorage bag) && !bag.IsEmpty())
|
||||
if (composite.TryGetSelfOrFeature<TEFeatureStorage>(out TEFeatureStorage bag) && !bag.IsEmpty() ||
|
||||
urn.Sample != null && !urn.Sample.IsEmpty())
|
||||
{
|
||||
GameManager.ShowTooltip(player, Localization.Get("necroCinerariumBagNotEmpty"), string.Empty, "ui_denied");
|
||||
return false;
|
||||
}
|
||||
bool logist = TEFeatureCinerarium.IsLogistBlock(bv.Block);
|
||||
bool worker = TEFeatureCinerarium.IsWorkerBlock(bv.Block);
|
||||
ItemValue iv = ItemClass.GetItem(logist
|
||||
? (urn.HasSkull ? TEFeatureCinerarium.LogistSkullBlockName : TEFeatureCinerarium.LogistBlockName)
|
||||
: (urn.HasSkull ? TEFeatureCinerarium.SkullBlockName : TEFeatureCinerarium.BlockName));
|
||||
: worker
|
||||
? (urn.HasSkull ? TEFeatureCinerarium.WorkerSkullBlockName : TEFeatureCinerarium.WorkerBlockName)
|
||||
: (urn.HasSkull ? TEFeatureCinerarium.SkullBlockName : TEFeatureCinerarium.BlockName));
|
||||
if (iv == null || iv.type <= 0)
|
||||
{
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user