Книга некроманта 1.0 — первая публичная версия
Мод для 7 Days to Die 3.2: навык «Некромантия», растущий от счётчика убитых зомби, тёмное оружие с шестью собственными модами, призывная нежить, пирамида духов и сюжетный финал через Чёрный портал. Локализация на 13 языках. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MaNro5hAGTzcQ7rJNN2tCX
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user