Урны ремонтника и лутера: большая зона, запуск кнопкой, кнопки-состояния
- 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:
co-authored by
Claude Opus 5.5
parent
85d8036e81
commit
a352a408a4
File diff suppressed because one or more lines are too long
@@ -613,5 +613,31 @@
|
||||
<property name="CreativeMode" value="None"/>
|
||||
<property name="DescriptionKey" value="necroCinerariumWorkerDesc"/>
|
||||
</block>
|
||||
<!-- Урна ремонтника и урна лутера (18.10.2026, BACKLOG «Урна ремонтника», «Урна лутера»; CinerariumZone.cs): большая зона -
|
||||
сторона притязания или меньше, весь столб мира; работа - по кнопке «Запустить» (один проход), кнопки-звонки с черепом
|
||||
на модели показывают состояние. Ремонтник чинит повреждённые блоки материалами из ящиков по нитям; лутер собирает
|
||||
предметы на земле, мешки с зомби и красные трупы в ящик по нити. Без зоны 5x5 и вне лимита, как логист. -->
|
||||
<block name="necroCinerariumRepair">
|
||||
<property name="Extends" value="necroCinerarium"/>
|
||||
<property name="CreativeMode" value="Player"/>
|
||||
<property name="DescriptionKey" value="necroCinerariumRepairDesc"/>
|
||||
<property name="CustomIcon" value="necroCinerarium"/> <!-- своя иконка - после модели -->
|
||||
</block>
|
||||
<block name="necroCinerariumRepairSkull">
|
||||
<property name="Extends" value="necroCinerariumRepair"/>
|
||||
<property name="CreativeMode" value="None"/>
|
||||
<property name="DescriptionKey" value="necroCinerariumRepairDesc"/>
|
||||
</block>
|
||||
<block name="necroCinerariumLoot">
|
||||
<property name="Extends" value="necroCinerarium"/>
|
||||
<property name="CreativeMode" value="Player"/>
|
||||
<property name="DescriptionKey" value="necroCinerariumLootDesc"/>
|
||||
<property name="CustomIcon" value="necroCinerarium"/> <!-- своя иконка - после модели -->
|
||||
</block>
|
||||
<block name="necroCinerariumLootSkull">
|
||||
<property name="Extends" value="necroCinerariumLoot"/>
|
||||
<property name="CreativeMode" value="None"/>
|
||||
<property name="DescriptionKey" value="necroCinerariumLootDesc"/>
|
||||
</block>
|
||||
</append>
|
||||
</config>
|
||||
|
||||
@@ -181,7 +181,7 @@
|
||||
пользуется, вода сидит на necroNecromancyLvl20. -->
|
||||
<unlock_entry item="necroModKnifeScavengersFeast,bookSummonRatSpirit" unlock_tier="3"/>
|
||||
<unlock_entry item="necroModKnifeGravesRepose" unlock_tier="4"/>
|
||||
<unlock_entry item="necroModKnifeDarkSense,necroCinerarium,necroCinerariumLogist,necroCinerariumWorker" unlock_tier="5"/>
|
||||
<unlock_entry item="necroModKnifeDarkSense,necroCinerarium,necroCinerariumLogist,necroCinerariumWorker,necroCinerariumRepair,necroCinerariumLoot" unlock_tier="5"/>
|
||||
</display_entry>
|
||||
<!-- Ступенчатая разблокировка внутри группы. ПЕРЕРАСПРЕДЕЛЕНО 2026-09-09: четыре из
|
||||
шести модов ножа (вода/еда/покой/чутьё) уехали отсюда в группу 1 - см. большой
|
||||
|
||||
@@ -680,5 +680,35 @@
|
||||
<ingredient name="resourceNecroBlackRoseJuice" count="1"/>
|
||||
<ingredient name="meleeToolRepairT1ClawHammer" count="1"/>
|
||||
</recipe>
|
||||
<!-- Урна ремонтника (18.10) - рецепт урны и ремкомплект; копия на соке чёрной розы. -->
|
||||
<recipe name="necroCinerariumRepair" count="1" tags="learnable,packMuleCrafting,necroNecromancyLvl300">
|
||||
<ingredient name="resourceClayLump" count="30"/>
|
||||
<ingredient name="resourceZombieAsh" count="100"/>
|
||||
<ingredient name="resourceBone" count="10"/>
|
||||
<ingredient name="resourceNecromancerBlood" count="1"/>
|
||||
<ingredient name="resourceRepairKit" count="1"/>
|
||||
</recipe>
|
||||
<recipe name="necroCinerariumRepair" count="1" tags="learnable,packMuleCrafting,necroNecromancyLvl300">
|
||||
<ingredient name="resourceClayLump" count="30"/>
|
||||
<ingredient name="resourceZombieAsh" count="100"/>
|
||||
<ingredient name="resourceBone" count="10"/>
|
||||
<ingredient name="resourceNecroBlackRoseJuice" count="1"/>
|
||||
<ingredient name="resourceRepairKit" count="1"/>
|
||||
</recipe>
|
||||
<!-- Урна лутера (18.10) - рецепт урны и отмычка; копия на соке чёрной розы. -->
|
||||
<recipe name="necroCinerariumLoot" count="1" tags="learnable,packMuleCrafting,necroNecromancyLvl300">
|
||||
<ingredient name="resourceClayLump" count="30"/>
|
||||
<ingredient name="resourceZombieAsh" count="100"/>
|
||||
<ingredient name="resourceBone" count="10"/>
|
||||
<ingredient name="resourceNecromancerBlood" count="1"/>
|
||||
<ingredient name="resourceLockPick" count="1"/>
|
||||
</recipe>
|
||||
<recipe name="necroCinerariumLoot" count="1" tags="learnable,packMuleCrafting,necroNecromancyLvl300">
|
||||
<ingredient name="resourceClayLump" count="30"/>
|
||||
<ingredient name="resourceZombieAsh" count="100"/>
|
||||
<ingredient name="resourceBone" count="10"/>
|
||||
<ingredient name="resourceNecroBlackRoseJuice" count="1"/>
|
||||
<ingredient name="resourceLockPick" count="1"/>
|
||||
</recipe>
|
||||
</append>
|
||||
</config>
|
||||
|
||||
+82
-18
@@ -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() + " - " +
|
||||
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)
|
||||
|
||||
@@ -0,0 +1,709 @@
|
||||
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();
|
||||
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())
|
||||
{
|
||||
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>
|
||||
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 && !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; // не всё влезло - ящик полон, кнопка мигает
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,43 @@ namespace NecromancerTome
|
||||
case "necrohold": Hold(pl, _params); break;
|
||||
case "necrobox": Box(w, _params); break;
|
||||
case "necrourn": Urn(w, _params); break;
|
||||
case "necrodamage":
|
||||
{
|
||||
// necrodamage x y z урон - повредить блок (проверка ремонтника)
|
||||
Vector3i dp = P(_params, 0);
|
||||
BlockValue dbv = w.GetBlock(dp);
|
||||
dbv.damage = Mathf.Min(dbv.Block.MaxDamage - 1, dbv.damage + int.Parse(_params[3]));
|
||||
w.SetBlockRPC(dp, dbv);
|
||||
Out("necrodamage: " + dbv.Block.GetBlockName() + " в " + dp + " повреждение " + dbv.damage + "/" + dbv.Block.MaxDamage);
|
||||
break;
|
||||
}
|
||||
case "necrodrop":
|
||||
{
|
||||
// necrodrop предмет число x y z - бросить на землю (проверка лутера)
|
||||
ItemValue div = ItemClass.GetItem(_params[0], true);
|
||||
GameManager.Instance.ItemDropServer(new ItemStack(div, int.Parse(_params[1])), P(_params, 2).ToVector3() + new Vector3(0.5f, 0.5f, 0.5f), Vector3.zero);
|
||||
Out("necrodrop: " + _params[0] + " x" + _params[1]);
|
||||
break;
|
||||
}
|
||||
case "necrobag":
|
||||
{
|
||||
// necrobag x y z - мешок с зомби (EntityLootContainer со списком cntDropBag), нетронутый
|
||||
Entity bag = EntityFactory.CreateEntity(EntityClass.FromString("DroppedLootContainer"), P(_params, 0).ToVector3() + new Vector3(0.5f, 0.3f, 0.5f));
|
||||
bag.lootList = "cntDropBag";
|
||||
w.SpawnEntityInWorld(bag);
|
||||
Out("necrobag: " + bag.entityId);
|
||||
break;
|
||||
}
|
||||
case "necrocorpse":
|
||||
{
|
||||
// necrocorpse x y z [класс] - зомби, убитый и помеченный красным трупом (как VictimPatch)
|
||||
string cls = _params.Count > 3 ? _params[3] : "zombieArlene";
|
||||
EntityAlive z = EntityFactory.CreateEntity(EntityClass.FromString(cls), P(_params, 0).ToVector3() + new Vector3(0.5f, 0.1f, 0.5f)) as EntityAlive;
|
||||
w.SpawnEntityInWorld(z);
|
||||
ThreadManager.StartCoroutine(KillAndMark(z));
|
||||
Out("necrocorpse: " + z.entityId + " " + cls);
|
||||
break;
|
||||
}
|
||||
case "necrogod": God(pl, _params.Count == 0 || _params[0] != "off"); break;
|
||||
case "necroclick":
|
||||
// necroclick 0|1 [x y z] - по блоку под прицелом или по указанной клетке (луч не всегда попадает в низкий череп).
|
||||
@@ -99,6 +136,18 @@ namespace NecromancerTome
|
||||
Out("necrogod: " + (_on ? "бог, ИИ не видит" : "обычный"));
|
||||
}
|
||||
|
||||
static System.Collections.IEnumerator KillAndMark(EntityAlive _z)
|
||||
{
|
||||
yield return new WaitForSeconds(1f);
|
||||
_z.DamageEntity(new DamageSource(EnumDamageSource.Internal, EnumDamageTypes.Suicide), 99999, false);
|
||||
yield return new WaitForSeconds(1f);
|
||||
if (_z.IsDead())
|
||||
{
|
||||
CorpseLoot.MarkCorpse(_z);
|
||||
}
|
||||
UnityEngine.Debug.Log("[NecromancerTome] mailbox: necrocorpse: " + _z.entityId + (_z.IsDead() ? " убит и помечен" : " НЕ умер"));
|
||||
}
|
||||
|
||||
static void Aim(World _w, EntityPlayerLocal _pl)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder("necroaim: игрок ").Append(_pl.position.ToString("F1")).Append(", взгляд ").Append(_pl.rotation.ToString("F0"));
|
||||
@@ -247,6 +296,9 @@ namespace NecromancerTome
|
||||
case "sample":
|
||||
u.SetSample(_p.Count > 5 ? new ItemStack(ItemClass.GetItem(_p[4], true), int.Parse(_p[5])) : ItemStack.Empty.Clone());
|
||||
break;
|
||||
case "run": u.ZoneCommand("zurn_run", GameManager.Instance.World.GetPrimaryPlayer()); break;
|
||||
case "stop": u.ZoneCommand("zurn_stop", null); break;
|
||||
case "size": u.ZoneSide = int.Parse(_p[4]); u.SetModified(); break;
|
||||
case "group":
|
||||
u.GroupMask = _p[4] == "нет" || _p[4] == "none" ? 0u : CinerariumGroups.All;
|
||||
u.SetModified();
|
||||
@@ -262,7 +314,7 @@ namespace NecromancerTome
|
||||
}
|
||||
Out("necrourn: " + pos + " " + _w.GetBlock(pos).Block?.GetBlockName() + ", череп " + u.HasSkull + ", застрял " + u.Stuck + ", мигание " + blink +
|
||||
", откуда " + u.ThreadFrom.Count + ", куда " + (u.HasThreadTo ? u.ThreadTo.ToString() : "нет") + ", от урны " + u.ThreadOut.Count +
|
||||
", образец " + (u.Sample == null || u.Sample.IsEmpty() ? "нет" : u.Sample.itemValue.ItemClass?.GetItemName() + "x" + u.Sample.count));
|
||||
", зона " + (u.IsBigZone ? u.ZoneSideNow + (u.ZoneRunning ? " идёт" : " ждёт") + (u.ZoneLacking ? " нехватка" : "") + " курсор " + u.ZoneCursor : "-") + ", образец " + (u.Sample == null || u.Sample.IsEmpty() ? "нет" : u.Sample.itemValue.ItemClass?.GetItemName() + "x" + u.Sample.count));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,4 +389,40 @@ namespace NecromancerTome
|
||||
public override bool AllowedInMainMenu => false;
|
||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necrogod", _params);
|
||||
}
|
||||
|
||||
[Preserve]
|
||||
public class ConsoleCmdNecroDamage : ConsoleCmdAbstract
|
||||
{
|
||||
public override string[] getCommands() => new[] { "necrodamage" };
|
||||
public override string getDescription() => "NecromancerTome (разработка): повредить блок x y z урон";
|
||||
public override bool AllowedInMainMenu => false;
|
||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necrodamage", _params);
|
||||
}
|
||||
|
||||
[Preserve]
|
||||
public class ConsoleCmdNecroDrop : ConsoleCmdAbstract
|
||||
{
|
||||
public override string[] getCommands() => new[] { "necrodrop" };
|
||||
public override string getDescription() => "NecromancerTome (разработка): бросить предмет: имя число x y z";
|
||||
public override bool AllowedInMainMenu => false;
|
||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necrodrop", _params);
|
||||
}
|
||||
|
||||
[Preserve]
|
||||
public class ConsoleCmdNecroBag : ConsoleCmdAbstract
|
||||
{
|
||||
public override string[] getCommands() => new[] { "necrobag" };
|
||||
public override string getDescription() => "NecromancerTome (разработка): мешок с зомби x y z";
|
||||
public override bool AllowedInMainMenu => false;
|
||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necrobag", _params);
|
||||
}
|
||||
|
||||
[Preserve]
|
||||
public class ConsoleCmdNecroCorpse : ConsoleCmdAbstract
|
||||
{
|
||||
public override string[] getCommands() => new[] { "necrocorpse" };
|
||||
public override string getDescription() => "NecromancerTome (разработка): красный труп x y z [класс]";
|
||||
public override bool AllowedInMainMenu => false;
|
||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necrocorpse", _params);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user