Урна-логист - отдельный блок: без зоны (ставится вплотную), нити только к ней, зелёные руны и глазницы, своя иконка; рецепт с набором для шитья, лимит общий

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlexCube
2026-10-16 21:00:19 +03:00
co-authored by Claude Opus 5.5
parent adc9371a09
commit f4ae9cbcb8
9 changed files with 116 additions and 23 deletions
File diff suppressed because one or more lines are too long
+16
View File
@@ -560,5 +560,21 @@
<property name="CreativeMode" value="None"/>
<property name="DescriptionKey" value="necroCinerariumDesc"/>
</block>
<!-- Урна-логист (16.10.2026): «Я не могу ставить урны рядом. Может нам для логиста вообще сделать отдельные урны?»
Без зоны (ставится вплотную к любым урнам), только нити и перевозка (CinerariumThreads.cs), лимит урн общий.
Модель - та же; отличие - код (TEFeatureCinerarium.SyncGlow): зелёные руны и глазницы
(Resources/necroCinerarium_e_logist.png), зеленоватая глазурь. Иконка - своя (зелёные руны, нити из горлышка).
Своя модель логиста - позже, в Blender. -->
<block name="necroCinerariumLogist">
<property name="Extends" value="necroCinerarium"/>
<property name="CreativeMode" value="Player"/>
<property name="DescriptionKey" value="necroCinerariumLogistDesc"/>
<property name="CustomIcon" value="necroCinerariumLogist"/>
</block>
<block name="necroCinerariumLogistSkull">
<property name="Extends" value="necroCinerariumLogist"/>
<property name="CreativeMode" value="None"/>
<property name="DescriptionKey" value="necroCinerariumLogistDesc"/>
</block>
</append>
</config>
+1 -1
View File
@@ -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" unlock_tier="5"/>
<unlock_entry item="necroModKnifeDarkSense,necroCinerarium,necroCinerariumLogist" unlock_tier="5"/>
</display_entry>
<!-- Ступенчатая разблокировка внутри группы. ПЕРЕРАСПРЕДЕЛЕНО 2026-09-09: четыре из
шести модов ножа (вода/еда/покой/чутьё) уехали отсюда в группу 1 - см. большой
+15
View File
@@ -621,5 +621,20 @@
<ingredient name="resourceBone" count="10"/>
<ingredient name="resourceNecroBlackRoseJuice" count="1"/>
</recipe>
<!-- Урна-логист (16.10: «Рецепт с набором для шитья») - рецепт урны и набор для шитья; копия на соке чёрной розы. -->
<recipe name="necroCinerariumLogist" 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="resourceSewingKit" count="1"/>
</recipe>
<recipe name="necroCinerariumLogist" 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="resourceSewingKit" count="1"/>
</recipe>
</append>
</config>
+67 -21
View File
@@ -31,6 +31,16 @@ namespace NecromancerTome
{
public const string BlockName = "necroCinerarium";
public const string SkullBlockName = "necroCinerariumSkull";
/// <summary>Урна-логист (16.10: «Я не могу ставить урны рядом. Может нам для логиста вообще сделать отдельные урны?»):
/// без зоны - ставится вплотную к любым урнам, только нити и перевозка, глазницы всегда зелёные. Лимит - общий.</summary>
public const string LogistBlockName = "necroCinerariumLogist";
public const string LogistSkullBlockName = "necroCinerariumLogistSkull";
public static bool IsLogistBlock(Block _b)
{
string n = _b?.GetBlockName();
return n == LogistBlockName || n == LogistSkullBlockName;
}
public const string SkullItemName = "necroSkull";
public const int HalfWidth = 2;
public const int Below = 1;
@@ -44,7 +54,26 @@ namespace NecromancerTome
public readonly List<Vector3i> ThreadFrom = new List<Vector3i>();
public bool HasThreadTo;
public Vector3i ThreadTo;
public bool IsLogist => HasThreadTo || ThreadFrom.Count > 0;
public bool HasThreads => HasThreadTo || ThreadFrom.Count > 0;
/// <summary>Урна-логист - по блоку (necroCinerariumLogist / ...Skull); узнаётся при постановке или по миру.</summary>
public bool IsLogist
{
get
{
if (!_logistKnown)
{
World w = GameManager.Instance?.World;
BlockValue bv = w != null ? w.GetBlock(ToWorldPos()) : BlockValue.Air;
if (IsUrn(bv.Block))
{
_logist = IsLogistBlock(bv.Block);
_logistKnown = true;
}
}
return _logist;
}
}
bool _logist, _logistKnown;
/// <summary>Фильтр логиста: бит на группу CinerariumGroups; по умолчанию все включены.</summary>
public uint GroupMask = CinerariumGroups.All;
@@ -76,7 +105,10 @@ namespace NecromancerTome
public override void OnAdded(Vector3i _blockPos, BlockValue _blockValue)
{
base.OnAdded(_blockPos, _blockValue);
if (_blockValue.Block?.GetBlockName() == SkullBlockName)
_logist = IsLogistBlock(_blockValue.Block);
_logistKnown = true;
string placed = _blockValue.Block?.GetBlockName();
if (placed == SkullBlockName || placed == LogistSkullBlockName)
{
HasSkull = true; // поставили урну, поднятую с черепом
}
@@ -290,7 +322,14 @@ namespace NecromancerTome
/// <summary>Нити к убранным ящикам снимаются (раз в 2 с); вид перестраивается после изменений.</summary>
void SyncThreads(World _world)
{
if (_world != null && ConnectionManager.Instance.IsServer && IsLogist && Time.time >= _nextThreadCheck)
if (_world != null && ConnectionManager.Instance.IsServer && HasThreads && !IsLogist && _logistKnown)
{
Debug.Log("[NecromancerTome] Cinerarium: нити у обычной урны " + ToWorldPos() + " сняты - нити только у урны-логиста");
ThreadFrom.Clear();
HasThreadTo = false;
ThreadsChanged();
}
if (_world != null && ConnectionManager.Instance.IsServer && HasThreads && Time.time >= _nextThreadCheck)
{
_nextThreadCheck = Time.time + 2f;
bool gone = false;
@@ -316,7 +355,7 @@ namespace NecromancerTome
ThreadsChanged();
}
}
if (!_threadsDirty && (_threadView != null || !IsLogist))
if (!_threadsDirty && (_threadView != null || !HasThreads))
{
return;
}
@@ -327,7 +366,7 @@ namespace NecromancerTome
}
_threadsDirty = false;
SyncGlow();
if (!IsLogist)
if (!HasThreads)
{
RemoveThreadView();
return;
@@ -545,8 +584,10 @@ namespace NecromancerTome
void SyncGlow()
{
Transform t = ModelTransform();
int want = HasSkull ? (IsLogist ? 2 : 1) : 0;
Texture logist = want == 2 ? LogistEmission() : null;
// Логист (16.10: «Так сразу будет видно что логистика, а что технолог») - всегда своя текстура свечения (руны и
// глазницы зелёные) и зеленоватая глазурь; светится, как и обычная урна, только с черепом.
int want = (HasSkull ? 1 : 0) + (IsLogist ? 2 : 0);
Texture logist = IsLogist ? LogistEmission() : null;
if (t == null || (_glowState == want && _glowModel == t))
{
return;
@@ -563,20 +604,17 @@ namespace NecromancerTome
if (logist != null)
{
_glowBlock.SetTexture(EmissionMapId, logist);
}
else if (_hadLogistTex)
{
_glowBlock.Clear(); // вернуть текстуру материала
_glowBlock.SetColor(EmissionColorId, HasSkull ? GlowColor : Color.black);
_glowBlock.SetColor(AlbedoColorId, LogistTint);
}
r.SetPropertyBlock(_glowBlock);
}
_glowState = want;
_glowModel = t;
_hadLogistTex = logist != null;
}
bool _hadLogistTex;
/// <summary>Глазурь урны-логиста - множитель цвета материала (чёрная глазурь с зелёным отливом).</summary>
public static Color LogistTint = new Color(0.55f, 1f, 0.75f, 1f);
static readonly int AlbedoColorId = Shader.PropertyToID("_Color");
static readonly int EmissionMapId = Shader.PropertyToID("_EmissionMap");
static Texture2D _logistTex;
static bool _logistTried;
@@ -704,7 +742,7 @@ namespace NecromancerTome
public static bool IsUrn(Block _b)
{
string n = _b?.GetBlockName();
return n == BlockName || n == SkullBlockName;
return n == BlockName || n == SkullBlockName || n == LogistBlockName || n == LogistSkullBlockName;
}
public static int UrnLimit(EntityPlayer _player)
@@ -714,9 +752,9 @@ namespace NecromancerTome
}
/// <summary>Ключ подсказки, почему урну сюда нельзя, или null.</summary>
public static string PlacementRefusal(Vector3i _pos)
public static string PlacementRefusal(Vector3i _pos, Block _placing)
{
if (CinerariumRegistry.Overlaps(_pos))
if (!IsLogistBlock(_placing) && CinerariumRegistry.Overlaps(_pos))
{
return "necroCinerariumOverlap";
}
@@ -833,8 +871,13 @@ namespace NecromancerTome
{
int w = TEFeatureCinerarium.HalfWidth * 2;
int h = TEFeatureCinerarium.Below + TEFeatureCinerarium.Above;
World world = GameManager.Instance?.World;
foreach (Vector3i p in Urns().Keys)
{
if (world != null && TEFeatureCinerarium.IsLogistBlock(world.GetBlock(p).Block))
{
continue; // у логиста зоны нет
}
if (p != _pos && Math.Abs(p.x - _pos.x) <= w && Math.Abs(p.z - _pos.z) <= w && Math.Abs(p.y - _pos.y) <= h)
{
return true;
@@ -886,11 +929,11 @@ namespace NecromancerTome
}
else if (text.EndsWith("bounds_show"))
{
_commands[i].enabled = !urn.ZoneShown;
_commands[i].enabled = !urn.ZoneShown && !urn.IsLogist;
}
else if (text.EndsWith("bounds_hide"))
{
_commands[i].enabled = urn.ZoneShown;
_commands[i].enabled = urn.ZoneShown && !urn.IsLogist;
}
}
}
@@ -968,7 +1011,7 @@ namespace NecromancerTome
{
return;
}
string why = TEFeatureCinerarium.PlacementRefusal(_blockPos);
string why = TEFeatureCinerarium.PlacementRefusal(_blockPos, __instance);
if (why != null)
{
__result = false;
@@ -1029,7 +1072,10 @@ namespace NecromancerTome
GameManager.ShowTooltip(player, Localization.Get("necroCinerariumBagNotEmpty"), string.Empty, "ui_denied");
return false;
}
ItemValue iv = ItemClass.GetItem(urn.HasSkull ? TEFeatureCinerarium.SkullBlockName : TEFeatureCinerarium.BlockName);
bool logist = TEFeatureCinerarium.IsLogistBlock(bv.Block);
ItemValue iv = ItemClass.GetItem(logist
? (urn.HasSkull ? TEFeatureCinerarium.LogistSkullBlockName : TEFeatureCinerarium.LogistBlockName)
: (urn.HasSkull ? TEFeatureCinerarium.SkullBlockName : TEFeatureCinerarium.BlockName));
if (iv == null || iv.type <= 0)
{
return true;
+13 -1
View File
@@ -12,7 +12,8 @@ namespace NecromancerTome
/// ящика. Дух в цинерарии будет забирать всё что лежит в ящике из которого в него тянется провод, и тащить в ящик, в
/// который тянется провод.» Решения: у урны несколько нитей «откуда» и одна «куда»; везётся по образцу (только то, что уже
/// лежит в ящике «куда»; пустой - всё подряд); «куда» полон - дух ждёт; нить призрачная; длина - как у провода (15 м);
/// ящики - любые. Урна с нитями - «логист»: её дух только перевозит, глазницы черепов светятся зелёным.
/// ящики - любые. Нити - только у отдельного блока «урна-логист» (necroCinerariumLogist, 16.10): без зоны, дух только перевозит,
/// глазницы всегда зелёные.
///
/// Инструмент - нож некроманта «хватом для нитей»: отдельный предмет necroWpnBladeNecroKnifeThread (класс ножа в сейве
/// менять нельзя), переключение - пункт в меню ножа по R (то же меню, что «человек/змея/чертоги», SnakeMode.cs; 16.10:
@@ -134,6 +135,11 @@ namespace NecromancerTome
}
if (!_start.HasValue)
{
if (urn != null && !urn.IsLogist)
{
GameManager.ShowTooltip(_pl, Localization.Get("necroThreadNeedLogist"));
return;
}
_start = owner;
_startUrn = urn != null;
_preview = new GameObject("NecroThreadPreview");
@@ -155,6 +161,12 @@ namespace NecromancerTome
GameManager.ShowTooltip(_pl, Localization.Get("necroThreadNeedPair"));
return;
}
TEFeatureCinerarium end = sUrn ? UrnAt(w, s) : urn;
if (end == null || !end.IsLogist)
{
GameManager.ShowTooltip(_pl, Localization.Get("necroThreadNeedLogist"));
return;
}
if (Vector3.Distance(s.ToVector3(), owner.ToVector3()) > MaxLength)
{
GameManager.ShowTooltip(_pl, Localization.Get("necroThreadTooFar"));
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB