Шесть указаний одного захода, которые сложились в одну механику: поглощение блока больше не бесплатно. Браслет требует модификацию в слоте, кровь ею стала, и она на это тратится. СТАК ПО ОДНОЙ БАНКЕ. Наследуемый medicalBloodBag даёт Stacknumber 15, и это надо перебивать явно - Extends копирует свойство целиком, а не "если не задано иначе". Дорого и так задумано: Чёрный портал просит десять банок, то есть десять ячеек. ПРОЧНОСТЬ 1000. Свойства с именем вроде Durability в игре нет; ручек две, и обе обязательны. Число - пассивный эффект DegradationMax (ItemValue.MaxUseTimesBase -> EffectManager.GetValue), полоска - отдельное свойство ShowQuality (XUiC_ItemStack.ShowDurability -> ItemClass.ShowQualityBar). Без первой прочность равна нулю, а полоска при MaxUseTimes == 0 рисуется ПОЛНОЙ, то есть забытый эффект выглядит как "всё работает". tiered="false" обязателен: ItemClass.HasQuality читается как Effects.IsOwnerTiered(), и тированная группа превратила бы банку в предмет с качеством, с тирами и рамкой. КРОВЬ ПЕРЕЕХАЛА В item_modifiers.xml. Это не настройка, а смена класса предмета: XUiC_ItemPartStack.CanSwap открывается строкой `if (!(stack.itemValue.ItemClass is ItemClassModifier ...)) return false;` - слот модификации не смотрит ни на теги, ни на свойства, пока предмет не ItemClassModifier, а этот класс создаётся только из <item_modifier>. Свойства вида CanBeInstalled не существует; остаться ресурсом в items.xml и вставляться в браслет физически нельзя. На старом месте оставлен комментарий-указатель. Что при этом проверено, а не понадеялось: рецепты резолвятся (ItemClassModifier наследует ItemClass, имена лежат в общем ItemClass.nameToItem, Recipe ищет через GetItemClass по тому же словарю); сейв цел (ItemClass.assignIdsFromMapping берёт айди из сохранённого name->id мэппинга, перестановка в конфигах предмет не подменит); Harmony-патч и ключи локализации ходят по имени, имя не менялось. ЛОВУШКА ПЕРЕЕЗДА: у модификации effect_group применяется к предмету, В КОТОРЫЙ её вставили - прочность 1000 начала бы выдаваться БРАСЛЕТУ. Пассивка гейтована tags="necroBloodFlask", тег добавлен в Tags флакона: MaxUseTimesBase зовёт GetValue с ItemTags того предмета, для которого считает. СЛОТОВ У БРАСЛЕТА 1, было 4. Слот из набора улучшений стал выбором. ПУСТОЙ СЛОТ ОТКАЗЫВАЕТ. Проверка стоит первой строкой Begin, впереди всех остальных отказов: прочие про ЦЕЛЬ (нет блока, не тот блок, хранилище полно), эта про ИНСТРУМЕНТ, и сказать "здесь нет блока", когда пуст браслет, значит отправить игрока искать не там. Тест - ItemValue.HasMods(), игровой собственный: обходит только Modifications, пропуская null и IsEmpty, и не считает CosmeticMods, иначе краска читалась бы как "браслет заряжен". Звук отказа достался бесплатно - Deny() в этом файле уже играет ванильный ui_denied. РАСХОД. Цена пула считается ОДИН раз, при старте, и едет в PickupJob.ChannelSeconds вместе с самим браслетом. Не потому, что так короче: ChannelSecondsFor меряет луч игрока, а за десять секунд игрок успевает отвернуться - второй вызов насчитал бы цену за другой блок, а не за тот, который забрали. Браслет хранится экземпляром по той же причине: моды живут на ItemValue, а колесо прокручивается. Списывается в SpendBlood, ПОСЛЕ SetBlockRPC и после того, как предмет лёг в хранилище: все отказы выходят раньше через return, так что кровь за отменённое поглощение невозможна по построению. Имя предмета берётся из NecromancerBloodPatch.BloodItemName, а не вторым литералом, чтобы не разъехались. Мод, который не кровь, не платит ничего и поглощению не мешает - слот задуман под другие вещи. КОНЧИЛАСЬ - РАЗБИВАЕТСЯ. Правило именно "прочность 0 или меньше", а не "не хватило на пул", и разница не косметическая: по второй формулировке флакон, которому хватило впритык, остался бы в слоте с нулём, HasMods() видел бы "что-то вставлено", и браслет работал бы бесплатно до конца света. Поэтому зажим по MaxUseTimes убран, а слот обнуляется через ItemValue.None - это type 0, ровно то, что проверяет IsEmpty(). Последнее поглощение проходит всегда, флакон его просто не переживает. Звук - ванильный itembreak, тот же, что играет ItemAction.HandleItemBreak. Защита: если MaxUseTimes окажется 0 (снесли passive_effect или тег), флакон НЕ удаляется, а в лог идёт предупреждение с указанием, где чинить. Без этой ветки ошибка в XML съедала бы игроку предмет на первом же поглощении, и выглядело бы это багом механики. Локализация: новый ключ braceletSpatialVaultNoMod в 13 языках, плюс описания флакона и браслета - механика стала условной и платной, и оба текста без этого стали бы враньём. Известное и намеренное: кровавого камня, который обещает сообщение о пустом слоте, ещё нет - он запланирован, разбор в BACKLOG.md. В игре ничего из этого не проверено. --- Necromancer's Blood is the Spatial Bracelet's fuel Six instructions from one session that add up to one mechanic: pulling a block into the vault is no longer free. The bracelet needs a mod in its slot, the blood became that mod, and it is spent doing the work. ONE JAR PER STACK. The inherited medicalBloodBag sets Stacknumber 15 and it has to be overridden explicitly - Extends copies a property wholesale, not "unless set". Expensive on purpose: the Black Portal asks for ten jars, so ten slots. DURABILITY 1000. There is no property called anything like Durability; there are two knobs and both are required. The number is a DegradationMax passive effect (ItemValue.MaxUseTimesBase -> EffectManager.GetValue); the bar is a separate ShowQuality property (XUiC_ItemStack.ShowDurability -> ItemClass.ShowQualityBar). Without the first, durability is zero - and the bar at MaxUseTimes == 0 draws FULL, so a forgotten effect looks exactly like success. tiered="false" is mandatory: ItemClass.HasQuality is Effects.IsOwnerTiered(), and a tiered group would have turned the jar into a quality item with tiers and a frame. THE BLOOD MOVED TO item_modifiers.xml. Not a setting but a change of item class: XUiC_ItemPartStack.CanSwap opens with `if (!(stack.itemValue.ItemClass is ItemClassModifier ...)) return false;` - a mod slot looks at neither tags nor properties until the item is an ItemClassModifier, and that class is only created from <item_modifier>. No CanBeInstalled property exists; staying a resource in items.xml and going into the bracelet is impossible. A pointer comment was left where it used to live. Checked rather than hoped: recipes still resolve (ItemClassModifier extends ItemClass, names live in the shared ItemClass.nameToItem, Recipe looks them up through GetItemClass); saves are safe (ItemClass.assignIdsFromMapping takes ids from the stored name->id mapping, so shuffling configs cannot swap the item); the Harmony patch and the localization keys go by name, and the name did not change. THE TRAP IN MOVING IT: a modifier's effect_group applies to the item it is INSTALLED IN - the 1000 durability would have been granted to the BRACELET. The passive is gated with tags="necroBloodFlask" and the tag added to the flask's own Tags: MaxUseTimesBase calls GetValue with the ItemTags of whatever it is computing for. THE BRACELET HAS 1 MOD SLOT, down from 4. The slot stopped being a set of upgrades and became a choice. AN EMPTY SLOT REFUSES. The check is the first line of Begin, ahead of every other refusal: the others are about the TARGET (no block, wrong block, vault full), this one is about the TOOL, and saying "no block there" when the real problem is an empty bracelet sends the player looking in the wrong place. The test is ItemValue.HasMods(), the game's own: it walks Modifications only, skipping nulls and IsEmpty, and does not count CosmeticMods - a dye would otherwise have read as "loaded". The refusal sound came free: Deny() in this file already plays vanilla's ui_denied. THE COST. The price of a pull is computed ONCE, at the start, and carried in PickupJob.ChannelSeconds along with the bracelet itself. Not for brevity: ChannelSecondsFor measures the player's ray, and ten seconds is long enough to turn away - a second call would charge for a different block than the one taken. The bracelet is kept as an instance for the same reason: mods live on the ItemValue and the hotbar scrolls. It is charged in SpendBlood, AFTER SetBlockRPC and after the item is in the vault: every refusal returns earlier, so blood charged for a cancelled pull is impossible by construction. The item name comes from NecromancerBloodPatch.BloodItemName rather than a second literal, so the two cannot drift. A mod that is not blood pays nothing and does not block the pull - the slot is meant for other things. RUNS OUT, SHATTERS. The rule is "durability 0 or less", not "could not cover the pull", and the difference is not cosmetic: under the second wording a flask with exactly enough left would sit in the slot at zero, HasMods() would see "something installed", and the bracelet would work for free forever. So the MaxUseTimes clamp is gone and the slot is cleared with ItemValue.None - type 0, exactly what IsEmpty() tests. The last pull always completes; the flask simply does not survive it. The sound is vanilla's itembreak, the same cue ItemAction.HandleItemBreak plays. A guard: if MaxUseTimes comes out 0 (the passive effect or the tag removed), the flask is NOT deleted and a warning naming the fix goes to the log. Without that branch a config error would eat the player's item on the first pull and look like a bug in the mechanic. Localization: a new braceletSpatialVaultNoMod key in 13 languages, plus the flask and bracelet descriptions - the mechanic became conditional and paid, and both texts would have been lies without it. Known and deliberate: the Blood Stone the empty-slot message promises does not exist yet - it is planned, written up in BACKLOG.md. None of this is tested in game. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MnwP2Dt1vk8bUPJ452EoVL
1520 lines
104 KiB
XML
1520 lines
104 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) ПЕРЕЕХАЛА В Config/item_modifiers.xml 2026-09-15.
|
||
|
||
Причина - указание «пусть флакон крови можно будет вставлять как модификацию для
|
||
пространственного хранилища», и это оказалось не настройкой, а сменой класса предмета.
|
||
XUiC_ItemPartStack.CanSwap открывается строкой
|
||
|
||
if (!(stack.itemValue.ItemClass is ItemClassModifier itemClassModifier)) return false;
|
||
|
||
- слот модификации не смотрит ни на теги, ни на свойства, пока предмет не ItemClassModifier,
|
||
а этот класс создаётся ТОЛЬКО из <item_modifier> в item_modifiers.xml. Никакого свойства
|
||
вида "CanBeInstalled" не существует; остаться в items.xml и стать модификацией нельзя.
|
||
|
||
Предмет при этом остался обычным во всём остальном: ItemClassModifier наследует ItemClass и
|
||
регистрируется в том же ItemClass.nameToItem, поэтому рецепты, крафт и Harmony-патч
|
||
(NecromancerBloodPatch.cs) находят его по имени как раньше. Разбор - в BACKLOG.md.
|
||
|
||
Само определение, со всеми старыми комментариями про банку, текстуры и прочность - там. -->
|
||
|
||
<!-- "Петля вора" (Thief's Loop) - REMOVED 2026-08-30. Existed briefly (dictated/implemented
|
||
2026-08-29, reworked several times through 2026-08-30 chasing load errors), removed per
|
||
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>
|