Урны: в лимит входят все урны; блок «всего / доступно / до следующей» в «Содержимом» (#65)

Лимит считает урны любого рода (сборщик, логист, рабочий, ремонтник, лутер, старатель); пересечение зон -
только у сборщика. Блок справа от ячеек урны (windowBagStorage, CinerariumLimitPanel.cs). Тексты описаний
урн и отказа по лимиту - на 13 языках.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlexCube
2026-10-23 22:01:17 +03:00
co-authored by Claude Opus 5.5
parent 108ff82914
commit aa26e97b71
6 changed files with 115 additions and 30 deletions
File diff suppressed because one or more lines are too long
+10
View File
@@ -266,6 +266,16 @@
<sprite depth="3" name="g22" pos="232,-219" width="48" height="48" atlas="ItemIconAtlas" sprite="oldCash" on_press="true" sound="[paging_click]" foregroundlayer="true" />
</window>
</append>
<!-- Лимит урн (#65, 23.10.2026; HarmonySrc/CinerariumLimitPanel.cs): блок справа от ячеек урны в «Содержимом» - всего,
доступно, до следующей. Позицию по числу ячеек ставит контроллер; вне урны блок скрыт. -->
<append xpath="/windows/window[@name='windowBagStorage']">
<rect name="necroUrnLimit" pos="87,-49" width="420" height="75" depth="6" controller="NecromancerTome.NecroUrnLimit, NecromancerHarmony">
<sprite depth="6" pos="0,0" width="420" height="75" color="0,0,0,190" sprite="menu_empty2px" type="sliced" />
<label depth="7" name="placed" pos="10,-4" width="404" height="22" font_size="20" color="[white]" />
<label depth="7" name="available" pos="10,-27" width="404" height="22" font_size="20" color="[white]" />
<label depth="7" name="next" pos="10,-50" width="404" height="22" font_size="20" color="200,200,205" />
</rect>
</append>
<!-- Вкладка «Некромантия» (ui_display.xml): в рюкзаке и на верстаке 10 вкладок вместо 9 - шаг значков 43 -> 39, значки прежние (28). -->
<set xpath="/windows/window[@name='windowCraftingList']//grid[@name='categories']/@cols">10</set>
<set xpath="/windows/window[@name='windowCraftingList']//grid[@name='categories']/@cell_width">39</set>
+26 -24
View File
@@ -1125,30 +1125,34 @@ namespace NecromancerTome
int d = Math.Max(Math.Abs(other.x - _pos.x), Math.Abs(other.z - _pos.z));
return string.Format(Localization.Get("necroCinerariumOverlap"), d, HalfWidth * 2 + 1);
}
if (IsZonelessBlock(_placing))
// 20.10 (#65, пользователь): в лимит входят ВСЕ урны - сборщик, логист, рабочий, ремонтник, лутер, старатель; пересечение
// зон проверяется только у сборщика (выше). Череп-декор урной не считается.
if (LimitNumbers(out int have, out int limit, out int kills, out int next) && have >= limit)
{
return null; // логисты и рабочие в лимит не входят (16.10, 17.10 - пользователь)
}
World world = GameManager.Instance?.World;
EntityPlayerLocal player = world?.GetPrimaryPlayer();
// Реестр живёт на сервере; клиент в сети лимит не проверяет (мультиплеер поддержан не полностью).
if (player != null && world != null && !world.IsRemote())
{
PlatformUserIdentifierAbs me = PlatformManager.InternalLocalUserIdentifier;
int have = CinerariumRegistry.CountFor(me), limit = UrnLimit(player);
if (have >= limit)
{
float kills = player.Buffs != null ? player.Buffs.GetCustomVar(Patch_EntityPlayer_AddKillXP_NecromancyCount.KillsCVarName) : 0f;
int next = Mathf.CeilToInt((limit + 1) * KillsPerUrn - kills);
if (kills <= 0f)
{
next = Mathf.CeilToInt(KillsPerUrn);
}
return string.Format(Localization.Get("necroCinerariumLimit"), have, limit, (int)KillsPerUrn, (int)kills, Math.Max(1, next));
}
return string.Format(Localization.Get("necroCinerariumLimit"), have, limit, (int)KillsPerUrn, kills, next);
}
return null;
}
/// <summary>Урн поставлено, сколько можно, упокоено и сколько ещё упокоить до следующей урны. false - нет игрока или
/// клиент в сети (реестр живёт на сервере; мультиплеер поддержан не полностью).</summary>
public static bool LimitNumbers(out int _have, out int _limit, out int _kills, out int _next)
{
_have = _limit = _kills = _next = 0;
World world = GameManager.Instance?.World;
EntityPlayerLocal player = world?.GetPrimaryPlayer();
if (player == null || world.IsRemote())
{
return false;
}
_have = CinerariumRegistry.CountFor(PlatformManager.InternalLocalUserIdentifier);
_limit = UrnLimit(player);
float kills = player.Buffs != null ? player.Buffs.GetCustomVar(Patch_EntityPlayer_AddKillXP_NecromancyCount.KillsCVarName) : 0f;
_kills = (int)kills;
_next = kills <= 0f ? Mathf.CeilToInt(KillsPerUrn) : Math.Max(1, Mathf.CeilToInt((_limit + 1) * KillsPerUrn - kills));
return true;
}
}
/// <summary>Все урны сейва: позиция и владелец, файл в папке сейва (как сумка волка). Сервер.</summary>
@@ -1247,14 +1251,12 @@ namespace NecromancerTome
public static int CountFor(PlatformUserIdentifierAbs _owner)
{
// 20.10 (#65): считаются все урны владельца, любого рода (метка «L» в реестре для лимита больше не нужна).
string id = _owner != null ? _owner.CombinedString : "";
int n = 0;
World world = GameManager.Instance?.World;
foreach (KeyValuePair<Vector3i, string> kv in Urns())
{
// Логисты не считаются; записанные до метки «L» узнаются по блоку в мире (чанк рядом загружен).
if (kv.Value == id && !_logists.Contains(kv.Key) &&
!(world != null && TEFeatureCinerarium.IsZonelessBlock(world.GetBlock(kv.Key).Block)))
if (kv.Value == id)
{
n++;
}
+70
View File
@@ -0,0 +1,70 @@
using UnityEngine;
using UnityEngine.Scripting;
namespace NecromancerTome
{
/// <summary>
/// Лимит урн в «Содержимом» (#65; 20.10: «индикатор, сколько урн уже поставлено и сколько ещё можно»; 23.10, по скриншоту
/// с цифрами в заголовке: «У нас есть куча пространства под черепом или справа от него... Всего цинерариев. Доступно
/// цинерариев. И до разблокировки ещё одного нужно упокоить зомби столько-то»). Блок дописан в окно игры windowBagStorage
/// (Config/XUi_InGame/windows.xml), справа от ячеек урны; в сумках волка и транспорта (то же окно) скрыт.
/// </summary>
[Preserve]
public class XUiC_NecroUrnLimit : XUiController
{
XUiV_Label _placed, _available, _next;
float _nextRefresh;
public override void Init()
{
base.Init();
_placed = GetChildById("placed")?.ViewComponent as XUiV_Label;
_available = GetChildById("available")?.ViewComponent as XUiV_Label;
_next = GetChildById("next")?.ViewComponent as XUiV_Label;
}
public override void OnOpen()
{
base.OnOpen();
_nextRefresh = 0f;
Refresh();
}
public override void Update(float _dt)
{
base.Update(_dt);
if (Time.time >= _nextRefresh)
{
Refresh();
}
}
void Refresh()
{
_nextRefresh = Time.time + 1f;
TEFeatureCinerarium urn = CinerariumContents.Urn;
int have = 0, limit = 0, next = 0;
bool show = urn != null && TEFeatureCinerarium.LimitNumbers(out have, out limit, out int _, out next);
ViewComponent.IsVisible = show;
if (!show)
{
return;
}
// Справа от ячеек урны: у рабочего и старателя их две (череп и образец).
int slots = urn.IsWorker || urn.IsProspector ? 2 : 1;
ViewComponent.Position = new Vector2i(slots * 75 + 12, -49);
if (_placed != null)
{
_placed.Text = string.Format(Localization.Get("necroCinerariumPlaced"), have);
}
if (_available != null)
{
_available.Text = string.Format(Localization.Get("necroCinerariumAvailable"), Mathf.Max(0, limit - have));
}
if (_next != null)
{
_next.Text = string.Format(Localization.Get("necroCinerariumNext"), next);
}
}
}
}
Binary file not shown.
Binary file not shown.