using System.Collections.Generic; using UnityEngine; namespace NecromancerTome { /// /// Работа духа урны - этап 4 некротехнолога (указание 16.10: дух - УНИВЕРСАЛЬНЫЙ работник зоны; BACKLOG «Некротехнолог v2»). /// Раз в StepSeconds - одно дело, по порядку: /// 1. пополнить расходники коллекторов из ящиков зоны: ячейки топлива (банки, цветы, корм, сферы - что коллектор /// принимает, GetFuelTypes) и катализатора (куры - GetCatalystTypes; по одной в пустую ячейку). Модификации /// коллекторов не трогаются; /// 2. забрать готовое из коллекторов (Items) и станций (TileEntityWorkstation.Output - верстак, кузня, химстанция, /// костёр, бетономешалка) и сложить в ящики зоны: сначала к таким же предметам, потом в свободное место; ящики полны - /// в сумку урны; некуда - дух чернеет и замирает над урной, оживает сам, когда место появится; /// 3. грядки - последними (ещё не сделано). /// Коллектор или станция подходят любые - будущий крольчатник обслуживается тем же кодом. Ящик - составной блок игрока /// с хранилищем (TEFeatureStorage, PlayerPlaced), кроме урн. Открытое у игрока (IsUserAccessing) не трогается. /// Работает на сервере: зовётся из TEFeatureCinerarium.UpdateTick. /// public static class CinerariumWork { public static float StepSeconds = 3f; /// Что в зоне: собирается заново на каждом шаге (150 клеток - дёшево, а ящики могут переставить). sealed class Zone { public readonly List Boxes = new List(); public readonly List Collectors = new List(); public readonly List Stations = new List(); public readonly Dictionary Where = new Dictionary(); public TEFeatureStorage Bag; } static readonly HashSet _seen = new HashSet(); /// Одно дело. Возвращает, где дух работал (для полёта модели), или null; _stuck - есть что забрать, но некуда. 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(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(out TEFeatureCinerarium _) && comp.TryGetSelfOrFeature(out TEFeatureStorage box)) { z.Boxes.Add(box); z.Where[comp] = owner; } } } } return z; } /// Дополнить ячейки коллектора тем, что он принимает, из ящиков и сумки. _single - по одному предмету в /// пустую ячейку (куры), иначе до полного стака. true - что-то положено. 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; } /// Одна стопка готового - в ящики (к таким же, потом в пустое), затем в сумку урны. true - что-то перенесено; /// _blocked - была стопка, которой не нашлось места. 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 _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; } } } /// Откуда брать расходники: сумка урны (там то, что дух накопил), потом ящики зоны. static IEnumerable Sources(Zone _z) { if (_z.Bag != null) { yield return _z.Bag; } foreach (TEFeatureStorage b in _z.Boxes) { yield return b; } } /// Ячейка, закреплённая игроком (замок в окне ящика), - не трогать. 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(); } } }