Эндгейм-версия расходника Пространственного браслета: то же место в слоте, что у
Кровавой сферы, но не тратится вовсе. Кода менять не пришлось - только конфиги.
ЗАОДНО ЭТО ПОЧИНКА УЖЕ ОТГРУЖЕННОГО ТЕКСТА. Сообщение о пустом слоте
(braceletSpatialVaultNoMod) и описание браслета обещают игроку "кровавую сферу
ИЛИ кровавый камень" на всех 13 языках с 15.09. Пока камня не было, оба текста
отправляли за предметом, которого в игре не существует.
СОВМЕСТИМОСТЬ СЕЙВОВ - ПЕРВОЕ, ЧТО ПРОВЕРЕНО. resourceBloodStone - НОВОЕ имя, в
существующих сейвах его нет, значит нет и ни одного стака, который читался бы по
другой байтовой раскладке. Это ровно то условие, нарушение которого в e362c62
убило персонажа: ItemValue.Read/Write гейтят блок модификаций одним условием
!(itemClass is ItemClassModifier), и класс предмета определяет раскладку каждого
стака в сейве. Камень заведён новым предметом, а не переводом существующего -
тем же решением, что и Кровавая сфера. Добавление нового <item_modifier>
раскладку уже записанных стаков не меняет: айди новых предметов дописываются в
name->id мэппинг сейва, байты старых стаков остаются как были.
БЕСКОНЕЧНАЯ ПРОЧНОСТЬ СДЕЛАНА ОТСУТСТВИЕМ СТРОК, А НЕ БОЛЬШИМ ЧИСЛОМ. У камня
сознательно нет ни ShowQuality, ни DegradationBreaksAfter, ни effect_group с
DegradationMax - сравнить со сферой, у которой все три есть. SpendCharge
(SpatialVaultPickupPatch.cs) списывает прочность только у модификации, чьё имя
равно ChargeItemName ("resourceBloodSphere"); камень под условие не попадает,
цикл делает continue, его UseTimes не трогает никто. Большой DegradationMax был
бы хуже: неподвижная полоска прочности бесполезна, а при MaxUseTimes == 0 она
рисуется ПОЛНОЙ, то есть забытая пассивка выглядела бы как "всё работает".
Три места в коде сверены на то, что камень пройдёт весь путь:
1. Гейт пустого слота в Begin - это !_bracelet.HasMods(), то есть "в слоте
что-нибудь есть", а НЕ "есть сфера по имени". Камень гейт проходит. Это был
главный риск правки: искал бы гейт сферу по имени, камень не работал бы.
2. SpendCharge вызывается ПОСЛЕ SetBlockRPC(..., Air) и bag.AddItem, а его
результат только логируется - ветки "не заплатил -> откажи" нет. Блок с
камнем уходит в хранилище, в логе "took ... into the vault for 0 of charge".
3. Ветка max <= 0 в SpendCharge камня не касается: до неё доходят только
предметы, прошедшие проверку имени.
ФАЙЛЫ
Config/item_modifiers.xml - новый item_modifier resourceBloodStone,
installable_tags="necroBracelet", свой modifier_tags="necroBraceletStone".
Extends="modGeneralMaster" param1="CustomIcon" - НЕ resourceRockSmall: с ним
приехали бы Action0 ThrowAway и ThrowableDecoy, и камень можно было бы метать
как отвлекающий (на сфере эта ошибка уже была и была исправлена). Вид в руке -
HoldType 40 плюс все три меша rock_smallPrefab плюс TintColor "220, 30, 45", как
у сферы и трёх камней мода. CustomIcon="BloodStone", без CustomIconTint:
иконка своя, рисованная, лежит в обеих папках атласа с 15.09.
Config/recipes.xml - рецепт на химстанции, craft_area="chemistryStation",
tags="learnable,chemStationCrafting,necroNecromancyApprentice": 50 праха зомби,
20 костей, 4 обычной крови, 1 кровь некроманта. Все четыре имени проверены по
файлам. Цена выше, чем читается по списку: крафт самой крови некроманта снимает
90% текущего ХП.
Config/progression.xml - камень добавлен в unlock_entry третьего display_entry
(unlock_level="2000", unlock_tier="1"). Это только строка в панели скилла;
открытие рецепта делает тег. 2000 здесь и 2000 у RecipeTagUnlocked обязаны
совпадать, иначе замок на панели разойдётся с доступностью рецепта.
Config/Localization.csv - два ключа, resourceBloodStone и
resourceBloodStoneDesc, 13 языков; ключей стало 135. Названия камня взяты из уже
переведённого braceletSpatialVaultNoMod, так что сообщение об отказе и имя в
инвентаре совпадают слово в слово. braceletSpatialVaultDesc дополнено на всех 13
языках: камень не тратится. Файл переписан через csv-модуль, round-trip без
правок даёт побайтово тот же файл - кавычки и CRLF остальных строк не поехали.
ОПИСАНИЯ. README.md, SITE_DESCRIPTION.html и NEXUS_DESCRIPTION.bbcode доведены
до текущего состояния мода: сфера и камень добавлены в таблицы тиров и в список
арсенала, у браслета сказано, что забор блоков питается зарядом в слоте,
счётчик ключей 130 -> 135. Заодно снято утверждение "весь контент проходит тесты
в игре" - оно перестало быть верным: расход сферы в игре подтверждён, камень в
игру ещё не грузился.
НЕ ПРОВЕРЕНО В ИГРЕ: камень целиком. Всё выше - сверка по файлам и по
декомпиляции. Сценарий проверки в BACKLOG.md; главное, на что смотреть, -
отсутствие полоски прочности у камня в слоте и "for 0 of charge" в логе.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
521 lines
46 KiB
XML
521 lines
46 KiB
XML
<config>
|
||
<!-- Модификации Ножа некроманта. Продиктовано 2026-09-07: "добавь в нож слоты для
|
||
модификаций. Но модификации там будут особые, именно для ножа некроманта, а не для
|
||
обычного ножа."
|
||
|
||
HOW THE EXCLUSIVITY WORKS (both halves verified against the decompiled assembly, not
|
||
guessed - see the long comment on the knife's Tags in items.xml for the full audit):
|
||
|
||
- installable_tags="necroKnife" is the positive half. The install UI checks
|
||
"InstallableTags.IsEmpty || itemClass.HasAnyTags(InstallableTags)"
|
||
(XUiC_ItemPartStack.CanSwap / XUiC_ItemStack / XUiM_AssembleItem all agree), matched
|
||
against the TARGET ITEM's Tags. necroWpnBladeNecroKnife is the only item anywhere -
|
||
vanilla or this mod - that carries "necroKnife", so these three fit it and nothing
|
||
else. Note the IsEmpty short-circuit above: a modifier with no installable_tags at all
|
||
goes into ANY item, so leaving it off would be the opposite of what was asked.
|
||
- The knife's own "noMods" tag is the negative half, blocking all 87 vanilla modifiers
|
||
from going the other way. That lives on the knife, not here.
|
||
|
||
WHY EACH ONE HAS ITS OWN modifier_tags: mods whose modifier_tags overlap are counted
|
||
against ItemClass.MaxModsAllowed, which defaults to 1 (ItemClass.cs line 376), and
|
||
CanSwap refuses once the count is reached. Giving all three a shared tag like
|
||
"necroKnifeMod" would therefore have let the player install exactly ONE of them at a time
|
||
- the four slots would be unusable. Distinct tags per mod is also what vanilla does
|
||
(damageBleed / barrelAttachment / droneArmor ...).
|
||
|
||
type="attachment" (not "mod") so they can be pulled back out and moved to another knife -
|
||
a type="mod" is permanent once installed.
|
||
|
||
ICONS UPDATED 2026-09-07: real generated art for all six, drawn by the user and dropped in
|
||
via c:\Exchange\exch. Each mod now points CustomIcon at its own sprite in
|
||
UIAtlases/ItemIconAtlas (TearsOfTheDead / ScavengersFeast / DeadMansGrip / GravesRepose /
|
||
DeadStorm / DarkSense, all 160x160 RGBA like the other 21). This replaced the old
|
||
reused-vanilla-sprite placeholders (drinkJarBoiledWater, foodShamSandwich,
|
||
modMeleeGraveDigger, modArmorInsulatedLinerT2, modMeleeStunBatonRepulsor,
|
||
modGunScopeSmall) - the same progression the Knife and Victim's Skin went through on
|
||
2026-08-29. CustomIconTint dropped from all six along with them: it existed only to stop
|
||
the borrowed vanilla sprites reading as the items they came from, and on purpose-drawn art
|
||
it would just darken the picture.
|
||
|
||
If a tint is ever needed again here, note the format trap that cost a round the first
|
||
time: CustomIconTint is HEX ("785AB4"), NOT the "R, G, B" triplet that TintColor takes on
|
||
the knife itself. They look interchangeable and are not - decompiled, ItemClass parses this
|
||
one with StringParsers.ParseHexColor while TintColor goes through the Color32 comma path,
|
||
and every vanilla CustomIconTint value is hex (FF00FF, 6441A5, C68C53...). -->
|
||
<append xpath="/item_modifiers">
|
||
|
||
<!-- "Слёзы мертвеца" - вода. Продиктовано 2026-09-07: "Если он вставлен в нож, то каждый
|
||
убитый зомби даёт 2 единицы воды."
|
||
|
||
IMPORTANT - this deliberately does NOT just add to $waterAmountAdd and stop there.
|
||
That CVar is only a QUEUE; on its own it hydrates nobody. Every vanilla drink
|
||
(drinkJarRiverWater, Data/Config/items.xml ~21744) pairs the add with
|
||
"AddBuff buffProcessConsumables", and it is buffProcessConsumables that adds
|
||
buffHealWaterMax (Data/Config/buffs.xml ~8527: AddBuff buffHealWaterMax requires
|
||
$waterAmountAdd GT 0), whose own onSelfBuffUpdate is what finally moves the number
|
||
into the water stat - .1 per .1s tick, so the 2 units land over about two seconds.
|
||
Missing that second line is exactly the bug that made the knife's lifesteal do
|
||
nothing at all for over a week (see items.xml), so it is spelled out here on purpose.
|
||
|
||
onSelfKilledOther is fired by ItemActionAttack (decompiled, lines ~798 and ~846) as
|
||
"entityAlive.FireEvent(MinEventTypes.onSelfKilledOther, flag4)", guarded by
|
||
"!wasAlreadyDead && entity.IsDead()" so it means a real kill, not a hit on a corpse.
|
||
flag4 is "the damaging item IS the held item", which is true for a melee swing - and
|
||
that same flag4 is what routes the event through Inventory -> ItemValue -> installed
|
||
Modifications. It is the identical argument the knife's already-working
|
||
onSelfAttackedOther effects ride on, so if those fire, this fires. (No vanilla
|
||
item_modifier happens to use onSelfKilledOther, but it is in item_modifiers.xml's own
|
||
documented TRIGGER LIST and the dispatch path above is unconditional.)
|
||
|
||
Gated to zombies via EntityTagCompare on "other" - killing a bear or a bird gives
|
||
nothing, matching how every other on-hit effect in this mod is gated. -->
|
||
<item_modifier name="necroModKnifeTearsOfTheDead" installable_tags="necroKnife" modifier_tags="necroKnifeWater" type="attachment">
|
||
<property name="Extends" value="modGeneralMaster" param1="CustomIcon"/>
|
||
<property name="CustomIcon" value="TearsOfTheDead"/>
|
||
<property name="EconomicValue" value="0"/>
|
||
<property name="SellableToTrader" value="false"/>
|
||
|
||
<effect_group tiered="false">
|
||
<requirement name="EntityTagCompare" target="other" tags="zombie"/>
|
||
<triggered_effect trigger="onSelfKilledOther" action="ModifyCVar" cvar="$waterAmountAdd" operation="add" value="2"/>
|
||
<triggered_effect trigger="onSelfKilledOther" action="AddBuff" buff="buffProcessConsumables"/>
|
||
</effect_group>
|
||
</item_modifier>
|
||
|
||
<!-- "Пир падальщика" - еда. Продиктовано 2026-09-07: "Следующий мод, на еду. Принцип тот
|
||
же." - same 2 units, same per-kill trigger, same zombie gate.
|
||
|
||
Food rides the mirror-image path of the water mod above: $foodAmountAdd is the queue,
|
||
buffProcessConsumables is what notices it (Data/Config/buffs.xml ~8525: AddBuff
|
||
buffHealFood requires $foodAmountAdd GT 0), buffHealFood is what actually feeds you.
|
||
One AddBuff covers both mods if they are installed together - buffProcessConsumables
|
||
checks water and food independently and hands out whichever buffs apply, so stacking
|
||
the two mods costs nothing extra and neither one cancels the other. -->
|
||
<item_modifier name="necroModKnifeScavengersFeast" installable_tags="necroKnife" modifier_tags="necroKnifeFood" type="attachment">
|
||
<property name="Extends" value="modGeneralMaster" param1="CustomIcon"/>
|
||
<property name="CustomIcon" value="ScavengersFeast"/>
|
||
<property name="EconomicValue" value="0"/>
|
||
<property name="SellableToTrader" value="false"/>
|
||
|
||
<effect_group tiered="false">
|
||
<requirement name="EntityTagCompare" target="other" tags="zombie"/>
|
||
<triggered_effect trigger="onSelfKilledOther" action="ModifyCVar" cvar="$foodAmountAdd" operation="add" value="2"/>
|
||
<triggered_effect trigger="onSelfKilledOther" action="AddBuff" buff="buffProcessConsumables"/>
|
||
</effect_group>
|
||
</item_modifier>
|
||
|
||
<!-- "Хватка мертвеца" - замедление. Chosen by the user from the proposed list 2026-09-07.
|
||
|
||
Reuses buffInjurySlow, the same vanilla debuff the mod's own Зомбособака already
|
||
applies on its bite (necroMeleeHandZombieDog in items.xml) - proven working in this
|
||
mod rather than a fresh guess, and the same EntityTagCompare zombie gate.
|
||
|
||
onSelfAttackedOther, not onSelfKilledOther: the point is to slow a zombie that is
|
||
still coming at you, so it has to land on the hit, not on the kill. -->
|
||
<item_modifier name="necroModKnifeDeadMansGrip" installable_tags="necroKnife" modifier_tags="necroKnifeSlow" type="attachment">
|
||
<property name="Extends" value="modGeneralMaster" param1="CustomIcon"/>
|
||
<property name="CustomIcon" value="DeadMansGrip"/>
|
||
<property name="EconomicValue" value="0"/>
|
||
<property name="SellableToTrader" value="false"/>
|
||
|
||
<effect_group tiered="false">
|
||
<requirement name="EntityTagCompare" target="other" tags="zombie"/>
|
||
<triggered_effect trigger="onSelfAttackedOther" action="AddBuff" target="other" buff="buffInjurySlow"/>
|
||
</effect_group>
|
||
</item_modifier>
|
||
|
||
<!-- "Могильный покой" - защита от переохлаждения и перегрева. Продиктовано 2026-09-07:
|
||
"Пусть защищает от переохлаждения и перегрева... Есть параметр устойчивости к холоду и
|
||
жаре. Он бывает на предметах одежды и на некоторых модах на одежду."
|
||
|
||
Найденные параметры - HypothermalResist (холод) и HyperthermalResist (жара). Живой
|
||
ванильный образец: modArmorInsulatedLinerT1/T2/T3 (Data/Config/item_modifiers.xml
|
||
~1873), они ставят ровно эту пару. Величина у них по тирам: T1 1->2.5, T2 2.8->4.3,
|
||
T3 4.6->6 на ОДИН элемент брони, а элементов четыре.
|
||
|
||
ЗНАЧЕНИЕ 5 -> 50, 2026-09-13, прямое указание пользователя ("по факту она поднимает
|
||
сопротивление всего на 5, а надо на 50"). Изначально стояло 5 - примерно уровень одной
|
||
детали брони с T3-подкладкой, и ровно то число, которым ваниль пользуется во
|
||
вкомментированных modArmorInsulatedLiner/modArmorCoolingMesh. Это было осознанно
|
||
скромно; пользователь хочет иначе, и его решение тут главнее моей балансной оценки.
|
||
|
||
ЧТО 50 ОЗНАЧАЕТ НА САМОМ ДЕЛЕ, раз единица - градусы, а не проценты (формула ниже):
|
||
любая уличная температура в пределах 50 градусов от комфортных 70 подтягивается К 70
|
||
ЦЕЛИКОМ, потому что там стоит min/max-ограничение. То есть от 20 до 120 по шкале игры
|
||
это не "сильная защита", а полный иммунитет: и снежная вершина, и пустынный полдень
|
||
перестают быть угрозой. Это примерно в 10 раз больше, чем даёт набор брони с
|
||
T3-подкладками на всех четырёх деталях. Записано не в укор, а чтобы через месяц не
|
||
пришлось гадать, почему термометр перестал что-либо значить.
|
||
|
||
ЕДИНИЦА ИЗМЕРЕНИЯ - градусы, на которые сдвигается уличная температура в сторону
|
||
комфортной, а не проценты (PlayerEntityStats, декомпиляция):
|
||
if (outsideTemperature < 70) { v = GetValue(HypothermalResist);
|
||
outsideTemperature = min(70, outsideTemperature + v); }
|
||
else { v = GetValue(HyperthermalResist);
|
||
outsideTemperature = max(70, outsideTemperature - v); }
|
||
|
||
ВАЖНО - РАБОТАЕТ ТОЛЬКО ПОКА НОЖ В РУКАХ. Это не оплошность, а то, как движок вообще
|
||
умеет учитывать не-броню, и проверено по всей цепочке, потому что термостойкость - это
|
||
обычно броневой стат, а нож не броня:
|
||
1. PlayerEntityStats зовёт EffectManager.GetValue(HypothermalResist, null, 0f, entity)
|
||
со всеми параметрами по умолчанию, а в сигнатуре GetValue значения по умолчанию -
|
||
calcHoldingItem: true и useMods: true. То есть предмет в руках и его моды учитываются.
|
||
2. Внутри GetValue ветка "else if (calcHoldingItem && ...)" зовёт Inventory.ModifyValue.
|
||
3. Inventory.ModifyValue пропускает предмет, если его теги попадают в
|
||
ignoreWhenHeld = FastTags.Parse("clothing,armor"). У ножа ни того, ни другого нет,
|
||
так что он проходит - а вот на реальной броне в руках это бы не сработало.
|
||
4. ItemValue.ModifyValue в конце обходит Modifications[j].ModifyValue под флагом
|
||
_useMods. Заметь: здесь, в отличие от FireEvent, НЕТ отсечки "if (!HasQuality)
|
||
return;" - пассивки модов считаются независимо от качества. Но нож всё равно уже
|
||
починен по tiered (см. items.xml), так что вопрос снят в обе стороны.
|
||
Убрал нож в рюкзак - защита пропала. Так и задумано, и так это описано в Localization.csv.
|
||
|
||
Пассивка, а не triggered_effect, поэтому ни onSelfKilledOther, ни гейта на зомби здесь
|
||
нет - эффект просто висит, пока нож в руке.
|
||
|
||
Иконка - спрайт настоящего ванильного мода-подкладки (у item_modifier без своего
|
||
CustomIcon спрайт называется как он сам), то есть по смыслу ровно та картинка. Тинт
|
||
тот же фиолетовый, что у остальных трёх, до появления собственной графики. -->
|
||
<item_modifier name="necroModKnifeGravesRepose" installable_tags="necroKnife" modifier_tags="necroKnifeThermal" type="attachment">
|
||
<property name="Extends" value="modGeneralMaster" param1="CustomIcon"/>
|
||
<property name="CustomIcon" value="GravesRepose"/>
|
||
<property name="EconomicValue" value="0"/>
|
||
<property name="SellableToTrader" value="false"/>
|
||
|
||
<effect_group tiered="false">
|
||
<passive_effect name="HypothermalResist" operation="base_add" value="50"/>
|
||
<passive_effect name="HyperthermalResist" operation="base_add" value="50"/>
|
||
</effect_group>
|
||
</item_modifier>
|
||
|
||
<!-- "Мёртвая буря" - переделывает силовую атаку. Продиктовано 2026-09-07: "-5 HP повышаем
|
||
до -10 HP. Эффект делаем AOE в радиусе 20 блоков (если это много, то скажи). Расход
|
||
стамины на силовую атаку увеличиваем вдвое. Добавляем дебаф кровотечения и искрения."
|
||
|
||
ФОРМА И РАЗМЕР ОБЛАСТИ. Пользователь потом уточнил: "Нужна именно сфера. Иначе птиц не
|
||
заденет." Сфера не нужна и недоступна - движок умеет ровно одну форму, и она уже
|
||
объёмная. MinEventActionTargetedBase, ветка otherAOE:
|
||
entsInRange = World.GetLivingEntitiesInBounds(_params.Self,
|
||
new Bounds(_params.Other.position, Vector3.one * (maxRange * 2f)));
|
||
Unity-шный Bounds(center, SIZE) берёт РАЗМЕР, а не extents, здесь size = range*2 по
|
||
каждой оси - то есть это КУБ, расходящийся на range блоков во все шесть сторон,
|
||
ВКЛЮЧАЯ вверх и вниз. Дальше World.GetLivingEntitiesInBounds обходит чанки по X/Z и
|
||
сверяет коробки; никакого доп. фильтра по дистанции после выборки нет (см. цикл сразу
|
||
за вызовом - только isValidTarget и singleTargetCheck, оба про теги, не про радиус).
|
||
Поэтому вертикаль покрыта уже сейчас, и куб для летящей цели даже ЛУЧШЕ сферы: сфера
|
||
радиуса R целиком помещается внутрь куба с полу-размером R.
|
||
|
||
Заодно из этого следует, что 20 - это очень много: куб 40x40x40 вокруг цели, сквозь
|
||
стены и перекрытия (проверки линии взгляда тут нет вовсе). Весь ванильный диапазон
|
||
AOE для сравнения: 1.1, 1.3, 1.4, 2.7, 3, 6 и ровно один случай 10
|
||
(buffRingOfFireEffect). Поставлено 6 - это куб 12x12x12, то есть 6 блоков вверх, чего
|
||
хватает на пикирующего стервятника, и при этом не выкашивает соседний этаж POI. Если
|
||
нужно доставать высоко кружащих птиц - поднимать; помнить, что то же число уходит и
|
||
в горизонталь. Менять - четыре атрибута range ниже.
|
||
|
||
ЧТО ЭТОТ МОД ДОБАВЛЯЕТ К БАЗОВОЙ СИЛОВОЙ. Мод умеет только ДОБАВЛЯТЬ эффекты, снять
|
||
собственные эффекты предмета он не может, поэтому "-5 HP -> -10 HP" сделано вторым
|
||
списанием на 5, а не переписыванием первого. Порядок детерминирован: в
|
||
ItemValue.FireEvent собственные эффекты предмета (itemClass.FireEvent) идут ДО цикла
|
||
по Modifications, так что сперва снимается базовая пятёрка, затем эта. Оба списания
|
||
несут свой порог "Health GT 5", поэтому убить себя силовой атакой по-прежнему нельзя;
|
||
следствие - при здоровье между 5 и 10 спишется только часть.
|
||
|
||
ПРО СТАМИНУ. База берётся в ItemActionDynamicMelee.cs:384:
|
||
_actionData.StaminaUsage = EffectManager.GetValue(PassiveEffects.StaminaLoss,
|
||
itemValue, 2f, holdingEntity, null, _actionData.ActionTags) * StaminaUsageMultiplier;
|
||
- значение по умолчанию 2 (своего StaminaLoss у этого ножа нет), и читается оно с
|
||
ActionTags, то есть "secondary" для силовой. GetValue возвращает
|
||
_originalValue * _perc_value, где _perc_value стартует с 1, а perc_add к нему
|
||
прибавляет - поэтому value="1" это ровно "вдвое", а не "+1".
|
||
|
||
ПРО КРОВОТЕЧЕНИЕ - две строки, а не одна, и это не перестраховка. buffInjuryBleeding
|
||
снимает сам себя, если у цели bleedCounter == 0:
|
||
<triggered_effect trigger="onSelfBuffUpdate" action="RemoveBuff" buff="buffInjuryBleeding">
|
||
<requirement name="CVarCompare" cvar="bleedCounter" operation="Equals" value="0"/>
|
||
а урон берёт как HealthChangeOT base_subtract @$bleedAmount, где $bleedAmount
|
||
выставляется из bleedCounter на старте баффа. Просто повесить бафф - он мгновенно
|
||
снимется, не сделав ничего. Поэтому сначала счётчик, потом бафф - как и у ванильного
|
||
modMeleeSerratedBlade. Счётчик задан через "set 2", а не "add 1", чтобы не зависеть от
|
||
$maxBleedCounter (его выставляют перки игрока, buffs.xml ~530) и чтобы эффект не
|
||
накапливался бесконечно от серии ударов. 2 -> 2 HP/сек в течение 20 секунд.
|
||
|
||
ПРО ИСКРЕНИЕ - это ванильный buffShocked, тот самый, что вешают электродубинка и
|
||
электрозабор. Своя частица p_electric_shock и звук electric_fence_impact уже внутри
|
||
баффа, подключать отдельно нечего. По умолчанию 4 секунды и -5 HP/сек через
|
||
HealthChangeOT. Его замедление лежит в ОТДЕЛЬНЫХ effect_group, гейтованных на
|
||
$shockDurationMax >= 4 - это важно для птиц, см. ниже.
|
||
|
||
СРАБОТАЕТ ЛИ НА ПТИЦ (прямой вопрос пользователя) - да, в той части, которая наносит
|
||
урон:
|
||
+ Стервятник попадает в выборку: коробка трёхмерная, вертикаль включена.
|
||
+ Тег есть: animalZombieVulture несёт "entity,animal,zombie,zombieAnimal,hostile,
|
||
vulture,special", а target_tags="zombie" гейтит именно по нему.
|
||
+ Кровотечение работает: HealthChangeOT применяется в EntityStats.cs:142, это общий
|
||
путь для любого EntityAlive, полёт ему безразличен.
|
||
+ Урон от искрения работает по той же причине.
|
||
- Замедление от buffShocked по птице НЕ отработает: оно давит на RunSpeed/WalkSpeed,
|
||
а EntityVulture.cs (~563/567) умножает на сырые поля moveSpeed/moveSpeedAggro и
|
||
геттеры GetMoveSpeed()/GetMoveSpeedAggro() - единственные, кто эти пассивки
|
||
применяет - не зовёт вовсе.
|
||
? Ragdoll по летящей цели - НЕ ПРОВЕРЕНО. Ни в EntityVulture, ни в EntityFlying нет
|
||
ни строчки про ragdoll, полёт-специфичной поддержки точно нет. Проверять в игре.
|
||
Итого по птице: сбить с ног скорее всего не выйдет, но кровотечение и разряд догрызут.
|
||
|
||
Иконка-заглушка - спрайт ванильного мода-репульсора электродубинки, тематически
|
||
ближайшее, что есть готового. -->
|
||
<item_modifier name="necroModKnifeDeadStorm" installable_tags="necroKnife" modifier_tags="necroKnifePower" type="attachment">
|
||
<property name="Extends" value="modGeneralMaster" param1="CustomIcon"/>
|
||
<property name="CustomIcon" value="DeadStorm"/>
|
||
<property name="EconomicValue" value="0"/>
|
||
<property name="SellableToTrader" value="false"/>
|
||
|
||
<effect_group tiered="false">
|
||
<!-- Вдвое дороже по стамине, только силовая. -->
|
||
<passive_effect name="StaminaLoss" operation="perc_add" value="1" tags="secondary"/>
|
||
|
||
<!-- Вторая половина платы: базовые 5 + эти 5 = 10 HP. -->
|
||
<triggered_effect trigger="onSelfSecondaryActionRayHit" action="ModifyStats" stat="Health" operation="subtract" value="5">
|
||
<requirement name="EntityTagCompare" target="other" tags="zombie"/>
|
||
<!-- "Health GT 0" на цели - тот же гейт на трупы, что у самого ножа, чтобы
|
||
силовой удар по трупу не брал 5 HP впустую; подробный разбор в items.xml
|
||
у лечения. AOE-строкам ниже он не нужен: GetLivingEntitiesInBounds сама
|
||
отсеивает мёртвых. -->
|
||
<requirement name="StatCompareCurrent" target="other" stat="Health" operation="GT" value="0"/>
|
||
<requirement name="StatCompareCurrent" stat="Health" operation="GT" value="5"/>
|
||
</triggered_effect>
|
||
|
||
<!-- AOE вокруг задетой цели. Базовое сбивание с ног у ножа одиночное, это его
|
||
расширяет; двойное попадание по самой цели безвредно - Ragdoll это действие,
|
||
а не стакающийся бафф. -->
|
||
<triggered_effect trigger="onSelfSecondaryActionRayHit" action="Ragdoll" target="otherAOE" range="6" target_tags="zombie" duration="1.5" force="150"/>
|
||
|
||
<!-- Кровотечение: сначала счётчик, затем бафф - см. большой комментарий выше. -->
|
||
<triggered_effect trigger="onSelfSecondaryActionRayHit" action="ModifyCVar" target="otherAOE" range="6" target_tags="zombie" cvar="bleedCounter" operation="set" value="2"/>
|
||
<triggered_effect trigger="onSelfSecondaryActionRayHit" action="AddBuff" target="otherAOE" range="6" target_tags="zombie" buff="buffInjuryBleeding"/>
|
||
|
||
<!-- Искрение. -->
|
||
<triggered_effect trigger="onSelfSecondaryActionRayHit" action="AddBuff" target="otherAOE" range="6" target_tags="zombie" buff="buffShocked"/>
|
||
</effect_group>
|
||
</item_modifier>
|
||
|
||
<!-- "Тёмное чутьё" - радар зомби, пока нож в руке. Вся механика и разбор, почему это
|
||
вообще выполнимо на XML, лежат в комментарии к buffNecroDarkSense в Config/buffs.xml -
|
||
здесь только выключатель.
|
||
|
||
onSelfEquipStart / onSelfEquipStop - ванильный паттерн для "работает, только пока
|
||
предмет в руках" (так сделаны эффекты кирок, Data/Config/items.xml ~158 и ~175).
|
||
Оба события шлёт Inventory на ItemValue держимого предмета (строки ~1226 и ~1245), а
|
||
ItemValue.FireEvent прокидывает их в установленные Modifications - при условии, что у
|
||
предмета есть качество, что у этого ножа теперь так (см. правку tiered в items.xml).
|
||
|
||
Бафф снимается и при смерти игрока: remove_on_death у него не выставлен, а по
|
||
умолчанию это true - то есть залипнуть после респавна он не может. -->
|
||
<item_modifier name="necroModKnifeDarkSense" installable_tags="necroKnife" modifier_tags="necroKnifeSense" type="attachment">
|
||
<property name="Extends" value="modGeneralMaster" param1="CustomIcon"/>
|
||
<property name="CustomIcon" value="DarkSense"/>
|
||
<property name="EconomicValue" value="0"/>
|
||
<property name="SellableToTrader" value="false"/>
|
||
|
||
<effect_group tiered="false">
|
||
<triggered_effect trigger="onSelfEquipStart" action="AddBuff" buff="buffNecroDarkSense"/>
|
||
<triggered_effect trigger="onSelfEquipStop" action="RemoveBuff" buff="buffNecroDarkSense"/>
|
||
</effect_group>
|
||
</item_modifier>
|
||
|
||
</append>
|
||
|
||
<!-- "Кровавая сфера" (Blood Sphere) - расходный заряд Пространственного браслета.
|
||
Название и рецепт продиктованы 2026-09-15: «Кровавая сфера. Доступна на первом грейде.
|
||
Станки не нужны. Ингридиенты: Кровь некроманта, 5 праха зомби. По одному рецепту
|
||
изготавливается две сферы. Прочность сферы 500.»
|
||
|
||
ЗАЧЕМ ОНА ВООБЩЕ СУЩЕСТВУЕТ - и почему это НЕ «кровь, вставляемая в браслет». 15.09.2026
|
||
кровь перенесли в этот файл, чтобы она вставлялась в браслет, и это уничтожило персонажа в
|
||
сейве вместе с бэкапом: класс предмета определяет байтовую раскладку его стака
|
||
(ItemValue.Read:1094 / Write:1228 - обычный предмет пишет байт числа модификаций,
|
||
ItemClassModifier не пишет), и старый сейв стал нечитаемым. Полный разбор - в BACKLOG.md и
|
||
в большом предупреждении у крови в items.xml.
|
||
|
||
Сфера обходит это тем, что она НОВЫЙ предмет: в старых сейвах её нет, значит нет и ни
|
||
одного стака, который читался бы по другой раскладке. Это общее правило, а не уловка:
|
||
нужна модификация - заводи новый предмет, никогда не переводи существующий.
|
||
|
||
Атрибуты - по образцу модов ножа:
|
||
installable_tags="necroBracelet" - иначе модификация лезла бы в ЛЮБОЙ предмет
|
||
(CanSwap короткозамыкается на InstallableTags.IsEmpty);
|
||
modifier_tags="necroBraceletSphere" - свой, чтобы не конкурировать с будущим Кровавым
|
||
камнем через MaxModsAllowed;
|
||
blocked_tags НЕ задан - у браслета в тегах "noMods", объявить его тут значило бы
|
||
заблокировать самому себе установку;
|
||
type="attachment" - сферу можно вынуть обратно. -->
|
||
<append xpath="/item_modifiers">
|
||
<item_modifier name="resourceBloodSphere" installable_tags="necroBracelet" modifier_tags="necroBraceletSphere" type="attachment">
|
||
<!-- Extends на modGeneralMaster - та же база, что у шести модов ножа: даёт Group
|
||
"Mods", звуки mod_grab/mod_place, Stacknumber 1 и CreativeMode None.
|
||
param1="CustomIcon" исключает наследование родительского missingIcon.
|
||
|
||
РАНЬШЕ ЗДЕСЬ СТОЯЛ Extends="resourceRockSmall", И ЭТО БЫЛА ОШИБКА. Камень нужен
|
||
был только ради вида, а вместе с ним приезжали Action0 Class="ThrowAway",
|
||
ThrowableDecoy="true" и DistractionTags - то есть сферу можно было бы метать как
|
||
отвлекающий камень. Меш берётся строкой Meshfile ниже; наследовать ради него
|
||
весь предмет не нужно. -->
|
||
<property name="Extends" value="modGeneralMaster" param1="CustomIcon"/>
|
||
<property name="DescriptionKey" value="resourceBloodSphereDesc"/>
|
||
|
||
<!-- В РУКЕ - КАМЕНЬ С АЛЫМ ТИНТОМ (указание 2026-09-15: «в руке и сфера и кровавый
|
||
камень пусть будут как камень с алым тинтом»).
|
||
|
||
Это уже проверенный в этом моде путь, а не догадка: Камень духов, Синий и Чёрный
|
||
порталы (items.xml) сидят ровно на этом меше с ровно таким тинтом и в игре
|
||
работают - зелёный, синий и чёрный камни соответственно. Поэтому взят их набор
|
||
целиком: HoldType 40 плюс ВСЕ ТРИ меша. Три, а не один, потому что это три разные
|
||
ситуации - Meshfile общий, HandMeshfile в руке, DropMeshfile лежащим на земле, и
|
||
у тех трёх предметов они выписаны явно именно поэтому.
|
||
|
||
TintColor - ТРИПЛЕТ "R, G, B", а НЕ hex. Это другая ручка, чем CustomIconTint
|
||
выше (там hex): ItemClass парсит их разными путями - Color32 через запятые против
|
||
ParseHexColor. Перепутать легко, и на модах ножа это уже стоило круга.
|
||
|
||
ПОЧЕМУ ЗДЕСЬ ТИНТ РАБОТАЕТ, А НА БАНКЕ КРОВИ НЕ СРАБОТАЛ. 10.09 тинт предмета на
|
||
меш чая из золотарника не подействовал вообще: у шейдера Game_EntityTintMaskSSS
|
||
выигрывает собственный _Color материала. У rock_smallPrefab такого конфликта нет -
|
||
доказательство лежит в самом моде, три перекрашенных камня в игре видны. -->
|
||
<property name="HoldType" value="40"/>
|
||
<property name="Meshfile" value="@:Other/Items/Crafting/rock_smallPrefab.prefab"/>
|
||
<property name="HandMeshfile" value="@:Other/Items/Crafting/rock_smallPrefab.prefab"/>
|
||
<property name="DropMeshfile" value="@:Other/Items/Crafting/rock_smallPrefab.prefab"/>
|
||
<property name="TintColor" value="220, 30, 45"/>
|
||
|
||
<!-- Своя рисованная иконка, получена 2026-09-15 (exch/bloodSphere.png, 160x160 RGBA,
|
||
как все остальные 28). Заглушка со спрайтом Камня духов и багровым тинтом,
|
||
стоявшая тут несколько часов, снята.
|
||
|
||
CustomIconTint НЕ ЗАДАЁТСЯ, и это тот же принцип, что у всех рисованных иконок
|
||
мода (шесть модов ножа, Кровь, Жертвенная кожа): тинт существует, чтобы
|
||
заимствованный ванильный спрайт не читался как предмет, из которого он взят. На
|
||
готовой работе он бы просто её затемнил. -->
|
||
<property name="CustomIcon" value="BloodSphere"/>
|
||
|
||
<!-- Прочность 500 (указание). Две ручки, обе обязательны - тот же разбор, что у крови
|
||
в items.xml: ShowQuality рисует полоску, а само число идёт пассивкой
|
||
DegradationMax. Без пассивки прочность равна нулю, а полоска при MaxUseTimes == 0
|
||
рисуется ПОЛНОЙ, то есть забытый эффект выглядит как "всё работает".
|
||
|
||
tiered="false" обязателен: HasQuality читается как Effects.IsOwnerTiered(), и
|
||
тированная группа превратила бы сферу в предмет с качеством - тиры, рамка.
|
||
|
||
Гейта по тегу здесь, в отличие от крови, НЕ НУЖНО. У модификации effect_group
|
||
применяется к предмету-хозяину, и у крови пассивку приходилось гейтить, чтобы
|
||
1000 прочности не досталась браслету. Сфера же и есть расходник браслета: пусть
|
||
он её и тратит. Браслету от DegradationMax ничего не будет - ему эту прочность
|
||
никто не списывает (SpendCharge трогает только сферу), а полоски у него нет. -->
|
||
<property name="ShowQuality" value="true"/>
|
||
<!-- true: опустевшая сфера должна исчезать, а не лежать "сломанной" в ожидании
|
||
ремонта. Само удаление из слота делает SpendCharge - см. SpatialVaultPickupPatch. -->
|
||
<property name="DegradationBreaksAfter" value="true"/>
|
||
<effect_group name="resourceBloodSphere" tiered="false">
|
||
<passive_effect name="DegradationMax" operation="base_set" value="500"/>
|
||
</effect_group>
|
||
|
||
<!-- Stacknumber 1 приходит из modGeneralMaster, своя строка не нужна. -->
|
||
<property name="EconomicValue" value="0"/>
|
||
<property name="SellableToTrader" value="false"/>
|
||
</item_modifier>
|
||
</append>
|
||
|
||
<!-- КРОВАВЫЙ КАМЕНЬ (Blood Stone), продиктовано 2026-09-15, сделано 2026-09-16: «По сути это
|
||
будет тот же флакон, но с бесконечной прочностью и иконкой камня с алым тинтом.»
|
||
Эндгейм-версия расходника браслета: то же, что Кровавая сфера, только не тратится.
|
||
|
||
ПОЧЕМУ ЭТО БЫЛО СРОЧНО, А НЕ «ФИЧА ИЗ СПИСКА». Сообщение о пустом слоте
|
||
(braceletSpatialVaultNoMod) и описание браслета с 15.09 обещают игроку «кровавую сферу
|
||
ИЛИ кровавый камень» на всех 13 языках. Пока камня не было, отгруженный текст отправлял
|
||
за предметом, которого в игре нет. Так что эта правка - ещё и починка текста.
|
||
|
||
ПОЧЕМУ ЭТО БЕЗОПАСНО ДЛЯ СЕЙВОВ, и это здесь главный вопрос, а не побочный.
|
||
15.09 перенос Крови некроманта из items.xml сюда уничтожил персонажа в сейве: ItemValue
|
||
гейтит блок модификаций одним условием на чтении (Read, ~1094) и на записи (Write, ~1228)
|
||
- "!(itemClass is ItemClassModifier)", то есть КЛАСС ПРЕДМЕТА ОПРЕДЕЛЯЕТ БАЙТОВУЮ
|
||
РАСКЛАДКУ КАЖДОГО ЕГО СТАКА В СЕЙВЕ. Обычный предмет пишет байт "сколько модификаций",
|
||
ItemClassModifier не пишет ничего; после переезда читатель этот байт пропустил, поток
|
||
съехал, ближайший ReadString() упал, и .ttp с .ttp.bak умерли оба.
|
||
resourceBloodStone - НОВОЕ имя. В сейвах, записанных до этой правки, нет ни одного его
|
||
стака, значит нет и ни одной записи, которую пришлось бы читать по другой раскладке.
|
||
Именно поэтому камень заведён новым предметом, а не переводом уже существующего - тем же
|
||
решением, что и Кровавая сфера. Правило целиком: раздел про аварию в BACKLOG.md.
|
||
|
||
Атрибуты - по образцу сферы:
|
||
- installable_tags="necroBracelet" - обязательно. У CanSwap есть короткое замыкание
|
||
"InstallableTags.IsEmpty || ...", то есть модификация БЕЗ этого атрибута лезет в любой
|
||
предмет игры.
|
||
- modifier_tags СВОЙ (necroBraceletStone, не ...Sphere). Мод с совпадающим modifier_tags
|
||
считается против ItemClass.MaxModsAllowed - разбор в шапке этого файла. Слот у
|
||
браслета сейчас один, так что практической разницы нет, но правило "свой тег на
|
||
каждую модификацию" в моде записано, и ломать его незачем.
|
||
- blocked_tags НЕ задаётся: у браслета в тегах noMods, отрицательную половину он
|
||
обеспечивает сам.
|
||
-->
|
||
<append xpath="/item_modifiers">
|
||
<item_modifier name="resourceBloodStone" installable_tags="necroBracelet" modifier_tags="necroBraceletStone" type="attachment">
|
||
<!-- Та же база, что у сферы и у шести модов ножа: Group "Mods", звуки
|
||
mod_grab/mod_place, Stacknumber 1, CreativeMode None. param1="CustomIcon"
|
||
исключает наследование родительского missingIcon.
|
||
НЕ наследовать resourceRockSmall ради вида камня: вместе с видом приедут
|
||
Action0 Class="ThrowAway", ThrowableDecoy="true" и DistractionTags, то есть
|
||
камень можно будет метать как отвлекающий. Меш берётся строками ниже. На сфере
|
||
эта ошибка уже была и была исправлена 15.09. -->
|
||
<property name="Extends" value="modGeneralMaster" param1="CustomIcon"/>
|
||
<property name="DescriptionKey" value="resourceBloodStoneDesc"/>
|
||
|
||
<!-- В РУКЕ - КАМЕНЬ С АЛЫМ ТИНТОМ (указание 2026-09-15: «в руке и сфера и кровавый
|
||
камень пусть будут как камень с алым тинтом»). Набор скопирован со сферы, а та -
|
||
с трёх камней мода (Камень духов, Синий и Чёрный порталы), которые на этом же
|
||
меше с этим же тинтом в игре видны зелёным, синим и чёрным.
|
||
|
||
Три меша, а не один, потому что это три разные ситуации: Meshfile общий,
|
||
HandMeshfile в руке, DropMeshfile лежащим на земле.
|
||
|
||
TintColor - ТРИПЛЕТ "R, G, B", а не hex; hex берёт CustomIconTint, и это другая
|
||
ручка (Color32 через запятые против ParseHexColor). Перепутать легко, на модах
|
||
ножа это уже стоило круга. -->
|
||
<property name="HoldType" value="40"/>
|
||
<property name="Meshfile" value="@:Other/Items/Crafting/rock_smallPrefab.prefab"/>
|
||
<property name="HandMeshfile" value="@:Other/Items/Crafting/rock_smallPrefab.prefab"/>
|
||
<property name="DropMeshfile" value="@:Other/Items/Crafting/rock_smallPrefab.prefab"/>
|
||
<property name="TintColor" value="220, 30, 45"/>
|
||
|
||
<!-- Своя рисованная иконка, лежит с 15.09 в обеих папках атласа
|
||
(UIAtlases/ItemIconAtlas/BloodStone.png + ItemIconAtlasGreyscale/BloodStone.png).
|
||
Серая копия обязательна: заблокированная запись в панели скилла рисуется из
|
||
greyscale-атласа, без неё у записи не было бы картинки вообще.
|
||
CustomIconTint НЕ ЗАДАЁТСЯ - тинт нужен заимствованным ванильным спрайтам, на
|
||
готовой работе он бы её просто затемнил. Тот же принцип, что у сферы. -->
|
||
<property name="CustomIcon" value="BloodStone"/>
|
||
|
||
<!-- БЕСКОНЕЧНАЯ ПРОЧНОСТЬ СДЕЛАНА ОТСУТСТВИЕМ СТРОК, А НЕ БОЛЬШИМ ЧИСЛОМ.
|
||
Здесь СОЗНАТЕЛЬНО нет ни ShowQuality, ни DegradationBreaksAfter, ни effect_group
|
||
с DegradationMax - сравнить со сферой выше, у которой все три есть.
|
||
|
||
Почему этого достаточно: SpendCharge (HarmonySrc/SpatialVaultPickupPatch.cs)
|
||
списывает прочность ТОЛЬКО у модификации, чьё имя совпадает с ChargeItemName
|
||
("resourceBloodSphere"). Камень под это условие не попадает по определению -
|
||
цикл делает continue, UseTimes камня не трогает никто и никогда. Кода менять не
|
||
пришлось вообще; комментарий в SpendCharge это заранее и обещает («a mod that is
|
||
not the charge pays nothing, is not emptied, and the pull still happens»).
|
||
|
||
Почему НЕ выставлять огромный DegradationMax: полоска прочности, которая никогда
|
||
не двигается, хуже её отсутствия. Плюс ловушка, уже разобранная на флаконе крови:
|
||
при MaxUseTimes == 0 полоска рисуется ПОЛНОЙ, то есть забытая пассивка выглядит
|
||
как "всё работает".
|
||
|
||
Тег necroBloodFlask здесь тоже не нужен, но уже по другой причине, чем было
|
||
записано в спеке: после полного отката Крови некроманта (15.09) этого тега в моде
|
||
не осталось совсем - он существовал только чтобы гейтить пассивку прочности
|
||
флакона. Пункт снят сам собой. -->
|
||
<property name="EconomicValue" value="0"/>
|
||
<property name="SellableToTrader" value="false"/>
|
||
</item_modifier>
|
||
</append>
|
||
</config>
|