717 lines
23 KiB
C#
717 lines
23 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.Scripting;
|
|
|
|
namespace NecromancerTome
|
|
{
|
|
/// <summary>
|
|
/// Урны с большой зоной - ремонтник и лутер (решения 17.10 и 18.10, BACKLOG «Урна ремонтника», «Урна лутера»).
|
|
///
|
|
/// Общее. Зона - квадрат со стороной блока притязания (LandClaimSize сервера, по умолчанию 41) или меньше (31/21/11, пункт
|
|
/// меню «Размер зоны»), по высоте - весь столб мира; зоны таких урн могут пересекаться. Работа - только по кнопке (17.10: «А
|
|
/// если сделать рубильник или кнопку. Включил, ремонтник начал работу»): «Запустить» - один проход по зоне, конец прохода -
|
|
/// снова ожидание; до нажатия урна ничего не считает. Кнопки - на самой урне с четырёх сторон («как у дверного звонка, но с
|
|
/// черепом»), их цвет - состояние (CinerariumButtons): не горят - нет черепа; жёлтая ровно - ожидание; жёлтая мигает - не
|
|
/// хватает материалов (ремонтник) или ящик полон (лутер); зелёная - работа; красная - прилив (работа стоит, дух в урне).
|
|
///
|
|
/// Ремонтник. Обход - по столбам зоны, порциями из общего бюджета клеток на кадр (CellsPerFrame на все урны); в столбе - от
|
|
/// низа до высоты чанка, земля мира (форма terrain) и воздух пропускаются сразу. Найденный повреждённый блок с RepairItems
|
|
/// встаёт в очередь (не длиннее QueueMax - обход ждёт), блок берёт тот, кто нашёл первым (Claimed). Ремонт - неспешно: одно
|
|
/// действие в RepairSeconds, дух летит к блоку и машет руками; материалы - RepairItems блока, доля по повреждению (как молотком),
|
|
/// из ящиков с нитями к урне. Не хватает - кнопка мигает, урна ждёт (положил в ящик - продолжит сама). Разрушенное не
|
|
/// восстанавливает - только повреждённое (17.10: «Восстанавливаю порушенное, затем включаю ремонтника и ухожу по делам»).
|
|
///
|
|
/// Лутер. За действие - одна цель в зоне: предмет на земле, затем мешок с зомби, затем красный труп (тем же путём, что игрок:
|
|
/// LootBagOpened + NecroSkull.Roll, стадия добычи - того, кто нажал кнопку); добыча - в ящик с любой нитью к урне. Ящики
|
|
/// зданий, аэродроп и рюкзаки погибших игроков не трогает (18.10 «Принимаю всё»). Целей нет - проход окончен.
|
|
/// </summary>
|
|
public partial class TEFeatureCinerarium
|
|
{
|
|
public const string RepairBlockName = "necroCinerariumRepair";
|
|
public const string RepairSkullBlockName = "necroCinerariumRepairSkull";
|
|
public const string LootBlockName = "necroCinerariumLoot";
|
|
public const string LootSkullBlockName = "necroCinerariumLootSkull";
|
|
|
|
public static bool IsRepairBlock(Block _b)
|
|
{
|
|
string n = _b?.GetBlockName();
|
|
return n == RepairBlockName || n == RepairSkullBlockName;
|
|
}
|
|
|
|
public static bool IsLootBlock(Block _b)
|
|
{
|
|
string n = _b?.GetBlockName();
|
|
return n == LootBlockName || n == LootSkullBlockName;
|
|
}
|
|
|
|
bool _repair, _loot;
|
|
|
|
public bool IsRepairer
|
|
{
|
|
get
|
|
{
|
|
_ = IsLogist;
|
|
return _repair;
|
|
}
|
|
}
|
|
|
|
public bool IsLooter
|
|
{
|
|
get
|
|
{
|
|
_ = IsLogist;
|
|
return _loot;
|
|
}
|
|
}
|
|
|
|
public bool IsBigZone => IsRepairer || IsLooter;
|
|
|
|
/// <summary>Сторона зоны, выбранная в меню (0 - по притязанию). Сохраняется (v6).</summary>
|
|
public int ZoneSide;
|
|
public bool ZoneRunning;
|
|
/// <summary>Обход ремонтника: номер следующего столба зоны (строка за строкой).</summary>
|
|
public int ZoneCursor;
|
|
/// <summary>Кто нажал «Запустить» - стадия добычи лутера.</summary>
|
|
public int ZonePressedBy = -1;
|
|
/// <summary>Не хватает материалов / ящик полон - кнопка мигает жёлтым.</summary>
|
|
public bool ZoneLacking;
|
|
|
|
public static float RepairSeconds = 2.5f;
|
|
public static float LootSeconds = 1.5f;
|
|
public static int CellsPerFrame = 4000;
|
|
public static int QueueMax = 32;
|
|
|
|
static int _cellsFrame = -1, _cellsUsed;
|
|
/// <summary>Блоки, взятые ремонтниками в очередь (зоны пересекаются - чинит взявший первым).</summary>
|
|
static readonly HashSet<Vector3i> Claimed = new HashSet<Vector3i>();
|
|
|
|
readonly List<Vector3i> _queue = new List<Vector3i>();
|
|
float _zoneNext;
|
|
bool _zonePending;
|
|
float _zoneActAt;
|
|
Vector3i _zoneTarget;
|
|
int _zoneTargetEntity = -1;
|
|
|
|
/// <summary>Стороны зоны: притязание сервера и меньшие нечётные (31, 21, 11), не больше притязания.</summary>
|
|
public static List<int> ZoneSides()
|
|
{
|
|
int claim = Mathf.Max(11, GamePrefs.GetInt(EnumGamePrefs.LandClaimSize));
|
|
List<int> l = new List<int> { claim };
|
|
foreach (int s in new[] { 31, 21, 11 })
|
|
{
|
|
if (s < claim)
|
|
{
|
|
l.Add(s);
|
|
}
|
|
}
|
|
return l;
|
|
}
|
|
|
|
public int ZoneSideNow
|
|
{
|
|
get
|
|
{
|
|
List<int> l = ZoneSides();
|
|
return ZoneSide > 0 && l.Contains(ZoneSide) ? ZoneSide : l[0];
|
|
}
|
|
}
|
|
|
|
public enum ButtonState
|
|
{
|
|
Off,
|
|
Wait,
|
|
Lacking,
|
|
Busy,
|
|
Tide
|
|
}
|
|
|
|
public ButtonState Buttons
|
|
{
|
|
get
|
|
{
|
|
if (!HasSkull)
|
|
{
|
|
return ButtonState.Off;
|
|
}
|
|
if (CinerariumLoad.Tide)
|
|
{
|
|
return ButtonState.Tide;
|
|
}
|
|
if (!ZoneRunning)
|
|
{
|
|
return ButtonState.Wait;
|
|
}
|
|
return ZoneLacking ? ButtonState.Lacking : ButtonState.Busy;
|
|
}
|
|
}
|
|
|
|
string ZoneStateText()
|
|
{
|
|
string key = Buttons switch
|
|
{
|
|
ButtonState.Tide => "necroZoneUrnTide",
|
|
ButtonState.Lacking => IsLooter ? "necroZoneUrnFull" : "necroZoneUrnNoMaterials",
|
|
ButtonState.Busy => "necroZoneUrnBusy",
|
|
ButtonState.Wait => "necroZoneUrnWait",
|
|
_ => null
|
|
};
|
|
string side = ZoneSideNow.ToString();
|
|
return (key != null ? ", " + Localization.Get(key) : "") + ", " + string.Format(Localization.Get("necroZoneUrnSize"), side);
|
|
}
|
|
|
|
/// <summary>Пункты меню на E: запустить, остановить, размер зоны (по кругу).</summary>
|
|
public void ZoneCommand(string _cmd, EntityPlayerLocal _player)
|
|
{
|
|
if (_cmd.EndsWith("zurn_run"))
|
|
{
|
|
if (!HasSkull)
|
|
{
|
|
return;
|
|
}
|
|
ZoneRunning = true;
|
|
ZoneCursor = 0;
|
|
ZoneLacking = false;
|
|
ZonePressedBy = _player != null ? _player.entityId : -1;
|
|
_queue.Clear();
|
|
_lootDone.Clear();
|
|
Audio.Manager.BroadcastPlay(World.blockToTransformPos(ToWorldPos()), "button4");
|
|
Debug.Log("[NecromancerTome] Cinerarium: " + (IsLooter ? "лутер" : "ремонтник") + " " + ToWorldPos() + " запущен, зона " + ZoneSideNow);
|
|
}
|
|
else if (_cmd.EndsWith("zurn_stop"))
|
|
{
|
|
ZoneStop(true);
|
|
Audio.Manager.BroadcastPlay(World.blockToTransformPos(ToWorldPos()), "button4");
|
|
}
|
|
else if (_cmd.EndsWith("zurn_size"))
|
|
{
|
|
List<int> l = ZoneSides();
|
|
int i = l.IndexOf(ZoneSideNow);
|
|
ZoneSide = l[(i + 1) % l.Count];
|
|
if (_player != null)
|
|
{
|
|
GameManager.ShowTooltip(_player, string.Format(Localization.Get("necroZoneUrnSize"), ZoneSide.ToString()));
|
|
}
|
|
if (ZoneShown)
|
|
{
|
|
RemoveBounds();
|
|
SpawnBounds();
|
|
}
|
|
}
|
|
SetModified();
|
|
}
|
|
|
|
/// <summary>Остановить проход (кнопка, урна убрана): очередь и взятые блоки отпускаются.</summary>
|
|
public void ZoneStop(bool _log)
|
|
{
|
|
foreach (Vector3i p in _queue)
|
|
{
|
|
Claimed.Remove(p);
|
|
}
|
|
_queue.Clear();
|
|
_zonePending = false;
|
|
if (ZoneRunning && _log)
|
|
{
|
|
Debug.Log("[NecromancerTome] Cinerarium: " + (IsLooter ? "лутер" : "ремонтник") + " " + ToWorldPos() + " остановлен");
|
|
}
|
|
ZoneRunning = false;
|
|
ZoneLacking = false;
|
|
}
|
|
|
|
void ZoneWork(World _world)
|
|
{
|
|
SyncButtons();
|
|
if (!ZoneRunning)
|
|
{
|
|
return;
|
|
}
|
|
CinerariumGenieMotion motion = _genie != null ? _genie.GetComponent<CinerariumGenieMotion>() : null;
|
|
if (_zonePending)
|
|
{
|
|
if (Time.time < _zoneActAt)
|
|
{
|
|
return;
|
|
}
|
|
_zonePending = false;
|
|
if (IsRepairer)
|
|
{
|
|
RepairNow(_world, _zoneTarget);
|
|
}
|
|
else
|
|
{
|
|
LootNow(_world, _zoneTargetEntity);
|
|
}
|
|
return;
|
|
}
|
|
if (IsRepairer)
|
|
{
|
|
ScanColumns(_world);
|
|
}
|
|
if (Time.time < _zoneNext || !CinerariumLoad.TryTakeStep(ToWorldPos()))
|
|
{
|
|
return;
|
|
}
|
|
_zoneNext = Time.time + (IsRepairer ? RepairSeconds : LootSeconds);
|
|
if (IsRepairer)
|
|
{
|
|
RepairPick(_world, motion);
|
|
}
|
|
else
|
|
{
|
|
LootPick(_world, motion);
|
|
}
|
|
}
|
|
|
|
/// <summary>Слетать к месту и сделать дело по прилёте (как дух сборщика) или сразу, если духа нет.</summary>
|
|
void FlyThenAct(World _world, CinerariumGenieMotion _motion, Vector3i _at)
|
|
{
|
|
_zonePending = true;
|
|
_zoneActAt = Time.time;
|
|
if (_motion != null)
|
|
{
|
|
List<(Vector3, Vector3, bool)> stops = new List<(Vector3, Vector3, bool)>
|
|
{
|
|
(Stand(_world, _at, out bool beside) - Origin.position, World.blockToTransformPos(_at) - Origin.position, beside)
|
|
};
|
|
_zoneActAt = Time.time + _motion.Visit(stops);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// Ремонтник.
|
|
// ------------------------------------------------------------------
|
|
|
|
void ScanColumns(World _world)
|
|
{
|
|
if (_queue.Count >= QueueMax)
|
|
{
|
|
return;
|
|
}
|
|
if (Time.frameCount != _cellsFrame)
|
|
{
|
|
_cellsFrame = Time.frameCount;
|
|
_cellsUsed = 0;
|
|
}
|
|
int side = ZoneSideNow, half = side / 2, total = side * side;
|
|
Vector3i c = ToWorldPos();
|
|
while (ZoneCursor < total && _cellsUsed < CellsPerFrame && _queue.Count < QueueMax)
|
|
{
|
|
int x = c.x - half + ZoneCursor % side, z = c.z - half + ZoneCursor / side;
|
|
ZoneCursor++;
|
|
if (!(_world.GetChunkFromWorldPos(x, 0, z) is Chunk ch))
|
|
{
|
|
continue; // не загружен - пропустить столб
|
|
}
|
|
int lx = World.toBlockXZ(x), lz = World.toBlockXZ(z);
|
|
int top = Mathf.Min(254, ch.GetHeight(lx, lz) + 1);
|
|
_cellsUsed += top;
|
|
for (int y = 1; y <= top; y++)
|
|
{
|
|
BlockValue bv = ch.GetBlock(lx, y, lz);
|
|
if (bv.isair || bv.ischild || bv.damage <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
Block b = bv.Block;
|
|
if (b == null || b.shape.IsTerrain() || b.RepairItems == null || b.RepairItems.Count == 0)
|
|
{
|
|
continue;
|
|
}
|
|
Vector3i p = new Vector3i(x, y, z);
|
|
if (Claimed.Add(p))
|
|
{
|
|
_queue.Add(p);
|
|
}
|
|
}
|
|
}
|
|
if (ZoneCursor >= total && _queue.Count == 0 && !_zonePending)
|
|
{
|
|
Debug.Log("[NecromancerTome] Cinerarium: ремонтник " + c + " обошёл зону - ожидание");
|
|
ZoneStop(false);
|
|
SetModified();
|
|
}
|
|
}
|
|
|
|
void RepairPick(World _world, CinerariumGenieMotion _motion)
|
|
{
|
|
while (_queue.Count > 0)
|
|
{
|
|
Vector3i p = _queue[0];
|
|
BlockValue bv = _world.GetBlock(p);
|
|
if (bv.isair || bv.damage <= 0 || bv.Block?.RepairItems == null)
|
|
{
|
|
_queue.RemoveAt(0);
|
|
Claimed.Remove(p);
|
|
continue; // починили до нас или блок убран
|
|
}
|
|
if (!HasMaterials(_world, bv, false))
|
|
{
|
|
if (!ZoneLacking)
|
|
{
|
|
Debug.Log("[NecromancerTome] Cinerarium: ремонтнику " + ToWorldPos() + " не хватает материалов для " + bv.Block.GetBlockName() + " в " + p);
|
|
}
|
|
ZoneLacking = true;
|
|
return; // ждём: положат в ящик - продолжим с того же блока
|
|
}
|
|
ZoneLacking = false;
|
|
_zoneTarget = p;
|
|
FlyThenAct(_world, _motion, p);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void RepairNow(World _world, Vector3i _p)
|
|
{
|
|
_queue.Remove(_p);
|
|
Claimed.Remove(_p);
|
|
BlockValue bv = _world.GetBlock(_p);
|
|
if (bv.isair || bv.damage <= 0 || bv.Block?.RepairItems == null)
|
|
{
|
|
return;
|
|
}
|
|
if (!HasMaterials(_world, bv, true))
|
|
{
|
|
ZoneLacking = true;
|
|
_queue.Insert(0, _p);
|
|
Claimed.Add(_p);
|
|
return;
|
|
}
|
|
int was = bv.damage;
|
|
bv.damage = 0;
|
|
_world.SetBlockRPC(_p, bv);
|
|
Debug.Log("[NecromancerTome] Cinerarium: ремонтник " + ToWorldPos() + " починил " + bv.Block.GetBlockName() + " в " + _p + " (было повреждение " + was + ")");
|
|
}
|
|
|
|
/// <summary>Хватает ли в ящиках с нитями к урне материалов на ремонт (RepairItems блока, доля по повреждению, не меньше
|
|
/// одного); _take - сразу и забрать.</summary>
|
|
bool HasMaterials(World _world, BlockValue _bv, bool _take)
|
|
{
|
|
Block b = _bv.Block;
|
|
float share = Mathf.Clamp01(_bv.damage / (float)Mathf.Max(1, b.MaxDamage));
|
|
List<TEFeatureStorage> boxes = ThreadBoxes(_world);
|
|
foreach (Block.SItemNameCount need in b.RepairItems)
|
|
{
|
|
ItemValue iv = ItemClass.GetItem(need.ItemName);
|
|
if (iv == null || iv.type == 0)
|
|
{
|
|
continue;
|
|
}
|
|
int n = Mathf.Max(1, Mathf.CeilToInt(need.Count * share));
|
|
int have = 0;
|
|
foreach (TEFeatureStorage s in boxes)
|
|
{
|
|
have += Count(s, iv);
|
|
}
|
|
if (have < n)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
if (!_take)
|
|
{
|
|
return true;
|
|
}
|
|
foreach (Block.SItemNameCount need in b.RepairItems)
|
|
{
|
|
ItemValue iv = ItemClass.GetItem(need.ItemName);
|
|
if (iv == null || iv.type == 0)
|
|
{
|
|
continue;
|
|
}
|
|
int n = Mathf.Max(1, Mathf.CeilToInt(need.Count * share));
|
|
foreach (TEFeatureStorage s in boxes)
|
|
{
|
|
n -= TakeFrom(s, iv, n);
|
|
if (n <= 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int Count(TEFeatureStorage _s, ItemValue _iv)
|
|
{
|
|
int n = 0;
|
|
for (int i = 0; i < _s.ItemGrid.Length; i++)
|
|
{
|
|
ItemStack st = _s.ItemGrid[i];
|
|
if (st != null && !st.IsEmpty() && st.itemValue.type == _iv.type && !CinerariumWork.Locked(_s, i))
|
|
{
|
|
n += st.count;
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
static int TakeFrom(TEFeatureStorage _s, ItemValue _iv, int _n)
|
|
{
|
|
int took = 0;
|
|
for (int i = 0; i < _s.ItemGrid.Length && took < _n; i++)
|
|
{
|
|
ItemStack st = _s.ItemGrid[i];
|
|
if (st == null || st.IsEmpty() || st.itemValue.type != _iv.type || CinerariumWork.Locked(_s, i))
|
|
{
|
|
continue;
|
|
}
|
|
int k = Mathf.Min(st.count, _n - took);
|
|
CinerariumWork.Take(_s, i, k);
|
|
took += k;
|
|
}
|
|
return took;
|
|
}
|
|
|
|
/// <summary>Ящики урны по нитям: у ремонтника - «откуда» (материалы), у лутера - любая нить (куда класть).</summary>
|
|
List<TEFeatureStorage> ThreadBoxes(World _world)
|
|
{
|
|
List<TEFeatureStorage> l = new List<TEFeatureStorage>();
|
|
void Add(Vector3i _p)
|
|
{
|
|
TEFeatureStorage s = CinerariumWork.BoxAt(_world, _p);
|
|
if (s != null && !l.Contains(s))
|
|
{
|
|
l.Add(s);
|
|
}
|
|
}
|
|
foreach (Vector3i p in ThreadFrom)
|
|
{
|
|
Add(p);
|
|
}
|
|
if (IsLooter)
|
|
{
|
|
if (HasThreadTo)
|
|
{
|
|
Add(ThreadTo);
|
|
}
|
|
foreach (Vector3i p in ThreadOut)
|
|
{
|
|
Add(p);
|
|
}
|
|
}
|
|
return l;
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// Лутер.
|
|
// ------------------------------------------------------------------
|
|
|
|
static readonly List<Entity> _ents = new List<Entity>();
|
|
/// <summary>Цели, уже обысканные за этот проход (пустой мешок, нечего взять) - второй раз не трогать.</summary>
|
|
readonly HashSet<int> _lootDone = new HashSet<int>();
|
|
|
|
/// <summary>Цель лутера в зоне: предмет на земле, потом мешок с зомби, потом красный труп. Ничего - проход окончен.</summary>
|
|
void LootPick(World _world, CinerariumGenieMotion _motion)
|
|
{
|
|
List<TEFeatureStorage> boxes = ThreadBoxes(_world);
|
|
if (boxes.Count == 0)
|
|
{
|
|
ZoneLacking = true; // некуда класть - как полный ящик
|
|
return;
|
|
}
|
|
int side = ZoneSideNow;
|
|
Vector3 c = World.blockToTransformPos(ToWorldPos());
|
|
Bounds bb = new Bounds(new Vector3(c.x, 128f, c.z), new Vector3(side, 256f, side));
|
|
Entity best = null;
|
|
int bestRank = int.MaxValue;
|
|
_ents.Clear();
|
|
_world.GetEntitiesInBounds(typeof(Entity), bb, _ents);
|
|
foreach (Entity e in _ents)
|
|
{
|
|
int rank = LootRank(e);
|
|
if (rank < bestRank && !_lootDone.Contains(e.entityId) && !LockManager.Instance.IsLockedServer(e, 0))
|
|
{
|
|
best = e;
|
|
bestRank = rank;
|
|
}
|
|
}
|
|
_ents.Clear();
|
|
if (best == null)
|
|
{
|
|
Debug.Log("[NecromancerTome] Cinerarium: лутер " + ToWorldPos() + " собрал всё в зоне - ожидание");
|
|
ZoneStop(false);
|
|
SetModified();
|
|
return;
|
|
}
|
|
ZoneLacking = false;
|
|
_zoneTargetEntity = best.entityId;
|
|
FlyThenAct(_world, _motion, World.worldToBlockPos(best.GetPosition()));
|
|
}
|
|
|
|
/// <summary>Что собирать и в каком порядке (меньше - раньше); int.MaxValue - не трогать. Рюкзаки игроков (EntityBackpack) и
|
|
/// аэродроп (EntitySupplyCrate) - не трогать.</summary>
|
|
static int LootRank(Entity _e)
|
|
{
|
|
if (_e == null || _e.IsDespawned)
|
|
{
|
|
return int.MaxValue;
|
|
}
|
|
if (_e is EntityBackpack || _e is EntitySupplyCrate)
|
|
{
|
|
return int.MaxValue;
|
|
}
|
|
if (_e is EntityLootContainer lc)
|
|
{
|
|
return lc.bag != null ? 1 : int.MaxValue;
|
|
}
|
|
if (_e is EntityItem it && !(_e is EntityContainerAbs))
|
|
{
|
|
return it.itemStack != null && !it.itemStack.IsEmpty() ? 0 : int.MaxValue;
|
|
}
|
|
if (_e is EntityAlive a && a.IsDead() && CorpseLoot.IsSearchable(a) && a.bag != null)
|
|
{
|
|
return 2;
|
|
}
|
|
return int.MaxValue;
|
|
}
|
|
|
|
void LootNow(World _world, int _id)
|
|
{
|
|
Entity e = _world.GetEntity(_id);
|
|
if (e == null || LootRank(e) == int.MaxValue || LockManager.Instance.IsLockedServer(e, 0))
|
|
{
|
|
return;
|
|
}
|
|
List<TEFeatureStorage> boxes = ThreadBoxes(_world);
|
|
if (e is EntityItem it && !(e is EntityContainerAbs))
|
|
{
|
|
ItemStack s = it.itemStack.Clone();
|
|
int before = s.count;
|
|
CinerariumWork.Place(s, boxes);
|
|
if (s.count == before)
|
|
{
|
|
ZoneLacking = true;
|
|
return;
|
|
}
|
|
if (s.count <= 0)
|
|
{
|
|
_world.RemoveEntity(e.entityId, EnumRemoveEntityReason.Despawned);
|
|
}
|
|
else
|
|
{
|
|
it.itemStack = s;
|
|
}
|
|
Debug.Log("[NecromancerTome] Cinerarium: лутер " + ToWorldPos() + " подобрал " + s.itemValue.ItemClass?.GetItemName() + " x" + (before - s.count));
|
|
return;
|
|
}
|
|
Bag bag = e is EntityAlive a ? a.bag : (e as EntityLootContainer)?.bag;
|
|
if (bag == null)
|
|
{
|
|
return;
|
|
}
|
|
if (!bag.Touched)
|
|
{
|
|
// Как игрок: первое открытие наполняет сумку по списку добычи (стадия - нажавшего кнопку); у красного трупа - и
|
|
// находка черепа (CorpseLootPatch: OnLockRequestServer).
|
|
int who = ZonePressedBy >= 0 ? ZonePressedBy : (_world.GetPrimaryPlayer()?.entityId ?? -1);
|
|
GameManager.Instance.lootManager.LootBagOpened(bag, e, who);
|
|
if (e is EntityAlive corpse)
|
|
{
|
|
NecroSkull.Roll(corpse, bag);
|
|
}
|
|
}
|
|
bool moved = false, left = false;
|
|
ItemStackGrid slots = bag.ItemGrid;
|
|
for (int i = 0; i < slots.Length; i++)
|
|
{
|
|
if (slots[i] == null || slots[i].IsEmpty())
|
|
{
|
|
continue;
|
|
}
|
|
ItemStack s = slots[i].Clone();
|
|
int before = s.count;
|
|
CinerariumWork.Place(s, boxes);
|
|
if (s.count != before)
|
|
{
|
|
moved = true;
|
|
}
|
|
if (s.count > 0)
|
|
{
|
|
left = true;
|
|
}
|
|
bag.SetSlot(i, s.count > 0 ? s : ItemStack.Empty.Clone());
|
|
}
|
|
ZoneLacking = left; // не всё влезло - ящик полон, кнопка мигает
|
|
if (!left)
|
|
{
|
|
_lootDone.Add(e.entityId); // опустошён (или был пуст) - больше не цель; пустой убирает игра
|
|
}
|
|
Debug.Log("[NecromancerTome] Cinerarium: лутер " + ToWorldPos() + " обыскал " + e.GetType().Name + " " + e.entityId + (left ? " - не всё влезло" : ""));
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// Кнопки на модели.
|
|
// ------------------------------------------------------------------
|
|
|
|
void SyncButtons()
|
|
{
|
|
Transform t = ModelTransform();
|
|
if (t == null)
|
|
{
|
|
return;
|
|
}
|
|
CinerariumButtons b = t.GetComponent<CinerariumButtons>();
|
|
if (b == null)
|
|
{
|
|
b = t.gameObject.AddComponent<CinerariumButtons>();
|
|
}
|
|
b.State = Buttons;
|
|
}
|
|
}
|
|
|
|
/// <summary>Кнопки-звонки с черепом на урне ремонтника/лутера: рендереры модели с «button» в имени. Цвет свечения - по
|
|
/// состоянию урны (TEFeatureCinerarium.Buttons), жёлтая мигает - нехватка.</summary>
|
|
[Preserve]
|
|
public class CinerariumButtons : MonoBehaviour
|
|
{
|
|
public TEFeatureCinerarium.ButtonState State;
|
|
List<Renderer> _buttons;
|
|
static readonly MaterialPropertyBlock _block = new MaterialPropertyBlock();
|
|
static readonly int EmissionColorId = Shader.PropertyToID("_EmissionColor");
|
|
public static Color Yellow = new Color(2.2f, 1.6f, 0.15f);
|
|
public static Color Green = new Color(0.3f, 2.2f, 0.4f);
|
|
public static Color Red = new Color(2.4f, 0.15f, 0.1f);
|
|
Color _last = new Color(-1f, 0f, 0f);
|
|
|
|
public static bool IsButton(Renderer _r) => _r != null && _r.name.ToLowerInvariant().Contains("button");
|
|
|
|
void Update()
|
|
{
|
|
if (_buttons == null)
|
|
{
|
|
_buttons = new List<Renderer>();
|
|
foreach (Renderer r in GetComponentsInChildren<Renderer>(true))
|
|
{
|
|
if (IsButton(r))
|
|
{
|
|
_buttons.Add(r);
|
|
}
|
|
}
|
|
}
|
|
Color c;
|
|
switch (State)
|
|
{
|
|
case TEFeatureCinerarium.ButtonState.Wait: c = Yellow; break;
|
|
case TEFeatureCinerarium.ButtonState.Lacking: c = Mathf.Repeat(Time.time, 0.8f) < 0.4f ? Yellow : Color.black; break;
|
|
case TEFeatureCinerarium.ButtonState.Busy: c = Green; break;
|
|
case TEFeatureCinerarium.ButtonState.Tide: c = Red; break;
|
|
default: c = Color.black; break;
|
|
}
|
|
if (c == _last)
|
|
{
|
|
return;
|
|
}
|
|
_last = c;
|
|
foreach (Renderer r in _buttons)
|
|
{
|
|
if (r == null)
|
|
{
|
|
continue;
|
|
}
|
|
r.GetPropertyBlock(_block);
|
|
_block.SetColor(EmissionColorId, c);
|
|
r.SetPropertyBlock(_block);
|
|
}
|
|
}
|
|
}
|
|
}
|