Раз в 3 с одно дело: (1) пополнить из ящиков зоны и сумки урны ячейки расходников любого коллектора (банки, цветы, корм, сферы) и катализатора (куры, по одной в пустую ячейку); (2) забрать готовое из коллекторов и станций (верстак, кузня, химстанция, костёр...) в ящики зоны - сначала к таким же, потом в пустое, ящики полны - в сумку урны; некуда - дух чернеет и замирает, оживает сам. Модификации, инструменты, топливо и сырьё станций не трогаются; открытое у игрока и закреплённые ячейки - тоже. Модель духа плывёт к месту работы и обратно. Грядки - позже. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
272 lines
9.2 KiB
C#
272 lines
9.2 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
namespace NecromancerTome
|
|
{
|
|
/// <summary>
|
|
/// Работа духа урны - этап 4 некротехнолога (указание 16.10: дух - УНИВЕРСАЛЬНЫЙ работник зоны; BACKLOG «Некротехнолог v2»).
|
|
/// Раз в StepSeconds - одно дело, по порядку:
|
|
/// 1. пополнить расходники коллекторов из ящиков зоны: ячейки топлива (банки, цветы, корм, сферы - что коллектор
|
|
/// принимает, GetFuelTypes) и катализатора (куры - GetCatalystTypes; по одной в пустую ячейку). Модификации
|
|
/// коллекторов не трогаются;
|
|
/// 2. забрать готовое из коллекторов (Items) и станций (TileEntityWorkstation.Output - верстак, кузня, химстанция,
|
|
/// костёр, бетономешалка) и сложить в ящики зоны: сначала к таким же предметам, потом в свободное место; ящики полны -
|
|
/// в сумку урны; некуда - дух чернеет и замирает над урной, оживает сам, когда место появится;
|
|
/// 3. грядки - последними (ещё не сделано).
|
|
/// Коллектор или станция подходят любые - будущий крольчатник обслуживается тем же кодом. Ящик - составной блок игрока
|
|
/// с хранилищем (TEFeatureStorage, PlayerPlaced), кроме урн. Открытое у игрока (IsUserAccessing) не трогается.
|
|
/// Работает на сервере: зовётся из TEFeatureCinerarium.UpdateTick.
|
|
/// </summary>
|
|
public static class CinerariumWork
|
|
{
|
|
public static float StepSeconds = 3f;
|
|
|
|
/// <summary>Что в зоне: собирается заново на каждом шаге (150 клеток - дёшево, а ящики могут переставить).</summary>
|
|
sealed class Zone
|
|
{
|
|
public readonly List<TEFeatureStorage> Boxes = new List<TEFeatureStorage>();
|
|
public readonly List<TileEntityCollector> Collectors = new List<TileEntityCollector>();
|
|
public readonly List<TileEntityWorkstation> Stations = new List<TileEntityWorkstation>();
|
|
public readonly Dictionary<TileEntity, Vector3i> Where = new Dictionary<TileEntity, Vector3i>();
|
|
public TEFeatureStorage Bag;
|
|
}
|
|
|
|
static readonly HashSet<TileEntity> _seen = new HashSet<TileEntity>();
|
|
|
|
/// <summary>Одно дело. Возвращает, где дух работал (для полёта модели), или null; _stuck - есть что забрать, но некуда.</summary>
|
|
public static Vector3i? Step(World _world, TEFeatureCinerarium _urn, out bool _stuck)
|
|
{
|
|
_stuck = false;
|
|
Zone z = Scan(_world, _urn);
|
|
// 1. Расходники.
|
|
foreach (TileEntityCollector c in z.Collectors)
|
|
{
|
|
if (Refill(c, c.FuelSlots, c.GetFuelTypes(), false, z) || Refill(c, c.CatalystSlots, c.GetCatalystTypes(), true, z))
|
|
{
|
|
c.SetModified();
|
|
return z.Where[c];
|
|
}
|
|
}
|
|
// 2. Готовое.
|
|
bool blocked = false;
|
|
foreach (TileEntityCollector c in z.Collectors)
|
|
{
|
|
ItemStack[] items = c.Items;
|
|
if (Unload(items, z, ref blocked))
|
|
{
|
|
c.Items = items;
|
|
c.SetModified();
|
|
return z.Where[c];
|
|
}
|
|
}
|
|
foreach (TileEntityWorkstation w in z.Stations)
|
|
{
|
|
ItemStack[] output = w.Output;
|
|
if (Unload(output, z, ref blocked))
|
|
{
|
|
w.Output = output;
|
|
return z.Where[w];
|
|
}
|
|
}
|
|
_stuck = blocked;
|
|
return null;
|
|
}
|
|
|
|
static Zone Scan(World _world, TEFeatureCinerarium _urn)
|
|
{
|
|
Zone z = new Zone();
|
|
_seen.Clear();
|
|
Vector3i c = _urn.ToWorldPos();
|
|
if (_urn.Parent != null)
|
|
{
|
|
_urn.Parent.TryGetSelfOrFeature<TEFeatureStorage>(out z.Bag);
|
|
_seen.Add(_urn.Parent);
|
|
}
|
|
int w = TEFeatureCinerarium.HalfWidth;
|
|
for (int dy = -TEFeatureCinerarium.Below; dy <= TEFeatureCinerarium.Above; dy++)
|
|
{
|
|
for (int dx = -w; dx <= w; dx++)
|
|
{
|
|
for (int dz = -w; dz <= w; dz++)
|
|
{
|
|
Vector3i p = new Vector3i(c.x + dx, c.y + dy, c.z + dz);
|
|
BlockValue bv = _world.GetBlock(p);
|
|
if (bv.isair)
|
|
{
|
|
continue;
|
|
}
|
|
Vector3i owner = bv.ischild ? p + bv.parent : p; // часть составного блока (верстак 2x1) - к главной клетке
|
|
TileEntity te = _world.GetTileEntity(owner);
|
|
if (te == null || !_seen.Add(te) || te.IsUserAccessing())
|
|
{
|
|
continue;
|
|
}
|
|
if (te is TileEntityCollector col)
|
|
{
|
|
z.Collectors.Add(col);
|
|
z.Where[col] = owner;
|
|
}
|
|
else if (te is TileEntityWorkstation ws)
|
|
{
|
|
z.Stations.Add(ws);
|
|
z.Where[ws] = owner;
|
|
}
|
|
else if (te is TileEntityComposite comp && comp.PlayerPlaced &&
|
|
!comp.TryGetSelfOrFeature<TEFeatureCinerarium>(out TEFeatureCinerarium _) &&
|
|
comp.TryGetSelfOrFeature<TEFeatureStorage>(out TEFeatureStorage box))
|
|
{
|
|
z.Boxes.Add(box);
|
|
z.Where[comp] = owner;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return z;
|
|
}
|
|
|
|
/// <summary>Дополнить ячейки коллектора тем, что он принимает, из ящиков и сумки. _single - по одному предмету в
|
|
/// пустую ячейку (куры), иначе до полного стака. true - что-то положено.</summary>
|
|
static bool Refill(TileEntityCollector _c, ItemStack[] _slots, string[] _allowed, bool _single, Zone _z)
|
|
{
|
|
if (_slots == null || _allowed == null || _allowed.Length == 0)
|
|
{
|
|
return false;
|
|
}
|
|
for (int i = 0; i < _slots.Length; i++)
|
|
{
|
|
ItemStack slot = _slots[i];
|
|
ItemClass want = null;
|
|
int room;
|
|
if (slot == null || slot.IsEmpty())
|
|
{
|
|
room = -1; // любой допустимый
|
|
}
|
|
else
|
|
{
|
|
if (_single)
|
|
{
|
|
continue;
|
|
}
|
|
want = slot.itemValue.ItemClass;
|
|
room = want != null ? want.MaxCount - slot.count : 0;
|
|
if (room <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
foreach (TEFeatureStorage src in Sources(_z))
|
|
{
|
|
for (int k = 0; k < src.ItemGrid.Length; k++)
|
|
{
|
|
ItemStack s = src.ItemGrid[k];
|
|
if (s == null || s.IsEmpty() || Locked(src, k))
|
|
{
|
|
continue;
|
|
}
|
|
ItemClass ic = s.itemValue.ItemClass;
|
|
if (ic == null || (want != null ? ic != want : System.Array.IndexOf(_allowed, ic.GetItemName()) < 0))
|
|
{
|
|
continue;
|
|
}
|
|
int take = _single ? 1 : (room < 0 ? Mathf.Min(s.count, ic.MaxCount) : Mathf.Min(s.count, room));
|
|
if (take <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
if (want == null)
|
|
{
|
|
_slots[i] = new ItemStack(s.itemValue.Clone(), take);
|
|
}
|
|
else
|
|
{
|
|
_slots[i].count += take;
|
|
}
|
|
Take(src, k, take);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>Одна стопка готового - в ящики (к таким же, потом в пустое), затем в сумку урны. true - что-то перенесено;
|
|
/// _blocked - была стопка, которой не нашлось места.</summary>
|
|
static bool Unload(ItemStack[] _items, Zone _z, ref bool _blocked)
|
|
{
|
|
if (_items == null)
|
|
{
|
|
return false;
|
|
}
|
|
for (int i = 0; i < _items.Length; i++)
|
|
{
|
|
ItemStack s = _items[i];
|
|
if (s == null || s.IsEmpty())
|
|
{
|
|
continue;
|
|
}
|
|
int before = s.count;
|
|
// Сначала ящики зоны (к таким же, потом в пустое), они полны - тогда сумка урны (решение 06.10).
|
|
Place(s, _z.Boxes);
|
|
if (s.count > 0 && _z.Bag != null)
|
|
{
|
|
Place(s, new[] { _z.Bag });
|
|
}
|
|
if (s.count == 0)
|
|
{
|
|
_items[i] = ItemStack.Empty.Clone();
|
|
return true;
|
|
}
|
|
if (s.count != before)
|
|
{
|
|
return true;
|
|
}
|
|
_blocked = true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void Place(ItemStack _s, IList<TEFeatureStorage> _dst)
|
|
{
|
|
for (int i = 0; i < _dst.Count && _s.count > 0; i++)
|
|
{
|
|
_dst[i].TryStackItem(0, _s);
|
|
}
|
|
for (int i = 0; i < _dst.Count && _s.count > 0; i++)
|
|
{
|
|
if (_dst[i].AddItem(_s.Clone()))
|
|
{
|
|
_s.count = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Откуда брать расходники: сумка урны (там то, что дух накопил), потом ящики зоны.</summary>
|
|
static IEnumerable<TEFeatureStorage> Sources(Zone _z)
|
|
{
|
|
if (_z.Bag != null)
|
|
{
|
|
yield return _z.Bag;
|
|
}
|
|
foreach (TEFeatureStorage b in _z.Boxes)
|
|
{
|
|
yield return b;
|
|
}
|
|
}
|
|
|
|
/// <summary>Ячейка, закреплённая игроком (замок в окне ящика), - не трогать.</summary>
|
|
static bool Locked(TEFeatureStorage _src, int _slot)
|
|
{
|
|
PackedBoolArray locks = _src.ItemGrid.SlotLocks;
|
|
return locks != null && _slot < locks.Length && locks[_slot];
|
|
}
|
|
|
|
static void Take(TEFeatureStorage _src, int _slot, int _count)
|
|
{
|
|
ItemStack s = _src.ItemGrid[_slot].Clone();
|
|
s.count -= _count;
|
|
_src.UpdateSlot(_slot, s.count > 0 ? s : ItemStack.Empty.Clone());
|
|
_src.SetModified();
|
|
}
|
|
}
|
|
}
|