Чинит поломку сейвов, внесённую предыдущим коммитомe362c62, и заменяет её механику на безопасную. ЧТО БЫЛО СЛОМАНО.e362c62перенёс resourceNecromancerBlood из items.xml в item_modifiers.xml, чтобы кровь вставлялась в Пространственный браслет. Класс предмета определяет БАЙТОВУЮ РАСКЛАДКУ каждого его стака в сейве: ItemValue.Read строка 1094: if ((version > 4 || HasQuality) && !(itemClass is ItemClassModifier)) ItemValue.Write строка 1228: if (!(ItemClass is ItemClassModifier)) Обычный предмет пишет байт числа модификаций и байт числа косметических слотов; ItemClassModifier не пишет ни того, ни другого. Сейв, записанный до переноса, после переноса читается со сдвигом - поток съезжает на первом же стаке крови, и PlayerDataFile.Load падает. Бэкап .ttp.bak умирает вместе с основным файлом: он того же формата. В тестовом мире персонаж потерян вместе с бэкапом, игра откатилась на Respawning: NewGame. В коммитеe362c62написано "сейв цел" со ссылкой на assignIdsFromMapping. Айди действительно берутся из сохранённого name->id мэппинга - но ломается не айди, а раскладка байтов, и к мэппингу это отношения не имеет. Была проверена не та вещь. ОТКАТ. Кровь вернулась в items.xml обычным <item>. Набор её свойств сверен с7172681и совпадает посимвольно: убраны и Stacknumber=1, и прочность (ShowQuality + DegradationBreaksAfter + effect_group DegradationMax). Обе правки были безвредны для сейва, но существовали ради отменённой механики - по указанию пользователя откат доведён до "как было", а не до "как было плюс безвредное". Стак снова 15, наследуется от medicalBloodBag. В комментарии у предмета оставлено предупреждение с номерами строк Read/Write - единственное, что вынесено из аварии, и единственное, ради чего стоит читать этот комментарий целиком. КРОВАВАЯ СФЕРА. Расходник браслета теперь отдельный предмет, и это ровно то, что делает правку безопасной: resourceBloodSphere - НОВОЕ имя, в старых сейвах его нет, значит нет и ни одного стака, который читался бы по другой раскладке. Общее правило, выведенное из аварии: предмет, который уже мог попасть в чужой инвентарь, нельзя переводить между ItemClass и ItemClassModifier ни в какую сторону - нужна модификация, заводи новый предмет. Продиктовано: доступна на первом грейде, без станка, 1 кровь некроманта + 5 праха зомби дают две сферы, прочность 500. В руке - камень с алым тинтом. - item_modifiers.xml: installable_tags="necroBracelet", свой modifier_tags, type="attachment", DegradationMax 500 в tiered="false" группе. Extends=modGeneralMaster, а НЕ resourceRockSmall: вместе с камнем приезжали бы Action0 ThrowAway и ThrowableDecoy, то есть сферу можно было бы метать. Меш берётся строкой Meshfile, наследовать ради него весь предмет не нужно. - Вид в руке: HoldType 40 и все три меша (Meshfile/HandMeshfile/DropMeshfile) на rock_smallPrefab плюс TintColor "220, 30, 45". Путь проверенный - на этом же меше с таким же тинтом в моде живут Камень духов и оба портальных камня. - recipes.xml: count=2, без craft_area и без тега разблокировки (группа 1 открыта с уровня 1, рецепт без тега доступен всегда - как у Камня духов). - progression.xml: сфера в unlock_entry группы 1, unlock_tier="1". - SpatialVaultPickupPatch: SpendBlood -> SpendCharge, имя из новой константы ChargeItemName. Гейт пустого слота в Begin снова включён - после отката он был временно закомментирован, потому что вставлять было нечего. ИКОНКИ. Свои, от пользователя: BloodSphere.png и BloodStone.png, 160x160 RGBA, в ItemIconAtlas и ItemIconAtlasGreyscale. Серая копия обязательна - без неё у заблокированной записи в скиллах не будет картинки вообще. Способ её получения подобран сверкой с существующими файлами и совпал ПОБИТОВО: convert('L') на RGB без альфы, альфа приклеивается обратно отдельным каналом. Записано в BACKLOG.md, раньше это нигде не было зафиксировано. Иконка камня положена заранее - самого предмета ещё нет, он запланирован. ПРОВЕРЕНО, ЧТО НИЧЕГО БОЛЬШЕ НЕ ЕДЕТ. Сверка с7172681: набор <item> в items.xml не изменился, в item_modifiers.xml единственное добавление - resourceBloodSphere, и ни у одного существующего предмета не менялись Tags, ShowQuality и Stacknumber. То есть ни один предмет не сменил класс и не сменил раскладку. В игре: мир грузится без ошибок, сфера крафтится и тратится - в логе "sphere in slot 0 now 12/500 used" за 12-секундный канал. --- Revert the blood, and a Blood Sphere in its place Fixes the save corruption introduced bye362c62and replaces the mechanic behind it with a safe one. WHAT WAS BROKEN.e362c62moved resourceNecromancerBlood from items.xml into item_modifiers.xml so it could be installed in the Spatial Bracelet. An item's CLASS decides the BYTE LAYOUT of every stack of it in a save: ItemValue.Read line 1094: if ((version > 4 || HasQuality) && !(itemClass is ItemClassModifier)) ItemValue.Write line 1228: if (!(ItemClass is ItemClassModifier)) A plain item writes a modifications count and a cosmetic-slot count; an ItemClassModifier writes neither. A save written before the move reads out of step after it - the stream slips on the first blood stack and PlayerDataFile.Load throws. The .ttp.bak backup dies with the main file, being the same format. In the test world the character was lost along with its backup and the game fell back to Respawning: NewGame. e362c62's message claimed "saves are safe", citing assignIdsFromMapping. Item ids really do come from the stored name->id mapping - but what breaks is not the id, it is the byte layout, and the mapping has nothing to do with it. The wrong thing was verified. THE REVERT. The blood is a plain <item> in items.xml again. Its property set was diffed against7172681and matches character for character: both Stacknumber=1 and the durability (ShowQuality + DegradationBreaksAfter + the DegradationMax effect_group) are gone. Both were harmless to the save format, but both existed only to serve the cancelled mechanic - on the user's instruction the revert goes back to "as it was", not "as it was plus whatever I judged harmless". The stack is 15 again, inherited from medicalBloodBag. A warning carrying the Read/Write line numbers stays in the item's comment - the one thing worth keeping out of this accident. THE BLOOD SPHERE. The bracelet's charge is its own item now, and that is precisely what makes this safe: resourceBloodSphere is a NEW name, absent from every existing save, so no stack of it can be read under the wrong layout. The general rule the accident produced: an item that may already sit in someone's inventory must never be moved between ItemClass and ItemClassModifier in either direction - if a modifier is wanted, make a new item. Dictated: available at the first grade, no workstation, 1 Necromancer's Blood + 5 zombie ash makes two spheres, durability 500. Held, it is a stone with a scarlet tint. - item_modifiers.xml: installable_tags="necroBracelet", its own modifier_tags, type="attachment", DegradationMax 500 in a tiered="false" group. Extends=modGeneralMaster, NOT resourceRockSmall: the rock would have brought Action0 ThrowAway and ThrowableDecoy with it, making the sphere throwable. The mesh comes from the Meshfile line; inheriting a whole item for it is not needed. - Held look: HoldType 40 and all three meshes (Meshfile/HandMeshfile/ DropMeshfile) on rock_smallPrefab, plus TintColor "220, 30, 45". A proven path - the Spirit Stone and both portal stones already live on that mesh with that same kind of tint. - recipes.xml: count=2, no craft_area and no unlock tag (group 1 opens at level 1, and a recipe with no tag is simply always available, as with the Spirit Stone). - progression.xml: the sphere joins group 1's unlock_entry at unlock_tier="1". - SpatialVaultPickupPatch: SpendBlood -> SpendCharge, the name coming from a new ChargeItemName constant. The empty-slot gate in Begin is switched back on - it was commented out during the revert because nothing could be installed. ICONS. The user's own art: BloodSphere.png and BloodStone.png, 160x160 RGBA, in both ItemIconAtlas and ItemIconAtlasGreyscale. The greyscale copy is mandatory - without it a locked skill entry has no picture at all. How those copies are made was worked out by diffing against the existing files and matched BIT FOR BIT: convert('L') over RGB without the alpha, with the alpha merged back as its own channel. Written up in BACKLOG.md; it had never been recorded anywhere. The stone's icon is filed ahead of the item, which is still only planned. VERIFIED THAT NOTHING ELSE SHIFTS. Diffed against7172681: the set of <item> entries in items.xml is unchanged, the only addition to item_modifiers.xml is resourceBloodSphere, and no existing item had its Tags, ShowQuality or Stacknumber changed. No item changed class, and no item changed layout. In game: the world loads clean, and the sphere crafts and drains - the log shows "sphere in slot 0 now 12/500 used" for a 12-second channel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MnwP2Dt1vk8bUPJ452EoVL
427 lines
36 KiB
XML
427 lines
36 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>
|
||
</config>
|