Мод для 7 Days to Die 3.2: навык «Некромантия», растущий от счётчика убитых зомби, тёмное оружие с шестью собственными модами, призывная нежить, пирамида духов и сюжетный финал через Чёрный портал. Локализация на 13 языках. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MaNro5hAGTzcQ7rJNN2tCX
189 lines
15 KiB
XML
189 lines
15 KiB
XML
<config>
|
||
<!-- Step 1: lifetime zombie kill counter display.
|
||
buffStatusCheck01 is the vanilla hidden buff every player always has,
|
||
used here purely as a hook to (re)apply our display buff on game entry/respawn. -->
|
||
<append xpath="buffs/buff[@name='buffStatusCheck01']">
|
||
<effect_group name="necro zombie kill tracker">
|
||
<triggered_effect trigger="onSelfEnteredGame" action="AddBuff" buff="buffNecroZombieKillTrackerDisplay"/>
|
||
<triggered_effect trigger="onSelfRespawn" action="AddBuff" buff="buffNecroZombieKillTrackerDisplay"/>
|
||
</effect_group>
|
||
</append>
|
||
|
||
<append xpath="/buffs">
|
||
<!-- Never expires (duration 0): a persistent lifetime total, not a per-day/session counter. -->
|
||
<buff name="buffNecroZombieKillTrackerDisplay" icon="ui_game_symbol_zombie" icon_color="150,0,0" name_key="buffNecroZombieKillTrackerDisplayName" description_key="buffNecroZombieKillTrackerDisplayDesc">
|
||
<stack_type value="ignore"/>
|
||
<duration value="0"/>
|
||
<update_rate value=".1"/>
|
||
<display_value value="necroZombieKillsCVar"/>
|
||
|
||
<!-- Нож некроманта (BACKLOG.md item 5, user request 2026-08-28): "урон умножается на
|
||
скилл некроманта ... и делится на 10". Computed here (this buff already ticks
|
||
10x/sec on every player via update_rate) rather than on the knife item itself,
|
||
since a held item has no update tick of its own - reading a live CVar from a
|
||
passive_effect (confirmed working: vanilla's own $PlayerLevelBonus, computed in
|
||
buffs.xml and read elsewhere via passive_effect value="@$PlayerLevelBonus") needs
|
||
something that keeps recomputing it. $necroKnifeDamageCVar = necroZombieKillsCVar
|
||
/ 10, read by necroWpnBladeNecroKnife's own EntityDamage passive_effect in
|
||
items.xml. $necroKnifeHealCVar = half of that, read by the same item's lifesteal
|
||
trigger. Both "@cvar" (write) and "@$cvar" (write) forms exist in vanilla data for
|
||
plain vs. "$"-prefixed cvars - $-prefix here just follows the naming convention
|
||
vanilla uses for these player-effect scratch values, not a different mechanism.
|
||
|
||
BUG FIXED 2026-08-28 (knife dealt no damage, no lifesteal, victim debuff never
|
||
applied): these four triggered_effect elements were direct children of <buff>,
|
||
with no <effect_group> wrapper - unlike buffNecroDeviatorCharm below or vanilla's
|
||
own buffStatusCheck01 (Data/Config/buffs.xml line ~334), which both wrap every
|
||
triggered_effect in one. triggered_effect isn't valid straight under <buff> (only
|
||
things like display_value are) - the loader didn't throw on this (no "buffs.xml
|
||
failed to load" the way loot.xml did for its own ordering bug), it just silently
|
||
never ran them, so $necroKnifeDamageCVar/$necroKnifeHealCVar never got computed
|
||
and the knife's damage passive_effect read an undefined (0) value the whole time. -->
|
||
<effect_group name="necro knife damage tracking">
|
||
<triggered_effect trigger="onSelfBuffUpdate" action="ModifyCVar" cvar="$necroKnifeDamageCVar" operation="set" value="@necroZombieKillsCVar"/>
|
||
<triggered_effect trigger="onSelfBuffUpdate" action="ModifyCVar" cvar="$necroKnifeDamageCVar" operation="divide" value="10"/>
|
||
<triggered_effect trigger="onSelfBuffUpdate" action="ModifyCVar" cvar="$necroKnifeHealCVar" operation="set" value="@$necroKnifeDamageCVar"/>
|
||
<triggered_effect trigger="onSelfBuffUpdate" action="ModifyCVar" cvar="$necroKnifeHealCVar" operation="divide" value="2"/>
|
||
</effect_group>
|
||
</buff>
|
||
|
||
<!-- "Жертва" (Victim) marker - applied by the Necromancer's Knife on hit (see
|
||
necroWpnBladeNecroKnife in items.xml). HarmonySrc/VictimPatch.cs reacts to its
|
||
presence at the moment the zombie actually dies (patches Entity.DropBagServer) to
|
||
force-spawn the guaranteed green "Жертва" bag instead of the normal loot roll -
|
||
nothing in XML alone can make Entity.DropBagServer's LootDropEntityClass choice
|
||
conditional on a live buff, confirmed by decompiling it (that property is a static
|
||
per-species entity_class value, not CVar-driven like EntityDamage above).
|
||
|
||
BUG FIXED 2026-08-28 (buff kept vanishing before death-loot checked it, despite being
|
||
permanent - duration="0" confirmed via decompiling BuffClass to genuinely mean "never
|
||
expires", not the bug): missing remove_on_death="false". EntityAlive.ClientKill()
|
||
calls Buffs.OnDeath() BEFORE OnEntityDeath()/dropItemOnDeath() ever run (confirmed by
|
||
decompiling both) - EntityBuffs.OnDeath() strips any buff whose BuffClass.RemoveOnDeath
|
||
is true, which is the default when remove_on_death isn't specified at all. So the buff
|
||
really was being added, really was permanent under normal circumstances, and really
|
||
was still getting wiped out from under VictimPatch.cs's HasBuff check by the death
|
||
sequence itself, one step before that check ever ran. remove_on_death="false" is a
|
||
real, documented buff attribute - vanilla's own buffStatusCheck01 uses it for exactly
|
||
this "must survive death" reason.
|
||
|
||
Visual (user request 2026-08-28, "подсвети бафнутого зомби... фиолетовым"): same
|
||
RadiatedParticlesOnMesh glow as buffNecroDeviatorCharm below, reused rather than a
|
||
different particle - HarmonySrc/ParticlePatch.cs recolors it purple specifically for
|
||
THIS buff (checks which buff is present before deciding tint), since XML's
|
||
AttachParticleEffectToEntity has no color attribute at all (confirmed by decompiling
|
||
it - same reason the charm's own glow can't be XML-recolored either). Removed on
|
||
death too (onSelfDied) even though the buff FLAG itself survives death on purpose
|
||
(remove_on_death="false" above) - the glow is cosmetic and shouldn't linger on a
|
||
corpse; VictimPatch.cs's own HasBuff check doesn't care whether the particle is gone. -->
|
||
<buff name="buffNecroVictim" hidden="true" remove_on_death="false">
|
||
<stack_type value="ignore"/>
|
||
<duration value="0"/>
|
||
<effect_group>
|
||
<triggered_effect trigger="onSelfBuffStart" action="AttachParticleEffectToEntity" particle="RadiatedParticlesOnMesh" local_offset="0,0.75,0"/>
|
||
<triggered_effect trigger="onSelfBuffRemove" action="RemoveParticleEffectFromEntity" particle="RadiatedParticlesOnMesh"/>
|
||
<triggered_effect trigger="onSelfDied" action="RemoveParticleEffectFromEntity" particle="RadiatedParticlesOnMesh"/>
|
||
</effect_group>
|
||
</buff>
|
||
|
||
<!-- "Девиатор" charm marker. Purely a flag for HarmonySrc/CharmPatch.cs to react to -
|
||
the actual "fight for the player now" AI behavior happens in that Harmony patch, not
|
||
here. hidden/no duration: permanent for the charmed zombie's remaining lifetime.
|
||
The visible cue is the particle below - reuses the same mesh-hugging glow vanilla
|
||
radiated zombies wear (ParticleEffects/RadiatedParticlesOnMesh.prefab), the closest
|
||
thing to an actual recolor achievable from XML alone (no way to retint a live
|
||
entity's skin material via buff - AttachParticleEffectToEntity is as close as it gets).
|
||
It reads green/energy rather than "necromantic purple", but it's a real full-body
|
||
glow, not just a floating cloud; RemoveParticleEffectFromEntity on death/removal so it
|
||
doesn't linger on the corpse. -->
|
||
<buff name="buffNecroDeviatorCharm" hidden="true">
|
||
<stack_type value="ignore"/>
|
||
<duration value="0"/>
|
||
<effect_group>
|
||
<triggered_effect trigger="onSelfBuffStart" action="AttachParticleEffectToEntity" particle="RadiatedParticlesOnMesh" local_offset="0,0.75,0"/>
|
||
<triggered_effect trigger="onSelfBuffRemove" action="RemoveParticleEffectFromEntity" particle="RadiatedParticlesOnMesh"/>
|
||
<triggered_effect trigger="onSelfDied" action="RemoveParticleEffectFromEntity" particle="RadiatedParticlesOnMesh"/>
|
||
</effect_group>
|
||
</buff>
|
||
|
||
<!-- "Портальный вихрь" - BACKLOG.md item 6, user request 2026-08-29 ("пусть вокруг
|
||
персонажа летают частицы, лучше чёрные, как дым"). Purely a marker/particle-carrier
|
||
buff, same shape as buffNecroVictim/buffNecroDeviatorCharm above - NOT added/removed
|
||
by any trigger here at all (no onSelf* triggers reference it), because there's
|
||
nothing in the buff-trigger vocabulary that means "for the duration of an open
|
||
XUiC_Timer window" - HarmonySrc/PortalStonePatch.cs adds it directly
|
||
(player.Buffs.AddBuff) the instant the 10s channel opens and removes it
|
||
(player.Buffs.RemoveBuff) the instant the channel ends, however it ends (completed,
|
||
cancelled by damage, cancelled by power attack) - see that file.
|
||
|
||
Reuses the same RadiatedParticlesOnMesh glow the two zombie-facing buffs above
|
||
already use (same reasoning: no XML color attribute exists on
|
||
AttachParticleEffectToEntity, HarmonySrc/ParticlePatch.cs has to recolor it in code
|
||
regardless of which buff triggers it) - generalized ParticlePatch.cs to handle
|
||
EntityAlive broadly instead of EntityZombie-only, since this buff's target is the
|
||
PLAYER, not a zombie. Tinted near-black/smoke grey, per the user's own request. -->
|
||
<buff name="buffNecroPortalChannel" hidden="true">
|
||
<stack_type value="ignore"/>
|
||
<duration value="0"/>
|
||
<effect_group>
|
||
<triggered_effect trigger="onSelfBuffStart" action="AttachParticleEffectToEntity" particle="RadiatedParticlesOnMesh" local_offset="0,0.9,0"/>
|
||
<triggered_effect trigger="onSelfBuffRemove" action="RemoveParticleEffectFromEntity" particle="RadiatedParticlesOnMesh"/>
|
||
<triggered_effect trigger="onSelfDied" action="RemoveParticleEffectFromEntity" particle="RadiatedParticlesOnMesh"/>
|
||
</effect_group>
|
||
</buff>
|
||
<!-- "Тёмное чутьё" - постоянный радар зомби, пока Нож некроманта в руке и в нём стоит
|
||
модификатор necroModKnifeDarkSense. Продиктовано 2026-09-07: "В квестах на зачистку на
|
||
радаре показывает где находятся зомби. Нужен мод который включит подобный радар на
|
||
постоянку, пока мод в ноже, а нож в руке."
|
||
|
||
Да, реально, и целиком на XML - Harmony не нужен. Ключ в том, что метки на компасе и
|
||
карте ставятся через NavObject, а для них есть готовое XML-действие
|
||
MinEventActionSetNavObject (ваниль зовёт его в buffs.xml ~15281 для твич-эффектов):
|
||
entityAlive.AddNavObject(navObjectName, overrideSprite, overrideText)
|
||
и оно наследует MinEventActionTargetedBase, то есть умеет target="selfAOE" с range и
|
||
target_tags - ровно то, что нужно для "все зомби вокруг игрока".
|
||
|
||
ПОЧЕМУ nav_object="zombie", А НЕ "clear_sleeper". Квест на зачистку использует
|
||
clear_sleeper, но у того класса стоит <property name="requirement_type"
|
||
value="QuestBounds"/> - он показывается только внутри границ активного квеста, то есть
|
||
вне квеста не работал бы вообще. Класс "zombie" (nav_objects.xml ~431) - без такого
|
||
требования: красная пульсирующая иконка ui_game_symbol_zombie и на карте, и на компасе
|
||
(max_distance компаса 1024).
|
||
|
||
ПОЧЕМУ БАФФ, А НЕ ПРЯМО В МОДЕ. У модификатора нет своего тика. onSelfEquipUpdate
|
||
существует (Inventory.OnUpdate шлёт его на держимый предмет), но он идёт каждый
|
||
апдейт, а тут на каждом тике делается пространственная выборка сущностей - слишком
|
||
часто. Бафф даёт честный троттлинг через update_rate, поэтому мод только вешает и
|
||
снимает этот бафф по onSelfEquipStart/onSelfEquipStop (ванильный паттерн, так сделаны
|
||
эффекты кирок в items.xml ~158/175).
|
||
|
||
ПОЧЕМУ КАЖДЫЙ ТИК СНАЧАЛА СНИМАЕТ, ПОТОМ ВЕШАЕТ. Метка живёт на самой сущности
|
||
(RegisterNavObject(name, this) - она ездит за зомби), и сама собой не пропадает, когда
|
||
зомби отошёл. NavObjectManager.Update чистит только те, у которых TrackedEntity стал
|
||
null, то есть уже уничтоженный объект - мёртвый, но ещё не исчезнувший труп метку
|
||
сохраняет. Поэтому радиус снятия (60) заведомо больше радиуса добавления
|
||
(30): всё, что вышло из зоны показа, но ещё рядом, чистится само.
|
||
AddNavObjectClass дедуплицирует (NavObject.cs ~346: "if (!NavObjectClassList.Contains)"),
|
||
так что повторное добавление одному и тому же зомби безвредно.
|
||
|
||
ИЗВЕСТНЫЙ РИСК, ПРОВЕРИТЬ В ИГРЕ: снятие-и-добавление в одном тике для зомби, который
|
||
остаётся в зоне, технически пересоздаёт NavObject (RemoveNavObjectClass опустошает
|
||
список -> UnRegisterNavObject -> следующий Add регистрирует заново). Итоговое
|
||
состояние кадра верное, но пульсация иконки может подёргиваться раз в секунду. Если
|
||
будет заметно - убрать строку снятия из onSelfBuffUpdate и оставить чистку только на
|
||
onSelfBuffRemove, ценой залипших меток на ушедших зомби. -->
|
||
<buff name="buffNecroDarkSense" name_key="buffNecroDarkSenseName" description_key="buffNecroDarkSenseDesc" icon="ui_game_symbol_zombie" icon_color="150,0,255">
|
||
<stack_type value="replace"/>
|
||
<duration value="0"/>
|
||
<update_rate value="1"/>
|
||
|
||
<effect_group name="necro dark sense radar">
|
||
<triggered_effect trigger="onSelfBuffUpdate" action="SetNavObject" target="selfAOE" range="60" target_tags="zombie" nav_object="zombie" add="false"/>
|
||
<triggered_effect trigger="onSelfBuffUpdate" action="SetNavObject" target="selfAOE" range="30" target_tags="zombie" nav_object="zombie" add="true"/>
|
||
<triggered_effect trigger="onSelfBuffRemove" action="SetNavObject" target="selfAOE" range="60" target_tags="zombie" nav_object="zombie" add="false"/>
|
||
</effect_group>
|
||
</buff>
|
||
|
||
</append>
|
||
</config>
|