Урны ремонтника и лутера: большая зона, запуск кнопкой, кнопки-состояния

- necroCinerariumRepair/+Skull и necroCinerariumLoot/+Skull (пока на модели урны сборщика),
  рецепты (урна + ремкомплект / отмычка, на крови и на соке), открываются с урнами.
- Зона - сторона притязания сервера или 31/21/11 (пункт «Размер зоны»), весь столб мира,
  каркас по «Показать границы»; работа - по «Запустить», один проход, «Остановить».
- Ремонтник: обход столбов из общего бюджета клеток, очередь повреждённых блоков с
  RepairItems (зоны пересекаются - блок берёт первый), ремонт раз в 2.5 с материалами из
  ящиков по нитям (доля по повреждению), не хватает - ждёт с мигающей кнопкой.
- Лутер: предметы на земле, мешки с зомби, красные трупы (тем же путём, что игрок, с
  находкой черепа) - в ящик с любой нитью; рюкзаки игроков и аэродроп не трогает.
- Кнопки на модели (рендереры «button»): жёлтая - ждёт, мигает - нехватка/ящик полон,
  зелёная - работа, красная - прилив. Запись урны v6 (зона, проход, курсор, кто нажал).
- Тексты 13 языков; команды разработки necrodamage, necrodrop, necrobag, necrocorpse,
  necrourn run|stop|size.

Проверено в игре: ремонтник (нехватка -> материалы -> починил 2 блока), лутер (предметы
с земли). Мешки и красные трупы - не проверены.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlexCube
2026-10-18 14:55:33 +03:00
co-authored by Claude Opus 5.5
parent 85d8036e81
commit a352a408a4
9 changed files with 953 additions and 21 deletions
+83 -19
View File
@@ -27,7 +27,7 @@ namespace NecromancerTome
/// Имена команд не должны кончаться на zone_show/zone_hide/effect_on/effect_off: заплатка пирамиды на
/// BlockCompositeTileEntity.OnBlockActivated ловит их по EndsWith у ЛЮБОГО составного блока.
/// </summary>
public class TEFeatureCinerarium : TEFeatureAbs
public partial class TEFeatureCinerarium : TEFeatureAbs
{
public const string BlockName = "necroCinerarium";
public const string SkullBlockName = "necroCinerariumSkull";
@@ -62,8 +62,9 @@ namespace NecromancerTome
return n == WorkerBlockName || n == WorkerSkullBlockName;
}
/// <summary>Урна без зоны (логист, рабочий): ставится вплотную к любым урнам, в лимит не входит, работает по нитям.</summary>
public static bool IsZonelessBlock(Block _b) => IsLogistBlock(_b) || IsWorkerBlock(_b);
/// <summary>Урна без зоны 5x5 (логист, рабочий, ремонтник, лутер): ставится вплотную к любым урнам, в лимит не входит,
/// работает по нитям. У ремонтника и лутера своя большая зона (CinerariumZone.cs) - зоны таких урн могут пересекаться.</summary>
public static bool IsZonelessBlock(Block _b) => IsLogistBlock(_b) || IsWorkerBlock(_b) || IsRepairBlock(_b) || IsLootBlock(_b);
public const string SkullItemName = "necroSkull";
public const int HalfWidth = 2;
public const int Below = 1;
@@ -71,7 +72,7 @@ namespace NecromancerTome
/// <summary>Убийств на одну урну сборщика. 17.10: «лимит урн - ослабить/убрать» - нагрузку теперь держит общий бюджет
/// шагов (CinerariumLoad), лимит остался мягким игровым порогом: было 500, стало 200.</summary>
public const float KillsPerUrn = 200f;
const byte SaveVersion = 5; // 2 - нити, 3 - фильтр логиста (16.10), 4 - нити рабочего «куда» и образец (18.10), 5 - черепа-ретрансляторы (18.10)
const byte SaveVersion = 6; // 2 - нити, 3 - фильтр логиста (16.10), 4 - нити рабочего «куда» и образец (18.10), 5 - черепа-ретрансляторы (18.10), 6 - зона и запуск ремонтника/лутера (18.10)
public bool HasSkull;
public bool ZoneShown;
@@ -102,7 +103,7 @@ namespace NecromancerTome
}
}
bool _worker;
public bool IsZoneless => IsLogist || IsWorker;
public bool IsZoneless => IsLogist || IsWorker || IsBigZone;
/// <summary>Урна-логист - по блоку (necroCinerariumLogist / ...Skull); узнаётся при постановке или по миру.</summary>
public bool IsLogist
{
@@ -116,6 +117,8 @@ namespace NecromancerTome
{
_logist = IsLogistBlock(bv.Block);
_worker = IsWorkerBlock(bv.Block);
_repair = IsRepairBlock(bv.Block);
_loot = IsLootBlock(bv.Block);
_logistKnown = true;
}
}
@@ -163,15 +166,18 @@ namespace NecromancerTome
base.OnAdded(_blockPos, _blockValue);
_logist = IsLogistBlock(_blockValue.Block);
_worker = IsWorkerBlock(_blockValue.Block);
_repair = IsRepairBlock(_blockValue.Block);
_loot = IsLootBlock(_blockValue.Block);
_logistKnown = true;
string placed = _blockValue.Block?.GetBlockName();
if (placed == SkullBlockName || placed == LogistSkullBlockName || placed == LogistWallSkullBlockName || placed == WorkerSkullBlockName)
if (placed == SkullBlockName || placed == LogistSkullBlockName || placed == LogistWallSkullBlockName || placed == WorkerSkullBlockName ||
placed == RepairSkullBlockName || placed == LootSkullBlockName)
{
HasSkull = true; // поставили урну, поднятую с черепом
}
if (ConnectionManager.Instance.IsServer)
{
CinerariumRegistry.Add(_blockPos, Parent?.Owner, _logist || _worker);
CinerariumRegistry.Add(_blockPos, Parent?.Owner, _logist || _worker || _repair || _loot);
}
}
@@ -183,6 +189,7 @@ namespace NecromancerTome
RemoveThreadView();
RemoveGenie(false);
CinerariumContents.CloseIfShowing(this);
ZoneStop(false);
if (!ConnectionManager.Instance.IsServer)
{
return;
@@ -253,6 +260,8 @@ namespace NecromancerTome
AppendVia(via, l);
}
CinerariumWriteHelper.WriteInts(_bw, via.ToArray());
// v6: ремонтник/лутер - сторона зоны, идёт ли проход, где курсор обхода, кто нажал кнопку.
CinerariumWriteHelper.WriteInts(_bw, new[] { ZoneSide, ZoneRunning ? 1 : 0, ZoneCursor, ZonePressedBy });
}
static void AppendVia(List<int> _dst, List<Vector3i> _l)
@@ -389,6 +398,23 @@ namespace NecromancerTome
}
}
AlignVia();
ZoneSide = 0;
ZoneRunning = false;
ZoneCursor = 0;
ZonePressedBy = -1;
if (version >= 6)
{
int n = _br.ReadInt32();
int[] v = new int[n];
for (int i = 0; i < n; i++)
{
v[i] = _br.ReadInt32();
}
ZoneSide = n > 0 ? v[0] : 0;
ZoneRunning = n > 1 && v[1] != 0;
ZoneCursor = n > 2 ? v[2] : 0;
ZonePressedBy = n > 3 ? v[3] : -1;
}
_threadsDirty = true;
if (version > SaveVersion)
{
@@ -410,13 +436,18 @@ namespace NecromancerTome
_addCallback(new BlockActivationCommand("urn_repair", "wrench", _enabled: false), TileEntityComposite.EBlockCommandOrder.Normal, FeatureData);
_addCallback(new BlockActivationCommand("bounds_show", "frames", _enabled: false), TileEntityComposite.EBlockCommandOrder.Normal, FeatureData);
_addCallback(new BlockActivationCommand("bounds_hide", "frames", _enabled: false), TileEntityComposite.EBlockCommandOrder.Normal, FeatureData);
// Ремонтник, лутер (CinerariumZone.cs): запуск прохода кнопкой, остановка, размер зоны.
_addCallback(new BlockActivationCommand("zurn_run", "electric_switch", _enabled: false), TileEntityComposite.EBlockCommandOrder.Normal, FeatureData);
_addCallback(new BlockActivationCommand("zurn_stop", "electric_switch", _enabled: false), TileEntityComposite.EBlockCommandOrder.Normal, FeatureData);
_addCallback(new BlockActivationCommand("zurn_size", "map_cursor", _enabled: false), TileEntityComposite.EBlockCommandOrder.Normal, FeatureData);
}
public override string GetActivationText(WorldBase _world, Vector3i _blockPos, BlockValue _blockValue, EntityAlive _entityFocusing, string _activateHotkeyMarkup, string _focusedTileEntityName)
{
base.GetActivationText(_world, _blockPos, _blockValue, _entityFocusing, _activateHotkeyMarkup, _focusedTileEntityName);
return _activateHotkeyMarkup + " " + _blockValue.Block.GetLocalizedBlockName() + " - " +
Localization.Get(HasSkull ? "necroCinerariumStateSkull" : "necroCinerariumStateEmpty");
string s = _activateHotkeyMarkup + " " + _blockValue.Block.GetLocalizedBlockName() + " - " +
Localization.Get(HasSkull ? "necroCinerariumStateSkull" : "necroCinerariumStateEmpty");
return IsBigZone ? s + ZoneStateText() : s;
}
public override void UpdateTick(World _world)
@@ -716,6 +747,11 @@ namespace NecromancerTome
_pending = false;
return;
}
if (IsBigZone)
{
ZoneWork(_world); // ремонтник, лутер - CinerariumZone.cs
return;
}
CinerariumGenieMotion motion = _genie != null ? _genie.GetComponent<CinerariumGenieMotion>() : null;
if (_pending)
{
@@ -926,7 +962,7 @@ namespace NecromancerTome
foreach (Renderer r in t.GetComponentsInChildren<Renderer>(true))
{
if (_genie != null && r.transform.IsChildOf(_genie.transform) || _bounds != null && r.transform.IsChildOf(_bounds.transform) ||
_threadView != null && r.transform.IsChildOf(_threadView.transform))
_threadView != null && r.transform.IsChildOf(_threadView.transform) || CinerariumButtons.IsButton(r))
{
continue;
}
@@ -1013,11 +1049,13 @@ namespace NecromancerTome
lr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
lr.receiveShadows = false;
// Углы по граням блоков: x и z от -2.5 до +2.5 от центра урны, y от низа блока под ней до верха +4.
// Углы по граням блоков: x и z от -2.5 до +2.5 от центра урны, y от низа блока под ней до верха +4. Большая зона
// (ремонтник, лутер) - сторона ZoneSideNow, по высоте весь столб мира.
Vector3 c = World.blockToTransformPos(ToWorldPos()) - Origin.position;
float x0 = c.x - HalfWidth - 0.5f, x1 = c.x + HalfWidth + 0.5f;
float z0 = c.z - HalfWidth - 0.5f, z1 = c.z + HalfWidth + 0.5f;
float y0 = c.y - Below, y1 = c.y + Above + 1f;
int hw = IsBigZone ? ZoneSideNow / 2 : HalfWidth;
float x0 = c.x - hw - 0.5f, x1 = c.x + hw + 0.5f;
float z0 = c.z - hw - 0.5f, z1 = c.z + hw + 0.5f;
float y0 = IsBigZone ? -Origin.position.y + 1f : c.y - Below, y1 = IsBigZone ? -Origin.position.y + 254f : c.y + Above + 1f;
Vector3 b0 = new Vector3(x0, y0, z0), b1 = new Vector3(x1, y0, z0), b2 = new Vector3(x1, y0, z1), b3 = new Vector3(x0, y0, z1);
Vector3 t0 = new Vector3(x0, y1, z0), t1 = new Vector3(x1, y1, z0), t2 = new Vector3(x1, y1, z1), t3 = new Vector3(x0, y1, z1);
// Одна ломаная по всем 12 рёбрам (часть рёбер проходится дважды - на глаз не видно).
@@ -1046,7 +1084,8 @@ namespace NecromancerTome
{
string n = _b?.GetBlockName();
return n == BlockName || n == SkullBlockName || n == LogistBlockName || n == LogistSkullBlockName ||
n == LogistWallBlockName || n == LogistWallSkullBlockName || n == WorkerBlockName || n == WorkerSkullBlockName;
n == LogistWallBlockName || n == LogistWallSkullBlockName || n == WorkerBlockName || n == WorkerSkullBlockName ||
IsRepairBlock(_b) || IsLootBlock(_b);
}
public static int UrnLimit(EntityPlayer _player)
@@ -1261,11 +1300,23 @@ namespace NecromancerTome
}
else if (text.EndsWith("bounds_show"))
{
_commands[i].enabled = !urn.ZoneShown && !urn.IsZoneless;
_commands[i].enabled = !urn.ZoneShown && (!urn.IsZoneless || urn.IsBigZone);
}
else if (text.EndsWith("bounds_hide"))
{
_commands[i].enabled = urn.ZoneShown && !urn.IsZoneless;
_commands[i].enabled = urn.ZoneShown && (!urn.IsZoneless || urn.IsBigZone);
}
else if (text.EndsWith("zurn_run"))
{
_commands[i].enabled = urn.IsBigZone && urn.HasSkull && !urn.ZoneRunning;
}
else if (text.EndsWith("zurn_stop"))
{
_commands[i].enabled = urn.IsBigZone && urn.ZoneRunning;
}
else if (text.EndsWith("zurn_size"))
{
_commands[i].enabled = urn.IsBigZone;
}
}
}
@@ -1284,7 +1335,8 @@ namespace NecromancerTome
bool repair = _commandName.EndsWith("urn_repair");
bool bag = _commandName.EndsWith("TEFeatureCinerarium:bag");
bool bounds = _commandName.EndsWith("bounds_show") || _commandName.EndsWith("bounds_hide");
if (!skull && !bounds && !repair && !bag)
bool zone = _commandName.EndsWith("zurn_run") || _commandName.EndsWith("zurn_stop") || _commandName.EndsWith("zurn_size");
if (!skull && !bounds && !repair && !bag && !zone)
{
return true;
}
@@ -1299,6 +1351,12 @@ namespace NecromancerTome
__result = composite.OnBlockActivated(null, "TEFeatureStorage:Search", world, _blockPos, world.GetBlock(_blockPos), _player);
return false;
}
if (zone)
{
urn.ZoneCommand(_commandName, _player);
__result = true;
return false;
}
if (skull)
{
CinerariumContents.Open(_player, urn);
@@ -1408,7 +1466,13 @@ namespace NecromancerTome
bool logist = TEFeatureCinerarium.IsLogistBlock(bv.Block);
bool wall = TEFeatureCinerarium.IsLogistWallBlock(bv.Block);
bool worker = TEFeatureCinerarium.IsWorkerBlock(bv.Block);
ItemValue iv = ItemClass.GetItem(wall
bool repairUrn = TEFeatureCinerarium.IsRepairBlock(bv.Block);
bool lootUrn = TEFeatureCinerarium.IsLootBlock(bv.Block);
ItemValue iv = ItemClass.GetItem(repairUrn
? (urn.HasSkull ? TEFeatureCinerarium.RepairSkullBlockName : TEFeatureCinerarium.RepairBlockName)
: lootUrn
? (urn.HasSkull ? TEFeatureCinerarium.LootSkullBlockName : TEFeatureCinerarium.LootBlockName)
: wall
? (urn.HasSkull ? TEFeatureCinerarium.LogistWallSkullBlockName : TEFeatureCinerarium.LogistWallBlockName)
: logist
? (urn.HasSkull ? TEFeatureCinerarium.LogistSkullBlockName : TEFeatureCinerarium.LogistBlockName)