Чинит поломку сейвов, внесённую предыдущим коммитом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
1583 lines
110 KiB
XML
1583 lines
110 KiB
XML
<config>
|
||
<!-- REMOVED: the "books read" counter/skill patch that used to live here (patched
|
||
schematicMaster's effect_group) never actually worked. items.xml effect_group does not
|
||
inherit through Extends in this game version - the loader parses each item's
|
||
effect_group from its own XML node only, never a parent's, regardless of Extends
|
||
(confirmed by decompiling ItemClassesFromXml.parseItem). Every one of the ~150 skill
|
||
magazine/schematic items would need its own individual append to actually react to being
|
||
read - not worth it, so the books-read counter buff and the skill it drove were both
|
||
dropped entirely. See progression.xml/entityclasses.xml: the skill now levels from
|
||
zombie kills instead, which already worked. -->
|
||
|
||
<!-- Reusable tin cans: eating canned food leaves you an empty can, which works like the
|
||
vanilla mason jar (drinkJarEmpty/drinkJarRiverWater/drinkJarBoiledWater - fill from
|
||
water, drink, get the empty can back). Ported from AC-EnergyBow (__NoMods reference
|
||
library), fixing one bug in it along the way: it extends the jar items but never
|
||
overrides Meshfile, so its "tin can" is visually a mason jar in hand. We use the mesh
|
||
from vanilla's own disabled-but-fully-built "drinkCanEmpty" item (items.xml, commented
|
||
out under "*** Drinks") instead - a real can model, just never wired up by TFP.
|
||
|
||
BUG FIXED 2026-09-01 (canned water didn't quench thirst): the initial port only copied
|
||
each item's own property/property-class nodes and dropped the effect_group blocks,
|
||
on the wrong assumption that Extends would pull those in from drinkJarRiverWater/
|
||
drinkJarBoiledWater - it doesn't (see the comment above: effect_group is parsed from
|
||
each item's own XML node only, never inherited through Extends). So tinCanRiverWater/
|
||
tinCanBoiledWater ran their Eat action (sound, delay, empty-can-back) but never applied
|
||
$waterAmountAdd - the can "drank" and did nothing. Restored the effect_group from
|
||
AC-EnergyBow verbatim (river water still carries dysentery risk/HP hit without
|
||
buffWaterPurifier, same as a jar; boiled water is clean), fixing one more small bug in
|
||
the source along the way: its dDysenteryRisk display_value said .16 while the actual
|
||
roll it sets is 8 (i.e. .08) - tooltip now matches what the roll does. -->
|
||
<append xpath="/items">
|
||
<item name="tinCanEmpty">
|
||
<property name="Extends" value="drinkJarEmpty"/>
|
||
<property name="CustomIcon" value="tinCanEmpty"/>
|
||
<property name="DescriptionKey" value="tinCanEmptyDesc"/>
|
||
<property name="Meshfile" value="@:Other/Items/Food/can_emptyPrefab.prefab"/>
|
||
<property name="HoldType" value="14"/>
|
||
<property name="Material" value="Mmetal"/>
|
||
<property class="Action1"> <!-- UseAction -->
|
||
<property name="Class" value="CollectWater"/>
|
||
<property name="Delay" value="3.4"/>
|
||
<property name="Change_item_to" value="tinCanRiverWater"/>
|
||
<property name="Sound_start" value="bucketfill_water"/>
|
||
</property>
|
||
</item>
|
||
|
||
<item name="tinCanRiverWater">
|
||
<property name="Extends" value="drinkJarRiverWater"/>
|
||
<property name="CustomIcon" value="tinCanRiverWater"/>
|
||
<property name="DescriptionKey" value="tinCanRiverWaterDesc"/>
|
||
<property name="Meshfile" value="@:Other/Items/Food/can_emptyPrefab.prefab"/>
|
||
<property name="HoldType" value="14"/>
|
||
<property name="Material" value="Mmetal"/>
|
||
<property class="Action0">
|
||
<property name="Class" value="Eat"/>
|
||
<property name="Delay" value="4"/>
|
||
<property name="Sound_start" value="player_drinking"/>
|
||
<property name="Create_item" value="tinCanEmpty"/>
|
||
<property name="Use_jar_refund" value="false"/>
|
||
</property>
|
||
|
||
<effect_group tiered="false" name="Drink Tier 0">
|
||
<requirement name="!HasBuff" buff="buffIsOnFire"/>
|
||
|
||
<triggered_effect trigger="onSelfPrimaryActionEnd" action="ModifyCVar" cvar="$waterAmountAdd" operation="add" value="10"/>
|
||
<display_value name="dStaminaRegen" value=".15"/>
|
||
<triggered_effect trigger="onSelfPrimaryActionEnd" action="AddBuff" buff="buffProcessConsumables"/>
|
||
<triggered_effect trigger="onSelfPrimaryActionEnd" action="ModifyStats" stat="Health" operation="add" value="-5">
|
||
<requirement name="!HasBuff" buff="buffWaterPurifier"/>
|
||
</triggered_effect>
|
||
<display_value name="foodHealthAmount" value="-5"/>
|
||
<triggered_effect trigger="onSelfPrimaryActionEnd" action="ModifyCVar" cvar=".DiseaseRoll" operation="set" value="8"/>
|
||
<display_value name="dDysenteryRisk" value=".08"/>
|
||
<triggered_effect trigger="onSelfPrimaryActionEnd" action="ModifyCVar" cvar=".DiseaseRoll" operation="add" value="@$MetabolismResist"/>
|
||
<triggered_effect trigger="onSelfPrimaryActionEnd" action="AddBuff" buff="buffDysenteryCatchDrink">
|
||
<requirement name="RandomRoll" seed_type="Random" min_max="1,100" operation="LTE" value="@.DiseaseRoll"/>
|
||
</triggered_effect>
|
||
<triggered_effect trigger="onSelfPrimaryActionEnd" action="PlaySound" sound="player#vomit">
|
||
<requirement name="!HasBuff" buff="buffWaterPurifier"/>
|
||
</triggered_effect>
|
||
</effect_group>
|
||
|
||
<effect_group tiered="false">
|
||
<requirement name="HasBuff" buff="buffIsOnFire"/>
|
||
<triggered_effect trigger="onSelfPrimaryActionEnd" action="AddBuff" buff="buffExtinguishFire"/>
|
||
</effect_group>
|
||
</item>
|
||
|
||
<item name="tinCanBoiledWater">
|
||
<property name="Extends" value="drinkJarBoiledWater"/>
|
||
<property name="CustomIcon" value="tinCanBoiledWater"/>
|
||
<property name="DescriptionKey" value="tinCanBoiledWaterDesc"/>
|
||
<property name="Meshfile" value="@:Other/Items/Food/can_emptyPrefab.prefab"/>
|
||
<property name="HoldType" value="14"/>
|
||
<property name="Material" value="Mmetal"/>
|
||
<property class="Action0">
|
||
<property name="Class" value="Eat"/>
|
||
<property name="Delay" value="4"/>
|
||
<property name="Sound_start" value="player_drinking"/>
|
||
<property name="Create_item" value="tinCanEmpty"/>
|
||
<property name="Use_jar_refund" value="false"/>
|
||
</property>
|
||
|
||
<effect_group tiered="false" name="Drink Tier 0/1">
|
||
<requirement name="!HasBuff" buff="buffIsOnFire"/>
|
||
|
||
<triggered_effect trigger="onSelfPrimaryActionEnd" action="ModifyCVar" cvar="$waterAmountAdd" operation="add" value="16"/>
|
||
<display_value name="dStaminaRegen" value=".15"/>
|
||
<triggered_effect trigger="onSelfPrimaryActionEnd" action="AddBuff" buff="buffProcessConsumables"/>
|
||
</effect_group>
|
||
|
||
<effect_group tiered="false">
|
||
<requirement name="HasBuff" buff="buffIsOnFire"/>
|
||
<triggered_effect trigger="onSelfPrimaryActionEnd" action="AddBuff" buff="buffExtinguishFire"/>
|
||
</effect_group>
|
||
</item>
|
||
</append>
|
||
|
||
<!-- Only foodCanBeef needs patching, even though Chicken/Lamb/Catfood should also leave a
|
||
can behind: those three Extends="foodCanBeef" without their own Action0 element, so
|
||
there's nothing for a foodCanChicken/foodCanLamb/foodCanCatfood xpath to match - but for
|
||
the same reason, they'll all inherit foodCanBeef's patched Action0 (Create_item included)
|
||
at load time anyway, same as how our zombie kill counter above only had to patch
|
||
zombieTemplateMale once. -->
|
||
<append xpath="/items/item[@name='foodCanBeef']/property[@class='Action0']">
|
||
<property name="Create_item" value="tinCanEmpty"/>
|
||
</append>
|
||
|
||
<!-- "Камень духов" (Spirit Stone): the first necromancer item, replacing the earlier
|
||
"Девиатор" (Deviator) placeholder book with a real thrown object - a vanilla small rock,
|
||
reused via resourceRockSmall's own mesh/icon, tinted green (TintColor for the 3D mesh,
|
||
CustomIconTint for the UI icon - both multiply onto the material's base color, which is
|
||
why this works even though we never touch the rock's actual textures).
|
||
|
||
Back on Molotov Cocktail's own architecture: Class="ItemClassTimeBomb" +
|
||
Action0 Class="ThrowAway". An earlier version used Class="ThrownWeapon" (spear-style)
|
||
specifically to get a precise "who exactly got hit" target for the buff - that DOES work,
|
||
but it drags in ThrownWeaponMoveScript.checkCollision(), which unconditionally registers a
|
||
map/compass marker on impact (NavObjectManager.RegisterNavObject) and crashes hard if the
|
||
item has no NavObject configured. Molotov never touches any of that, because it doesn't
|
||
need a specific target - it applies its buff to everything within a small radius of where
|
||
it landed (target="positionAOE") instead of to "whatever I directly hit". We do the same
|
||
here with a tight 2m radius: in practice that's just the one zombie you threw it at, and
|
||
if it catches a second zombie standing right next to it, that's a fine bonus for a
|
||
necromancer item, not a bug. This trades "always exactly one" for "no exotic crash".
|
||
|
||
The buff it delivers (buffNecroDeviatorCharm) is what actually flips the hit zombie -
|
||
see buffs.xml + HarmonySrc/CharmPatch.cs. The buff name itself stays "Deviator" even
|
||
though the item that delivers it is now the Spirit Stone - CharmPatch.cs/DamagePatch.cs/
|
||
ParticlePatch.cs all key off the buff name, not the item name, so nothing there needs to
|
||
change for this rename. -->
|
||
<append xpath="/items">
|
||
<item name="thrownStoneSpirit">
|
||
<property name="Tags" value="T0,weapon,attPerception"/>
|
||
<property name="ItemTypeIcon" value="explosion"/>
|
||
<!-- ICON UPDATED 2026-08-29: real generated art (UIAtlases/ItemIconAtlas/
|
||
SpiritStone.png) replaces the reused-resourceRockSmall-icon+tint trick - no
|
||
CustomIconTint on top of it per the user's own instruction, real art shouldn't
|
||
be recolored. -->
|
||
<property name="CustomIcon" value="SpiritStone"/>
|
||
<property name="DescriptionKey" value="thrownStoneSpiritDesc"/>
|
||
<property name="DisplayType" value="ammoGrenadeFire"/>
|
||
<property name="Class" value="ItemClassTimeBomb"/>
|
||
<!-- HoldType 40 is what resourceRockSmall itself uses - matches how
|
||
rock_smallPrefab.prefab was actually authored/rigged to be held. -->
|
||
<property name="HoldType" value="40"/>
|
||
<property name="Meshfile" value="@:Other/Items/Crafting/rock_smallPrefab.prefab"/> <!-- in-flight -->
|
||
<property name="HandMeshfile" value="@:Other/Items/Crafting/rock_smallPrefab.prefab"/> <!-- held -->
|
||
<property name="DropMeshfile" value="@:Other/Items/Crafting/rock_smallPrefab.prefab"/> <!-- world-dropped -->
|
||
<property name="Material" value="MresourceRockSmall"/>
|
||
<property name="TintColor" value="30, 200, 60"/>
|
||
<property name="Weight" value="0"/>
|
||
<property name="Stacknumber" value="20"/>
|
||
<property name="FuseTime" value="4000"/>
|
||
<property name="ExplodeOnHit" value="true"/>
|
||
<property name="StickPercent" value="0"/>
|
||
<!-- Zero radius/damage here on purpose: this block's own RadiusEntities/EntityDamage
|
||
is for native splash DAMAGE (we don't want any), separate from the buff's own
|
||
"range" below (confirmed by how Molotov itself splits these: its Explosion block
|
||
does real radius damage, while its buff uses its own independent range="2.7").
|
||
ParticleIndex=7 is the same index Molotov's own Action0 puff already uses safely
|
||
in this same ItemClassTimeBomb context (unlike ThrownWeapon's separate, apparently
|
||
broken, particle-index space that crashed above). -->
|
||
<property class="Explosion">
|
||
<property name="ParticleIndex" value="7"/>
|
||
<property name="RadiusBlocks" value="0"/>
|
||
<property name="RadiusEntities" value="0"/>
|
||
</property>
|
||
<property name="EconomicValue" value="0"/>
|
||
<property name="Group" value="Ammo/Weapons,Ammo"/>
|
||
<property name="UsableUnderwater" value="false"/>
|
||
<property name="SoundPickup" value="stone_grab"/>
|
||
<property name="SoundPlace" value="stone_place"/>
|
||
<!-- Deliberately just Action0, no Action1/FusePrimeOnActivate. Molotov requires that
|
||
second "prime" step (RMB to light the rag) before throwing, and mustPrime gates
|
||
ItemClassTimeBomb's ExplodeOnHit check in OnDroppedUpdate: `(!mustPrime ||
|
||
itemValue.Meta > 0)`. If FusePrimeOnActivate is set but the item is thrown without
|
||
ever activating (Meta stays 0), that condition is permanently false, and every
|
||
single collision this update produces early-returns without ever firing
|
||
onProjectileImpact - which is exactly why every earlier test bounced with no
|
||
effect at all, buff or otherwise, regardless of anything else about the item.
|
||
Leaving FusePrimeOnActivate unset keeps mustPrime at its C# default (false), so
|
||
ExplodeOnHit fires unconditionally on first contact - one throw, no priming ritual. -->
|
||
<property class="Action0">
|
||
<property name="Class" value="ThrowAway"/>
|
||
<property name="Delay" value="1.2"/>
|
||
<property name="Throw_strength_default" value="20"/>
|
||
<property name="Throw_strength_max" value="50"/>
|
||
<property name="Max_strain_time" value="1.25"/>
|
||
<property name="Sound_start" value="swoosh"/>
|
||
</property>
|
||
|
||
<effect_group tiered="false">
|
||
<triggered_effect trigger="onProjectileImpact" action="AddBuff" target="positionAOE" range="2" buff="buffNecroDeviatorCharm">
|
||
<requirement name="EntityTagCompare" target="other" tags="zombie"/>
|
||
</triggered_effect>
|
||
<!-- Generic stone-on-flesh thud, deliberately left UNCONDITIONAL: it fires on every
|
||
impact (ground, wall, zombie) so a miss still sounds like something landed. -->
|
||
<triggered_effect trigger="onProjectileImpact" action="PlaySound" sound="stonehitorganic"/>
|
||
<!-- The charm's OWN cue - fires only on a hit that actually charms, gated by the exact
|
||
same "other is a zombie" requirement the AddBuff above uses, so it can never fire on
|
||
a miss. Requirements on a PlaySound effect are the vanilla stun baton pattern
|
||
(Data/Config/items.xml:4005 - IsAlive/EntityTagCompare on target="other"), not
|
||
invented here; onProjectileImpact populating "other" with the hit entity is proven
|
||
by the AddBuff right above, which already works in game.
|
||
|
||
target="other" is meant to play the clip FROM the zombie, i.e. positional at the
|
||
point of impact instead of at the thrower's head (the attribute itself is vanilla -
|
||
items.xml:5514 uses target="self"). If it turns out silent in game, drop just the
|
||
attribute: the effect then plays on self and the gating still holds.
|
||
|
||
THE MOD'S OWN SOUND, the first one in the whole mod that is not a borrowed vanilla
|
||
id: "necroSpiritStoneHit" is defined in this mod's Config/sounds.xml and its clip
|
||
lives in Resources/necrosounds, built from the Unity project (see that file's header
|
||
for why a plain wav next to the XML cannot work). If the sound is missing in game,
|
||
the failure is SILENT - look for "AudioManager LoadAudio failed to load audio clip"
|
||
in the game log, and note that the generic stonehitorganic above will still play, so
|
||
"I heard something" is not proof this one fired. -->
|
||
<triggered_effect trigger="onProjectileImpact" action="PlaySound" target="other" sound="necroSpiritStoneHit">
|
||
<requirement name="EntityTagCompare" target="other" tags="zombie"/>
|
||
</triggered_effect>
|
||
</effect_group>
|
||
</item>
|
||
</append>
|
||
|
||
<!-- NAMING CONVENTION 2026-08-29 (user request): every book-type item that gets consumed on
|
||
use is now called a "свиток"/"scroll" in its DISPLAY text (Localization.csv), not a
|
||
"фолиант"/"tome"/"книга"/"book" any more - applies to this item (renamed "Гримуар
|
||
девиации" -> "Свиток девиации"), bookSummonInsectSwarm (description text only, its own
|
||
name "Жуки Властелина" didn't say "book" to begin with), and bookBanshee (renamed "Книга
|
||
банши" -> "Свиток банши"). bookSummonZombieDog/Bear/Wolf/Griffin stay "книги" (books) -
|
||
they're NOT consumed (ConsumesBook=false, see SummonPatch.cs), matching the same rule.
|
||
Internal item ids (thrownBookGrimoireDeviation, bookSummonInsectSwarm, bookBanshee) were
|
||
deliberately LEFT UNCHANGED - only the user-visible name/description text was renamed,
|
||
not the id every recipe/Harmony patch/save file references. Renaming ids too would orphan
|
||
any copies already in the user's inventory/world from before this rename. Say if the ids
|
||
should be renamed as well despite that risk.
|
||
Model/mesh is still the vanilla bookPrefab placeholder for all of these - per the user's
|
||
own note ("не представляю пока какую использовать модельку"), swap Meshfile/HandMeshfile/
|
||
DropMeshfile (and probably HoldType) once a real scroll model exists; nothing to do here
|
||
until then.
|
||
|
||
"Гримуар девиации" (now "Свиток девиации"): BACKLOG.md item 2. A stronger, later-game
|
||
sibling of the Spirit Stone above, not a replacement for it - same underlying charm
|
||
(buffNecroDeviatorCharm, same Harmony patches), but hits every zombie caught in a wide
|
||
blast instead of just whatever's closest. That's the ENTIRE difference from the Spirit
|
||
Stone mechanically: target="positionAOE" already applies to every matching entity within
|
||
"range" of the impact point, not just the nearest one - the Spirit Stone's range="2" just
|
||
happens to be small enough that it's usually only ever one zombie. Bumping range to "5"
|
||
here is the whole AOE upgrade; no new Harmony code needed.
|
||
|
||
It's a book again (unlike the Spirit Stone, which moved off being a book - see BACKLOG.md),
|
||
so it reuses the original Deviator's book meshes/hack: bookPrefab.prefab held/dropped, but
|
||
vomitBulbPrefab.prefab in flight, because a flat book mesh tumbling through the air via
|
||
ItemClassTimeBomb/ThrowAway didn't read well - the rounded vomit bulb was already proven to
|
||
fly fine there. Tinted green the same way as the Spirit Stone (TintColor for the 3D mesh,
|
||
CustomIconTint for the UI icon), per user request - same color on both items ties them
|
||
together visually as "the same kind of magic".
|
||
|
||
Gated at Necromancy level 50 via the recipe's necroNecromancyLvl50 tag (see progression.xml
|
||
for why this needed its own one-off tag instead of one of the 5 existing groups). -->
|
||
<append xpath="/items">
|
||
<item name="thrownBookGrimoireDeviation">
|
||
<property name="Tags" value="T0,weapon,attPerception"/>
|
||
<property name="ItemTypeIcon" value="explosion"/>
|
||
<!-- ICON UPDATED 2026-08-29: real generated art (ScrollOfDeviation.png), no tint. -->
|
||
<property name="CustomIcon" value="ScrollOfDeviation"/>
|
||
<property name="DescriptionKey" value="thrownBookGrimoireDeviationDesc"/>
|
||
<property name="DisplayType" value="ammoGrenadeFire"/>
|
||
<property name="Class" value="ItemClassTimeBomb"/>
|
||
<!-- 3D MODEL UPDATED 2026-08-29 per direct user request, for the whole "свиток"
|
||
family (this item + bookBanshee + bookSummonInsectSwarm): user pointed at the
|
||
vanilla first aid kit (medicalFirstAidKit, itself Extends="medicalBandage") -
|
||
that item's real Meshfile is @:Other/Items/Health/bandage.FBX (confirmed by
|
||
reading medicalBandage directly), a rolled bandage - tinted parchment
|
||
yellow-brown per the user's own instruction ("покрасить в жёлтый или
|
||
жёлто-коричневый"), it reads as a rolled scroll. HoldType switched to 64
|
||
(medicalBandage's own HoldType) to match how that mesh was actually rigged to be
|
||
held - keeping HoldType 21 (book pose) with a bandage-shaped mesh would likely
|
||
look wrong.
|
||
In-flight Meshfile (vomitBulbPrefab) deliberately LEFT UNCHANGED - this item is
|
||
thrown (Class="ItemClassTimeBomb"), and the comment on the Spirit Stone above
|
||
already documents that a flat/elongated mesh tumbling through the air "didn't
|
||
read well" here (that's why vomitBulbPrefab was used in the first place, a
|
||
rounded shape proven to fly correctly) - only HandMeshfile/DropMeshfile (held/
|
||
on the ground) switch to the bandage roll, matching how the player actually sees
|
||
this item most of the time anyway. -->
|
||
<property name="HoldType" value="64"/>
|
||
<property name="Meshfile" value="@:Other/Items/Weapons/Ranged/Vomit/vomitBulbPrefab.prefab"/> <!-- in-flight -->
|
||
<property name="HandMeshfile" value="@:Other/Items/Health/bandage.FBX"/> <!-- held -->
|
||
<property name="DropMeshfile" value="@:Other/Items/Health/bandage.FBX"/> <!-- world-dropped -->
|
||
<property name="Material" value="Mpaper"/>
|
||
<property name="TintColor" value="210, 170, 90"/>
|
||
<property name="Weight" value="0"/>
|
||
<property name="Stacknumber" value="20"/>
|
||
<property name="FuseTime" value="4000"/>
|
||
<property name="ExplodeOnHit" value="true"/>
|
||
<property name="StickPercent" value="0"/>
|
||
<!-- Zero radius/damage here on purpose, same reasoning as the Spirit Stone above: this
|
||
block's own RadiusEntities/EntityDamage is for native splash DAMAGE (we don't want
|
||
any), separate from the buff's own "range" below. -->
|
||
<property class="Explosion">
|
||
<property name="ParticleIndex" value="7"/>
|
||
<property name="RadiusBlocks" value="0"/>
|
||
<property name="RadiusEntities" value="0"/>
|
||
</property>
|
||
<property name="EconomicValue" value="0"/>
|
||
<property name="Group" value="Ammo/Weapons,Ammo"/>
|
||
<property name="UsableUnderwater" value="false"/>
|
||
<property name="SoundPickup" value="schematics_grab"/>
|
||
<property name="SoundPlace" value="schematics_place"/>
|
||
<!-- Deliberately just Action0, no Action1/FusePrimeOnActivate - see the Spirit Stone's
|
||
item block above for the full explanation of why (mustPrime/ExplodeOnHit trap). -->
|
||
<property class="Action0">
|
||
<property name="Class" value="ThrowAway"/>
|
||
<property name="Delay" value="1.2"/>
|
||
<property name="Throw_strength_default" value="20"/>
|
||
<property name="Throw_strength_max" value="50"/>
|
||
<property name="Max_strain_time" value="1.25"/>
|
||
<property name="Sound_start" value="hulkvomitwarning"/>
|
||
</property>
|
||
|
||
<effect_group tiered="false">
|
||
<triggered_effect trigger="onProjectileImpact" action="AddBuff" target="positionAOE" range="5" buff="buffNecroDeviatorCharm">
|
||
<requirement name="EntityTagCompare" target="other" tags="zombie"/>
|
||
</triggered_effect>
|
||
<triggered_effect trigger="onProjectileImpact" action="PlaySound" sound="vomitimpact"/>
|
||
</effect_group>
|
||
</item>
|
||
</append>
|
||
|
||
<!-- Two portal stones, BACKLOG.md item 6 (dictated 2026-08-28, implemented 2026-08-29 "без
|
||
вопросов" per user request). Same rock mesh/icon as the Spirit Stone above (reused via
|
||
Meshfile/CustomIcon, tinted per-stone) but used IN PLACE, not thrown - no
|
||
Class="ItemClassTimeBomb"/Action0 ThrowAway here, just a plain item with a click-to-
|
||
start-channel Action0.
|
||
|
||
REWRITTEN 2026-08-29: the first version's Delay="10" on Class="Eat" turned out NOT to be
|
||
a 10-second hold at all (user tested: "срабатывает мгновенно") - re-decompiling
|
||
ItemActionEat showed Delay there is really just a re-click cooldown, and the actual
|
||
multi-second "eating" duration for a UseAnimation item comes from a fixed-per-HoldType
|
||
table that isn't XML-exposed anywhere (checked - no "RayCast"/"AnimationDelay" anywhere in
|
||
Data/Config). Class="Eat" is now used ONLY as a reliable "detect a click" trigger -
|
||
Delay="1" here is just a debounce against accidental double-fires, not the channel length
|
||
at all any more. HarmonySrc/PortalStonePatch.cs Prefixes ItemActionEat.ExecuteAction
|
||
itself and, for these two items, skips the vanilla method entirely and opens the game's
|
||
own real countdown-timer UI directly (XUiC_Timer.OpenTimer - the same generic primitive
|
||
Block.TakeItemWithTimer itself is built on, decompiled to confirm) for the full 10
|
||
seconds, with a real visible progress bar and TimerEventData.CloseOnHit=true (cancels
|
||
automatically on taking damage - a real engine feature, not hand-rolled). Consume="false"
|
||
is now irrelevant (the whole ItemActionEat consumption path is skipped either way) but
|
||
left in place as documentation of intent - the stone is a reusable travel tool, unlike the
|
||
one-shot thrown Spirit Stone.
|
||
|
||
Actual teleportation is NOT an XML-reachable action anywhere in this game version
|
||
(confirmed - no ItemAction class does it, only console commands/GameEvent sequences do,
|
||
both C#-only) - needs Harmony. The same NetPackageTeleportPlayer the game's own
|
||
"teleportplayer" console command uses (decompiled from
|
||
ConsoleCmdTeleportsAbs.ExecuteTeleport) fires once the timer above completes - not some
|
||
invented mechanism.
|
||
|
||
Blue stone: teleports to the player's own bedroll/respawn point
|
||
(EntityPlayer.PersistentPlayerData.BedrollPos, decompiled from PersistentPlayerData - the
|
||
same field the game's own respawn-at-bedroll flow reads). No crafting gate specified by
|
||
the user beyond "простой ранний рецепт по аналогии с Камнем духов" (guessed, per the
|
||
backlog's own note) - available from the start, no craft_area/level tag.
|
||
|
||
Black stone: gated at necroNecromancyMaster (level 5000, the existing "Мастер" tier -
|
||
no new one-off level tag needed, per the user's own explicit instruction). Deliberately
|
||
a placeholder per direct user instruction - crafts and can be "used" (same 10s channel,
|
||
for UX consistency with the blue stone) but PortalStonePatch.cs's blue/black switch has
|
||
no real destination wired up for it yet, just a tooltip explaining that. Not a dead
|
||
button by accident - a real placeholder, matching what was asked for.
|
||
|
||
Sounds/icon tints are guesses (no dedicated "portal"/"teleport" sound exists anywhere in
|
||
vanilla sounds.xml - checked directly, not assumed) - "swoosh" for the channel start,
|
||
"spawnInStinger" (a real vanilla sound, the cue used when something materializes into the
|
||
world) for the actual teleport moment. Both now played directly from
|
||
PortalStonePatch.cs's C# code (not Sound_start here) since ItemActionEat's own
|
||
ExecuteAction - the method that would normally play Sound_start - is skipped entirely by
|
||
the Harmony Prefix, so a Sound_start property here would just never fire. -->
|
||
<append xpath="/items">
|
||
<item name="thrownStonePortalBlue">
|
||
<property name="Tags" value="T0,weapon,attPerception"/>
|
||
<!-- ItemTypeIcon="explosion" REMOVED 2026-09-07 (user: "убери explosion у портальных
|
||
камней тоже"), same call as the Spatial Vault's bogus "melee" badge just above -
|
||
this is the 12x12 corner badge over the item's icon in the recipe list, not the
|
||
workstation indicator. The sprite was fine; the meaning wasn't. This stone
|
||
teleports, it does not explode. Removed rather than replaced: with no
|
||
ItemTypeIcon property the {hasitemtypeicon} gate hides the widget entirely.
|
||
Kept on thrownStoneSpirit and thrownBookGrimoireDeviation, which really do go off
|
||
as an area burst on impact - only the two portal stones were asked for. -->
|
||
<!-- ICON UPDATED 2026-08-29: real generated art (BluePortalStone.png), no tint.
|
||
TintColor below is unrelated - it colors the 3D rock mesh (still the plain
|
||
rock_smallPrefab), not the 2D icon, so it's left alone. -->
|
||
<property name="CustomIcon" value="BluePortalStone"/>
|
||
<property name="DescriptionKey" value="thrownStonePortalBlueDesc"/>
|
||
<property name="DisplayType" value="ammoGrenadeFire"/>
|
||
<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="Material" value="MresourceRockSmall"/>
|
||
<property name="TintColor" value="30, 100, 255"/>
|
||
<property name="Weight" value="0"/>
|
||
<property name="Stacknumber" value="10"/>
|
||
<property name="EconomicValue" value="0"/>
|
||
<property name="Group" value="Ammo/Weapons,Ammo"/>
|
||
<property name="SoundPickup" value="stone_grab"/>
|
||
<property name="SoundPlace" value="stone_place"/>
|
||
<!-- Delay="1" is just a re-click debounce now, not a channel length - see the big
|
||
comment above for why (the real 10s channel is PortalStonePatch.cs's own
|
||
XUiC_Timer, not this ItemAction's Delay at all). -->
|
||
<property class="Action0">
|
||
<property name="Class" value="Eat"/>
|
||
<property name="Delay" value="1"/>
|
||
<property name="Consume" value="false"/>
|
||
</property>
|
||
</item>
|
||
</append>
|
||
|
||
<append xpath="/items">
|
||
<item name="thrownStonePortalBlack">
|
||
<property name="Tags" value="T0,weapon,attPerception"/>
|
||
<!-- ItemTypeIcon="explosion" REMOVED 2026-09-07, same reason as the Blue stone above -
|
||
see that comment for the full explanation of which widget this actually is. Even
|
||
less accurate here than there: this one is a 10-second channelled ritual ending in
|
||
a confirmation dialog, a fullscreen video and an exit to the main menu. Nothing
|
||
about it is an explosion. -->
|
||
<!-- ICON UPDATED 2026-08-29: real generated art (BlackPortalStone.png), no tint. -->
|
||
<property name="CustomIcon" value="BlackPortalStone"/>
|
||
<property name="DescriptionKey" value="thrownStonePortalBlackDesc"/>
|
||
<property name="DisplayType" value="ammoGrenadeFire"/>
|
||
<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="Material" value="MresourceRockSmall"/>
|
||
<property name="TintColor" value="0, 0, 0"/>
|
||
<property name="Weight" value="0"/>
|
||
<property name="Stacknumber" value="10"/>
|
||
<property name="EconomicValue" value="0"/>
|
||
<property name="Group" value="Ammo/Weapons,Ammo"/>
|
||
<property name="SoundPickup" value="stone_grab"/>
|
||
<property name="SoundPlace" value="stone_place"/>
|
||
<property class="Action0">
|
||
<property name="Class" value="Eat"/>
|
||
<property name="Delay" value="1"/>
|
||
<property name="Consume" value="false"/>
|
||
</property>
|
||
</item>
|
||
</append>
|
||
|
||
<!-- "Призыв зомбособаки" (Summon Zombie Dog): BACKLOG.md item 3. A book, per user request -
|
||
but unlike the Deviator/Grimoire above, it's not thrown - Class="SpawnEntity" (same
|
||
ItemAction vanilla's own meleeHandZombieRancher uses to have a zombie spit out an insect
|
||
swarm) plays a short cast animation in place and spawns the pet at the player's feet, no
|
||
projectile/physics involved. CustomIcon is the user's own hand-drawn art (2026-08-28,
|
||
redrawn from the exch/schematicMaster.png reference copy), dropped into
|
||
UIAtlases/ItemIconAtlas/schematicDogSummon.png the same way tinCanEmpty.png etc. already
|
||
work in this mod - no CustomIconTint on top of it, that would recolor real art.
|
||
|
||
BUG FIXED 2026-08-28 (book did nothing but play the warn sound in-game): AnimWait was
|
||
1.87s, copied from meleeHandZombieRancher's own AnimType/AnimWait without checking what
|
||
it actually gates. Confirmed by decompiling ItemActionSpawnEntity.ExecuteAction/
|
||
OnHoldingUpdate: the use button has to stay HELD DOWN for the full AnimWait duration -
|
||
releasing early (a normal quick click, which is how every other item in this mod works)
|
||
resets state back to None and Spawn() never runs at all, silently, no error anywhere -
|
||
only the SoundWarn from the initial press ever plays. AI-held items never hit this
|
||
because AI code holds the action for its full duration on purpose; a player quick-clicking
|
||
does not. Dropped to 0.3s so an ordinary click is long enough to clear it reliably while
|
||
still giving a short, visible "cast" beat - raise it back if a deliberate hold-to-cast feel
|
||
is wanted later, but document the tradeoff if so.
|
||
|
||
Ownership/limit/consuming the book on cast is NOT handled by SpawnEntity itself (it's
|
||
built for zombie mobs, which have no concept of "owner" or "ammo") - see
|
||
HarmonySrc/SummonPatch.cs for the Harmony patch that adds all three, modeled on how
|
||
vanilla's own drone (ItemActionSpawnTurret + EntityDrone.isValidForPlayer) behaves: only
|
||
one active pet per player PER SPECIES (dog and insect swarm below are tracked
|
||
independently - see SummonPatch.cs's LimitedPets table), summoning a second one of the
|
||
same species while the first is alive is BLOCKED (not a replace) with a tooltip, same as
|
||
"xuiMaxDeployedDronesReached" for the drone.
|
||
|
||
Gated at Necromancy level 200 (necroNecromancyLvl200, see progression.xml) and crafted at
|
||
a workbench specifically (craft_area="workbench" in recipes.xml), per user request - not
|
||
available from the player's own personal crafting menu like the Spirit Stone/Grimoire.
|
||
|
||
BUG FIXED 2026-08-28 (dogs launching the player into the air on spawn): EntityOffset was
|
||
"0, -.1, 1" - copied from meleeHandZombieRancher's own tiny insect-swarm offset without
|
||
scaling it up for a full-sized quadruped. That's barely below head height and only 1m
|
||
forward, easily still overlapping the caster's own collider - Unity's physics shoving two
|
||
overlapping colliders apart is exactly what launched the player. This was made much worse
|
||
by the SummonPatch.cs "<=0 vs -1" bug (see that file) piling up a dozen undespawned dogs,
|
||
all later teleported onto the exact same point by PetFollowPatch's leash every second -
|
||
but the underlying spawn-offset was too tight even for a single dog. Widened to
|
||
"0, -1, 2.5" (2.5m forward, roughly ground height instead of head height; gravity settles
|
||
the rest). -->
|
||
<append xpath="/items">
|
||
<item name="bookSummonZombieDog">
|
||
<property name="Tags" value="T0,weapon,attPerception"/>
|
||
<property name="ItemTypeIcon" value="book"/>
|
||
<!-- ICON REPLACED 2026-08-29: swapped the user's earlier hand-drawn
|
||
schematicDogSummon.png for the new AI-generated SummonZombieDog.png, to match
|
||
the rest of the item set's unified style (same prompt/generator as the other 12
|
||
icons). The old file is left in UIAtlases/ItemIconAtlas/ unused, not deleted, in
|
||
case this should be reverted. -->
|
||
<property name="CustomIcon" value="SummonZombieDog"/>
|
||
<property name="DescriptionKey" value="bookSummonZombieDogDesc"/>
|
||
<property name="DisplayType" value="ammoGrenadeFire"/>
|
||
<!-- HoldType 21 is what schematicMaster (the vanilla book item) itself uses - matches
|
||
how bookPrefab.prefab was actually authored/rigged to be held. No flight mesh
|
||
needed here (unlike the thrown books above) since this item is never thrown. -->
|
||
<property name="HoldType" value="21"/>
|
||
<property name="Meshfile" value="@:Other/Items/Misc/bookPrefab.prefab"/>
|
||
<property name="Material" value="Mpaper"/>
|
||
<!-- Yellow-green (per user request), distinct from the pure green of the Spirit
|
||
Stone/Grimoire above - keeps the "summon" books visually distinguishable from the
|
||
"throw" books at a glance even before real icon art exists. -->
|
||
<property name="TintColor" value="160, 220, 40"/>
|
||
<property name="Weight" value="0"/>
|
||
<property name="Stacknumber" value="10"/>
|
||
<property name="EconomicValue" value="0"/>
|
||
<property name="Group" value="Ammo/Weapons,Ammo"/>
|
||
<property name="SoundPickup" value="schematics_grab"/>
|
||
<property name="SoundPlace" value="schematics_place"/>
|
||
<property class="Action0">
|
||
<property name="Class" value="SpawnEntity"/>
|
||
<property name="AnimType" value="4"/>
|
||
<property name="AnimWait" value="0.3"/>
|
||
<property name="SoundWarn" value="zombiedogalert"/>
|
||
<property name="SoundAttack" value="zombiedogattack"/>
|
||
<property name="Entity" value="necroZombieDog"/>
|
||
<property name="EntityOffset" value="0, -1, 2.5"/>
|
||
</property>
|
||
<!-- Recall (user's own suggestion 2026-08-28): Action1 - the "power attack"/secondary
|
||
click - uses the same SpawnEntity Class/Entity as Action0, but SummonPatch.cs's
|
||
Prefix treats index 1 as recall-only and never lets vanilla actually spawn
|
||
anything from it - see that file. AnimWait kept short (no real "cast" needed to
|
||
dismiss a pet); "swoosh" reused from the Spirit Stone's throw as a quick
|
||
"gone" cue instead of the summon warning sound - SoundAttack here never actually
|
||
plays either way, since the Prefix always blocks Action1's Spawn() before it
|
||
reaches that line. -->
|
||
<property class="Action1">
|
||
<property name="Class" value="SpawnEntity"/>
|
||
<property name="AnimType" value="4"/>
|
||
<property name="AnimWait" value="0.1"/>
|
||
<property name="SoundWarn" value="swoosh"/>
|
||
<property name="Entity" value="necroZombieDog"/>
|
||
<property name="EntityOffset" value="0, -1, 2.5"/>
|
||
</property>
|
||
</item>
|
||
</append>
|
||
|
||
<!-- Zombie Dog's bite: user request 2026-08-28 ("добавь собаке возможность накладывать баф
|
||
замедления"). Same shape as necroMeleeHandInsectSwarm below - Extends the vanilla hand
|
||
item so Action0's Delay/Range/Class etc. inherit unchanged (property class="Action0" DOES
|
||
merge per-property through Extends), only DamageEntity is unlisted here so even that
|
||
inherits (kept at vanilla's own 8 - no request to change damage, only to add the slow). -->
|
||
<append xpath="/items">
|
||
<item name="necroMeleeHandZombieDog">
|
||
<property name="Extends" value="meleeHandAnimalZombieDog"/>
|
||
<property name="CreativeMode" value="None"/>
|
||
<!-- effect_group does NOT inherit through Extends (see the note at the top of this
|
||
file) - meleeHandAnimalZombieDog's own triggers have to be restated from scratch
|
||
if wanted; dropped its player-injury-system debuff soup (buffFatiguedTrigger etc.,
|
||
the exact same set the Insect Swarm dropped in necroMeleeHandInsectSwarm below)
|
||
since it doesn't mean much against a zombie, kept only the slow that was asked
|
||
for. Gated to zombies only, same EntityTagCompare pattern as everywhere else in
|
||
this mod that adds a buff on hit. -->
|
||
<effect_group name="necroMeleeHandZombieDog" tiered="false">
|
||
<passive_effect name="ModSlots" operation="base_set" value="0"/>
|
||
<triggered_effect trigger="onSelfAttackedOther" action="AddBuff" target="other" buff="buffInjurySlow">
|
||
<requirement name="EntityTagCompare" target="other" tags="zombie"/>
|
||
</triggered_effect>
|
||
</effect_group>
|
||
</item>
|
||
</append>
|
||
|
||
<!-- "Жуки Властелина" (Insect Swarm summon): user request 2026-08-28, added alongside the
|
||
Zombie Dog bugfix above since it reuses the exact same summon plumbing (SpawnEntity,
|
||
HarmonySrc/SummonPatch.cs ownership/limit).
|
||
|
||
BEHAVIOR CHANGE 2026-08-28 (user request, replacing the original damage+slow design):
|
||
no longer deals meaningful damage or applies buffInjurySlow - a sting now applies
|
||
buffNecroDeviatorCharm instead, the SAME charm the Spirit Stone/Grimoire use (see
|
||
Config/buffs.xml + HarmonySrc/CharmPatch.cs), converting the stung zombie to fight for the
|
||
player exactly like a thrown-book hit would. So the swarm is really a mobile, persistent
|
||
version of the Deviator/Grimoire's charm effect - see necroInsectSwarm in entityclasses.xml
|
||
and HarmonySrc/SwarmTargetPatch.cs for why targeting zombies at all took a Harmony patch,
|
||
not just XML (EntitySwarm's real base class hardcodes player-hunting in C#), and for how it
|
||
skips already-charmed zombies so it moves on to the next one instead of camping the first.
|
||
|
||
Recipe has no craft_area (personal crafting), unlike the Dog's workbench requirement -
|
||
the user only asked for the Dog to move to the workbench; this is a guess at matching the
|
||
Spirit Stone/Grimoire's default. Say if it should be workbench-gated too. Gated at the
|
||
same necroNecromancyLvl200 threshold as the Dog for now, also a guess - it's a comparably
|
||
strong summon, but nothing pins it to that exact number. -->
|
||
<append xpath="/items">
|
||
<item name="necroMeleeHandInsectSwarm">
|
||
<property name="Extends" value="meleeHandAnimalInsectSwarm"/>
|
||
<property name="CreativeMode" value="None"/>
|
||
<!-- DamageEntity reverted to vanilla's own 3 (was bumped to 15 for the old damage-
|
||
dealing design, no longer the point - this is a bite that converts, not a weapon).
|
||
Delay/Range/Sphere/Class etc. inherit from meleeHandAnimalInsectSwarm's own
|
||
Action0 fine (property class="Action0" DOES merge per-property through Extends,
|
||
unlike effect_group - see below). -->
|
||
<property class="Action0">
|
||
<property name="DamageEntity" value="3"/>
|
||
</property>
|
||
<!-- effect_group does NOT inherit through Extends (see the note at the top of this
|
||
file) - meleeHandAnimalInsectSwarm's own triggers have to be restated here from
|
||
scratch, not just added to. EntityTagCompare gate matches how the Spirit Stone/
|
||
Grimoire's own onProjectileImpact AddBuff is gated in items.xml above - only
|
||
charms actual zombies, never anything else the swarm might somehow end up hitting
|
||
(e.g. if provoked into retaliating against the player). -->
|
||
<effect_group name="necroMeleeHandInsectSwarm" tiered="false">
|
||
<passive_effect name="ModSlots" operation="base_set" value="0"/>
|
||
<triggered_effect trigger="onSelfAttackedOther" action="AddBuff" target="other" buff="buffNecroDeviatorCharm">
|
||
<requirement name="EntityTagCompare" target="other" tags="zombie"/>
|
||
</triggered_effect>
|
||
</effect_group>
|
||
</item>
|
||
</append>
|
||
|
||
<!-- User request 2026-08-28: "пусть вызов насекомых будет одноразовым" - unlike the Dog, NO
|
||
Action1/recall here on purpose. SummonPatch.cs's recall branch only ever runs for
|
||
indexInEntityOfAction==1, and with no Action1 block declared at all there's no way to
|
||
reach that index for this item - the swarm is cast-and-forget, plumbing-wise identical to
|
||
just never wiring recall up for it. Still capped at one active swarm at a time (same
|
||
LimitedPets/ownedEntities check as the Dog) - "one-time" was about not being able to call
|
||
it back, not about being unlimited.
|
||
|
||
CustomIcon is the user's own hand-drawn art (2026-08-28, exch/schematicInsectoSummon.png
|
||
-> UIAtlases/ItemIconAtlas/), same as bookSummonZombieDog's schematicDogSummon.png above -
|
||
no CustomIconTint on top of it either, for the same reason. -->
|
||
<append xpath="/items">
|
||
<item name="bookSummonInsectSwarm">
|
||
<property name="Tags" value="T0,weapon,attPerception"/>
|
||
<property name="ItemTypeIcon" value="book"/>
|
||
<!-- ICON REPLACED 2026-08-29: same reasoning as bookSummonZombieDog above - swapped
|
||
the earlier hand-drawn schematicInsectoSummon.png for BeetlesOfTheLord.png. -->
|
||
<property name="CustomIcon" value="BeetlesOfTheLord"/>
|
||
<property name="DescriptionKey" value="bookSummonInsectSwarmDesc"/>
|
||
<property name="DisplayType" value="ammoGrenadeFire"/>
|
||
<!-- 3D MODEL UPDATED 2026-08-29: part of the "свиток" family model swap - see
|
||
thrownBookGrimoireDeviation's own comment above for the full reasoning
|
||
(medicalBandage's real Meshfile, bandage.FBX, tinted parchment yellow-brown,
|
||
HoldType switched to 64 to match). No separate in-flight mesh here (this item
|
||
isn't thrown, Class="SpawnEntity"), so one Meshfile covers both held and
|
||
dropped. -->
|
||
<property name="HoldType" value="64"/>
|
||
<property name="Meshfile" value="@:Other/Items/Health/bandage.FBX"/>
|
||
<property name="Material" value="Mpaper"/>
|
||
<property name="TintColor" value="210, 170, 90"/>
|
||
<property name="Weight" value="0"/>
|
||
<property name="Stacknumber" value="10"/>
|
||
<property name="EconomicValue" value="0"/>
|
||
<property name="Group" value="Ammo/Weapons,Ammo"/>
|
||
<property name="SoundPickup" value="schematics_grab"/>
|
||
<property name="SoundPlace" value="schematics_place"/>
|
||
<property class="Action0">
|
||
<property name="Class" value="SpawnEntity"/>
|
||
<property name="AnimType" value="4"/>
|
||
<property name="AnimWait" value="0.3"/>
|
||
<property name="SoundWarn" value="swarmalert"/>
|
||
<property name="SoundAttack" value="swarmattack"/>
|
||
<property name="Entity" value="necroInsectSwarm"/>
|
||
<property name="EntityOffset" value="0, -1, 2.5"/>
|
||
</property>
|
||
</item>
|
||
</append>
|
||
|
||
<!-- Three more summon books, BACKLOG.md item 4a. REPLACED 2026-08-29 - see entityclasses.xml
|
||
for why (the previous three, Stripper/Cop/Soldier, came back hostile in testing; new
|
||
concept is Зомбогриф/Зомбомедведь/Зомбоволк, extending necroZombieGriffin/Bear/Wolf).
|
||
Same plumbing as bookSummonZombieDog above in every respect: Class="SpawnEntity",
|
||
Action0=summon/Action1=recall via HarmonySrc/SummonPatch.cs's LimitedPets table,
|
||
ConsumesBook=false (book never leaves the inventory, matches "по аналогии с
|
||
Зомбособакой"). No hand-drawn icon exists yet for any of these three (CustomIcon
|
||
intentionally omitted, same as the Dog/Swarm before the user drew their own art). Gated
|
||
at necroNecromancyLvl200 + craft_area="workbench" in recipes.xml, matching the Dog's own
|
||
tier. -->
|
||
<append xpath="/items">
|
||
<item name="bookSummonZombieGriffin">
|
||
<property name="Tags" value="T0,weapon,attPerception"/>
|
||
<property name="ItemTypeIcon" value="book"/>
|
||
<!-- ICON ADDED 2026-08-29: real generated art (SummonZombieGriffin.png). -->
|
||
<property name="CustomIcon" value="SummonZombieGriffin"/>
|
||
<property name="DescriptionKey" value="bookSummonZombieGriffinDesc"/>
|
||
<property name="DisplayType" value="ammoGrenadeFire"/>
|
||
<property name="HoldType" value="21"/>
|
||
<property name="Meshfile" value="@:Other/Items/Misc/bookPrefab.prefab"/>
|
||
<property name="Material" value="Mpaper"/>
|
||
<property name="TintColor" value="160, 220, 40"/>
|
||
<property name="Weight" value="0"/>
|
||
<property name="Stacknumber" value="10"/>
|
||
<property name="EconomicValue" value="0"/>
|
||
<property name="Group" value="Ammo/Weapons,Ammo"/>
|
||
<property name="SoundPickup" value="schematics_grab"/>
|
||
<property name="SoundPlace" value="schematics_place"/>
|
||
<property class="Action0">
|
||
<property name="Class" value="SpawnEntity"/>
|
||
<property name="AnimType" value="4"/>
|
||
<property name="AnimWait" value="0.3"/>
|
||
<property name="SoundWarn" value="mlionalert"/>
|
||
<property name="SoundAttack" value="mlionattack"/>
|
||
<property name="Entity" value="necroZombieGriffin"/>
|
||
<property name="EntityOffset" value="0, -1, 2.5"/>
|
||
</property>
|
||
<property class="Action1">
|
||
<property name="Class" value="SpawnEntity"/>
|
||
<property name="AnimType" value="4"/>
|
||
<property name="AnimWait" value="0.1"/>
|
||
<property name="SoundWarn" value="swoosh"/>
|
||
<property name="Entity" value="necroZombieGriffin"/>
|
||
<property name="EntityOffset" value="0, -1, 2.5"/>
|
||
</property>
|
||
</item>
|
||
</append>
|
||
|
||
<append xpath="/items">
|
||
<item name="bookSummonZombieBear">
|
||
<property name="Tags" value="T0,weapon,attPerception"/>
|
||
<property name="ItemTypeIcon" value="book"/>
|
||
<!-- ICON ADDED 2026-08-29: real generated art (SummonZombieBear.png). -->
|
||
<property name="CustomIcon" value="SummonZombieBear"/>
|
||
<property name="DescriptionKey" value="bookSummonZombieBearDesc"/>
|
||
<property name="DisplayType" value="ammoGrenadeFire"/>
|
||
<property name="HoldType" value="21"/>
|
||
<property name="Meshfile" value="@:Other/Items/Misc/bookPrefab.prefab"/>
|
||
<property name="Material" value="Mpaper"/>
|
||
<property name="TintColor" value="160, 220, 40"/>
|
||
<property name="Weight" value="0"/>
|
||
<property name="Stacknumber" value="10"/>
|
||
<property name="EconomicValue" value="0"/>
|
||
<property name="Group" value="Ammo/Weapons,Ammo"/>
|
||
<property name="SoundPickup" value="schematics_grab"/>
|
||
<property name="SoundPlace" value="schematics_place"/>
|
||
<property class="Action0">
|
||
<property name="Class" value="SpawnEntity"/>
|
||
<property name="AnimType" value="4"/>
|
||
<property name="AnimWait" value="0.3"/>
|
||
<property name="SoundWarn" value="bearalert"/>
|
||
<property name="SoundAttack" value="bearattack"/>
|
||
<property name="Entity" value="necroZombieBear"/>
|
||
<property name="EntityOffset" value="0, -1, 2.5"/>
|
||
</property>
|
||
<property class="Action1">
|
||
<property name="Class" value="SpawnEntity"/>
|
||
<property name="AnimType" value="4"/>
|
||
<property name="AnimWait" value="0.1"/>
|
||
<property name="SoundWarn" value="swoosh"/>
|
||
<property name="Entity" value="necroZombieBear"/>
|
||
<property name="EntityOffset" value="0, -1, 2.5"/>
|
||
</property>
|
||
</item>
|
||
</append>
|
||
|
||
<append xpath="/items">
|
||
<item name="bookSummonZombieWolf">
|
||
<property name="Tags" value="T0,weapon,attPerception"/>
|
||
<property name="ItemTypeIcon" value="book"/>
|
||
<!-- ICON ADDED 2026-08-29: real generated art (SummonZombieWolf.png). -->
|
||
<property name="CustomIcon" value="SummonZombieWolf"/>
|
||
<property name="DescriptionKey" value="bookSummonZombieWolfDesc"/>
|
||
<property name="DisplayType" value="ammoGrenadeFire"/>
|
||
<property name="HoldType" value="21"/>
|
||
<property name="Meshfile" value="@:Other/Items/Misc/bookPrefab.prefab"/>
|
||
<property name="Material" value="Mpaper"/>
|
||
<property name="TintColor" value="160, 220, 40"/>
|
||
<property name="Weight" value="0"/>
|
||
<property name="Stacknumber" value="10"/>
|
||
<property name="EconomicValue" value="0"/>
|
||
<property name="Group" value="Ammo/Weapons,Ammo"/>
|
||
<property name="SoundPickup" value="schematics_grab"/>
|
||
<property name="SoundPlace" value="schematics_place"/>
|
||
<property class="Action0">
|
||
<property name="Class" value="SpawnEntity"/>
|
||
<property name="AnimType" value="4"/>
|
||
<property name="AnimWait" value="0.3"/>
|
||
<property name="SoundWarn" value="wolfdirealert"/>
|
||
<property name="SoundAttack" value="wolfdireattack"/>
|
||
<property name="Entity" value="necroZombieWolf"/>
|
||
<property name="EntityOffset" value="0, -1, 2.5"/>
|
||
</property>
|
||
<property class="Action1">
|
||
<property name="Class" value="SpawnEntity"/>
|
||
<property name="AnimType" value="4"/>
|
||
<property name="AnimWait" value="0.1"/>
|
||
<property name="SoundWarn" value="swoosh"/>
|
||
<property name="Entity" value="necroZombieWolf"/>
|
||
<property name="EntityOffset" value="0, -1, 2.5"/>
|
||
</property>
|
||
</item>
|
||
</append>
|
||
|
||
<!-- "Книга банши" (Banshee's Book): BACKLOG.md item 7 (dictated 2026-08-28, implemented
|
||
2026-08-29 "без вопросов" per user request). Consumed on use, plays a screamer's own
|
||
alert sound, spawns a small HOSTILE horde near the caster - see
|
||
HarmonySrc/BansheePatch.cs for the full mechanism and why it's a deliberate
|
||
simplification of the real AIScoutHordeSpawner system, not a faithful copy of it.
|
||
|
||
Class="Eat"/Delay="0.3" - same short "ordinary click is long enough" pattern the summon
|
||
books above settled on (see bookSummonZombieDog's own comment for why 0.3s, not a longer
|
||
hold), reusing the same ItemActionEat.consume() completion hook the portal stones above
|
||
already use, just a separate Harmony patch class. Consume defaults to true (not
|
||
overridden here, unlike the portal stones) - the book really is spent per the user's own
|
||
description ("при применении книга исчезает"), not a reusable tool.
|
||
|
||
No name/icon/craft/tier given by the user beyond the mechanic - guessed: reuses
|
||
schematicMaster's icon (same reused-vanilla-icon trick as the Grimoire), tinted purple to
|
||
read as "dangerous/hostile" rather than the green friendly-summon family or the blue/black
|
||
portal stones. Gated at necroNecromancyLvl200 (same tier as the Dog/Swarm/three zombie
|
||
pets) and workbench-crafted, since it's also an "advanced ritual" item, not an early one -
|
||
say if this should be different. -->
|
||
<append xpath="/items">
|
||
<item name="bookBanshee">
|
||
<property name="Tags" value="T0,weapon,attPerception"/>
|
||
<property name="ItemTypeIcon" value="book"/>
|
||
<!-- ICON UPDATED 2026-08-29: real generated art (BansheesScroll.png), no tint. -->
|
||
<property name="CustomIcon" value="BansheesScroll"/>
|
||
<property name="DescriptionKey" value="bookBansheeDesc"/>
|
||
<property name="DisplayType" value="ammoGrenadeFire"/>
|
||
<!-- 3D MODEL UPDATED 2026-08-29: part of the "свиток" family model swap - see
|
||
thrownBookGrimoireDeviation's own comment above for the full reasoning. Tinted
|
||
the same parchment yellow-brown as the other two scrolls for a consistent
|
||
"свиток" family look (the old purple TintColor was about signaling "dangerous",
|
||
not really about material color - dropped in favor of matching the set; say if
|
||
Banshee's Scroll should keep a darker/distinct tint instead). -->
|
||
<property name="HoldType" value="64"/>
|
||
<property name="Meshfile" value="@:Other/Items/Health/bandage.FBX"/>
|
||
<property name="Material" value="Mpaper"/>
|
||
<property name="TintColor" value="210, 170, 90"/>
|
||
<property name="Weight" value="0"/>
|
||
<property name="Stacknumber" value="10"/>
|
||
<property name="EconomicValue" value="0"/>
|
||
<property name="Group" value="Ammo/Weapons,Ammo"/>
|
||
<property name="SoundPickup" value="schematics_grab"/>
|
||
<property name="SoundPlace" value="schematics_place"/>
|
||
<property class="Action0">
|
||
<property name="Class" value="Eat"/>
|
||
<property name="Delay" value="0.3"/>
|
||
<property name="Sound_start" value="zombiefemalescoutalert"/>
|
||
</property>
|
||
</item>
|
||
</append>
|
||
|
||
<!-- "Нож некроманта" (Necromancer's Knife): BACKLOG.md item 5, user request 2026-08-28.
|
||
Extends the vanilla bone knife (meleeWpnBladeT0BoneKnife) - same swing/harvesting
|
||
mechanics (Action0 inherits fine through Extends, unlike effect_group), just:
|
||
|
||
- Fully black (TintColor="0,0,0").
|
||
- Damage = necroZombieKillsCVar / 10, computed continuously in buffs.xml's
|
||
buffNecroZombieKillTrackerDisplay (see that file for why it lives there and not here -
|
||
a held item has no update tick of its own) and read live via
|
||
passive_effect value="@$necroKnifeDamageCVar" - "base_set" REPLACES the base damage
|
||
entirely (own effect_group, doesn't inherit the parent's tiered damage - matches this
|
||
mod's own established items.xml effect_group-doesn't-inherit-through-Extends rule, so
|
||
there's nothing to actually override, just nothing inherited to begin with). At 0 kills
|
||
this is 0 damage - a deliberate consequence of the user's own formula, not a bug: the
|
||
knife is a payoff weapon for the necromancer playstyle, not a starter blade.
|
||
- Lifesteal: heals for half of that same damage value (a second computed CVar,
|
||
$necroKnifeHealCVar), applied instantly on the hit via ModifyStats. ORDINARY SWING
|
||
ONLY since 2026-09-09 - the power attack no longer heals at all, see the trigger
|
||
comment below. It originally went
|
||
through vanilla's medicalRegHealthAmount regen queue instead and healed NOTHING at all -
|
||
see the long comment on the triggered_effect below for why that never worked and why the
|
||
fix moved off that path rather than completing it. This is "half the weapon's listed
|
||
damage", not "half of what actually landed after the target's own resistances" - those
|
||
should be very close for melee-vs-zombie in practice, and getting the exact
|
||
post-mitigation number would need a Harmony patch on DamageEntity's return value instead
|
||
of this pure-XML approach.
|
||
- Applies buffNecroVictim (buffs.xml) to whatever zombie it hits - see
|
||
HarmonySrc/VictimPatch.cs for what that debuff actually does (guaranteed green bag drop
|
||
on death, not visible otherwise).
|
||
- Damage/heal/victim-debuff triggers all gated to zombies only (EntityTagCompare) so
|
||
swinging at a block, crate, or (PvP) another player doesn't heal you or tag anything.
|
||
|
||
No crafting recipe/level gate specified by the user beyond the mechanics - guessed modest
|
||
and early (5 bone + 1 blood + 10 fiber, no craft_area/level tag, personal crafting from
|
||
the start like the Spirit Stone), matching how the knife's real power comes from playing
|
||
the necromancer build over time, not an expensive unlock. Say if that should change. -->
|
||
<append xpath="/items">
|
||
<item name="necroWpnBladeNecroKnife">
|
||
<property name="Extends" value="meleeWpnBladeT0BoneKnife"/>
|
||
<!-- Not for sale, per user request 2026-08-29 ("убери у всех предметов некроманта
|
||
цену продажи") - overridden explicitly since this item never had its own
|
||
EconomicValue before (just inherited the vanilla bone knife's own sell price). -->
|
||
<property name="EconomicValue" value="0"/>
|
||
<!-- ICON UPDATED 2026-08-29: real generated art (NecromancersKnife.png) replaces the
|
||
old reused-vanilla-icon+black-tint trick entirely - no CustomIconTint needed any
|
||
more, the new art is already black. -->
|
||
<property name="CustomIcon" value="NecromancersKnife"/>
|
||
<!-- TintColor (0,0,0 = pure black multiply) is the same mechanism used successfully
|
||
elsewhere in this mod (e.g. the Spirit Stone/Grimoire/summon books), but this is
|
||
its first use on an actual weapon mesh rather than a resource/book - unconfirmed
|
||
whether melee weapons with ShowQuality="true" (inherited from the base knife)
|
||
render TintColor the same way. If it's still not black after this deploy, that's
|
||
the next thing to dig into (possibly needs ShowQuality or a cosmetic-slot
|
||
workaround), not a guess to make blind right now.
|
||
|
||
СОМНЕНИЕ СНЯТО 2026-09-10 по самой ванили: meleeWpnBladeT0BoneKnife задаёт себе
|
||
TintColor 107, 107, 71 (Data/Config/items.xml:2419) на том же самом
|
||
boneShivPrefab, то есть слот тинта у этого меша рабочий и на оружии с
|
||
ShowQuality тоже применяется. Копать тут больше нечего; осталось только
|
||
посмотреть глазами, достаточно ли чёрный получается клинок. -->
|
||
<!-- TintColor ОТКЛЮЧЁН 2026-09-10 вместе с переходом на свою текстуру.
|
||
|
||
TintColor - это МНОЖИТЕЛЬ цвета меша, а 0,0,0 множит в чистый чёрный. Он был нужен,
|
||
пока нож носил ванильную бежевую кость: другого способа затемнить клинок не было.
|
||
Теперь цвет несёт своя текстура necroKnife_d.png, и тот же множитель погасил бы
|
||
всё нарисованное в ноль - вместе с пурпурным свечением в альбедо.
|
||
|
||
Иконка от этого не зависит: она отдельный арт (CustomIcon выше), CustomIconTint
|
||
здесь никогда не стоял. И на иконке клинок именно КОСТЯНОЙ, а не чёрный, так что
|
||
чёрный множитель ей вдобавок противоречил.
|
||
|
||
Побочно это снимает неоднозначность теста трубы: красный клинок мог быть виден
|
||
только за счёт эмиссии, если тинт всё-таки давил альбедо нашего меша. Без тинта
|
||
вопроса больше нет.
|
||
<property name="TintColor" value="0, 0, 0"/> -->
|
||
|
||
<!-- СВОЯ МОДЕЛЬ. ВКЛЮЧЕНА 2026-09-10 вместе с бандлом Resources/necroknife (2.19 МБ,
|
||
собран Unity 2022.3.62f2 в batch-режиме; внутри necroKnifePrefab.prefab, три меша
|
||
boneShiv_LOD0/1/2, текстуры boneShiv_d/_n и материал necroKnife.mat - бандл
|
||
самодостаточный, проверено по составу).
|
||
|
||
ВНИМАНИЕ: в бандле сейчас ТЕСТОВЫЙ вид - ярко-красный материал с эмиссией. Это
|
||
проверка трубы, а не финальная модель. Красный выбран потому, что ванильный нож
|
||
бежевый и по нему не отличить, загрузился наш бандл или подставилась ваниль;
|
||
эмиссия - потому что TintColor ниже стоит 0,0,0, и если игра множит тинт на
|
||
материал нашего меша, красный альбедо ушёл бы в чёрный и тест ничего бы не показал.
|
||
Настоящая текстура рисуется поверх _private/Extracted/boneShiv_d.png после того,
|
||
как труба подтвердится.
|
||
|
||
Синтаксис: "#" - грузить из бандла, "@modfolder(NecromancerTome):" - путь от
|
||
корня этого мода, "?" отделяет путь к префабу ВНУТРИ бандла. Форма собрана из
|
||
двух подтверждённых кусков: ваниль пишет "#Entities/Trees?SnakeweedPrefab.prefab"
|
||
(Data/Config/blocks.xml), а поддержка "@modfolder:" лежит в Assembly-CSharp.
|
||
ФОРМА ПОДТВЕРЖДЕНА ЖИВЫМ ПРИМЕРОМ 2026-09-10: с этой строкой тестовый красный
|
||
нож появился в руке. Значит и запись работает, и бандл из папки мода читается,
|
||
и Standard-шейдер из Unity 2022.3.62f2 игра отрисовывает. Больше не гипотеза -
|
||
этой же формой можно подключать любые следующие свои модели.
|
||
|
||
necroknife - имя AssetBundle, заданное префабу в Unity; necroKnifePrefab.prefab -
|
||
имя префаба ВНУТРИ бандла. Оба должны совпасть с тем, что реально собралось, иначе
|
||
предмет останется без модели молча. Пересборка - меню NecromancerTome -> Ctrl+Shift+B
|
||
либо batch: Unity.exe -batchmode -nographics -quit -projectPath <проект>
|
||
-executeMethod NecroKnifeSetup.SetupAndBuild. Игру после каждой пересборки
|
||
перезапускать: моды и их бандлы читаются только при старте.
|
||
|
||
Геометрия остаётся ванильной, меняются материал и текстура, поэтому HoldType,
|
||
посадка в руке и анимации от базового ножа продолжают подходить без правок. -->
|
||
<property name="Meshfile" value="#@modfolder(NecromancerTome):Resources/necroknife?necroKnifePrefab.prefab"/>
|
||
|
||
<!-- MOD SLOTS ADDED 2026-09-07 (user request: "добавь в нож слоты для
|
||
модификаций... модификации там будут особые, именно для ножа
|
||
некроманта, а не для обычного ножа"). Tags has to be restated in full here
|
||
rather than appended to - a property DOES inherit through Extends (unlike
|
||
effect_group), so this whole list is the vanilla bone knife's own Tags value copied
|
||
verbatim, plus the two new ones at the end, MINUS canHaveCosmetic (see below).
|
||
Everything else the original had is kept deliberately:
|
||
perkDeepCuts/perkFlurryOfAgility/bladeSkill/attAgility are what the agility perks
|
||
match on, and corpseRemoval is vanilla plumbing.
|
||
|
||
canHaveCosmetic REMOVED 2026-09-07 on direct instruction ("хэв косметик убирай").
|
||
It was the one remaining hole in "necromancer mods only": the 10 vanilla modDye*
|
||
modifiers are the only mods in the game with no blocked_tags at all, so the noMods
|
||
tag below does not stop them, and their
|
||
installable_tags="weapon,tool,vehicle,drone" matches this knife's own "weapon"
|
||
tag. Dyes land in a separate cosmetic slot rather than one of the four real ones,
|
||
so they never actually cost the player a mod slot - but they would recolour a
|
||
weapon whose entire point is being pure black. That tag is exactly and only what
|
||
allocates the slot: decompiled, ItemValue's constructor does
|
||
"CosmeticMods = new ItemValue[itemClass.HasAnyTags(ItemClassModifier.CosmeticItemTags) ? 1 : 0]"
|
||
and CosmeticItemTags is literally FastTags.Parse("canHaveCosmetic"). No tag, no
|
||
slot, nowhere for a dye to go. Same save-compat caveat as the mod slots below: the
|
||
array is sized once when the item is created, so a knife already sitting in a save
|
||
keeps whatever cosmetic slot it was built with.
|
||
|
||
The two additions do exactly one job each, and together they are the entire
|
||
"necromancer mods only" mechanism:
|
||
|
||
noMods - blocks EVERY vanilla mod. Confirmed exhaustively: all 87 vanilla
|
||
item_modifiers that declare blocked_tags at all list noMods among
|
||
them, and blocked_tags maps to ItemClassModifier.DisallowedTags,
|
||
which the install UI itself refuses on (decompiled
|
||
XUiC_ItemPartStack.CanSwap + XUiM_AssembleItem, both do
|
||
"itemClass.HasAnyTags(DisallowedTags) -> return false"). The 24
|
||
modifiers with NO blocked_tags cannot reach this knife anyway:
|
||
they are the 10 dyes and 7 drone mods (cosmetic slot / drone-only),
|
||
two quest items, modGunBowAdminArcheryReloadRecovery
|
||
(installable_tags="perkArchery", which this knife lacks) and
|
||
modGunButtkick3000/4000 + modMeleeGunToolDecapitizer, all three of
|
||
which are CreativeMode="Test"/"Dev" and so unobtainable in normal
|
||
play. "noMods" itself has no meaning in code at all - grepping the
|
||
decompiled assembly for it finds nothing, it is purely a naming
|
||
convention vanilla data uses in blocked_tags.
|
||
necroKnife - the positive half: the mod's own three modifiers declare
|
||
installable_tags="necroKnife" (see the new Config/item_modifiers.xml),
|
||
and no other item in vanilla or this mod carries that tag, so they
|
||
fit this knife and nothing else. -->
|
||
<property name="Tags" value="T0,knife,melee,grunting,light,perkFlurryOfAgility,weapon,meleeWeapon,attAgility,perkDeepCuts,perkTheHuntsman,bladeSkill,corpseRemoval,noMods,necroKnife"/>
|
||
|
||
<property class="Action0">
|
||
<property name="DamageEntity" value="1"/>
|
||
</property>
|
||
|
||
<!-- tiered="false" REMOVED 2026-09-07 - it would have silently broken every mod
|
||
installed here, and this is not a cosmetic detail. ItemClass.HasQuality is not
|
||
driven by the ShowQuality property at all; decompiled, it is literally
|
||
"Effects.IsOwnerTiered()", and MinEffectController.IsOwnerTiered() returns true
|
||
only if at least ONE effect_group has OwnerTiered set - which defaults to true
|
||
(MinEffectGroup line 28) and is overridden only when a "tiered" attribute is
|
||
actually present. This item had exactly one effect_group and it said
|
||
tiered="false", so HasQuality was false. And ItemValue.FireEvent ends with:
|
||
|
||
if (!HasQuality) { return; }
|
||
for (i...) Modifications[i].FireEvent(...)
|
||
|
||
- i.e. installed mods receive NO triggered_effects whatsoever on an item without
|
||
quality, while the slots themselves would still have appeared and still accepted
|
||
mods (Modifications is allocated earlier, unconditionally). Exactly the same shape
|
||
of silent failure as the lifesteal bug fixed above: everything looks wired up,
|
||
nothing fires. Dropping the attribute restores the vanilla default - and note the
|
||
real bone knife this item extends has no "tiered" attribute on its own
|
||
effect_group either, so this now matches it. Nothing else about this group depends
|
||
on being untiered: every passive_effect below is a single value with no per-tier
|
||
list, which is exactly what the vanilla bone knife does inside its own tiered
|
||
group (AttacksPerMinute/StaminaLoss/MaxRange are all flat there).
|
||
|
||
ModSlots 0 -> 4 per the user's own choice ("4 фиксированно"): flat, not a
|
||
per-quality list like vanilla knives use, since quality is meaningless on this
|
||
weapon anyway (its damage is base_set from a CVar, not rolled from a stats table). -->
|
||
<effect_group name="necroWpnBladeNecroKnife">
|
||
<passive_effect name="ModSlots" operation="base_set" value="4"/>
|
||
<passive_effect name="EntityDamage" operation="base_set" value="@$necroKnifeDamageCVar"/>
|
||
<!-- User request 2026-08-28 ("еле-еле бьёт" - add speed): the base bone knife's
|
||
swing rate comes from its <stats> quality-roll table (Data/Config/items.xml),
|
||
not a flat value - base_set here overrides that outright, unconditionally
|
||
(no perk tag gate), same mechanism vanilla knives use for their own speed
|
||
bonuses (e.g. AttacksPerMinute base_set="120" tags="perkDeepCuts,..." on a
|
||
nearby T1 knife) minus the tag requirement, since this should just always be
|
||
fast. 120 matches that same reference point. -->
|
||
<passive_effect name="AttacksPerMinute" operation="base_set" value="120"/>
|
||
|
||
<triggered_effect trigger="onSelfAttackedOther" action="AddBuff" target="other" buff="buffNecroVictim">
|
||
<requirement name="EntityTagCompare" target="other" tags="zombie"/>
|
||
</triggered_effect>
|
||
<!-- ЛЕЧЕНИЕ СНЯТО С СИЛОВОГО УДАРА 2026-09-09 (пользователь: "лучше вообще не
|
||
лечить на силовом ударе"). Триггер сменён с onSelfAttackedOther на
|
||
onSelfPrimaryActionRayHit, и это единственное изменение по смыслу.
|
||
|
||
ЗАЧЕМ. onSelfAttackedOther шлётся на ЛЮБОЕ попадание - и обычное, и силовое
|
||
(ItemActionAttack.cs:790/838, внутри ItemActionAttack.Hit). Поэтому силовой
|
||
удар одновременно снимал 5 HP и лечил на половину урона ножа, то есть на
|
||
necroZombieKillsCVar/20. Порядок при этом был "сначала плата, потом лечение":
|
||
ItemActionDynamic.cs:541 шлёт onSelfSecondaryActionRayHit ДО вызова
|
||
ItemActionAttack.Hit в конце того же метода. Итоговая цена силовой атаки
|
||
получалась 5 - убийств/20, то есть:
|
||
100 убийств - ровно ноль,
|
||
500 убийств - ПЛЮС 20 HP за удар,
|
||
5000 убийств - плюс 245 HP за удар.
|
||
Силовая атака превращалась в лучший в моде способ лечиться, при том что её
|
||
цена прямо описана в Localization.csv. Перестановка порядка (лечить первым,
|
||
чтобы лечение упёрлось в потолок - Stat.Value клампится по ModifiedMax,
|
||
проверено) чинила бы только вид на полном здоровье: когда игрок реально ранен,
|
||
порядок ни на что не влияет и удар всё равно даёт чистый плюс. Поэтому убрано
|
||
совсем, а не переставлено.
|
||
|
||
ПОЧЕМУ ИМЕННО onSelfPrimaryActionRayHit. Это зеркало уже используемого здесь
|
||
onSelfSecondaryActionRayHit - та же строка ItemActionDynamic.cs:541, тот же
|
||
тернарник по indexInEntityOfAction, только ветка обычного удара. Ванильный,
|
||
широко используемый триггер (24 срабатывания в Data/Config/items.xml, в том
|
||
числе с target="other" у электродубинки), так что MinEventContext.Other к
|
||
этому моменту гарантированно проставлен (строка 532) и гейт на зомби
|
||
работает.
|
||
|
||
Два следствия, оба безобидные:
|
||
- Событие летит ДО применения урона, а не после. Сумме лечения это
|
||
безразлично: она берётся из CVar $necroKnifeHealCVar, который считает
|
||
бафф в buffs.xml, а не из фактически нанесённого урона.
|
||
- Событие летит и при попадании по блоку, но там Other пуст, и
|
||
EntityTagCompare tags="zombie" такой вызов отсекает - как и раньше.
|
||
buffNecroVictim НЕ трогали: он остался на onSelfAttackedOther, то есть метка
|
||
Жертвы по-прежнему вешается и обычным, и силовым ударом.
|
||
|
||
LIFESTEAL BUG FIXED 2026-09-07 (user report: "нож не подлечивает"). The old
|
||
version did only "ModifyCVar medicalRegHealthAmount add @$necroKnifeHealCVar",
|
||
which is just the FIRST of the three things vanilla's own medical items do -
|
||
and that CVar heals nothing by itself, it is only a queue. Every vanilla healer
|
||
(aloeCream/bandage/firstAidKit, Data/Config/items.xml ~18614 / ~18747 / ~18854)
|
||
follows that add with "ModifyCVar medRegHealthIncSpeed set ..." AND "AddBuff
|
||
buffProcessConsumables"; buffProcessConsumables then adds buffHealHealth, and it
|
||
is buffHealHealth's own onSelfBuffUpdate (Data/Config/buffs.xml ~8567) that
|
||
actually turns the queue into HP - medRegHealthIncSpeed HP per 1s tick while
|
||
draining 2 from the queue. The knife did neither of the other two steps, so it
|
||
just kept incrementing a number nothing ever read: zero healing, always. And
|
||
even adding buffProcessConsumables alone would not have been enough, because
|
||
medRegHealthIncSpeed has no default value anywhere in vanilla data (it is only
|
||
ever set by a medical item, or reset to 2 when buffHealHealth ends), so on a
|
||
player who had never used a bandage the tick would have added 0 HP.
|
||
|
||
Fixed with a direct ModifyStats rather than by completing the medical-queue
|
||
path: lifesteal should land on the swing, not trickle in over the following ten
|
||
seconds, and routing it through medicalRegHealthAmount would also let a knife
|
||
hit stomp the heal RATE of a first aid kit the player happens to have
|
||
regenerating at that moment (medRegHealthIncSpeed is one shared CVar, and a
|
||
physician-perked kit sets it well above 2). ModifyStats reading a live CVar via
|
||
value="@$cvar" is confirmed vanilla-supported (buffs.xml ~8569 and ~4565), it
|
||
targets self by default, and it clamps to max health on its own.
|
||
|
||
Still HALF the knife's damage, per the original spec - see BACKLOG.md item 5
|
||
and the item description in Localization.csv, both of which say "half". -->
|
||
<!-- ГЕЙТ НА ТРУПЫ, 2026-09-09. Без "Health GT 0" на цели каждый из эффектов ниже
|
||
срабатывал и по трупу: isHitValid берёт цель с _ignoreDead:false
|
||
(ItemActionDynamic.cs:673), труп остаётся тем же EntityZombie с тегом zombie, а
|
||
EntityAlive.DamageEntity не имеет раннего выхода по IsDead(). То есть разделка
|
||
трупа лечила игрока, а силовой удар по трупу брал с него 5 HP впустую.
|
||
|
||
Почему именно эти эффекты можно так гейтить, а метку Жертвы выше - НЕЛЬЗЯ.
|
||
Все они висят на *ActionRayHit, а это событие уходит ДО применения урона
|
||
(ItemActionDynamic.cs:541, ItemActionAttack.Hit вызывается в конце того же
|
||
метода) - значит у живой цели здоровье в этот момент ещё больше нуля, включая
|
||
смертельный удар. А buffNecroVictim сидит на onSelfAttackedOther, которое
|
||
уходит ПОСЛЕ DamageEntity (ItemActionAttack.cs:838, сразу за строкой 827): на
|
||
смертельном ударе зомби там уже мёртв и его здоровье 0, так что такой же гейт
|
||
снял бы метку ровно с добивающего удара - то есть отобрал бы мешок Жертвы у
|
||
всех, кого убили с одного удара. Поэтому метка оставлена без гейта; повесить
|
||
её на уже мёртвого зомби безвредно - мешок для него всё равно уже разыгран.
|
||
|
||
AOE-эффекты "Мёртвой бури" гейтить не нужно: World/Chunk.GetLivingEntitiesInBounds
|
||
(Chunk.cs:2920) сама отсеивает цели по !IsDead(), так что трупы в выборку не
|
||
попадают вовсе.
|
||
|
||
Требование само по себе безопасно и при попадании по блоку: там Other пуст, а
|
||
TargetedCompareRequirementBase.IsValid возвращает false, если цели нет. -->
|
||
<triggered_effect trigger="onSelfPrimaryActionRayHit" action="ModifyStats" stat="Health" operation="add" value="@$necroKnifeHealCVar">
|
||
<requirement name="EntityTagCompare" target="other" tags="zombie"/>
|
||
<requirement name="StatCompareCurrent" target="other" stat="Health" operation="GT" value="0"/>
|
||
</triggered_effect>
|
||
|
||
<!-- POWER ATTACK, added 2026-09-07. User: "У него есть основная атака и силовая. Но
|
||
силовая по мощности не отличается от основной. Может повесим на силовую
|
||
что-нибудь другое?" - then, on a first round of proposals: "Слишком имбалансно.
|
||
Нужно что-то другое", and finally "Гарантированное сбивание с ног и отъедание,
|
||
например 5 HP персонажа - это очень заманчиво."
|
||
|
||
WHY THE TWO ATTACKS DEALT IDENTICAL DAMAGE - not an engine bug, a direct
|
||
consequence of how this item sets damage. ItemActionAttack.GetDamageEntity
|
||
tags the lookup by which action swung:
|
||
tmpTag = ((actionIndex == 0) ? PrimaryTag : SecondaryTag); // primary/secondary
|
||
return EffectManager.GetValue(PassiveEffects.EntityDamage, ..., tmpTag);
|
||
Vanilla leans on that - the iron fireaxe, pickaxes and shovels all carry
|
||
"EntityDamage perc_add 1.25 tags=secondary" plus a heavier secondary
|
||
StaminaLoss. This knife's own EntityDamage is base_set with NO tag, so it
|
||
applies identically to both swings AND overwrites any inherited difference.
|
||
Deliberately NOT "fixed" by adding a secondary damage bonus: the user asked for
|
||
the power attack to be DIFFERENT, not stronger, and rejected the stronger
|
||
options outright as imbalanced. Damage, stamina and swing rate are untouched.
|
||
|
||
onSelfSecondaryActionRayHit is the clean hook and needs no extra XML plumbing:
|
||
ItemActionDynamic.cs line 541 fires
|
||
(indexInEntityOfAction == 0) ? onSelfPrimaryActionRayHit
|
||
: onSelfSecondaryActionRayHit
|
||
so it means "the power swing actually connected", not "the power swing was
|
||
started". That is why the 5 HP is charged here rather than on
|
||
onSelfSecondaryActionEnd - a whiff costs nothing, only a landed hit does.
|
||
(The UsePowerAttackTriggers property exists too, but it REROUTES the primary
|
||
triggers onto secondary ones and only three vanilla salvage tools use it -
|
||
wrong tool here, it would have broken the primary swing's own effects.)
|
||
|
||
The Ragdoll shape is copied from vanilla's stun baton (Data/Config/items.xml
|
||
~4090), which uses this exact trigger and action; narrowed from its otherAOE to
|
||
a single target, since knocking down a whole group would be the kind of power
|
||
spike that was already turned down once.
|
||
|
||
С 2026-09-09 эта плата наконец настоящая: раньше тот же удар вешал ещё и
|
||
вампиризм через onSelfAttackedOther и с некоторого уровня перекрывал её с
|
||
запасом - см. большой комментарий у лечения выше.
|
||
|
||
The 5 HP is gated on Health GT 5 so this can never kill the player. Dying to
|
||
your own knife mid-horde would read as a bug, not a cost. Note the consequence:
|
||
below 5 HP the knockdown becomes free. If that mercy is unwanted, drop the
|
||
StatCompareCurrent requirement.
|
||
|
||
Works on zombie ANIMALS too (user asked directly). Everything here gates on the
|
||
"zombie" tag, and animalZombieDog/Bear/Vulture/Boar all carry
|
||
"entity,animal,zombie,zombieAnimal,hostile,..." - verified by resolving their
|
||
Tags through the extends chain. Note this is NOT true of two other parts of the
|
||
mod - the kill counter and the Deviator charm both miss zombie animals for
|
||
unrelated reasons; see BACKLOG.md 2026-09-07. -->
|
||
<triggered_effect trigger="onSelfSecondaryActionRayHit" action="Ragdoll" target="other" duration="1.5" force="150">
|
||
<requirement name="EntityTagCompare" target="other" tags="zombie"/>
|
||
<requirement name="StatCompareCurrent" target="other" stat="Health" operation="GT" value="0"/>
|
||
</triggered_effect>
|
||
<triggered_effect trigger="onSelfSecondaryActionRayHit" action="ModifyStats" stat="Health" operation="subtract" value="5">
|
||
<requirement name="EntityTagCompare" target="other" tags="zombie"/>
|
||
<requirement name="StatCompareCurrent" target="other" stat="Health" operation="GT" value="0"/>
|
||
<requirement name="StatCompareCurrent" stat="Health" operation="GT" value="5"/>
|
||
</triggered_effect>
|
||
</effect_group>
|
||
</item>
|
||
</append>
|
||
|
||
<!-- "Кожа жертвы" (Victim's Skin): rare drop from the guaranteed green "Жертва" bag (see
|
||
HarmonySrc/VictimPatch.cs, recipes/loot.xml groupNecroVictimPack). Per user: an ingredient
|
||
for summon books - not wired into the existing Dog/Swarm recipes here (not asked to
|
||
retrofit those), just a real, valid crafting resource ready for that or a future recipe. -->
|
||
<append xpath="/items">
|
||
<item name="resourceVictimSkin">
|
||
<property name="Extends" value="resourceLeather"/>
|
||
<property name="DescriptionKey" value="resourceVictimSkinDesc"/>
|
||
<!-- ICON UPDATED 2026-08-29: real generated art (VictimsSkin.png) replaces the reused
|
||
resourceLeather-icon+yellow-tint trick - no CustomIconTint any more. Extends
|
||
alone still doesn't give an item a working icon on its own (see the Knife's own
|
||
history), so CustomIcon stays explicit even though this item already extends
|
||
resourceLeather for its other properties. -->
|
||
<property name="CustomIcon" value="VictimsSkin"/>
|
||
<!-- 3D MODEL UPDATED 2026-08-29, SECOND ATTEMPT - the bedroll model (first attempt,
|
||
same day) was rejected by the user ("не тот префаб") without it ever being
|
||
confirmed as the one they remembered; a plain string search of items.xml/
|
||
blocks.xml/entityclasses.xml for hide/pelt/leather/roll/rug/tarp/canvas/curled
|
||
turned up nothing more convincing (rugBearPrefab/mattressCurledPrefab were the
|
||
only other candidates, offered but not chosen). User's own pick instead:
|
||
foodShamSandwich's real Meshfile, @:Other/Items/Food/parcelGenericPrefab.prefab
|
||
(confirmed by reading that item directly) - a generic wrapped parcel/bundle
|
||
shape. HoldType switched to 31 (foodShamSandwich's own HoldType) to match how
|
||
that mesh was actually rigged to be held, same reasoning as the bandage/HoldType
|
||
64 swap on the "свиток" family above. TintColor (dark blood-red) kept unchanged
|
||
from before - still reads fine as a stained wrapped skin/hide bundle. -->
|
||
<property name="HoldType" value="31"/>
|
||
<property name="Meshfile" value="@:Other/Items/Food/parcelGenericPrefab.prefab"/>
|
||
<property name="HandMeshfile" value="@:Other/Items/Food/parcelGenericPrefab.prefab"/>
|
||
<property name="DropMeshfile" value="@:Other/Items/Food/parcelGenericPrefab.prefab"/>
|
||
<property name="TintColor" value="60, 10, 10"/>
|
||
<property name="EconomicValue" value="0"/>
|
||
</item>
|
||
</append>
|
||
|
||
<!-- "Прах зомби" (Zombie Ash): BACKLOG.md item 8, dictated 2026-08-29, implemented
|
||
2026-08-29. Replaces foodRottingFlesh/resourceBone in the "Жертва" loot bag (see
|
||
loot.xml groupZpackVictim below) and becomes a crafting ingredient across the other
|
||
necromancy recipes - but deliberately NOT the Knife's own recipe (see recipes.xml for
|
||
why: the knife has to stay craftable from scratch, and ash only exists because the
|
||
knife already tagged a zombie as a Victim - adding it to the knife's own recipe would
|
||
make it uncraftable from zero).
|
||
|
||
No dedicated "ash" resource exists anywhere in vanilla items.xml to reuse wholesale
|
||
(checked directly - resourceCoal/MresourceCoal is the closest powdery/granular resource,
|
||
but it's coal, not ash).
|
||
|
||
ICON UPDATED AGAIN 2026-08-29 (same day, superseding the gunpowder placeholder above):
|
||
real generated art now exists (UIAtlases/ItemIconAtlas/ZombieAsh.png) - no CustomIconTint
|
||
needed. TintColor below still colors the 3D mesh (still resourceCoal-based, unrelated to
|
||
the 2D icon) - left alone, only the icon changed. -->
|
||
<append xpath="/items">
|
||
<item name="resourceZombieAsh">
|
||
<property name="Extends" value="resourceCoal"/>
|
||
<property name="DescriptionKey" value="resourceZombieAshDesc"/>
|
||
<property name="CustomIcon" value="ZombieAsh"/>
|
||
<property name="TintColor" value="200, 200, 200"/>
|
||
<property name="EconomicValue" value="0"/>
|
||
</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).
|
||
|
||
ЭТОТ ПРЕДМЕТ НИКОГДА НЕ ДОЛЖЕН СТАТЬ <item_modifier>. ЭТО НЕ СТИЛЬ, ЭТО СЕЙВЫ.
|
||
15.09.2026 он был перенесён в item_modifiers.xml, чтобы вставляться в Пространственный
|
||
браслет, и это уничтожило персонажа в тестовом мире - вместе с бэкапом. Разбор целиком в
|
||
BACKLOG.md, здесь суть, потому что соблазн повторить велик:
|
||
|
||
ItemValue.Read строка 1094: if ((version > 4 || HasQuality) && !(itemClass is ItemClassModifier))
|
||
ItemValue.Write строка 1228: if (!(ItemClass is ItemClassModifier))
|
||
|
||
Класс предмета решает БАЙТОВУЮ РАСКЛАДКУ каждого его стака в сейве: обычный предмет пишет
|
||
байт числа модификаций, ItemClassModifier - не пишет. Значит любой сейв, записанный до
|
||
переноса, после переноса читается со сдвигом: поток съезжает, ближайший ReadString()
|
||
получает мусор, PlayerDataFile.Load падает с "output char buffer is too small", и игра
|
||
откатывается на NewGame. Бэкап .ttp.bak умирает вместе с основным файлом - он старого
|
||
формата ровно так же. Кровь лежит в сейвах у всех, кто поставил 1.1.0 с Nexus.
|
||
|
||
Правило на будущее: предмет, который уже мог попасть в чужой инвентарь, нельзя переводить
|
||
между ItemClass и ItemClassModifier ни в какую сторону. Нужна модификация - это НОВЫЙ
|
||
предмет с новым именем, которого в старых сейвах нет. Проверка "айди не поедут"
|
||
(assignIdsFromMapping) к этому отношения не имеет и ничего тут не гарантирует - именно на
|
||
неё я и посмотрел вместо раскладки.
|
||
|
||
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. -->
|
||
<append xpath="/items">
|
||
<item name="resourceNecromancerBlood">
|
||
<property name="Extends" value="medicalBloodBag"/>
|
||
<property name="DescriptionKey" value="resourceNecromancerBloodDesc"/>
|
||
<!-- Своя рисованная иконка, 2026-08-30. CustomIcon задаётся явно даже при Extends. -->
|
||
<property name="CustomIcon" value="NecromantsBlood"/>
|
||
|
||
<!-- Своя банка с кровью, 2026-09-10 (указание: «берём чай из золотарника, и жёлтое
|
||
заменяем на кровавый цвет, с фиолетовыми оттенками»). Заодно чинилось расхождение
|
||
текста и модели: описание говорит «Банка, наполненная кровью», а наследуемый
|
||
medicalBloodBag показывал sackPrefab - обычный мешок.
|
||
|
||
ПОЧЕМУ НЕ ХВАТИЛО ТИНТА - ПРОВЕРЕНО В ИГРЕ. Ванильный префаб чая плюс TintColor:
|
||
банка осталась чаем из золотарника, тинт предмета на этот меш НЕ ПОДЕЙСТВОВАЛ
|
||
ВООБЩЕ - у шейдера Game_EntityTintMaskSSS выигрывает собственный _Color материала.
|
||
Поэтому TintColor здесь не задаётся совсем.
|
||
|
||
И по сути: кровь отличается от чая не цветом, а тем, что она непрозрачная, тёмная
|
||
и густая, с плёнкой на стекле. Жидкость ПЕРЕРИСОВАНА по яркости, а не перекрашена
|
||
множителем - генератор _private/tools/make_necroblood_textures.py.
|
||
|
||
HoldType 3 - хват банки вместо 45 (мешок), Material Mglass - стекло вместо ткани. -->
|
||
<property name="Meshfile" value="#@modfolder(NecromancerTome):Resources/necroblood?necroBloodPrefab.prefab"/>
|
||
<property name="HoldType" value="3"/>
|
||
<property name="Material" value="Mglass"/>
|
||
|
||
<!-- НИ Stacknumber, НИ ПРОЧНОСТИ ЗДЕСЬ НЕТ, И ЭТО НАМЕРЕННО.
|
||
|
||
15.09.2026 крови на один заход выдали и то, и другое: стак по одной банке и
|
||
прочность 1000. Обе правки существовали ради одного - кровь должна была стать
|
||
расходником, вставляемым в Пространственный браслет. Эта затея откачена (она
|
||
ломала сейвы, см. предупреждение выше), расходником стала Кровавая сфера, и по
|
||
прямому указанию пользователя кровь возвращена к тому, чем была:
|
||
|
||
- стак снова 15 - наследуется от medicalBloodBag, своей строки Stacknumber
|
||
больше нет. Своя строка была нужна только чтобы перебить наследуемое 15 на 1;
|
||
- прочности нет вовсе: ни ShowQuality, ни DegradationBreaksAfter, ни
|
||
effect_group с DegradationMax. Тратил её браслет; тратить стало некому, а
|
||
полоска, которая никогда не двигается, хуже, чем её отсутствие.
|
||
|
||
Кровь снова то, чем была с самого начала: ингредиент рецептов, и только.
|
||
Если прочность когда-нибудь понадобится - разбор обеих ручек (пассивка
|
||
DegradationMax плюс отдельное свойство ShowQuality, и ловушка с полной полоской
|
||
при MaxUseTimes == 0) лежит в BACKLOG.md, повторно раскапывать не нужно. -->
|
||
<property name="EconomicValue" value="0"/>
|
||
</item>
|
||
</append>
|
||
|
||
<!-- "Петля вора" (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
|
||
direct instruction once the real root cause of its last error became clear: its
|
||
Action1 zoom (Class="Zoom") needs a genuine weapon-rigged mesh (an "Attachments"
|
||
transform, confirmed by decompiling the real NullReferenceException it threw), which
|
||
meant giving up the "invisible/seed-like" look the user actually wanted for it - "т.е.
|
||
зум завязан на меш? тогда отмена... петлю вора убираем, раз не работает." Its own Harmony
|
||
file (HarmonySrc/ThiefLoopPatch.cs) was deleted outright, not just disabled - nothing
|
||
else in the mod depended on it. If a remote-loot item is wanted again later, the
|
||
raycast/EntityLootContainer-draining approach documented in this session's history is
|
||
still valid, just needs a non-Zoom item shell (or a real weapon mesh accepted as the
|
||
visual, since that's what Zoom specifically requires).
|
||
|
||
"Пространственный браслет" (Spatial Bracelet) below is unaffected - it never used
|
||
Class="Zoom" at all (both its actions are Class="Eat"), so it never hit this problem. -->
|
||
|
||
<!-- Second bracelet, dictated 2026-08-30, implemented same day - no explicit name given, so
|
||
kept internally descriptive: "Пространственный браслет" (Spatial Bracelet).
|
||
|
||
- Power attack: opens a large, resizable personal storage window - "как у дрона, но
|
||
большой" (like a drone's, but bigger). See HarmonySrc/SpatialVaultPatch.cs for the real
|
||
mechanism (XUiC_BagStorageWindowGroup.Open - the exact same API EntityDrone itself uses
|
||
to show ITS OWN storage, decompiled directly to confirm, not guessed) - not a
|
||
declarative XML feature, Harmony required. Slot count is NOT fixed - user's own later
|
||
correction: "1*скилл_некроманта/10 округлённый до целого" - scales with the player's
|
||
live Necromancy skill level (same skill the Knife's damage already scales with,
|
||
craftingNecroNecromancy, capped at level 5000) divided by 10 and rounded, growing over
|
||
time exactly like the Knife's own damage does, same "payoff scales with progression"
|
||
family as every other kill-count-tied mechanic in this mod.
|
||
- Regular attack: DISABLED 2026-08-30 per direct request ("не срабатывает никак" ->
|
||
"пусть тогда обычная атака... не делает ничего") - originally meant to knock back and
|
||
slow whatever zombie the crosshair is aimed at (raycast + Entity.SetPosition shove +
|
||
buffInjurySlow, see HarmonySrc/SpatialVaultPatch.cs's own ShoveZombieAtCrosshair method,
|
||
kept in the file unused rather than deleted in case this gets revisited) - the click is
|
||
now just silently absorbed, no effect.
|
||
|
||
"Seed"-style grip: foodCropYuccaFruit's own HoldType="31" + parcelGenericPrefab.prefab
|
||
(per direct correction after an earlier rock-mesh placeholder didn't match what was
|
||
meant) - never touches Class="Zoom", so it never needed a real weapon mesh the way
|
||
Thief's Loop briefly did.
|
||
|
||
Not for sale (EconomicValue=0), same as every other necromancer item. TintColor green
|
||
(2026-08-30, direct request). Custom art delivered 2026-08-30
|
||
(exch/ProstranstvennoeHranilische.png, 160x160, copied to UIAtlases/ItemIconAtlas/) -
|
||
CustomIcon set below, no CustomIconTint (same "don't recolor finished art" rule as the
|
||
rest of the mod's hand-drawn icons). -->
|
||
<append xpath="/items">
|
||
<item name="braceletSpatialVault">
|
||
<!-- MOD SLOTS ADDED 2026-09-13 ("добавь хранилищу 4 слота под модификации. Сами
|
||
модификации реализуем потом"), УБАВЛЕНЫ ДО ОДНОГО 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:
|
||
|
||
noMods - blocks every vanilla mod. All 87 vanilla item_modifiers that
|
||
declare blocked_tags at all list noMods among them; the
|
||
remaining 24 cannot reach this item anyway (10 dyes and 7 drone
|
||
mods need a cosmetic slot or the drone tag, 2 are quest items,
|
||
1 needs perkArchery, 3 are CreativeMode Test/Dev). "noMods"
|
||
means nothing in code - it is purely a naming convention used
|
||
inside other items' blocked_tags.
|
||
necroBracelet - the positive half, reserved for the mods that come later. Every
|
||
future bracelet mod MUST declare
|
||
installable_tags="necroBracelet": a modifier with no
|
||
installable_tags at all fits ANYTHING (XUiM_AssembleItem short-
|
||
circuits on InstallableTags.IsEmpty), so forgetting it produces
|
||
the exact opposite of what is wanted.
|
||
|
||
Deliberately NOT adding canHaveCosmetic: that tag alone is what creates the paint
|
||
slot (ItemValue's constructor sizes CosmeticMods by it), and the knife had to have
|
||
it removed for precisely this reason. No tag, no slot, no dyes. -->
|
||
<property name="Tags" value="T0,weapon,attPerception,noMods,necroBracelet"/>
|
||
<!-- ItemTypeIcon="melee" REMOVED 2026-09-07 (user report: "поверх пиктограмм некоторых
|
||
рецептов стоят странные пиктограммы... то ли факел, то ли спичка"). This was the
|
||
small badge drawn in the TOP-LEFT corner over the item's own icon in the recipe
|
||
list - a different widget from the workstation indicator the user was expecting to
|
||
see. Both live in XUi_InGame/templates.xml's recipe_entry:
|
||
|
||
<sprite name="itemtypeicon" width="12" height="12"
|
||
sprite="ui_game_symbol_{itemtypeicon}" pos="2,-2" .../> <- this one
|
||
<sprite name="Unlocked" sprite="{unlockicon}" size="24,24"
|
||
pos="352,-10" visible="{isunlockable}" .../> <- workbench/lock
|
||
|
||
So the value is pasted straight into a sprite name: ItemTypeIcon="melee" asked for
|
||
"ui_game_symbol_melee", which really does exist (it is the icon vanilla gives
|
||
buffMeleePreferred - a club), and at 12x12 in the corner a club reads exactly like
|
||
a match or a torch. Not a missing-sprite bug, a wrong-sprite one.
|
||
|
||
Wrong on the merits too: this is a bracelet, not a melee weapon, and its regular
|
||
melee attack was deliberately disabled on 2026-08-30 (see SpatialVaultPatch.cs and
|
||
the BACKLOG entry) - so the badge advertised an attack the item no longer has.
|
||
Dropped entirely rather than swapped for another symbol: ItemClass.ItemTypeIcon
|
||
defaults to "" and the widget is gated on {hasitemtypeicon}, so with no property at
|
||
all the badge simply doesn't draw, leaving the row showing just the item icon plus
|
||
the real workbench indicator on the right - which is what was wanted. There is no
|
||
sensible "amulet/magic" symbol in the set vanilla actually uses for items anyway
|
||
(bundle, computer, forge, explosion, campfire, gunsmithing, book). -->
|
||
<property name="DescriptionKey" value="braceletSpatialVaultDesc"/>
|
||
<property name="CustomIcon" value="ProstranstvennoeHranilische"/>
|
||
<!-- МЕШ И ХВАТ, 2026-09-13. Просьба в два захода: сперва "пусть будет камень, а
|
||
хват давай сделаем как когда пытаешься ставить какой-нибудь блок", затем
|
||
уточнение - "браслет это браслет... в идеале меш камня вообще убрать". Было:
|
||
свёрток-«семечко» parcelGenericPrefab.prefab (коробочка, перевязанная бечёвкой -
|
||
для браслета нелепо) + HoldType="31", и то и другое унаследовано от Петли вора.
|
||
|
||
ИТОГ: в руке НЕТ НИЧЕГО, только кулак. Пустой префаб собирать не пришлось - в
|
||
движке есть готовое свойство, и вся связка целиком списана с ванильного
|
||
vehicleMinibikePlaceable (items.xml:13385), у которого стоят ровно те же две
|
||
строки подряд: HoldType="7" + HoldingItemHidden="true".
|
||
|
||
HoldType="7" - это и есть блочный хват ("кулак вниз, как будто держишь руль"),
|
||
не угаданный номер. Декомпилировано Mono.Cecil'ом из Assembly-CSharp 3.2.0
|
||
(сам Mono.Cecil.dll лежит в Mods/0_TFP_Harmony, отдельный декомпилятор не нужен):
|
||
|
||
ItemClassBlock..ctor -> HoldType = new DataItem<int>(7)
|
||
AnimationDelayData.AnimationDelay[7] =
|
||
new AnimationDelays(0, 0f, 0f, .31f, .31f, true) <- последний флаг TwoHanded
|
||
|
||
В blocks.xml свойства HoldType нет ни разу (0 вхождений), то есть КАЖДЫЙ блок в
|
||
игре держится именно семёркой из этого конструктора. Костет
|
||
(meleeWpnKnucklesT0LeatherKnuckles) - это HoldType="70", запасной вариант не
|
||
понадобился.
|
||
|
||
HoldingItemHidden="true" - штатное свойство ItemClass, а не трюк:
|
||
ItemClass..cctor заводит PropHoldingItemHidden = "HoldingItemHidden",
|
||
ItemClass.Init читает его через StringParsers.ParseBool, а
|
||
Inventory.setHoldingItemTransform в самом конце делает
|
||
holdingItemTransform.gameObject.SetActive(!HoldingItemHidden). Гасится ТОЛЬКО
|
||
модель в руке: иконка в инвентаре (своя рисованная ProstranstvennoeHranilische)
|
||
и мешок на земле не трогаются, действия предмета живут в ItemActionEat и от
|
||
этого GameObject не зависят.
|
||
|
||
Пустой меш поставить было НЕЛЬЗЯ, и это проверено, а не предположено:
|
||
ItemClass.CloneModel, если имя меша пустое и ассет не загрузился, подставляет
|
||
заглушку "@:Other/Items/Crafting/leather.fbx" - в руке оказался бы кусок кожи.
|
||
Единственный ванильный предмет вообще без Meshfile - meleeHandMaster (голые
|
||
руки), и он выкручивается через Canhold="false", что нам не подходит: браслет
|
||
надо держать, чтобы им пользоваться.
|
||
|
||
Meshfile оставлен камнем как безобидная затычка (в руке он скрыт, а для
|
||
MeshPurpose World/Local/Preview что-то иметь надо), DropMeshfile - ванильный
|
||
мешок sack_droppedPrefab, ровно тем же приёмом и по той же причине, что у
|
||
vehicleMinibikePlaceable: выброшенный предмет должно быть видно на земле, а
|
||
своей модели у него нет. HandMeshfile убран за ненадобностью.
|
||
|
||
Про HoldType и действия: единственное место, где ItemActionEat вообще читает
|
||
HoldType, - AnimationDelay[HoldType].RayCast (в PercentDone и IsActionRunning),
|
||
и он равен 0f и у старого 31, и у нового 7 (InitStatic заполняет все 100 слотов
|
||
нулями, ItemClassBlock переписывает слот 7, оставляя RayCast нулём).
|
||
ExecuteAction, за которую держится SpatialVaultPatch.cs, HoldType не читает
|
||
вовсе - проверено сканом IL по всей сборке. -->
|
||
<property name="Material" value="Morganic"/>
|
||
<property name="Meshfile" value="@:Other/Items/Crafting/rock_smallPrefab.prefab"/>
|
||
<property name="DropMeshfile" value="@:Other/Items/Misc/sack_droppedPrefab.prefab"/>
|
||
<property name="TintColor" value="30, 200, 60"/>
|
||
<property name="HoldType" value="7"/>
|
||
<property name="HoldingItemHidden" value="true"/>
|
||
<property name="Weight" value="0"/>
|
||
<property name="Stacknumber" value="1"/>
|
||
<property name="EconomicValue" value="0"/>
|
||
<property name="Group" value="Ammo/Weapons,Ammo"/>
|
||
<property class="Action0">
|
||
<property name="Class" value="Eat"/>
|
||
<property name="Delay" value="0.3"/>
|
||
</property>
|
||
<property class="Action1">
|
||
<property name="Class" value="Eat"/>
|
||
<property name="Delay" value="0.3"/>
|
||
</property>
|
||
|
||
<!-- FOUR MOD SLOTS. The count is a passive_effect, not an item property - same shape
|
||
the knife uses, and flat rather than a per-quality list because quality means
|
||
nothing on this item.
|
||
|
||
NOTE THE MISSING ATTRIBUTE: this effect_group has NO tiered="false", and that is
|
||
the entire point. ItemClass.HasQuality is literally Effects.IsOwnerTiered(), and
|
||
ItemValue.FireEvent bails out with `if (!HasQuality) return;` BEFORE it walks
|
||
Modifications[] - so on an untiered item the slots still appear and still accept
|
||
mods, and not one triggered_effect inside them ever fires. That silent failure
|
||
cost a whole debugging session on the knife on 2026-09-07; it is not repeated
|
||
here. The slots themselves would work either way (Modifications is allocated
|
||
unconditionally, earlier), which is exactly what makes the failure so quiet.
|
||
|
||
Quality is not SHOWN, though: ShowQuality is a separate property that defaults to
|
||
false (vanilla sets it to true explicitly on the ~80 items that want a quality
|
||
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: 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="1"/>
|
||
</effect_group>
|
||
</item>
|
||
</append>
|
||
</config>
|