Кровь некроманта - топливо Пространственного браслета
Шесть указаний одного захода, которые сложились в одну механику: поглощение блока больше не бесплатно. Браслет требует модификацию в слоте, кровь ею стала, и она на это тратится. СТАК ПО ОДНОЙ БАНКЕ. Наследуемый medicalBloodBag даёт Stacknumber 15, и это надо перебивать явно - Extends копирует свойство целиком, а не "если не задано иначе". Дорого и так задумано: Чёрный портал просит десять банок, то есть десять ячеек. ПРОЧНОСТЬ 1000. Свойства с именем вроде Durability в игре нет; ручек две, и обе обязательны. Число - пассивный эффект DegradationMax (ItemValue.MaxUseTimesBase -> EffectManager.GetValue), полоска - отдельное свойство ShowQuality (XUiC_ItemStack.ShowDurability -> ItemClass.ShowQualityBar). Без первой прочность равна нулю, а полоска при MaxUseTimes == 0 рисуется ПОЛНОЙ, то есть забытый эффект выглядит как "всё работает". tiered="false" обязателен: ItemClass.HasQuality читается как Effects.IsOwnerTiered(), и тированная группа превратила бы банку в предмет с качеством, с тирами и рамкой. КРОВЬ ПЕРЕЕХАЛА В item_modifiers.xml. Это не настройка, а смена класса предмета: XUiC_ItemPartStack.CanSwap открывается строкой `if (!(stack.itemValue.ItemClass is ItemClassModifier ...)) return false;` - слот модификации не смотрит ни на теги, ни на свойства, пока предмет не ItemClassModifier, а этот класс создаётся только из <item_modifier>. Свойства вида CanBeInstalled не существует; остаться ресурсом в items.xml и вставляться в браслет физически нельзя. На старом месте оставлен комментарий-указатель. Что при этом проверено, а не понадеялось: рецепты резолвятся (ItemClassModifier наследует ItemClass, имена лежат в общем ItemClass.nameToItem, Recipe ищет через GetItemClass по тому же словарю); сейв цел (ItemClass.assignIdsFromMapping берёт айди из сохранённого name->id мэппинга, перестановка в конфигах предмет не подменит); Harmony-патч и ключи локализации ходят по имени, имя не менялось. ЛОВУШКА ПЕРЕЕЗДА: у модификации effect_group применяется к предмету, В КОТОРЫЙ её вставили - прочность 1000 начала бы выдаваться БРАСЛЕТУ. Пассивка гейтована tags="necroBloodFlask", тег добавлен в Tags флакона: MaxUseTimesBase зовёт GetValue с ItemTags того предмета, для которого считает. СЛОТОВ У БРАСЛЕТА 1, было 4. Слот из набора улучшений стал выбором. ПУСТОЙ СЛОТ ОТКАЗЫВАЕТ. Проверка стоит первой строкой Begin, впереди всех остальных отказов: прочие про ЦЕЛЬ (нет блока, не тот блок, хранилище полно), эта про ИНСТРУМЕНТ, и сказать "здесь нет блока", когда пуст браслет, значит отправить игрока искать не там. Тест - ItemValue.HasMods(), игровой собственный: обходит только Modifications, пропуская null и IsEmpty, и не считает CosmeticMods, иначе краска читалась бы как "браслет заряжен". Звук отказа достался бесплатно - Deny() в этом файле уже играет ванильный ui_denied. РАСХОД. Цена пула считается ОДИН раз, при старте, и едет в PickupJob.ChannelSeconds вместе с самим браслетом. Не потому, что так короче: ChannelSecondsFor меряет луч игрока, а за десять секунд игрок успевает отвернуться - второй вызов насчитал бы цену за другой блок, а не за тот, который забрали. Браслет хранится экземпляром по той же причине: моды живут на ItemValue, а колесо прокручивается. Списывается в SpendBlood, ПОСЛЕ SetBlockRPC и после того, как предмет лёг в хранилище: все отказы выходят раньше через return, так что кровь за отменённое поглощение невозможна по построению. Имя предмета берётся из NecromancerBloodPatch.BloodItemName, а не вторым литералом, чтобы не разъехались. Мод, который не кровь, не платит ничего и поглощению не мешает - слот задуман под другие вещи. КОНЧИЛАСЬ - РАЗБИВАЕТСЯ. Правило именно "прочность 0 или меньше", а не "не хватило на пул", и разница не косметическая: по второй формулировке флакон, которому хватило впритык, остался бы в слоте с нулём, HasMods() видел бы "что-то вставлено", и браслет работал бы бесплатно до конца света. Поэтому зажим по MaxUseTimes убран, а слот обнуляется через ItemValue.None - это type 0, ровно то, что проверяет IsEmpty(). Последнее поглощение проходит всегда, флакон его просто не переживает. Звук - ванильный itembreak, тот же, что играет ItemAction.HandleItemBreak. Защита: если MaxUseTimes окажется 0 (снесли passive_effect или тег), флакон НЕ удаляется, а в лог идёт предупреждение с указанием, где чинить. Без этой ветки ошибка в XML съедала бы игроку предмет на первом же поглощении, и выглядело бы это багом механики. Локализация: новый ключ braceletSpatialVaultNoMod в 13 языках, плюс описания флакона и браслета - механика стала условной и платной, и оба текста без этого стали бы враньём. Известное и намеренное: кровавого камня, который обещает сообщение о пустом слоте, ещё нет - он запланирован, разбор в BACKLOG.md. В игре ничего из этого не проверено. --- Necromancer's Blood is the Spatial Bracelet's fuel Six instructions from one session that add up to one mechanic: pulling a block into the vault is no longer free. The bracelet needs a mod in its slot, the blood became that mod, and it is spent doing the work. ONE JAR PER STACK. The inherited medicalBloodBag sets Stacknumber 15 and it has to be overridden explicitly - Extends copies a property wholesale, not "unless set". Expensive on purpose: the Black Portal asks for ten jars, so ten slots. DURABILITY 1000. There is no property called anything like Durability; there are two knobs and both are required. The number is a DegradationMax passive effect (ItemValue.MaxUseTimesBase -> EffectManager.GetValue); the bar is a separate ShowQuality property (XUiC_ItemStack.ShowDurability -> ItemClass.ShowQualityBar). Without the first, durability is zero - and the bar at MaxUseTimes == 0 draws FULL, so a forgotten effect looks exactly like success. tiered="false" is mandatory: ItemClass.HasQuality is Effects.IsOwnerTiered(), and a tiered group would have turned the jar into a quality item with tiers and a frame. THE BLOOD MOVED TO item_modifiers.xml. Not a setting but a change of item class: XUiC_ItemPartStack.CanSwap opens with `if (!(stack.itemValue.ItemClass is ItemClassModifier ...)) return false;` - a mod slot looks at neither tags nor properties until the item is an ItemClassModifier, and that class is only created from <item_modifier>. No CanBeInstalled property exists; staying a resource in items.xml and going into the bracelet is impossible. A pointer comment was left where it used to live. Checked rather than hoped: recipes still resolve (ItemClassModifier extends ItemClass, names live in the shared ItemClass.nameToItem, Recipe looks them up through GetItemClass); saves are safe (ItemClass.assignIdsFromMapping takes ids from the stored name->id mapping, so shuffling configs cannot swap the item); the Harmony patch and the localization keys go by name, and the name did not change. THE TRAP IN MOVING IT: a modifier's effect_group applies to the item it is INSTALLED IN - the 1000 durability would have been granted to the BRACELET. The passive is gated with tags="necroBloodFlask" and the tag added to the flask's own Tags: MaxUseTimesBase calls GetValue with the ItemTags of whatever it is computing for. THE BRACELET HAS 1 MOD SLOT, down from 4. The slot stopped being a set of upgrades and became a choice. AN EMPTY SLOT REFUSES. The check is the first line of Begin, ahead of every other refusal: the others are about the TARGET (no block, wrong block, vault full), this one is about the TOOL, and saying "no block there" when the real problem is an empty bracelet sends the player looking in the wrong place. The test is ItemValue.HasMods(), the game's own: it walks Modifications only, skipping nulls and IsEmpty, and does not count CosmeticMods - a dye would otherwise have read as "loaded". The refusal sound came free: Deny() in this file already plays vanilla's ui_denied. THE COST. The price of a pull is computed ONCE, at the start, and carried in PickupJob.ChannelSeconds along with the bracelet itself. Not for brevity: ChannelSecondsFor measures the player's ray, and ten seconds is long enough to turn away - a second call would charge for a different block than the one taken. The bracelet is kept as an instance for the same reason: mods live on the ItemValue and the hotbar scrolls. It is charged in SpendBlood, AFTER SetBlockRPC and after the item is in the vault: every refusal returns earlier, so blood charged for a cancelled pull is impossible by construction. The item name comes from NecromancerBloodPatch.BloodItemName rather than a second literal, so the two cannot drift. A mod that is not blood pays nothing and does not block the pull - the slot is meant for other things. RUNS OUT, SHATTERS. The rule is "durability 0 or less", not "could not cover the pull", and the difference is not cosmetic: under the second wording a flask with exactly enough left would sit in the slot at zero, HasMods() would see "something installed", and the bracelet would work for free forever. So the MaxUseTimes clamp is gone and the slot is cleared with ItemValue.None - type 0, exactly what IsEmpty() tests. The last pull always completes; the flask simply does not survive it. The sound is vanilla's itembreak, the same cue ItemAction.HandleItemBreak plays. A guard: if MaxUseTimes comes out 0 (the passive effect or the tag removed), the flask is NOT deleted and a warning naming the fix goes to the log. Without that branch a config error would eat the player's item on the first pull and look like a bug in the mechanic. Localization: a new braceletSpatialVaultNoMod key in 13 languages, plus the flask and bracelet descriptions - the mechanic became conditional and paid, and both texts would have been lies without it. Known and deliberate: the Blood Stone the empty-slot message promises does not exist yet - it is planned, written up in BACKLOG.md. None of this is tested in game. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MnwP2Dt1vk8bUPJ452EoVL
This commit is contained in:
co-authored by
Claude Opus 5
parent
20af2bbe6c
commit
e362c627e7
+39
-70
@@ -1284,70 +1284,23 @@
|
||||
</item>
|
||||
</append>
|
||||
|
||||
<!-- "Кровь некроманта" (Necromancer's Blood): dictated 2026-08-30. Extends medicalBloodBag for
|
||||
its mesh/material/hold/pickup-sound (reused wholesale, thematically it IS a bag of blood,
|
||||
just a darker/necromantic one) - CustomIcon still set explicitly even though Extends is
|
||||
used, same lesson as every other item in this mod (Extends alone never gives a working
|
||||
icon, confirmed originally on the Knife). Reuses the REAL vanilla sprite named
|
||||
"medicalBloodBag" itself (that item has no CustomIcon of its own, so its sprite name
|
||||
equals its item name) rather than a generated-art file, per direct instruction ("Иконка
|
||||
такая же как и у обычной крови, но тинт затемнённый") - only TintColor differs (dark,
|
||||
near-black red vs. no tint on the vanilla bag).
|
||||
<!-- "Кровь некроманта" (Necromancer's Blood) ПЕРЕЕХАЛА В Config/item_modifiers.xml 2026-09-15.
|
||||
|
||||
Crafting rules ("для создания нужна пустая банка и наличие любого ножа. При крафте нужно
|
||||
отнимать у персонажа 90% имеющегося ХП") - the jar is a normal recipe ingredient (see
|
||||
recipes.xml), but "any knife present, not consumed" and "cost 90% of current HP" have NO
|
||||
vanilla XML equivalent (recipes.xml has no per-ingredient "required but not consumed" flag,
|
||||
and crafting a resource has no HP-cost hook at all) - both enforced in
|
||||
HarmonySrc/NecromancerBloodPatch.cs instead. See that file for the exact decompiled
|
||||
mechanism and an important caveat about ingredient-refund timing that's flagged there, not
|
||||
glossed over. -->
|
||||
<append xpath="/items">
|
||||
<item name="resourceNecromancerBlood">
|
||||
<property name="Extends" value="medicalBloodBag"/>
|
||||
<property name="DescriptionKey" value="resourceNecromancerBloodDesc"/>
|
||||
<!-- Custom art delivered 2026-08-30 (exch/NecromantsBlood.png, 160x160, copied to
|
||||
UIAtlases/ItemIconAtlas/) - replaces the earlier placeholder that reused the
|
||||
vanilla medicalBloodBag sprite with a darkened tint. No CustomIconTint here,
|
||||
same reasoning as every other hand-drawn icon in this mod (Dog/Insect summon
|
||||
books, etc.) - don't recolor finished art. -->
|
||||
<property name="CustomIcon" value="NecromantsBlood"/>
|
||||
Причина - указание «пусть флакон крови можно будет вставлять как модификацию для
|
||||
пространственного хранилища», и это оказалось не настройкой, а сменой класса предмета.
|
||||
XUiC_ItemPartStack.CanSwap открывается строкой
|
||||
|
||||
<!-- СВОЯ БАНКА С КРОВЬЮ, 2026-09-10 (указание: «берём чай из золотарника, и жёлтое
|
||||
заменяем на кровавый цвет, с фиолетовыми оттенками»).
|
||||
|
||||
Заодно чинится расхождение текста и модели: описание предмета
|
||||
(resourceNecromancerBloodDesc) с самого начала говорит «Банка, наполненная кровью
|
||||
самого некроманта», а наследуемый medicalBloodBag показывает
|
||||
@:Other/Items/Misc/sackPrefab.prefab - обычный мешок. Банка вернее и по механике:
|
||||
рецепт и так требует пустую банку (recipes.xml, NecromancerBloodPatch.cs).
|
||||
|
||||
ПОЧЕМУ НЕ ХВАТИЛО ТИНТА - ПРОВЕРЕНО В ИГРЕ. Сначала пробовали дёшево, без бандла:
|
||||
ванильный префаб чая плюс TintColor. Проверка 2026-09-10 показала, что банка
|
||||
осталась чаем из золотарника - тинт предмета на этот меш НЕ ПОДЕЙСТВОВАЛ ВООБЩЕ.
|
||||
У шейдера Game_EntityTintMaskSSS выигрывает собственный _Color материала (у чая
|
||||
жёлтый, 166,133,37), и свойство предмета его не перебивает. Поэтому TintColor здесь
|
||||
не задаётся совсем: он ничего не даёт и только вводил бы в заблуждение.
|
||||
|
||||
И по сути: кровь отличается от чая не цветом, а тем, что она непрозрачная, тёмная и
|
||||
густая, с плёнкой на стекле. Поэтому жидкость ПЕРЕРИСОВАНА по яркости, а не
|
||||
перекрашена множителем - генератор _private/tools/make_necroblood_textures.py.
|
||||
|
||||
HoldType 3 - хват банки вместо 45 (мешок), Material Mglass - стекло вместо ткани
|
||||
(звук удара и осколки при разбитии). Без них банка держалась бы как мешок.
|
||||
|
||||
ЧТО СМОТРЕТЬ ГЛАЗАМИ. Материал собран на встроенном Standard в режиме Fade: родной
|
||||
шейдер переиспользовать нельзя, AssetRipper выгрузил шейдеры заглушками. У Standard
|
||||
альфа текстуры - это прозрачность, поэтому она задана осознанно: стекло
|
||||
полупрозрачное, жидкость плотная. Банка должна читаться как стекло с густой кровью,
|
||||
а не как матовый сосуд. -->
|
||||
<property name="Meshfile" value="#@modfolder(NecromancerTome):Resources/necroblood?necroBloodPrefab.prefab"/>
|
||||
<property name="HoldType" value="3"/>
|
||||
<property name="Material" value="Mglass"/>
|
||||
|
||||
<property name="EconomicValue" value="0"/>
|
||||
</item>
|
||||
</append>
|
||||
if (!(stack.itemValue.ItemClass is ItemClassModifier itemClassModifier)) return false;
|
||||
|
||||
- слот модификации не смотрит ни на теги, ни на свойства, пока предмет не ItemClassModifier,
|
||||
а этот класс создаётся ТОЛЬКО из <item_modifier> в item_modifiers.xml. Никакого свойства
|
||||
вида "CanBeInstalled" не существует; остаться в items.xml и стать модификацией нельзя.
|
||||
|
||||
Предмет при этом остался обычным во всём остальном: ItemClassModifier наследует ItemClass и
|
||||
регистрируется в том же ItemClass.nameToItem, поэтому рецепты, крафт и Harmony-патч
|
||||
(NecromancerBloodPatch.cs) находят его по имени как раньше. Разбор - в BACKLOG.md.
|
||||
|
||||
Само определение, со всеми старыми комментариями про банку, текстуры и прочность - там. -->
|
||||
|
||||
<!-- "Петля вора" (Thief's Loop) - REMOVED 2026-08-30. Existed briefly (dictated/implemented
|
||||
2026-08-29, reworked several times through 2026-08-30 chasing load errors), removed per
|
||||
@@ -1398,7 +1351,8 @@
|
||||
<append xpath="/items">
|
||||
<item name="braceletSpatialVault">
|
||||
<!-- MOD SLOTS ADDED 2026-09-13 ("добавь хранилищу 4 слота под модификации. Сами
|
||||
модификации реализуем потом"). Two tags, exactly the scheme necroWpnBladeNecroKnife
|
||||
модификации реализуем потом"), УБАВЛЕНЫ ДО ОДНОГО 2026-09-15 - число стоит в
|
||||
effect_group в самом низу этого предмета, здесь только теги. Two tags, exactly the scheme necroWpnBladeNecroKnife
|
||||
already proved on 2026-09-07 - see that item's own comment for the full
|
||||
decompiled reasoning:
|
||||
|
||||
@@ -1536,14 +1490,29 @@
|
||||
bar), and it is deliberately left unset here. The item behaves as tiered for the
|
||||
mod system and still reads as a plain bracelet in the UI.
|
||||
|
||||
FOR THE MODS THEMSELVES, WHEN THEY GET WRITTEN: give each one its OWN
|
||||
modifier_tags. XUiC_ItemPartStack.CanSwap counts already-installed mods whose
|
||||
modifier_tags intersect the one being installed and refuses at
|
||||
num >= ItemClass.MaxModsAllowed, which defaults to 1 - so a shared tag like
|
||||
"necroBraceletMod" across all four would leave exactly one of these four slots
|
||||
usable. -->
|
||||
FOR THE MODS THEMSELVES: give each one its OWN modifier_tags.
|
||||
XUiC_ItemPartStack.CanSwap counts already-installed mods whose modifier_tags
|
||||
intersect the one being installed and refuses at num >= ItemClass.MaxModsAllowed,
|
||||
which defaults to 1. With a single slot this no longer costs slots - but it still
|
||||
matters, because a shared tag would ALSO make two different bracelet mods mutually
|
||||
exclusive in ways nothing in the UI explains. Keep them distinct.
|
||||
|
||||
ОДИН СЛОТ, указание 2026-09-15 («убавь у пространственного браслета количество
|
||||
слотов под модификации до одного»). Было 4, поставленные 2026-08-30 по прежнему
|
||||
выбору пользователя («4 фиксированно»).
|
||||
|
||||
Что это меняет по сути: слот из «набора улучшений» превратился в ВЫБОР. Сейчас
|
||||
единственный кандидат - флакон Крови некроманта (item_modifiers.xml, переехал туда
|
||||
2026-09-15), так что выбирать пока не из чего; но каждая следующая модификация
|
||||
браслета теперь конкурирует за один слот, а не добавляется к остальным. Это стоит
|
||||
держать в голове при их придумывании - иначе получится набор, из которого всегда
|
||||
берут одну и ту же.
|
||||
|
||||
Число только здесь. ModSlots - пассивка, а не свойство, и никакой другой файл на
|
||||
него не смотрит; менять обратно - эта же строка. У Ножа некроманта свои 4 слота
|
||||
(выше в этом файле, ~строка 1056) - их указание НЕ трогало. -->
|
||||
<effect_group name="braceletSpatialVault">
|
||||
<passive_effect name="ModSlots" operation="base_set" value="4"/>
|
||||
<passive_effect name="ModSlots" operation="base_set" value="1"/>
|
||||
</effect_group>
|
||||
</item>
|
||||
</append>
|
||||
|
||||
Reference in New Issue
Block a user