Книга некроманта 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:
File diff suppressed because one or more lines are too long
@@ -0,0 +1,119 @@
|
||||
<windows>
|
||||
<!-- Black portal video (BACKLOG.md, "чёрный портал" dialog/video feature, 2026-08-30 follow-up
|
||||
bugfix). Root cause of "пауза встала, видео не вижу": XUiC_VideoPlayer.PlayVideo opens a
|
||||
window/group named "videoPlayer" - real and fully functional (it's exactly what plays the
|
||||
TFP studio intro and the main-menu background loop), but that window is declared ONLY in
|
||||
Data/Config/XUi_Menu/windows.xml (the MAIN MENU's own UI layout) - it does not exist at
|
||||
all in Data/Config/XUi_InGame/windows.xml (confirmed by grepping both files directly), so
|
||||
GUIWindowManager.GetWindow/Open logged "Window \"videoPlayer\" unknown!" the moment the
|
||||
in-game code tried to open it. This appends the EXACT SAME window block vanilla's own menu
|
||||
already uses (copied verbatim from XUi_Menu/windows.xml, not reinvented) into the in-game
|
||||
UI instead, so the exact same controller="VideoPlayer" (XUiC_VideoPlayer, already compiled
|
||||
into the base game, no Harmony needed) and the <video> element (a generic engine-level UI
|
||||
primitive, not menu-specific) can be found by the in-game windowManager too. Needs a
|
||||
matching window_group entry - see Config/XUi_InGame/xui.xml.
|
||||
|
||||
THIS ONE WINDOW IS SHARED - both the Black Portal video AND Duke's note flashback
|
||||
(NoteFlashbackPatch.cs) call the same XUiC_VideoPlayer.PlayVideo, which always opens this
|
||||
same "videoPlayer" window/group by id - one fix here covers both.
|
||||
|
||||
"видео полупрозрачное" fix (user report 2026-08-30, after first successful in-game test):
|
||||
added globalopacitymod="0" to videoBackground/videoTexture/skipPrompt below. Root cause,
|
||||
decompiled, not guessed: every XUiV_ImageBased-derived view (sprite AND video both are -
|
||||
XUiV_Sprite/XUiV_TextureBased both extend it) has a globalOpacityModifier defaulting to 1,
|
||||
meaning by default its final alpha gets multiplied by xui.BackgroundGlobalOpacity (unless
|
||||
foregroundlayer="true", which uses a separate ForegroundGlobalOpacity instead) - this is a
|
||||
REAL, already-live game option, decompiled straight from EnumGamePrefs.Options:
|
||||
OptionsBackgroundGlobalOpacity defaults to 0.95, not 1.0, and is user-adjustable (further
|
||||
down is common - players lower it to see more of the game world through menu backgrounds).
|
||||
This window's copy (like vanilla's own menu copy) never set globalopacitymod, so both the
|
||||
"opaque" black background AND the video picture itself were always getting dimmed by
|
||||
whatever fraction of full opacity the user's own Background Opacity option is set to -
|
||||
in the menu this is invisible (nothing behind the menu to bleed through), but in-game the
|
||||
actual 3D world sits right behind this window and shows through the gap. The vanilla menu
|
||||
copy has this exact same latent issue, just never exposed because it's never used with a
|
||||
3D scene behind it - not something to also go "fix" in Data/Config, just worth knowing this
|
||||
mod's copy needed the explicit opt-out that context actually requires. -->
|
||||
<append xpath="/windows">
|
||||
<window name="videoPlayer" controller="VideoPlayer" pos="0,0" depth="900" cursor_area="true">
|
||||
<sprite name="videoBackground" depth="0" sprite="menu_empty" type="sliced" color="[black]" anchor_left="#cam,0,-10" anchor_right="#cam,1,10" anchor_bottom="#cam,0,-10" anchor_top="#cam,1,10" style="press" gamepad_selectable="false" globalopacitymod="0" />
|
||||
<video name="videoTexture" depth="1" anchor_left="#cam,0,0" anchor_right="#cam,1,0" anchor_bottom="#cam,0,0" anchor_top="#cam,1,0" globalopacitymod="0"/>
|
||||
|
||||
<sprite name="skipPrompt" depth="5" sprite="ui_game_panel_header" color="[black]" anchor_left="#cam,1,-200" anchor_right="#cam,1,0" anchor_bottom="#cam,0,-0" anchor_top="#cam,0,60" globalopacitymod="0">
|
||||
<label name="lblSkip" depth="6" text_key="Skip" justify="left" pivot="left" font_size="32" width="180" height="100" pos="20,-30" parse_actions="true" actions_default_format="( ### )"/>
|
||||
</sprite>
|
||||
</window>
|
||||
</append>
|
||||
|
||||
<!-- ФИНАЛЬНЫЕ СЛАЙДЫ ЧЁРНОГО ПОРТАЛА, 2026-09-09 (BACKLOG.md "концовка серией диалоговых окон
|
||||
вместо видео"). Шесть полноэкранных картинок, по одному окну на слайд; текст и кнопки
|
||||
Назад/Дальше/выбор рисует поверх них ванильный messageBox - см. HarmonySrc/FinalSlides.cs,
|
||||
там же вся логика листания.
|
||||
|
||||
ПОЧЕМУ ШЕСТЬ ОТДЕЛЬНЫХ ОКОН, А НЕ ОДНО С ПЕРЕКЛЮЧЕНИЕМ СПРАЙТА. Смена спрайта на лету
|
||||
требует либо привязки ({binding}), либо своего XUiController - а в разведке (BACKLOG.md)
|
||||
как раз осталось непроверенным, найдёт ли движок контроллер, объявленный в сборке мода.
|
||||
Шесть окон с ЖЁСТКО прописанным sprite= обходят вопрос целиком: ни привязок, ни
|
||||
контроллера, окно вообще без атрибута controller= (так объявлены и ванильные
|
||||
HUDLeftStatBars/windowFuel, проверено в Data/Config/XUi_InGame/windows.xml). Листание -
|
||||
это просто Close старого окна + Open нового из C#.
|
||||
|
||||
depth="900" - ниже messageBox (depth="1001" в шаблоне <messagebox>,
|
||||
Data/Config/XUi_Common/templates.xml:143), так что текстовая коробка 1200x400 ложится
|
||||
поверх картинки, а картинка видна вокруг неё. Открывать эти окна НЕ модально: в
|
||||
GUIWindowManager.openInternal модальное открытие зовёт CloseAllOpenModalWindows(), то есть
|
||||
модальный messageBox закрыл бы модальный слайд - а немодальный переживает это спокойно.
|
||||
|
||||
globalopacitymod="0" - та же правка, что у videoPlayer выше: без неё альфа умножается на
|
||||
пользовательскую настройку Background Opacity (по умолчанию 0.95), и сквозь "финальную"
|
||||
картинку просвечивал бы игровой мир.
|
||||
|
||||
ПРО ПРОПОРЦИИ: исходники 1536x1024 (3:2), а окно растягивается на весь экран (16:9). Чтобы
|
||||
не плющить лица на крупных планах, картинки залиты чёрными полями по бокам до 1824x1024
|
||||
(16:9) ЗАРАНЕЕ, при копировании в UIAtlases/NecroFinal - у XUiV_Sprite нет атрибута
|
||||
сохранения пропорций (декомпилировано: он парсит только atlas/sprite/color/fill*/gradient*),
|
||||
так что letterbox приходится запекать в сам PNG. Второй чёрный спрайт под картинкой -
|
||||
страховка на неродных соотношениях сторон (ультраширокие мониторы).
|
||||
|
||||
Атлас NecroFinal - папка UIAtlases/NecroFinal, механизм тот же, что у ItemIconAtlas.
|
||||
Шесть картинок 1824x1024 пакуются UIAtlasFromFolder.createUiAtlasFromTextures через
|
||||
PackTextures(..., 2, 8192): суммарно ~11.2 Мпикс, влезает в один атлас 4096x4096. -->
|
||||
<append xpath="/windows">
|
||||
<window name="necroFinalSlide1" pos="0,0" depth="900">
|
||||
<sprite name="slideLetterbox" depth="0" sprite="menu_empty" type="sliced" color="[black]" anchor_left="#cam,0,-10" anchor_right="#cam,1,10" anchor_bottom="#cam,0,-10" anchor_top="#cam,1,10" globalopacitymod="0"/>
|
||||
<sprite name="slideArt" depth="1" atlas="NecroFinal" sprite="NecroFinal1" type="simple" anchor_left="#cam,0,0" anchor_right="#cam,1,0" anchor_bottom="#cam,0,0" anchor_top="#cam,1,0" globalopacitymod="0"/>
|
||||
</window>
|
||||
<window name="necroFinalSlide2" pos="0,0" depth="900">
|
||||
<sprite name="slideLetterbox" depth="0" sprite="menu_empty" type="sliced" color="[black]" anchor_left="#cam,0,-10" anchor_right="#cam,1,10" anchor_bottom="#cam,0,-10" anchor_top="#cam,1,10" globalopacitymod="0"/>
|
||||
<sprite name="slideArt" depth="1" atlas="NecroFinal" sprite="NecroFinal2" type="simple" anchor_left="#cam,0,0" anchor_right="#cam,1,0" anchor_bottom="#cam,0,0" anchor_top="#cam,1,0" globalopacitymod="0"/>
|
||||
</window>
|
||||
<window name="necroFinalSlide3" pos="0,0" depth="900">
|
||||
<sprite name="slideLetterbox" depth="0" sprite="menu_empty" type="sliced" color="[black]" anchor_left="#cam,0,-10" anchor_right="#cam,1,10" anchor_bottom="#cam,0,-10" anchor_top="#cam,1,10" globalopacitymod="0"/>
|
||||
<sprite name="slideArt" depth="1" atlas="NecroFinal" sprite="NecroFinal3" type="simple" anchor_left="#cam,0,0" anchor_right="#cam,1,0" anchor_bottom="#cam,0,0" anchor_top="#cam,1,0" globalopacitymod="0"/>
|
||||
</window>
|
||||
<window name="necroFinalSlide4" pos="0,0" depth="900">
|
||||
<sprite name="slideLetterbox" depth="0" sprite="menu_empty" type="sliced" color="[black]" anchor_left="#cam,0,-10" anchor_right="#cam,1,10" anchor_bottom="#cam,0,-10" anchor_top="#cam,1,10" globalopacitymod="0"/>
|
||||
<sprite name="slideArt" depth="1" atlas="NecroFinal" sprite="NecroFinal4" type="simple" anchor_left="#cam,0,0" anchor_right="#cam,1,0" anchor_bottom="#cam,0,0" anchor_top="#cam,1,0" globalopacitymod="0"/>
|
||||
</window>
|
||||
<window name="necroFinalSlide5" pos="0,0" depth="900">
|
||||
<sprite name="slideLetterbox" depth="0" sprite="menu_empty" type="sliced" color="[black]" anchor_left="#cam,0,-10" anchor_right="#cam,1,10" anchor_bottom="#cam,0,-10" anchor_top="#cam,1,10" globalopacitymod="0"/>
|
||||
<sprite name="slideArt" depth="1" atlas="NecroFinal" sprite="NecroFinal5" type="simple" anchor_left="#cam,0,0" anchor_right="#cam,1,0" anchor_bottom="#cam,0,0" anchor_top="#cam,1,0" globalopacitymod="0"/>
|
||||
</window>
|
||||
<window name="necroFinalSlide6" pos="0,0" depth="900">
|
||||
<sprite name="slideLetterbox" depth="0" sprite="menu_empty" type="sliced" color="[black]" anchor_left="#cam,0,-10" anchor_right="#cam,1,10" anchor_bottom="#cam,0,-10" anchor_top="#cam,1,10" globalopacitymod="0"/>
|
||||
<sprite name="slideArt" depth="1" atlas="NecroFinal" sprite="NecroFinal6" type="simple" anchor_left="#cam,0,0" anchor_right="#cam,1,0" anchor_bottom="#cam,0,0" anchor_top="#cam,1,0" globalopacitymod="0"/>
|
||||
</window>
|
||||
</append>
|
||||
|
||||
<!-- ЭПИЛОГ, 2026-09-09. После выбора на шестом слайде картинки заканчиваются и текст идёт по
|
||||
чёрному - отсюда отдельное окно без спрайта из атласа, только чёрная заливка. Тот же
|
||||
depth="900", что у слайдов, так что текстовая коробка (depth 1001) снова ложится сверху.
|
||||
Заливка копирует приём videoPlayer'а выше: menu_empty, растянутый по #cam с запасом в
|
||||
10px по каждой стороне, плюс globalopacitymod="0" - без него сквозь "чёрный экран"
|
||||
просвечивал бы игровой мир на величину пользовательской настройки Background Opacity. -->
|
||||
<append xpath="/windows">
|
||||
<window name="necroFinalBlack" pos="0,0" depth="900">
|
||||
<sprite name="blackout" depth="0" sprite="menu_empty" type="sliced" color="[black]" anchor_left="#cam,0,-10" anchor_right="#cam,1,10" anchor_bottom="#cam,0,-10" anchor_top="#cam,1,10" globalopacitymod="0"/>
|
||||
</window>
|
||||
</append>
|
||||
</windows>
|
||||
@@ -0,0 +1,45 @@
|
||||
<xui>
|
||||
<!-- Pairs with Config/XUi_InGame/windows.xml's new "videoPlayer" window - see that file's
|
||||
comment for the full root-cause explanation. Copied verbatim from
|
||||
Data/Config/XUi_Menu/xui.xml's own "videoPlayer" window_group (no controller= on the
|
||||
group itself there - the controller lives on the window element instead, same as this
|
||||
mod's copy). Without this entry the window from windows.xml exists but has no group to be
|
||||
opened by name through (XUiC_VideoPlayer.PlayVideo/GetInstance both resolve via the GROUP
|
||||
id, not the raw window). -->
|
||||
<append xpath="/xui">
|
||||
<window_group name="videoPlayer">
|
||||
<window name="videoPlayer" />
|
||||
</window_group>
|
||||
</append>
|
||||
|
||||
<!-- Группы для шести финальных слайдов (см. XUi_InGame/windows.xml). Как и у videoPlayer,
|
||||
контроллера на группе нет - окно чисто декоративное, открывается и закрывается по имени
|
||||
из HarmonySrc/FinalSlides.cs. -->
|
||||
<append xpath="/xui">
|
||||
<window_group name="necroFinalSlide1">
|
||||
<window name="necroFinalSlide1" />
|
||||
</window_group>
|
||||
<window_group name="necroFinalSlide2">
|
||||
<window name="necroFinalSlide2" />
|
||||
</window_group>
|
||||
<window_group name="necroFinalSlide3">
|
||||
<window name="necroFinalSlide3" />
|
||||
</window_group>
|
||||
<window_group name="necroFinalSlide4">
|
||||
<window name="necroFinalSlide4" />
|
||||
</window_group>
|
||||
<window_group name="necroFinalSlide5">
|
||||
<window name="necroFinalSlide5" />
|
||||
</window_group>
|
||||
<window_group name="necroFinalSlide6">
|
||||
<window name="necroFinalSlide6" />
|
||||
</window_group>
|
||||
</append>
|
||||
|
||||
<!-- Группа для чёрного экрана эпилога (см. windows.xml). -->
|
||||
<append xpath="/xui">
|
||||
<window_group name="necroFinalBlack">
|
||||
<window name="necroFinalBlack" />
|
||||
</window_group>
|
||||
</append>
|
||||
</xui>
|
||||
@@ -0,0 +1,90 @@
|
||||
<config>
|
||||
<!-- "Пирамида Ереси" (Pyramid of Heresy) - user request 2026-08-31: a placeable ward that
|
||||
periodically applies the existing Deviator charm (see HarmonySrc/PyramidWardPatch.cs,
|
||||
reusing HarmonySrc/CharmPatch.cs's buffNecroDeviatorCharm wholesale - no new charm logic
|
||||
was written, this just triggers the same buff on a timer) to any un-charmed zombie that
|
||||
wanders into its radius, turning it against other zombies instead of the player/base - a
|
||||
"barrier" in effect, not in physics: it never pushes or physically blocks anything, it
|
||||
flips allegiance.
|
||||
|
||||
SHAPE: Shape="New" + Model="@:Shapes/pyramid.fbx" - the same standalone pyramid mesh
|
||||
vanilla itself ships and uses for imposterPyramid's own LOD model (Data/Config/blocks.xml,
|
||||
"*** blockNextGen imposters" section) - a real, solid, always-loaded asset, not invented
|
||||
for this mod. Checked the "tentBiohazardRoofXxx" tent-roof pieces first (they're the
|
||||
nearest thing to a "block with a pyramid top" already in vanilla) but rejected them - all
|
||||
of them are 3x1x1/3x2x3 multi-blocks meant as building-sized tent roofs, wrong shape/scale
|
||||
for a single placeable ward. Red per the user's request via TintColor="8A4142" - the exact
|
||||
hex vanilla's own tentBiohazardRoofRed/tentBiohazardRoofCenterRed use for their red tint,
|
||||
reused here rather than inventing a new one.
|
||||
|
||||
FIXED 2026-08-31 (user report: placed fine but genuinely invisible - "нету префаба"):
|
||||
missing `Texture` property. EVERY vanilla block using this exact Shape="New" + a custom
|
||||
standalone Model="@:Shapes/....fbx" (imposterPyramid itself, imposterQuarter,
|
||||
imposterCTREighth, poiFillerBlock - checked all of them directly) always pairs it with an
|
||||
explicit `Texture="N"` atlas index; ours never had one, which is the real, simple root
|
||||
cause. Also switched Material from plain "Msteel" to "Msteel_shapes" + Texture="356" -
|
||||
the exact Material/Texture pair vanilla's own `steelShapes` block (the real "shapes=All"
|
||||
steel building family) uses for this same "New" shape renderer - plain "Msteel" is meant
|
||||
for non-shape steel blocks (doors, hatches) and was never a matched pair with Shape="New"
|
||||
to begin with. TintColor="8A4142" still multiplies on top as before.
|
||||
|
||||
User specifically asked to reuse the "Пирамида 1 м" ("Pyramid 1m") shape/prefab -
|
||||
genuinely tried, but that specific shape turned out to be a NAMED VARIANT of the engine's
|
||||
procedural multi-shape building system (`shapes="All"` blocks like `steelShapes`/
|
||||
`concreteShapes` - see those in vanilla blocks.xml), not a standalone asset with its own
|
||||
file path at all: decompiled `BlockShapeNew` (the C# class behind every Shape="New" block)
|
||||
directly and it contains NO "Pyramid" string anywhere - the mapping from a shape's display
|
||||
name to its actual geometry lives in compiled/binary shape-node data this decompile
|
||||
couldn't reach, not in anything grep-able or copyable as a `Model=` path. Reproducing that
|
||||
exact shape would mean reverse-engineering that binary shape data, real extra work with an
|
||||
uncertain payoff - what shipped instead is the closest already-proven-working equivalent:
|
||||
the same "New"-shape pyramid mesh `imposterPyramid` itself uses, now actually configured
|
||||
correctly (see the fix above) so it renders for real. If the proportions look wrong next to
|
||||
the in-game "Pyramid 1m" (e.g. too tall/full-block instead of a short 1m cap), that's a
|
||||
scale tweak once it's visible, not a rendering bug - say so and it can be resized.
|
||||
|
||||
REWRITTEN 2026-09-01, Class changed from none to Class="CompositeTileEntity" +
|
||||
CompositeFeatures/TEFeaturePyramidWard - two direct user findings drove this:
|
||||
1. "Навожу прицел, но подсказка про E не появляется" - the original plain-Block +
|
||||
Harmony-patches-on-Block approach never showed the E-prompt at all, because a
|
||||
SEPARATE gate method (Block.HasBlockActivationCommands) was never patched and always
|
||||
said "nothing to activate" for a plain decorative block.
|
||||
2. "Сделай TileEntity" - direct request for the effect/zone toggle state to actually
|
||||
survive a save/reload, which the old static-Dictionary-in-Harmony-patch version could
|
||||
not do at all (documented as a known caveat before, now actually fixed).
|
||||
Same syntax vanilla's own keystoneBlock (Land Claim) already uses for this exact
|
||||
Class="CompositeTileEntity" + CompositeFeatures pattern - see HarmonySrc/
|
||||
PyramidWardPatch.cs's own class doc comment on TEFeaturePyramidWard for the full decompiled
|
||||
reasoning (why this is the sanctioned extension point, not a hardcoded-switch hack; the
|
||||
E-menu/persistence/per-tick mechanics all live there now, not in this XML file). -->
|
||||
<append xpath="/blocks">
|
||||
<block name="necroHeresyPyramid">
|
||||
<property name="CreativeMode" value="Player" />
|
||||
<property name="DescriptionKey" value="necroHeresyPyramidDesc" />
|
||||
<property name="Class" value="CompositeTileEntity" />
|
||||
<property class="CompositeFeatures">
|
||||
<property class="TEFeaturePyramidWard" />
|
||||
</property>
|
||||
<property name="Material" value="Msteel_shapes" />
|
||||
<property name="Shape" value="New" />
|
||||
<property name="Model" value="@:Shapes/pyramid.fbx" />
|
||||
<property name="Texture" value="356" />
|
||||
<property name="TintColor" value="8A4142" />
|
||||
<!-- Real generated art (exch/pyramidOfSpirit.png, copied to
|
||||
UIAtlases/ItemIconAtlas/) added 2026-09-02, per direct instruction - no
|
||||
CustomIconTint alongside it (same lesson as every other real icon in this mod,
|
||||
e.g. resourceZombieAsh: a real drawn icon already has its own correct color, tinting
|
||||
it on top would recolor it unintentionally). TintColor above is unaffected - that's
|
||||
the 3D block MESH's tint, a completely separate thing from the 2D inventory icon. -->
|
||||
<property name="CustomIcon" value="pyramidOfSpirit" />
|
||||
<property name="Group" value="Decoration" />
|
||||
<property name="FilterTags" value="MC_building,SC_decor" />
|
||||
<property name="Collide" value="movement,melee,bullet,arrow,rocket" />
|
||||
<property name="StabilitySupport" value="false" />
|
||||
<property name="MaxDamage" value="2000" />
|
||||
<property name="EconomicValue" value="0" />
|
||||
<property name="SellableToTrader" value="false" />
|
||||
<drop event="Destroy" name="resourceScrapIron" count="10,20" />
|
||||
</block>
|
||||
</append>
|
||||
</config>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,284 @@
|
||||
<config>
|
||||
<!-- Step 1: lifetime zombie kill counter.
|
||||
zombieTemplateMale is the root template every zombie entity_class extends
|
||||
(directly, or indirectly via zombieTemplateShort), so patching it here covers
|
||||
every zombie variant in the game without listing them individually. -->
|
||||
<append xpath="/entity_classes/entity_class[@name='zombieTemplateMale']">
|
||||
<effect_group>
|
||||
<requirement name="EntityTagCompare" target="other" tags="player"/>
|
||||
<triggered_effect trigger="onOtherKilledSelf" action="ModifyCVar" target="other" cvar="necroZombieKillsCVar" operation="add" value="1"/>
|
||||
<!-- "Некромантия" skill: +1 level per zombie kill, capped by its own max_level.
|
||||
See progression.xml for why this drives the skill instead of reading books. -->
|
||||
<triggered_effect trigger="onOtherKilledSelf" action="AddProgressionLevel" target="other" progression_name="craftingNecroNecromancy" level="1"/>
|
||||
</effect_group>
|
||||
</append>
|
||||
|
||||
<!-- "Зомбособака" (Zombie Dog pet): BACKLOG.md item 3. Extends the vanilla hostile
|
||||
animalZombieDog (same prefab/physics/sounds - a real zombie dog model, not a reskinned
|
||||
wolf) but flips it to fight FOR the player instead of against them:
|
||||
|
||||
- EntityFlags drops "zombie": EntityAlive.DamageEntity has a hardcoded rule that two
|
||||
entities BOTH flagged EntityFlags.Zombie can never damage each other (see
|
||||
HarmonySrc/DamagePatch.cs for the same rule affecting charmed zombies) - keeping our
|
||||
pet flagged "zombie" would make it literally unable to hurt real zombies. Dropping the
|
||||
flag sidesteps that block entirely; no Harmony damage patch needed here, unlike the
|
||||
charmed-zombie case (which can't drop the flag without breaking its own zombie-side
|
||||
kill-counter/quest logic - our pet has none of that to worry about).
|
||||
- Tags drops "zombie"/"hostile" (kept "entity,animal,dog"), IsEnemyEntity="false" - same
|
||||
shape vanilla's own player-owned entityJunkDrone uses for "friendly to the player".
|
||||
- AITask-3/AITarget-1/AITarget-4 retarget from EntityPlayer/EntityBandit to EntityZombie -
|
||||
purely data-driven, same AI task types animalZombieDog already used, just pointed at a
|
||||
different class. This is also exactly how HarmonySrc/CharmPatch.cs flips a charmed
|
||||
zombie's allegiance at runtime - confirmed here that the underlying AI system doesn't
|
||||
care whether the retarget happens via live Harmony rewrite or via XML at spawn time.
|
||||
|
||||
Spawned by HarmonySrc/SummonPatch.cs via item bookSummonZombieDog (items.xml) - see that
|
||||
file for the ownership/one-pet-limit logic (mirrors vanilla's own drone: EntityDrone.
|
||||
isValidForPlayer() blocks summoning a second one while you already own one, rather than
|
||||
replacing the old one - SummonPatch.cs does the same check via ownedEntities). -->
|
||||
<append xpath="/entity_classes">
|
||||
<entity_class name="necroZombieDog" extends="animalZombieDog">
|
||||
<property name="EntityFlags" value="animal"/>
|
||||
<property name="Tags" value="entity,animal,dog"/>
|
||||
<property name="IsEnemyEntity" value="false"/>
|
||||
<property name="Faction" value="none"/>
|
||||
<!-- User request 2026-08-28: bite applies buffInjurySlow, same debuff the Insect Swarm
|
||||
originally had before its own rework - see necroMeleeHandZombieDog in items.xml. -->
|
||||
<property name="HandItem" value="necroMeleeHandZombieDog"/>
|
||||
|
||||
<property name="AITask-3" value="ApproachAndAttackTarget" data="class=EntityZombie,20"/>
|
||||
<property name="AITarget-1" value="SetAsTargetIfHurt" data="class=EntityZombie"/>
|
||||
<property name="AITarget-4" value="SetNearestEntityAsTarget" data="class=EntityZombie,22,20"/>
|
||||
</entity_class>
|
||||
</append>
|
||||
|
||||
<!-- "Жуки Властелина" (insect swarm pet): user request 2026-08-28, alongside the Zombie Dog
|
||||
bugfix (see items.xml). Extends vanilla's own animalInsectSwarm (same prefab/sounds/
|
||||
tanky-vs-melee stats - PhysicalDamageResist 75% vs non-ranged inherits unchanged, a nice
|
||||
free defensive fit for something meant to be swarmed by zombie melee).
|
||||
|
||||
BUG FIXED 2026-08-28 (swarm attacked the PLAYER, not zombies): the original version here
|
||||
overrode AITask-1/AITarget-1, mirroring animalInsectSwarm's own choice to blank those two
|
||||
indices - but animalInsectSwarm extends animalTemplateHostile, and re-reading THAT
|
||||
template (Data/Config/entityclasses.xml) shows the actual player-attacking task/target
|
||||
live at DIFFERENT indices: AITask-2 ("ApproachAndAttackTarget" class=EntityPlayer,...) and
|
||||
AITarget-4 ("SetNearestEntityAsTarget" class=EntityPlayer,...). animalInsectSwarm's
|
||||
AITask-1/AITarget-1 blanking only wipes animalTemplateHostile's harmless AITask-1
|
||||
(BreakBlock) and AITarget-1 (SetAsTargetIfHurt, generic) - the real player-hunting
|
||||
behavior at index 2/4 was never touched by that blanking, and my override at index 1
|
||||
just sat next to it as a no-op extra task while the REAL one kept targeting players.
|
||||
Fixed by overriding the actual indices (2 and 4) instead. AITask-1/AITarget-1 are left
|
||||
alone now - they inherit animalInsectSwarm's own blank override, same as vanilla.
|
||||
|
||||
BEHAVIOR CHANGE 2026-08-28 (user request): no longer deals real damage + slows zombies -
|
||||
it stings a zombie once and that zombie becomes a Deviator-charmed ally, same buff as the
|
||||
Spirit Stone/Grimoire (buffNecroDeviatorCharm - see necroMeleeHandInsectSwarm in
|
||||
items.xml). Search range widened (22/20 -> 40/30) per "ищут всех зомби в радиусе", though
|
||||
that number turned out not to matter much either - see below.
|
||||
|
||||
TARGETING IS NOT ACTUALLY DRIVEN BY THESE AITask/AITarget PROPERTIES AT ALL, despite the
|
||||
fix above (found out the hard way 2026-08-28, after that fix didn't stop the swarm from
|
||||
attacking the player): animalInsectSwarm's own Class="EntitySwarm" extends EntityVulture,
|
||||
which has entirely hardcoded C# target-finding (World.GetClosestPlayerSeen/GetClosestPlayer,
|
||||
literally typed to EntityPlayer) - it never consults the declarative AITask/AITarget system
|
||||
ground creatures like the Dog use. The AITask-2/AITarget-4 overrides above are harmless but
|
||||
functionally dead for this entity_class. The real fix is HarmonySrc/SwarmTargetPatch.cs,
|
||||
patching EntityAlive.SetAttackTarget to redirect player-targeting to the nearest zombie -
|
||||
see that file. It also skips zombies that already carry buffNecroDeviatorCharm (added after
|
||||
a second user report that the swarm would fly off in wide loops instead of moving straight
|
||||
to an uncharmed zombie standing right next to the one it had just charmed - re-targeting
|
||||
the same just-charmed zombie over and over, because it was still the nearest one, was
|
||||
exactly what caused that).
|
||||
|
||||
EntityFlags/EntityType: parent sets EntityFlags="animal,zombie" and EntityType="Zombie" -
|
||||
same DamageEntity zombie-vs-zombie block as the dog (see that entry above and
|
||||
HarmonySrc/DamagePatch.cs) would otherwise stop it stinging real zombies at all, so both
|
||||
get dropped to plain "animal". -->
|
||||
<append xpath="/entity_classes">
|
||||
<entity_class name="necroInsectSwarm" extends="animalInsectSwarm">
|
||||
<property name="EntityFlags" value="animal"/>
|
||||
<property name="EntityType" value="Animal"/>
|
||||
<property name="Tags" value="entity,noHead"/>
|
||||
<property name="IsEnemyEntity" value="false"/>
|
||||
<property name="Faction" value="none"/>
|
||||
<property name="HandItem" value="necroMeleeHandInsectSwarm"/>
|
||||
|
||||
<property name="AITask-2" value="ApproachAndAttackTarget" data="class=EntityZombie,20"/>
|
||||
<property name="AITarget-4" value="SetNearestEntityAsTarget" data="class=EntityZombie,40,30"/>
|
||||
</entity_class>
|
||||
</append>
|
||||
|
||||
<!-- Three more summon-pet zombies, BACKLOG.md item 4a. REPLACED AGAIN 2026-08-29, THIRD
|
||||
APPROACH, per the user's own direct suggestion after the second approach's Bear/Wolf
|
||||
fixes still didn't work ("почему нельзя взять зомбособаку, но добавить её ХП и силы
|
||||
атаки, и заменить ей модельку на медведя?"). Correct call - stop re-deriving AI/targeting
|
||||
data from each different vanilla template's own quirky format (full pipe-string vs
|
||||
indexed properties, different index numbering, etc.) and instead extend the Dog's OWN
|
||||
entity_class (necroZombieDog, defined above) directly. necroZombieDog already has
|
||||
EntityFlags/Tags/IsEnemyEntity/Faction AND the AITask-3/AITarget-1/AITarget-4 zombie-
|
||||
targeting overrides all confirmed working correctly in-game across multiple test rounds -
|
||||
none of that has to be re-verified or re-derived per creature any more, it's inherited
|
||||
unchanged. Bear/Wolf below only override what's actually cosmetic/stat-related: Prefab
|
||||
(swap the visible model), PhysicsBody/Mass (match the new model's real size so
|
||||
collision/knockback look right), HandItem (reuse the REAL vanilla hand-item for that
|
||||
creature - meleeHandAnimalZombieBear/meleeHandAnimalDireWolf, both real items with their
|
||||
own already-tuned damage - 60 each, vs. the Dog's own 8), and HealthMax (bumped up to
|
||||
match, see each entity's own comment for the exact vanilla reference number used).
|
||||
|
||||
Griffin NOT converted this way (left on the previous animalZombieVulture-based approach,
|
||||
unchanged) - flagged as a real, separate risk, not just left out by oversight: Bear/Wolf
|
||||
share the Dog's own quadruped body plan/animation rig, so swapping just the Prefab is
|
||||
low-risk (same skeleton shape, same generic AvatarAnimalController). A vulture is a
|
||||
bird - almost certainly a structurally different skeleton/rig - forcing that Prefab onto
|
||||
necroZombieDog's own ground-quadruped C# class (EntityZombieDog, no flight code at all)
|
||||
risks either a broken/distorted-looking model (mismatched bone names) or, best case, a
|
||||
"walking bird" that never actually flies. Worth a real decision, not a silent guess -
|
||||
see BACKLOG.md for the open question. -->
|
||||
<append xpath="/entity_classes">
|
||||
<entity_class name="necroZombieBear" extends="necroZombieDog">
|
||||
<!-- Real vanilla animalZombieBear's own Prefab/PhysicsBody/Mass (the same asset a
|
||||
genuine zombie bear uses) - not re-deriving AI from it any more, just borrowing
|
||||
its look and physical size so collision/knockback scale correctly for a
|
||||
bear-sized model sitting on the Dog's skeleton/rig. -->
|
||||
<property name="Prefab" value="@:Entities/Animals/Bear/animalBearZombiePrefab.prefab"/>
|
||||
<property name="PrefabCombined" value="true"/>
|
||||
<property name="PhysicsBody" value="bear"/>
|
||||
<property name="Mass" value="600"/>
|
||||
<property name="Tags" value="entity,animal,bear"/>
|
||||
<!-- FIXED 2026-08-30 (user report: "зомбомедведь лает" - swapping the Prefab only
|
||||
changes the visible model, sound properties are a totally separate set of
|
||||
properties and stayed inherited from necroZombieDog/animalZombieDog (dog barks)
|
||||
the whole time - same oversight needed fixing on Wolf/Griffin below too, not
|
||||
just this one. Real animalBear's own sound set (animalZombieBear itself doesn't
|
||||
override any of these, confirmed by reading it directly - it inherits animalBear's). -->
|
||||
<property name="SoundRandom" value="bearroam"/>
|
||||
<property name="SoundAlert" value="bearalert"/>
|
||||
<property name="SoundHurt" value="bearpain"/>
|
||||
<property name="SoundDeath" value="beardeath"/>
|
||||
<property name="SoundAttack" value="bearattack"/>
|
||||
<property name="SoundSense" value="bearsense"/>
|
||||
<property name="SoundGiveUp" value="beargiveup"/>
|
||||
<property name="SoundStepType" value="animalhvystep"/>
|
||||
<!-- Real vanilla hand item (claw damage 60, vs. the Dog's own bite at 8) - reused
|
||||
as-is, not customized further (no slow debuff etc. - not asked for). -->
|
||||
<property name="HandItem" value="meleeHandAnimalZombieBear"/>
|
||||
<effect_group name="Base Effects">
|
||||
<!-- 1500 is a guess between the Dog's 200 and real animalZombieBear's own 4000 -
|
||||
a tough pet, not necessarily boss-tier tanky. Say if it should be higher/lower. -->
|
||||
<passive_effect name="HealthMax" operation="base_set" value="1500"/>
|
||||
</effect_group>
|
||||
</entity_class>
|
||||
</append>
|
||||
|
||||
<append xpath="/entity_classes">
|
||||
<entity_class name="necroZombieWolf" extends="necroZombieDog">
|
||||
<!-- Real vanilla animalDireWolf's own Prefab/PhysicsBody/Mass/SizeScale. -->
|
||||
<property name="Prefab" value="@:Entities/Animals/DireWolf/animalDireWolfPrefab.prefab"/>
|
||||
<property name="PrefabCombined" value="true"/>
|
||||
<property name="PhysicsBody" value="AWolf"/>
|
||||
<property name="Mass" value="180"/>
|
||||
<property name="SizeScale" value="1.4"/>
|
||||
<property name="Tags" value="entity,animal,wolf"/>
|
||||
<!-- FIXED 2026-08-30, same oversight as the Bear above (see its comment) - real
|
||||
animalDireWolf's own sound set. -->
|
||||
<property name="SoundRandom" value="wolfdireroam"/>
|
||||
<property name="SoundAlert" value="wolfdirealert"/>
|
||||
<property name="SoundHurt" value="wolfdirepain"/>
|
||||
<property name="SoundDeath" value="wolfdiredeath"/>
|
||||
<property name="SoundAttack" value="wolfdireattack"/>
|
||||
<property name="SoundSense" value="wolfdiresense"/>
|
||||
<property name="SoundGiveUp" value="wolfdiregiveup"/>
|
||||
<property name="SoundStepType" value="animalpawstep"/>
|
||||
<!-- Real vanilla hand item (bite damage 60, vs. the Dog's own 8). -->
|
||||
<property name="HandItem" value="meleeHandAnimalDireWolf"/>
|
||||
<effect_group name="Base Effects">
|
||||
<!-- 1200 is a guess between the Dog's 200 and real animalDireWolf's own 3000 -
|
||||
slightly below the Bear, faster/leaner theme. Say if it should be different. -->
|
||||
<passive_effect name="HealthMax" operation="base_set" value="1200"/>
|
||||
</effect_group>
|
||||
</entity_class>
|
||||
</append>
|
||||
|
||||
<!-- Griffin CONVERTED 2026-08-29 to the same Dog-reskin trick as Bear/Wolf, after the user
|
||||
confirmed live in-game that the animalZombieVulture+Harmony-redirect approach genuinely
|
||||
doesn't work ("летает где-то в небе, и зомби его вообще не интересуют" - flies around
|
||||
doing EntityVulture's own default Wander behavior, never engaging anything). Not worth
|
||||
debugging the Harmony redirect further without another live test cycle - switched
|
||||
straight to the proven-reliable pattern instead, same as Wolf (confirmed working) and
|
||||
Bear (not yet confirmed, same trick).
|
||||
|
||||
No real griffin exists in this game and a bird's skeleton/rig was flagged as a real risk
|
||||
for reusing necroZombieDog's own quadruped rig (see the removed comment this replaces) -
|
||||
picked animalMountainLion as the substitute model instead of a bird: it EXTENDS
|
||||
animalWolf (Data/Config/entityclasses.xml, confirmed by reading it directly) - the SAME
|
||||
immediate parent animalZombieDog itself extends - almost certainly sharing the exact same
|
||||
quadruped skeleton/rig family the Dog's own model already uses, the lowest-risk pick
|
||||
available (lower risk than the Bear, which comes from a totally different
|
||||
animalBear->animalTemplateHostile lineage). Not a literal griffin visually any more (a
|
||||
mountain lion, not a bird/lion-eagle hybrid) - say if a different substitute or the name
|
||||
itself should change; kept "Зомбогриф"/"Summon Zombie Griffin" as-is for now since
|
||||
renaming would touch items.xml/recipes.xml/Localization.csv too and wasn't asked for. -->
|
||||
<append xpath="/entity_classes">
|
||||
<entity_class name="necroZombieGriffin" extends="necroZombieDog">
|
||||
<!-- Real vanilla animalMountainLion's own Prefab/PhysicsBody/Mass/SizeScale. -->
|
||||
<property name="Prefab" value="@:Entities/Animals/Cat/animalMountainLion.prefab"/>
|
||||
<property name="PrefabCombined" value="true"/>
|
||||
<property name="PhysicsBody" value="MountainLion"/>
|
||||
<property name="Mass" value="125"/>
|
||||
<property name="SizeScale" value="1"/>
|
||||
<property name="Tags" value="entity,animal,cat"/>
|
||||
<!-- FIXED 2026-08-30, same oversight as the Bear above (see its comment) - real
|
||||
animalMountainLion's own sound set. -->
|
||||
<property name="SoundRandom" value="mlionroam"/>
|
||||
<property name="SoundAlert" value="mlionalert"/>
|
||||
<property name="SoundHurt" value="mlionpain"/>
|
||||
<property name="SoundDeath" value="mliondeath"/>
|
||||
<property name="SoundAttack" value="mlionattack"/>
|
||||
<property name="SoundSense" value="mlionsense"/>
|
||||
<property name="SoundGiveUp" value="mliongiveup"/>
|
||||
<property name="SoundStepType" value="animalpawstep"/>
|
||||
<!-- Real vanilla hand item (claw damage 22 - lighter than the Bear/Wolf's 60, a
|
||||
faster/leaner theme fitting a big cat rather than a heavyweight brawler). -->
|
||||
<property name="HandItem" value="meleeHandAnimalMountainLion"/>
|
||||
<effect_group name="Base Effects">
|
||||
<!-- Real vanilla animalMountainLion's own HealthMax (750) - used as-is, not
|
||||
scaled further, since this pet is meant to be the "fast/agile" one, not the
|
||||
tankiest of the three. -->
|
||||
<passive_effect name="HealthMax" operation="base_set" value="750"/>
|
||||
</effect_group>
|
||||
</entity_class>
|
||||
</append>
|
||||
|
||||
<!-- "Жертва" loot bag - BACKLOG.md item 5, Necromancer's Knife. Same shape as vanilla's own
|
||||
EntityLootContainerStrong ("BLUE")/EntityLootContainerBoss ("RED") right above these in
|
||||
Data/Config/entityclasses.xml - only the Mesh and LootList differ.
|
||||
|
||||
BUG FIXED 2026-08-28 ("зомби погиб с бафом, гарантированный дроп запустился, но лута
|
||||
нет"): Mesh was a guess, zpackGreenPrefab.prefab - confirmed by the in-game test itself
|
||||
that it doesn't exist as a real asset. VictimPatch.cs's own warning fired: "created entity
|
||||
for 'EntityLootContainerVictim' wasn't an EntityLootContainer" - EntityFactory.CreateEntity
|
||||
didn't produce a usable entity at all once its mesh failed to resolve, so nothing ever
|
||||
got spawned, guaranteed roll or not. Reverted to zpackPrefab.prefab (the default/yellow
|
||||
mesh, confirmed to exist - every other loot container variant in vanilla config either
|
||||
uses this one directly or one of the three OTHER confirmed variants, blue/red/gold) per
|
||||
the fallback plan agreed on before this was ever guessed at - not visually distinct from a
|
||||
normal zombie drop, but a real, working bag with the right guaranteed contents.
|
||||
Force-spawned by HarmonySrc/VictimPatch.cs when a zombie with buffNecroVictim dies -
|
||||
never rolled by the normal LootDropProb/LootDropEntityClass system (not referenced from
|
||||
any zombie's own entity_class), so its own IsEnemyEntity/Faction/etc. just mirror the
|
||||
other loot containers for consistency, not because anything reads them via that path. -->
|
||||
<append xpath="/entity_classes">
|
||||
<entity_class name="EntityLootContainerVictim">
|
||||
<property name="Class" value="EntityLootContainer"/>
|
||||
<property name="UserSpawnType" value="Console"/>
|
||||
<property name="Mesh" value="@:Entities/LootContainers/zpackPrefab.prefab"/>
|
||||
<property name="ModelType" value="Custom"/>
|
||||
<property name="Prefab" value="Backpack"/>
|
||||
<property name="Parent" value="Backpack"/>
|
||||
<property name="IsEnemyEntity" value="false"/>
|
||||
<property name="TimeStayAfterDeath" value="3600"/>
|
||||
<property name="LootList" value="zPackVictim"/>
|
||||
<property name="Faction" value="none"/>
|
||||
</entity_class>
|
||||
</append>
|
||||
</config>
|
||||
@@ -0,0 +1,317 @@
|
||||
<config>
|
||||
<!-- Модификации Ножа некроманта. Продиктовано 2026-09-07: "добавь в нож слоты для
|
||||
модификаций. Но модификации там будут особые, именно для ножа некроманта, а не для
|
||||
обычного ножа."
|
||||
|
||||
HOW THE EXCLUSIVITY WORKS (both halves verified against the decompiled assembly, not
|
||||
guessed - see the long comment on the knife's Tags in items.xml for the full audit):
|
||||
|
||||
- installable_tags="necroKnife" is the positive half. The install UI checks
|
||||
"InstallableTags.IsEmpty || itemClass.HasAnyTags(InstallableTags)"
|
||||
(XUiC_ItemPartStack.CanSwap / XUiC_ItemStack / XUiM_AssembleItem all agree), matched
|
||||
against the TARGET ITEM's Tags. necroWpnBladeNecroKnife is the only item anywhere -
|
||||
vanilla or this mod - that carries "necroKnife", so these three fit it and nothing
|
||||
else. Note the IsEmpty short-circuit above: a modifier with no installable_tags at all
|
||||
goes into ANY item, so leaving it off would be the opposite of what was asked.
|
||||
- The knife's own "noMods" tag is the negative half, blocking all 87 vanilla modifiers
|
||||
from going the other way. That lives on the knife, not here.
|
||||
|
||||
WHY EACH ONE HAS ITS OWN modifier_tags: mods whose modifier_tags overlap are counted
|
||||
against ItemClass.MaxModsAllowed, which defaults to 1 (ItemClass.cs line 376), and
|
||||
CanSwap refuses once the count is reached. Giving all three a shared tag like
|
||||
"necroKnifeMod" would therefore have let the player install exactly ONE of them at a time
|
||||
- the four slots would be unusable. Distinct tags per mod is also what vanilla does
|
||||
(damageBleed / barrelAttachment / droneArmor ...).
|
||||
|
||||
type="attachment" (not "mod") so they can be pulled back out and moved to another knife -
|
||||
a type="mod" is permanent once installed.
|
||||
|
||||
ICONS UPDATED 2026-09-07: real generated art for all six, drawn by the user and dropped in
|
||||
via c:\Exchange\exch. Each mod now points CustomIcon at its own sprite in
|
||||
UIAtlases/ItemIconAtlas (TearsOfTheDead / ScavengersFeast / DeadMansGrip / GravesRepose /
|
||||
DeadStorm / DarkSense, all 160x160 RGBA like the other 21). This replaced the old
|
||||
reused-vanilla-sprite placeholders (drinkJarBoiledWater, foodShamSandwich,
|
||||
modMeleeGraveDigger, modArmorInsulatedLinerT2, modMeleeStunBatonRepulsor,
|
||||
modGunScopeSmall) - the same progression the Knife and Victim's Skin went through on
|
||||
2026-08-29. CustomIconTint dropped from all six along with them: it existed only to stop
|
||||
the borrowed vanilla sprites reading as the items they came from, and on purpose-drawn art
|
||||
it would just darken the picture.
|
||||
|
||||
If a tint is ever needed again here, note the format trap that cost a round the first
|
||||
time: CustomIconTint is HEX ("785AB4"), NOT the "R, G, B" triplet that TintColor takes on
|
||||
the knife itself. They look interchangeable and are not - decompiled, ItemClass parses this
|
||||
one with StringParsers.ParseHexColor while TintColor goes through the Color32 comma path,
|
||||
and every vanilla CustomIconTint value is hex (FF00FF, 6441A5, C68C53...). -->
|
||||
<append xpath="/item_modifiers">
|
||||
|
||||
<!-- "Слёзы мертвеца" - вода. Продиктовано 2026-09-07: "Если он вставлен в нож, то каждый
|
||||
убитый зомби даёт 2 единицы воды."
|
||||
|
||||
IMPORTANT - this deliberately does NOT just add to $waterAmountAdd and stop there.
|
||||
That CVar is only a QUEUE; on its own it hydrates nobody. Every vanilla drink
|
||||
(drinkJarRiverWater, Data/Config/items.xml ~21744) pairs the add with
|
||||
"AddBuff buffProcessConsumables", and it is buffProcessConsumables that adds
|
||||
buffHealWaterMax (Data/Config/buffs.xml ~8527: AddBuff buffHealWaterMax requires
|
||||
$waterAmountAdd GT 0), whose own onSelfBuffUpdate is what finally moves the number
|
||||
into the water stat - .1 per .1s tick, so the 2 units land over about two seconds.
|
||||
Missing that second line is exactly the bug that made the knife's lifesteal do
|
||||
nothing at all for over a week (see items.xml), so it is spelled out here on purpose.
|
||||
|
||||
onSelfKilledOther is fired by ItemActionAttack (decompiled, lines ~798 and ~846) as
|
||||
"entityAlive.FireEvent(MinEventTypes.onSelfKilledOther, flag4)", guarded by
|
||||
"!wasAlreadyDead && entity.IsDead()" so it means a real kill, not a hit on a corpse.
|
||||
flag4 is "the damaging item IS the held item", which is true for a melee swing - and
|
||||
that same flag4 is what routes the event through Inventory -> ItemValue -> installed
|
||||
Modifications. It is the identical argument the knife's already-working
|
||||
onSelfAttackedOther effects ride on, so if those fire, this fires. (No vanilla
|
||||
item_modifier happens to use onSelfKilledOther, but it is in item_modifiers.xml's own
|
||||
documented TRIGGER LIST and the dispatch path above is unconditional.)
|
||||
|
||||
Gated to zombies via EntityTagCompare on "other" - killing a bear or a bird gives
|
||||
nothing, matching how every other on-hit effect in this mod is gated. -->
|
||||
<item_modifier name="necroModKnifeTearsOfTheDead" installable_tags="necroKnife" modifier_tags="necroKnifeWater" type="attachment">
|
||||
<property name="Extends" value="modGeneralMaster" param1="CustomIcon"/>
|
||||
<property name="CustomIcon" value="TearsOfTheDead"/>
|
||||
<property name="EconomicValue" value="0"/>
|
||||
<property name="SellableToTrader" value="false"/>
|
||||
|
||||
<effect_group tiered="false">
|
||||
<requirement name="EntityTagCompare" target="other" tags="zombie"/>
|
||||
<triggered_effect trigger="onSelfKilledOther" action="ModifyCVar" cvar="$waterAmountAdd" operation="add" value="2"/>
|
||||
<triggered_effect trigger="onSelfKilledOther" action="AddBuff" buff="buffProcessConsumables"/>
|
||||
</effect_group>
|
||||
</item_modifier>
|
||||
|
||||
<!-- "Пир падальщика" - еда. Продиктовано 2026-09-07: "Следующий мод, на еду. Принцип тот
|
||||
же." - same 2 units, same per-kill trigger, same zombie gate.
|
||||
|
||||
Food rides the mirror-image path of the water mod above: $foodAmountAdd is the queue,
|
||||
buffProcessConsumables is what notices it (Data/Config/buffs.xml ~8525: AddBuff
|
||||
buffHealFood requires $foodAmountAdd GT 0), buffHealFood is what actually feeds you.
|
||||
One AddBuff covers both mods if they are installed together - buffProcessConsumables
|
||||
checks water and food independently and hands out whichever buffs apply, so stacking
|
||||
the two mods costs nothing extra and neither one cancels the other. -->
|
||||
<item_modifier name="necroModKnifeScavengersFeast" installable_tags="necroKnife" modifier_tags="necroKnifeFood" type="attachment">
|
||||
<property name="Extends" value="modGeneralMaster" param1="CustomIcon"/>
|
||||
<property name="CustomIcon" value="ScavengersFeast"/>
|
||||
<property name="EconomicValue" value="0"/>
|
||||
<property name="SellableToTrader" value="false"/>
|
||||
|
||||
<effect_group tiered="false">
|
||||
<requirement name="EntityTagCompare" target="other" tags="zombie"/>
|
||||
<triggered_effect trigger="onSelfKilledOther" action="ModifyCVar" cvar="$foodAmountAdd" operation="add" value="2"/>
|
||||
<triggered_effect trigger="onSelfKilledOther" action="AddBuff" buff="buffProcessConsumables"/>
|
||||
</effect_group>
|
||||
</item_modifier>
|
||||
|
||||
<!-- "Хватка мертвеца" - замедление. Chosen by the user from the proposed list 2026-09-07.
|
||||
|
||||
Reuses buffInjurySlow, the same vanilla debuff the mod's own Зомбособака already
|
||||
applies on its bite (necroMeleeHandZombieDog in items.xml) - proven working in this
|
||||
mod rather than a fresh guess, and the same EntityTagCompare zombie gate.
|
||||
|
||||
onSelfAttackedOther, not onSelfKilledOther: the point is to slow a zombie that is
|
||||
still coming at you, so it has to land on the hit, not on the kill. -->
|
||||
<item_modifier name="necroModKnifeDeadMansGrip" installable_tags="necroKnife" modifier_tags="necroKnifeSlow" type="attachment">
|
||||
<property name="Extends" value="modGeneralMaster" param1="CustomIcon"/>
|
||||
<property name="CustomIcon" value="DeadMansGrip"/>
|
||||
<property name="EconomicValue" value="0"/>
|
||||
<property name="SellableToTrader" value="false"/>
|
||||
|
||||
<effect_group tiered="false">
|
||||
<requirement name="EntityTagCompare" target="other" tags="zombie"/>
|
||||
<triggered_effect trigger="onSelfAttackedOther" action="AddBuff" target="other" buff="buffInjurySlow"/>
|
||||
</effect_group>
|
||||
</item_modifier>
|
||||
|
||||
<!-- "Могильный покой" - защита от переохлаждения и перегрева. Продиктовано 2026-09-07:
|
||||
"Пусть защищает от переохлаждения и перегрева... Есть параметр устойчивости к холоду и
|
||||
жаре. Он бывает на предметах одежды и на некоторых модах на одежду."
|
||||
|
||||
Найденные параметры - HypothermalResist (холод) и HyperthermalResist (жара). Живой
|
||||
ванильный образец: modArmorInsulatedLinerT1/T2/T3 (Data/Config/item_modifiers.xml
|
||||
~1873), они ставят ровно эту пару. Величина у них по тирам: T1 1->2.5, T2 2.8->4.3,
|
||||
T3 4.6->6 на ОДИН элемент брони, а элементов четыре. Взято 5 - примерно уровень
|
||||
одной детали брони с T3-подкладкой, и ровно то число, которое ваниль использовала во
|
||||
вкомментированных modArmorInsulatedLiner/modArmorCoolingMesh (там 5 на холод и 5 на
|
||||
жару, но двумя РАЗНЫМИ модами; здесь оба в одном, что щедрее - но это стоит слота из
|
||||
четырёх и работает только с ножом в руках, см. ниже). Крутить это число - одна правка.
|
||||
|
||||
ЕДИНИЦА ИЗМЕРЕНИЯ - градусы, на которые сдвигается уличная температура в сторону
|
||||
комфортной, а не проценты (PlayerEntityStats, декомпиляция):
|
||||
if (outsideTemperature < 70) { v = GetValue(HypothermalResist);
|
||||
outsideTemperature = min(70, outsideTemperature + v); }
|
||||
else { v = GetValue(HyperthermalResist);
|
||||
outsideTemperature = max(70, outsideTemperature - v); }
|
||||
|
||||
ВАЖНО - РАБОТАЕТ ТОЛЬКО ПОКА НОЖ В РУКАХ. Это не оплошность, а то, как движок вообще
|
||||
умеет учитывать не-броню, и проверено по всей цепочке, потому что термостойкость - это
|
||||
обычно броневой стат, а нож не броня:
|
||||
1. PlayerEntityStats зовёт EffectManager.GetValue(HypothermalResist, null, 0f, entity)
|
||||
со всеми параметрами по умолчанию, а в сигнатуре GetValue значения по умолчанию -
|
||||
calcHoldingItem: true и useMods: true. То есть предмет в руках и его моды учитываются.
|
||||
2. Внутри GetValue ветка "else if (calcHoldingItem && ...)" зовёт Inventory.ModifyValue.
|
||||
3. Inventory.ModifyValue пропускает предмет, если его теги попадают в
|
||||
ignoreWhenHeld = FastTags.Parse("clothing,armor"). У ножа ни того, ни другого нет,
|
||||
так что он проходит - а вот на реальной броне в руках это бы не сработало.
|
||||
4. ItemValue.ModifyValue в конце обходит Modifications[j].ModifyValue под флагом
|
||||
_useMods. Заметь: здесь, в отличие от FireEvent, НЕТ отсечки "if (!HasQuality)
|
||||
return;" - пассивки модов считаются независимо от качества. Но нож всё равно уже
|
||||
починен по tiered (см. items.xml), так что вопрос снят в обе стороны.
|
||||
Убрал нож в рюкзак - защита пропала. Так и задумано, и так это описано в Localization.csv.
|
||||
|
||||
Пассивка, а не triggered_effect, поэтому ни onSelfKilledOther, ни гейта на зомби здесь
|
||||
нет - эффект просто висит, пока нож в руке.
|
||||
|
||||
Иконка - спрайт настоящего ванильного мода-подкладки (у item_modifier без своего
|
||||
CustomIcon спрайт называется как он сам), то есть по смыслу ровно та картинка. Тинт
|
||||
тот же фиолетовый, что у остальных трёх, до появления собственной графики. -->
|
||||
<item_modifier name="necroModKnifeGravesRepose" installable_tags="necroKnife" modifier_tags="necroKnifeThermal" type="attachment">
|
||||
<property name="Extends" value="modGeneralMaster" param1="CustomIcon"/>
|
||||
<property name="CustomIcon" value="GravesRepose"/>
|
||||
<property name="EconomicValue" value="0"/>
|
||||
<property name="SellableToTrader" value="false"/>
|
||||
|
||||
<effect_group tiered="false">
|
||||
<passive_effect name="HypothermalResist" operation="base_add" value="5"/>
|
||||
<passive_effect name="HyperthermalResist" operation="base_add" value="5"/>
|
||||
</effect_group>
|
||||
</item_modifier>
|
||||
|
||||
<!-- "Мёртвая буря" - переделывает силовую атаку. Продиктовано 2026-09-07: "-5 HP повышаем
|
||||
до -10 HP. Эффект делаем AOE в радиусе 20 блоков (если это много, то скажи). Расход
|
||||
стамины на силовую атаку увеличиваем вдвое. Добавляем дебаф кровотечения и искрения."
|
||||
|
||||
ФОРМА И РАЗМЕР ОБЛАСТИ. Пользователь потом уточнил: "Нужна именно сфера. Иначе птиц не
|
||||
заденет." Сфера не нужна и недоступна - движок умеет ровно одну форму, и она уже
|
||||
объёмная. MinEventActionTargetedBase, ветка otherAOE:
|
||||
entsInRange = World.GetLivingEntitiesInBounds(_params.Self,
|
||||
new Bounds(_params.Other.position, Vector3.one * (maxRange * 2f)));
|
||||
Unity-шный Bounds(center, SIZE) берёт РАЗМЕР, а не extents, здесь size = range*2 по
|
||||
каждой оси - то есть это КУБ, расходящийся на range блоков во все шесть сторон,
|
||||
ВКЛЮЧАЯ вверх и вниз. Дальше World.GetLivingEntitiesInBounds обходит чанки по X/Z и
|
||||
сверяет коробки; никакого доп. фильтра по дистанции после выборки нет (см. цикл сразу
|
||||
за вызовом - только isValidTarget и singleTargetCheck, оба про теги, не про радиус).
|
||||
Поэтому вертикаль покрыта уже сейчас, и куб для летящей цели даже ЛУЧШЕ сферы: сфера
|
||||
радиуса R целиком помещается внутрь куба с полу-размером R.
|
||||
|
||||
Заодно из этого следует, что 20 - это очень много: куб 40x40x40 вокруг цели, сквозь
|
||||
стены и перекрытия (проверки линии взгляда тут нет вовсе). Весь ванильный диапазон
|
||||
AOE для сравнения: 1.1, 1.3, 1.4, 2.7, 3, 6 и ровно один случай 10
|
||||
(buffRingOfFireEffect). Поставлено 6 - это куб 12x12x12, то есть 6 блоков вверх, чего
|
||||
хватает на пикирующего стервятника, и при этом не выкашивает соседний этаж POI. Если
|
||||
нужно доставать высоко кружащих птиц - поднимать; помнить, что то же число уходит и
|
||||
в горизонталь. Менять - четыре атрибута range ниже.
|
||||
|
||||
ЧТО ЭТОТ МОД ДОБАВЛЯЕТ К БАЗОВОЙ СИЛОВОЙ. Мод умеет только ДОБАВЛЯТЬ эффекты, снять
|
||||
собственные эффекты предмета он не может, поэтому "-5 HP -> -10 HP" сделано вторым
|
||||
списанием на 5, а не переписыванием первого. Порядок детерминирован: в
|
||||
ItemValue.FireEvent собственные эффекты предмета (itemClass.FireEvent) идут ДО цикла
|
||||
по Modifications, так что сперва снимается базовая пятёрка, затем эта. Оба списания
|
||||
несут свой порог "Health GT 5", поэтому убить себя силовой атакой по-прежнему нельзя;
|
||||
следствие - при здоровье между 5 и 10 спишется только часть.
|
||||
|
||||
ПРО СТАМИНУ. База берётся в ItemActionDynamicMelee.cs:384:
|
||||
_actionData.StaminaUsage = EffectManager.GetValue(PassiveEffects.StaminaLoss,
|
||||
itemValue, 2f, holdingEntity, null, _actionData.ActionTags) * StaminaUsageMultiplier;
|
||||
- значение по умолчанию 2 (своего StaminaLoss у этого ножа нет), и читается оно с
|
||||
ActionTags, то есть "secondary" для силовой. GetValue возвращает
|
||||
_originalValue * _perc_value, где _perc_value стартует с 1, а perc_add к нему
|
||||
прибавляет - поэтому value="1" это ровно "вдвое", а не "+1".
|
||||
|
||||
ПРО КРОВОТЕЧЕНИЕ - две строки, а не одна, и это не перестраховка. buffInjuryBleeding
|
||||
снимает сам себя, если у цели bleedCounter == 0:
|
||||
<triggered_effect trigger="onSelfBuffUpdate" action="RemoveBuff" buff="buffInjuryBleeding">
|
||||
<requirement name="CVarCompare" cvar="bleedCounter" operation="Equals" value="0"/>
|
||||
а урон берёт как HealthChangeOT base_subtract @$bleedAmount, где $bleedAmount
|
||||
выставляется из bleedCounter на старте баффа. Просто повесить бафф - он мгновенно
|
||||
снимется, не сделав ничего. Поэтому сначала счётчик, потом бафф - как и у ванильного
|
||||
modMeleeSerratedBlade. Счётчик задан через "set 2", а не "add 1", чтобы не зависеть от
|
||||
$maxBleedCounter (его выставляют перки игрока, buffs.xml ~530) и чтобы эффект не
|
||||
накапливался бесконечно от серии ударов. 2 -> 2 HP/сек в течение 20 секунд.
|
||||
|
||||
ПРО ИСКРЕНИЕ - это ванильный buffShocked, тот самый, что вешают электродубинка и
|
||||
электрозабор. Своя частица p_electric_shock и звук electric_fence_impact уже внутри
|
||||
баффа, подключать отдельно нечего. По умолчанию 4 секунды и -5 HP/сек через
|
||||
HealthChangeOT. Его замедление лежит в ОТДЕЛЬНЫХ effect_group, гейтованных на
|
||||
$shockDurationMax >= 4 - это важно для птиц, см. ниже.
|
||||
|
||||
СРАБОТАЕТ ЛИ НА ПТИЦ (прямой вопрос пользователя) - да, в той части, которая наносит
|
||||
урон:
|
||||
+ Стервятник попадает в выборку: коробка трёхмерная, вертикаль включена.
|
||||
+ Тег есть: animalZombieVulture несёт "entity,animal,zombie,zombieAnimal,hostile,
|
||||
vulture,special", а target_tags="zombie" гейтит именно по нему.
|
||||
+ Кровотечение работает: HealthChangeOT применяется в EntityStats.cs:142, это общий
|
||||
путь для любого EntityAlive, полёт ему безразличен.
|
||||
+ Урон от искрения работает по той же причине.
|
||||
- Замедление от buffShocked по птице НЕ отработает: оно давит на RunSpeed/WalkSpeed,
|
||||
а EntityVulture.cs (~563/567) умножает на сырые поля moveSpeed/moveSpeedAggro и
|
||||
геттеры GetMoveSpeed()/GetMoveSpeedAggro() - единственные, кто эти пассивки
|
||||
применяет - не зовёт вовсе.
|
||||
? Ragdoll по летящей цели - НЕ ПРОВЕРЕНО. Ни в EntityVulture, ни в EntityFlying нет
|
||||
ни строчки про ragdoll, полёт-специфичной поддержки точно нет. Проверять в игре.
|
||||
Итого по птице: сбить с ног скорее всего не выйдет, но кровотечение и разряд догрызут.
|
||||
|
||||
Иконка-заглушка - спрайт ванильного мода-репульсора электродубинки, тематически
|
||||
ближайшее, что есть готового. -->
|
||||
<item_modifier name="necroModKnifeDeadStorm" installable_tags="necroKnife" modifier_tags="necroKnifePower" type="attachment">
|
||||
<property name="Extends" value="modGeneralMaster" param1="CustomIcon"/>
|
||||
<property name="CustomIcon" value="DeadStorm"/>
|
||||
<property name="EconomicValue" value="0"/>
|
||||
<property name="SellableToTrader" value="false"/>
|
||||
|
||||
<effect_group tiered="false">
|
||||
<!-- Вдвое дороже по стамине, только силовая. -->
|
||||
<passive_effect name="StaminaLoss" operation="perc_add" value="1" tags="secondary"/>
|
||||
|
||||
<!-- Вторая половина платы: базовые 5 + эти 5 = 10 HP. -->
|
||||
<triggered_effect trigger="onSelfSecondaryActionRayHit" action="ModifyStats" stat="Health" operation="subtract" value="5">
|
||||
<requirement name="EntityTagCompare" target="other" tags="zombie"/>
|
||||
<!-- "Health GT 0" на цели - тот же гейт на трупы, что у самого ножа, чтобы
|
||||
силовой удар по трупу не брал 5 HP впустую; подробный разбор в items.xml
|
||||
у лечения. AOE-строкам ниже он не нужен: GetLivingEntitiesInBounds сама
|
||||
отсеивает мёртвых. -->
|
||||
<requirement name="StatCompareCurrent" target="other" stat="Health" operation="GT" value="0"/>
|
||||
<requirement name="StatCompareCurrent" stat="Health" operation="GT" value="5"/>
|
||||
</triggered_effect>
|
||||
|
||||
<!-- AOE вокруг задетой цели. Базовое сбивание с ног у ножа одиночное, это его
|
||||
расширяет; двойное попадание по самой цели безвредно - Ragdoll это действие,
|
||||
а не стакающийся бафф. -->
|
||||
<triggered_effect trigger="onSelfSecondaryActionRayHit" action="Ragdoll" target="otherAOE" range="6" target_tags="zombie" duration="1.5" force="150"/>
|
||||
|
||||
<!-- Кровотечение: сначала счётчик, затем бафф - см. большой комментарий выше. -->
|
||||
<triggered_effect trigger="onSelfSecondaryActionRayHit" action="ModifyCVar" target="otherAOE" range="6" target_tags="zombie" cvar="bleedCounter" operation="set" value="2"/>
|
||||
<triggered_effect trigger="onSelfSecondaryActionRayHit" action="AddBuff" target="otherAOE" range="6" target_tags="zombie" buff="buffInjuryBleeding"/>
|
||||
|
||||
<!-- Искрение. -->
|
||||
<triggered_effect trigger="onSelfSecondaryActionRayHit" action="AddBuff" target="otherAOE" range="6" target_tags="zombie" buff="buffShocked"/>
|
||||
</effect_group>
|
||||
</item_modifier>
|
||||
|
||||
<!-- "Тёмное чутьё" - радар зомби, пока нож в руке. Вся механика и разбор, почему это
|
||||
вообще выполнимо на XML, лежат в комментарии к buffNecroDarkSense в Config/buffs.xml -
|
||||
здесь только выключатель.
|
||||
|
||||
onSelfEquipStart / onSelfEquipStop - ванильный паттерн для "работает, только пока
|
||||
предмет в руках" (так сделаны эффекты кирок, Data/Config/items.xml ~158 и ~175).
|
||||
Оба события шлёт Inventory на ItemValue держимого предмета (строки ~1226 и ~1245), а
|
||||
ItemValue.FireEvent прокидывает их в установленные Modifications - при условии, что у
|
||||
предмета есть качество, что у этого ножа теперь так (см. правку tiered в items.xml).
|
||||
|
||||
Бафф снимается и при смерти игрока: remove_on_death у него не выставлен, а по
|
||||
умолчанию это true - то есть залипнуть после респавна он не может. -->
|
||||
<item_modifier name="necroModKnifeDarkSense" installable_tags="necroKnife" modifier_tags="necroKnifeSense" type="attachment">
|
||||
<property name="Extends" value="modGeneralMaster" param1="CustomIcon"/>
|
||||
<property name="CustomIcon" value="DarkSense"/>
|
||||
<property name="EconomicValue" value="0"/>
|
||||
<property name="SellableToTrader" value="false"/>
|
||||
|
||||
<effect_group tiered="false">
|
||||
<triggered_effect trigger="onSelfEquipStart" action="AddBuff" buff="buffNecroDarkSense"/>
|
||||
<triggered_effect trigger="onSelfEquipStop" action="RemoveBuff" buff="buffNecroDarkSense"/>
|
||||
</effect_group>
|
||||
</item_modifier>
|
||||
|
||||
</append>
|
||||
</config>
|
||||
+1346
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,43 @@
|
||||
<config>
|
||||
<!-- "Жертва" (Victim) green loot bag - BACKLOG.md item 5, Necromancer's Knife. Guaranteed
|
||||
drop off a zombie killed while carrying buffNecroVictim (see HarmonySrc/VictimPatch.cs,
|
||||
which force-spawns EntityLootContainerVictim from entityclasses.xml on death, bypassing
|
||||
the normal LootDropProb roll entirely).
|
||||
|
||||
Contents per user: random hides (usually one or none), rotten flesh, bones, and a chance
|
||||
at "Кожа жертвы" (resourceVictimSkin, items.xml) - a crafting resource for summon books.
|
||||
Exact counts/chance are a guess where the user didn't give numbers - count="0,1" for the
|
||||
hide reads as "как правило одна или ноль" directly; flesh/bone ranges and the 15% skin
|
||||
chance are otherwise reasonable defaults, not specified. Say if any of these should change.
|
||||
|
||||
UPDATED 2026-08-29 (BACKLOG.md item 8, dictated same day): foodRottingFlesh/resourceBone
|
||||
removed per direct instruction - the mesh no longer drops the two generic zombie-corpse
|
||||
staples. Replaced with a random chance at resourceZombieAsh ("Прах зомби") instead, a new
|
||||
crafting resource fed into the other necromancy recipes (see recipes.xml - deliberately
|
||||
NOT the Knife's own recipe, to avoid a craft-the-knife-to-get-ash-to-craft-the-knife loop).
|
||||
Chance/count not specified by the user - guessed at the same shape as resourceVictimSkin's
|
||||
own 15% (bumped the count up a little since ash is meant to actually get used as a
|
||||
multi-unit crafting ingredient, unlike the skin). Say if this should change.
|
||||
|
||||
BUG FIXED 2026-08-28 (whole file failed to load, so NONE of this was active in-game -
|
||||
"Loading and parsing 'loot.xml' failed" / "lootgroup 'groupZpackVictim' does not exist or
|
||||
has not been defined before being referenced"): the lootgroup a lootcontainer's <item
|
||||
group="..."/> points at must be defined EARLIER in load order, same file or not - vanilla
|
||||
loot.xml itself always defines groupZpackReg etc. (~line 6589) well before the
|
||||
lootcontainer that references it (~line 9971). Originally had the container's <append>
|
||||
block first and the group's second; swapped so the group append comes first. Keep this
|
||||
append-order intact when editing below - it's what makes the file load at all. -->
|
||||
<append xpath="/lootcontainers">
|
||||
<lootgroup name="groupZpackVictim" count="all">
|
||||
<item name="resourceLeather" count="0,1"/>
|
||||
<item name="resourceZombieAsh" count="1,4" prob="0.4"/>
|
||||
<item name="resourceVictimSkin" count="1" prob="0.15"/>
|
||||
</lootgroup>
|
||||
</append>
|
||||
|
||||
<append xpath="/lootcontainers">
|
||||
<lootcontainer name="zPackVictim" count="1" size="6,3" sound_open="UseActions/open_backpack" sound_close="UseActions/close_backpack" open_time="1" loot_quality_template="qualBaseTemplate">
|
||||
<item group="groupZpackVictim"/>
|
||||
</lootcontainer>
|
||||
</append>
|
||||
</config>
|
||||
@@ -0,0 +1,194 @@
|
||||
<config>
|
||||
<!-- "Некромантия" (Necromancy) skill.
|
||||
Reuses the vanilla crafting_skill mechanism (same class that powers e.g.
|
||||
craftingHarvestingTools) parented to the vanilla "attCrafting" virtual
|
||||
attribute — this makes it auto-level from a game action (not spent perk
|
||||
points) and show up for free in the game's existing crafting-skills panel.
|
||||
|
||||
Originally this leveled from reading books (see git history / earlier
|
||||
comments), but items.xml effect_group does NOT inherit through Extends in
|
||||
this game version (confirmed by decompiling ItemClassesFromXml.parseItem -
|
||||
it always parses effect_group from the item's own XML node, passing null as
|
||||
the "parent node" argument to MinEffectController.ParseXml regardless of
|
||||
Extends; entity_class does the equivalent WITH the parent node passed in,
|
||||
which is why the zombie kill counter below always worked and reading books
|
||||
never did). Patching every single one of the ~150 skill magazine/schematic
|
||||
items individually to work around that was judged not worth it - so this
|
||||
levels from zombie kills instead, via the same entityclasses.xml patch on
|
||||
zombieTemplateMale that already drives necroZombieKillsCVar (see
|
||||
entityclasses.xml) - one kill, one level, capped at max_level.
|
||||
|
||||
max_level=5000, with 5 recipe groups gated at fixed total-zombie-kills
|
||||
thresholds (proportional to the original 1000-max version: 0.1% / 10% / 40%
|
||||
/ 60% / 100%):
|
||||
Group 1 "Адепт" - available from the start (level 1), but not all
|
||||
of it - individual recipes within the group still
|
||||
unlock at their own level as usual.
|
||||
Group 2 "Подмастерье" - level 500
|
||||
Group 3 "Ученик" - level 2000
|
||||
Group 4 "Некромант" - level 3000
|
||||
Group 5 "Мастер" - level 5000
|
||||
|
||||
Recipe unlocks: give a recipe Tags="necroNecromancyAdept" / "necroNecromancyJourneyman"
|
||||
/ "necroNecromancyApprentice" / "necroNecromancyNecromancer" / "necroNecromancyMaster"
|
||||
(whichever group it belongs to) and it will unlock automatically once the
|
||||
player crosses that group's threshold - no further change needed here.
|
||||
thrownStoneSpirit (Камень духов) carries none of these tags at all, since Group 1
|
||||
"Адепт" is unlocked from level 1 anyway - a recipe with no unlock tag is just always
|
||||
available, same effect, no need to spend a tag on it.
|
||||
|
||||
One genuine one-off exception: necroNecromancyLvl20 (level 20) - the Пространственный
|
||||
браслет, per direct instruction 2026-08-30 ("нож, камень духов и хранилище - это база...
|
||||
хранилище, когда убито минимум 20 зомби") - it belongs in the Group 1 "Адепт" display
|
||||
bucket (see below) but needs its own slightly-later unlock level within that same group,
|
||||
which is what unlock_tier is for (see display_entry below), not a reason to invent a
|
||||
whole separate group.
|
||||
|
||||
UPDATED 2026-08-29: display_entry blocks added below now that real necromancer items/
|
||||
recipes exist to point them at - per the user's own direct report ("до сих пор нету ни
|
||||
одного рецепта" in the skill panel).
|
||||
|
||||
UPDATED 2026-08-30 (user report: "превью пустые" - the group icons in the skill panel
|
||||
were blank). Root cause: `icon=` on display_entry needs a real ICON ATLAS SPRITE NAME
|
||||
(the same string you'd give CustomIcon), not the item's own internal id - fixed by
|
||||
pointing icon= at each group's actual CustomIcon sprite value.
|
||||
|
||||
REVERTED-AND-REDISTRIBUTED AGAIN 2026-08-30, two direct corrections in a row:
|
||||
1. "Что ещё за 'уровень'? Была же система! А рецепту можно без группы задать уровень
|
||||
скилла на котором он откроется." - an earlier edit had invented ad-hoc "Level
|
||||
20/50/200" display groups, abandoning the real 5-tier flavor-name system this
|
||||
comment already documents above (Адепт/Подмастерье/Ученик/Некромант/Мастер -
|
||||
craftingNecroNecromancyTier1-5Name, all already localized, unused since). A recipe's
|
||||
actual unlock level is controlled entirely by its own `tags=` + the
|
||||
RecipeTagUnlocked effect_group below - display_entry is PURELY the skill-panel
|
||||
preview widget, the two don't need to be 1:1. Confirmed against real vanilla
|
||||
precedent (Data/Config/progression.xml's own explosives group): one display_entry
|
||||
can list several items that reveal at different levels via a comma-list
|
||||
unlock_level plus multiple unlock_entry elements at increasing unlock_tier.
|
||||
2. "У тебя получается 5 групп, как и должно было быть. А вот уровень скилла ты
|
||||
распределил идиотски." - the fix for #1 above had (wrongly) folded EVERYTHING except
|
||||
the black stone into Group 1 alone (levels 1/20/50/200), leaving the three real
|
||||
mid-game tiers (Подмастерье@500/Ученик@2000/Некромант@3000) completely empty and
|
||||
unused - defeating the entire point of having 5 groups spread across the level
|
||||
range. Fixed by actually spreading the non-base items across all 5 real tiers
|
||||
instead of clustering them all near the bottom - see the effect_group tags below and
|
||||
recipes.xml for the final per-item distribution:
|
||||
Group 1 "Адепт" (level 1, +20 for the bracelet) - Spirit Stone, Knife,
|
||||
Blue Portal Stone, Spatial Vault, Pyramid of Spirits,
|
||||
and (since 2026-09-09) the four survival-flavoured knife
|
||||
mods at +30 / +60 / +100 / +300
|
||||
Group 2 "Подмастерье" (level 500) - Grimoire of Deviation, plus the two combat
|
||||
knife mods at 1400 / 1700
|
||||
Group 3 "Ученик" (level 2000) - Zombie Dog, Insect Swarm, Zombie Griffin
|
||||
Group 4 "Некромант" (level 3000) - Zombie Bear, Zombie Wolf, Banshee's Scroll
|
||||
Group 5 "Мастер" (level 5000) - Black Portal Stone
|
||||
necroNecromancyLvl50/Lvl200 tags removed (no longer used by anything - Grimoire and
|
||||
the summon books now use the real necroNecromancyJourneyman/Apprentice/Necromancer
|
||||
tags instead). necroNecromancyLvl20 stays - still the one legitimate one-off (see
|
||||
above). -->
|
||||
<append xpath="/progression/crafting_skills">
|
||||
<crafting_skill name="craftingNecroNecromancy" max_level="5000" parent="attCrafting" name_key="craftingNecroNecromancyName" desc_key="craftingNecroNecromancyDesc" long_desc_key="craftingNecroNecromancyLongDesc" icon="ui_game_symbol_zombie">
|
||||
|
||||
<!-- display_entry block kept in the SAME append as the crafting_skill itself (unlike
|
||||
an earlier draft of this edit, which tried appending display_entry via a second,
|
||||
separate xpath targeting crafting_skill[@name='craftingNecroNecromancy'] BEFORE
|
||||
this element even exists in load order - same class of bug that already broke
|
||||
loot.xml once, see that file's own load-order comment. Nesting it here avoids the
|
||||
ordering question entirely, and matches how vanilla itself writes display_entry -
|
||||
directly inside the crafting_skill tag, not as a separate append). -->
|
||||
<display_entry icon="SpiritStone" name_key="craftingNecroNecromancyTier1Name" has_quality="false" unlock_level="1,20,30,60,100,300">
|
||||
<!-- necroHeresyPyramid added here 2026-08-31 (user request: "добавь рецепт блока в
|
||||
скиллы") - unlock_tier="1" alongside the other always-available Tier-1 items,
|
||||
matching its recipe's own necroNecromancyAdept tag in recipes.xml (see that
|
||||
file's comment - both express the same "available immediately" intent). -->
|
||||
<unlock_entry item="thrownStoneSpirit,necroWpnBladeNecroKnife,thrownStonePortalBlue,necroHeresyPyramid" unlock_tier="1"/>
|
||||
<unlock_entry item="braceletSpatialVault" unlock_tier="2"/>
|
||||
<!-- ЧЕТЫРЕ МОДА НОЖА ПЕРЕЕХАЛИ СЮДА ИЗ ГРУППЫ 2, 2026-09-09. Продиктовано:
|
||||
"Питьё важно в тот же день. Оно должно быть доступно после 30 убитых зомби.
|
||||
Еда - 60. Это самые важные для начала выживания модификации. Модификация на
|
||||
покой - 100 зомби. Модификацию на тёмное чутьё я бы сделал доступной после
|
||||
300 убитых зомби."
|
||||
|
||||
ЧТО БЫЛО СЛОМАНО. Правка 2026-09-07 разложила все шесть модов ножа внутри
|
||||
группы 2, то есть в диапазоне 500-1700 убийств. Это ошибка баланса, а не
|
||||
кода (сама тройка "тег рецепта + RecipeTagUnlocked + unlock_tier" сходилась
|
||||
по всем шести): голод и жажда - проблема ПЕРВЫХ ДНЕЙ, а к 500 убийствам у
|
||||
игрока давно есть ферма, костёр, банки и фильтр, и +2 воды с трупа ему уже
|
||||
не нужны. Два самых "выживальческих" мода открывались ровно тогда, когда
|
||||
переставали быть нужны - мёртвый контент. Сам нож лежит в группе 1 и
|
||||
доступен с уровня 1, поэтому моды на воду, еду и тепло теперь идут сразу за
|
||||
ним, в той же группе.
|
||||
|
||||
Ступени группы 1 после правки: 1 - база, 20 - браслет, 30 - вода, 60 - еда,
|
||||
100 - покой, 300 - чутьё. Все шесть порогов ниже 500, то есть на группу 2
|
||||
они не заезжают. Хватка мертвеца (1400) и Мёртвая буря (1700) остались в
|
||||
группе 2 - это чисто боевые моды, ранний доступ им не нужен ("дальше уже не
|
||||
так принципиально").
|
||||
|
||||
Порядок вода -> еда не случаен и задан пользователем прямо: пить хочется в
|
||||
тот же день, есть - позже. -->
|
||||
<unlock_entry item="necroModKnifeTearsOfTheDead" unlock_tier="3"/>
|
||||
<unlock_entry item="necroModKnifeScavengersFeast" unlock_tier="4"/>
|
||||
<unlock_entry item="necroModKnifeGravesRepose" unlock_tier="5"/>
|
||||
<unlock_entry item="necroModKnifeDarkSense" unlock_tier="6"/>
|
||||
</display_entry>
|
||||
<!-- Ступенчатая разблокировка внутри группы. ПЕРЕРАСПРЕДЕЛЕНО 2026-09-09: четыре из
|
||||
шести модов ножа (вода/еда/покой/чутьё) уехали отсюда в группу 1 - см. большой
|
||||
комментарий там. Здесь остались Гримуар и два боевых мода ножа.
|
||||
|
||||
КАК ЭТО ЧИТАЕТСЯ (разобрано по декомпиляции, потому что семантика неочевидная):
|
||||
unlock_level - это список порогов (ProgressionClass.QualityStarts), а unlock_tier
|
||||
в XML 1-based, при разборе из него вычитается единица
|
||||
(ProgressionFromXml.cs:396 - "ParseSInt32(...) - 1"). Дальше
|
||||
GetUnlockItemLocked = GetQualityLevel(level) <= UnlockTier
|
||||
где GetQualityLevel возвращает индекс первого порога, который БОЛЬШЕ текущего
|
||||
уровня. В сумме это даёт простое правило: запись с unlock_tier="N" выходит
|
||||
из-под замка ровно на N-м значении unlock_level, считая с единицы. Поэтому здесь
|
||||
tier 1 -> 500, tier 2 -> 1400, tier 3 -> 1700, а в группе 1 выше -
|
||||
tier 1 -> 1, tier 2 -> 20, tier 3 -> 30, tier 4 -> 60, tier 5 -> 100,
|
||||
tier 6 -> 300.
|
||||
|
||||
Заблокированная запись рисуется греем из АТЛАСА ItemIconAtlasGreyscale плюс
|
||||
спрайт-замок ui_game_symbol_unlock поверх (XUi_InGame/windows.xml ~2523-2524,
|
||||
привязки unlock_icon_atlasN / unlock_icon_lockedN). Именно поэтому у мода теперь
|
||||
есть вторая папка UIAtlases/ItemIconAtlasGreyscale - без неё под замком у иконки
|
||||
не было бы картинки вообще. -->
|
||||
<display_entry icon="ScrollOfDeviation" name_key="craftingNecroNecromancyTier2Name" has_quality="false" unlock_level="500,1400,1700">
|
||||
<unlock_entry item="thrownBookGrimoireDeviation" unlock_tier="1"/>
|
||||
<unlock_entry item="necroModKnifeDeadMansGrip" unlock_tier="2"/>
|
||||
<unlock_entry item="necroModKnifeDeadStorm" unlock_tier="3"/>
|
||||
</display_entry>
|
||||
<display_entry icon="SummonZombieDog" name_key="craftingNecroNecromancyTier3Name" has_quality="false" unlock_level="2000">
|
||||
<unlock_entry item="bookSummonZombieDog,bookSummonInsectSwarm,bookSummonZombieGriffin" unlock_tier="1"/>
|
||||
</display_entry>
|
||||
<display_entry icon="SummonZombieBear" name_key="craftingNecroNecromancyTier4Name" has_quality="false" unlock_level="3000">
|
||||
<unlock_entry item="bookSummonZombieBear,bookSummonZombieWolf,bookBanshee" unlock_tier="1"/>
|
||||
</display_entry>
|
||||
<display_entry icon="BlackPortalStone" name_key="craftingNecroNecromancyTier5Name" has_quality="false" unlock_level="5000">
|
||||
<unlock_entry item="thrownStonePortalBlack" unlock_tier="1"/>
|
||||
</display_entry>
|
||||
|
||||
<effect_group>
|
||||
<passive_effect name="RecipeTagUnlocked" operation="base_set" level="1,5000" value="1" tags="necroNecromancyAdept"/>
|
||||
<passive_effect name="RecipeTagUnlocked" operation="base_set" level="20,5000" value="1" tags="necroNecromancyLvl20"/>
|
||||
<!-- Пороги модов ножа, 2026-09-09 (см. комментарий в recipes.xml и в группе 1
|
||||
выше). Каждый уровень тут ОБЯЗАН совпадать с соответствующим значением в
|
||||
unlock_level того display_entry, где лежит мод, иначе замок на панели скилла
|
||||
разойдётся с реальной доступностью рецепта: display_entry рисует замок сам по
|
||||
себе, по unlock_tier, и о тегах не знает.
|
||||
Lvl800 и Lvl1100 удалены вместе с этой правкой - ими больше никто не
|
||||
пользуется (Тёмное чутьё уехало на 300, Могильный покой на 100). -->
|
||||
<passive_effect name="RecipeTagUnlocked" operation="base_set" level="30,5000" value="1" tags="necroNecromancyLvl30"/>
|
||||
<passive_effect name="RecipeTagUnlocked" operation="base_set" level="60,5000" value="1" tags="necroNecromancyLvl60"/>
|
||||
<passive_effect name="RecipeTagUnlocked" operation="base_set" level="100,5000" value="1" tags="necroNecromancyLvl100"/>
|
||||
<passive_effect name="RecipeTagUnlocked" operation="base_set" level="300,5000" value="1" tags="necroNecromancyLvl300"/>
|
||||
<passive_effect name="RecipeTagUnlocked" operation="base_set" level="500,5000" value="1" tags="necroNecromancyJourneyman"/>
|
||||
<passive_effect name="RecipeTagUnlocked" operation="base_set" level="1400,5000" value="1" tags="necroNecromancyLvl1400"/>
|
||||
<passive_effect name="RecipeTagUnlocked" operation="base_set" level="1700,5000" value="1" tags="necroNecromancyLvl1700"/>
|
||||
<passive_effect name="RecipeTagUnlocked" operation="base_set" level="2000,5000" value="1" tags="necroNecromancyApprentice"/>
|
||||
<passive_effect name="RecipeTagUnlocked" operation="base_set" level="3000,5000" value="1" tags="necroNecromancyNecromancer"/>
|
||||
<passive_effect name="RecipeTagUnlocked" operation="base_set" level="5000,5000" value="1" tags="necroNecromancyMaster"/>
|
||||
</effect_group>
|
||||
</crafting_skill>
|
||||
</append>
|
||||
</config>
|
||||
@@ -0,0 +1,379 @@
|
||||
<config>
|
||||
<!-- Tin can water boiling - user request 2026-08-30 ("Вода в консервных банках должна
|
||||
кипятиться на костре даже если там нету кастрюли... так было сделано в другом моём моде
|
||||
'Энерголук'" = AC-EnergyBow, the same __NoMods reference the tin can items themselves were
|
||||
ported from - see items.xml's own comment on tinCanEmpty/tinCanRiverWater/
|
||||
tinCanBoiledWater).
|
||||
|
||||
Root cause this was missing at all: `Extends` copies an item's PROPERTIES, never its
|
||||
RECIPES - recipes are separate top-level <recipe> nodes matched by name, with no
|
||||
inheritance mechanism of their own (confirmed by grepping this mod's own recipes.xml -
|
||||
zero "tinCan" hits before this entry). tinCanRiverWater/tinCanBoiledWater extending
|
||||
drinkJarRiverWater/drinkJarBoiledWater therefore got NONE of the vanilla mason-jar-boiling
|
||||
recipe (`<recipe name="drinkJarBoiledWater" craft_area="campfire"
|
||||
craft_tool="toolCookingPot">`, Data/Config/recipes.xml) - boiling a tin can was never
|
||||
possible at all until this recipe existed, not merely pot-gated.
|
||||
|
||||
No `craft_tool="toolCookingPot"` here BY DESIGN, per the direct request - a metal can can
|
||||
sit right in a campfire's coals on its own, unlike a mason jar. Confirmed real vanilla
|
||||
precedent for a pot-less campfire recipe existing at all: `foodBakedPotato`/
|
||||
`foodCharredMeat` both use craft_area="campfire" with no craft_tool attribute at all - a
|
||||
campfire recipe is not implicitly pot-gated, craft_tool is an opt-in extra requirement,
|
||||
not something `craft_area="campfire"` implies on its own. -->
|
||||
<append xpath="/recipes">
|
||||
<recipe name="tinCanBoiledWater" count="1" craft_area="campfire">
|
||||
<ingredient name="tinCanRiverWater" count="1"/>
|
||||
</recipe>
|
||||
</append>
|
||||
|
||||
<!-- "Камень духов" (Spirit Stone): craftable from the start, no perk/skill gate -
|
||||
packMuleCrafting is a weight-while-crafting tag, not a gate (see vanilla
|
||||
campfire/candle recipes for the same pattern). Per BACKLOG.md item 1: 1 rock +
|
||||
20 grass fiber (resourceRockSmall doubles as both the crafted item's own mesh/icon
|
||||
source and its craft ingredient here, same as vanilla's own resourceRockSmallBundle). -->
|
||||
<append xpath="/recipes">
|
||||
<!-- resourceZombieAsh added 2026-08-29 (BACKLOG.md item 8) - count is a guess, not
|
||||
specified by the user, kept modest like the rest of this recipe's ingredients. -->
|
||||
<!-- resourceZombieAsh REMOVED 2026-08-31, direct user correction: this is the starter
|
||||
weapon, craftable before the player has the Knife at all - same circular-dependency
|
||||
problem items.xml's own resourceZombieAsh comment already flags and deliberately avoids
|
||||
for the Knife's own recipe ("the knife has to stay craftable from scratch, and ash only
|
||||
exists because the knife already tagged a zombie as a Victim"). Ash got added here
|
||||
anyway during the 2026-08-29 item-8 blanket rollout ("becomes a crafting ingredient
|
||||
across the other necromancy recipes") without checking this one against that same rule
|
||||
- missed it then, fixed now. Back to the original 2026-08-28 recipe (1 rock + 20
|
||||
fibers, BACKLOG.md item 1), nothing else changed. -->
|
||||
<recipe name="thrownStoneSpirit" count="1" tags="packMuleCrafting">
|
||||
<ingredient name="resourceRockSmall" count="1"/>
|
||||
<ingredient name="resourceYuccaFibers" count="20"/>
|
||||
</recipe>
|
||||
</append>
|
||||
|
||||
<!-- Two portal stones, BACKLOG.md item 6. Blue: no gate, simple early recipe by analogy with
|
||||
the Spirit Stone (per that backlog note) - not specified exactly by the user. Black:
|
||||
necroNecromancyMaster (level 5000, the existing "Мастер" tier, group 5) per direct
|
||||
instruction. Both carry resourceZombieAsh like the other necromancy recipes
|
||||
(BACKLOG.md item 8). -->
|
||||
<append xpath="/recipes">
|
||||
<!-- resourceZombieAsh bumped 5 -> 150, direct user correction 2026-08-31: unlike the Spirit
|
||||
Stone (removed entirely, see that fix above), the Blue Portal Stone is NOT a starter
|
||||
weapon - "он не является начальным оружием... больше про комфорт" - so the same
|
||||
circular-dependency concern doesn't apply, and the user wants it to actually cost a
|
||||
real chunk of ash rather than a token amount. -->
|
||||
<recipe name="thrownStonePortalBlue" count="1" tags="packMuleCrafting">
|
||||
<ingredient name="resourceRockSmall" count="1"/>
|
||||
<ingredient name="drinkJarPureMineralWater" count="3"/>
|
||||
<ingredient name="resourceYuccaFibers" count="15"/>
|
||||
<ingredient name="resourceZombieAsh" count="150"/>
|
||||
</recipe>
|
||||
</append>
|
||||
<!-- resourceNecromancerBlood x10 added 2026-08-30, direct instruction ("10 крови некроманта
|
||||
нужно будет для рецепта чёрного портала"). ADDITIVE to the existing medicalBloodBag
|
||||
ingredient, not a replacement - the user said "также" (also/additionally), not "instead
|
||||
of"; the plain blood bag stays as the mundane-blood component, Necromancer's Blood as the
|
||||
new, harder-to-get one. Say if this should replace medicalBloodBag instead. -->
|
||||
<append xpath="/recipes">
|
||||
<recipe name="thrownStonePortalBlack" count="1" tags="learnable,packMuleCrafting,necroNecromancyMaster">
|
||||
<ingredient name="resourceRockSmall" count="1"/>
|
||||
<ingredient name="medicalBloodBag" count="5"/>
|
||||
<ingredient name="resourceNecromancerBlood" count="10"/>
|
||||
<ingredient name="casinoCoin" count="20"/>
|
||||
<ingredient name="resourceYuccaFibers" count="15"/>
|
||||
<ingredient name="resourceZombieAsh" count="20"/>
|
||||
</recipe>
|
||||
</append>
|
||||
|
||||
<!-- "Кровь некроманта" (Necromancer's Blood): dictated 2026-08-30, see items.xml for the item
|
||||
itself and HarmonySrc/NecromancerBloodPatch.cs for the knife-requirement/HP-cost mechanic
|
||||
this recipe alone can't express. No level gate given by the user and none applied - same
|
||||
"base" tier as the jar it's made from being a mundane always-available item; the real
|
||||
gate on this ritual is the HP cost + knife requirement, not a skill level. drinkJarEmpty
|
||||
confirmed as the real vanilla empty-jar item (Data/Config/items.xml) - not invented. -->
|
||||
<append xpath="/recipes">
|
||||
<recipe name="resourceNecromancerBlood" count="1" tags="packMuleCrafting">
|
||||
<ingredient name="drinkJarEmpty" count="1"/>
|
||||
</recipe>
|
||||
</append>
|
||||
|
||||
<!-- "Пирамида Ереси" (Pyramid of Heresy): user request 2026-08-31, see blocks.xml/
|
||||
HarmonySrc/PyramidWardPatch.cs for the block itself. "Рецепт доступен сразу" - no
|
||||
"learnable" tag and no gate beyond necroNecromancyAdept (Group 1 "Адепт", unlocked from
|
||||
level 1 - see progression.xml's own comment: a recipe with no unlock tag at all is already
|
||||
always-available, the tag here is added anyway purely so the intent ("this is a Tier-1,
|
||||
immediately-available necromancy recipe") is visible directly in this file, matching how
|
||||
braceletSpatialVault documents itself with necroNecromancyLvl20 below rather than staying
|
||||
unmarked). craft_area="workbench" (unlike the hand-craftable Knife/Spirit Stone) - this is
|
||||
a heavy placeable structure, not a pocket item, matching braceletSpatialVault's own
|
||||
workbench gate.
|
||||
|
||||
INGREDIENTS: resourceZombieAsh(1200)/resourceRockSmall/resourceYuccaFibers dictated
|
||||
directly by the user ("прах зомби, камень, трава"). The rest chosen to fit a "necromantic
|
||||
iron pylon" ("остальные ингредиенты добавь сам исходя из контекста"):
|
||||
- resourceScrapIron: the block's own structural frame (real vanilla resource, matches
|
||||
its own Destroy-drop in blocks.xml).
|
||||
- resourceNecromancerBlood: the mod's existing magic-binding reagent (see above) - ties
|
||||
the ward's charm magic to the same "blood" reagent already used elsewhere in this
|
||||
mod's necromancy recipes, kept to a small count (3) since it costs the player 90% of
|
||||
their current HP per unit to make (NecromancerBloodPatch.cs) - this recipe shouldn't
|
||||
demand many. -->
|
||||
<append xpath="/recipes">
|
||||
<recipe name="necroHeresyPyramid" count="1" craft_area="workbench" tags="workbenchCrafting,necroNecromancyAdept">
|
||||
<ingredient name="resourceZombieAsh" count="1200"/>
|
||||
<ingredient name="resourceRockSmall" count="300"/>
|
||||
<ingredient name="resourceYuccaFibers" count="200"/>
|
||||
<ingredient name="resourceScrapIron" count="300"/>
|
||||
<ingredient name="resourceNecromancerBlood" count="3"/>
|
||||
</recipe>
|
||||
</append>
|
||||
|
||||
<!-- "Гримуар девиации" (Grimoire of Deviation): BACKLOG.md item 2. Gated at
|
||||
necroNecromancyJourneyman - group 2 "Подмастерье" (level 500), one of the 5 real skill
|
||||
tiers (see progression.xml; REDISTRIBUTED 2026-08-30, was a one-off level-50 tag before -
|
||||
direct user correction, "уровень скилла ты распределил идиотски"). -->
|
||||
<append xpath="/recipes">
|
||||
<recipe name="thrownBookGrimoireDeviation" count="1" tags="learnable,packMuleCrafting,necroNecromancyJourneyman">
|
||||
<ingredient name="resourcePaper" count="20"/>
|
||||
<ingredient name="resourceGlue" count="2"/>
|
||||
<ingredient name="resourceCoal" count="5"/>
|
||||
<ingredient name="resourceWood" count="10"/>
|
||||
<ingredient name="resourceYuccaFibers" count="10"/>
|
||||
<ingredient name="resourceZombieAsh" count="5"/>
|
||||
</recipe>
|
||||
</append>
|
||||
|
||||
<!-- "Призыв зомбособаки" (Summon Zombie Dog): BACKLOG.md item 3. Gated at
|
||||
necroNecromancyApprentice - group 3 "Ученик" (level 2000), one of the 5 real skill tiers
|
||||
(see progression.xml; REDISTRIBUTED 2026-08-30, was a one-off level-200 tag before -
|
||||
direct user correction, "уровень скилла ты распределил идиотски"). Per user request,
|
||||
crafted at a workbench specifically (craft_area="workbench"), same as advanced-tier weapons
|
||||
(e.g. vanilla gunHandgunT3SMG5) - NOT available from the player's own personal crafting
|
||||
menu like the Spirit Stone/Grimoire above (those have no craft_area, so default to
|
||||
personal crafting). workbenchCrafting is just the same UI-categorization tag vanilla's
|
||||
own workbench recipes carry alongside craft_area, not a separate gate. -->
|
||||
<append xpath="/recipes">
|
||||
<recipe name="bookSummonZombieDog" count="1" craft_area="workbench" tags="learnable,workbenchCrafting,necroNecromancyApprentice">
|
||||
<ingredient name="foodRottingFlesh" count="50"/>
|
||||
<ingredient name="medicalBloodBag" count="3"/>
|
||||
<ingredient name="drinkJarBoiledWater" count="4"/>
|
||||
<ingredient name="resourceYuccaFibers" count="50"/>
|
||||
<ingredient name="resourceCloth" count="3"/>
|
||||
<ingredient name="resourceLeather" count="10"/>
|
||||
<ingredient name="resourceBone" count="40"/>
|
||||
<ingredient name="casinoCoin" count="5"/>
|
||||
<ingredient name="resourceZombieAsh" count="10"/>
|
||||
</recipe>
|
||||
</append>
|
||||
|
||||
<!-- "Жуки Властелина": user request 2026-08-28. Personal crafting (no craft_area) - unlike the
|
||||
Dog, unconfirmed whether this should be workbench-gated too; see items.xml. Same
|
||||
necroNecromancyApprentice gate as the Dog - group 3 "Ученик" (level 2000, see
|
||||
progression.xml; REDISTRIBUTED 2026-08-30 like the others above). Ingredients per
|
||||
explicit user instruction: 1 resourceQueenBee ("одна пчеломатка") is required; the rest
|
||||
("сам додумай, но без фанатизма") is a modest hive/bait theme (honey to draw and bind the
|
||||
swarm, wood for a hive, fiber to hold it together) - kept deliberately small, no rare
|
||||
currency/high counts like the Dog's recipe. -->
|
||||
<append xpath="/recipes">
|
||||
<recipe name="bookSummonInsectSwarm" count="1" tags="learnable,necroNecromancyApprentice">
|
||||
<ingredient name="resourceQueenBee" count="1"/>
|
||||
<ingredient name="foodHoney" count="20"/>
|
||||
<ingredient name="resourceWood" count="10"/>
|
||||
<ingredient name="resourceYuccaFibers" count="15"/>
|
||||
<ingredient name="resourceZombieAsh" count="5"/>
|
||||
</recipe>
|
||||
</append>
|
||||
|
||||
<!-- "Книга банши" (Banshee's Book): BACKLOG.md item 7. Gated at necroNecromancyNecromancer -
|
||||
group 4 "Некромант" (level 3000), alongside Bear/Wolf (see progression.xml;
|
||||
REDISTRIBUTED 2026-08-30, was necroNecromancyLvl200 before) - not specified by the user,
|
||||
guessed consistent with the other "advanced ritual" summon items. -->
|
||||
<append xpath="/recipes">
|
||||
<recipe name="bookBanshee" count="1" craft_area="workbench" tags="learnable,workbenchCrafting,necroNecromancyNecromancer">
|
||||
<ingredient name="resourcePaper" count="15"/>
|
||||
<ingredient name="resourceGlue" count="3"/>
|
||||
<ingredient name="foodRottingFlesh" count="10"/>
|
||||
<ingredient name="resourceYuccaFibers" count="15"/>
|
||||
<ingredient name="resourceZombieAsh" count="10"/>
|
||||
</recipe>
|
||||
</append>
|
||||
|
||||
<!-- "Нож некроманта" (Necromancer's Knife): BACKLOG.md item 5, user request 2026-08-28.
|
||||
No recipe/gate specified beyond the item's own mechanics - guessed modest and available
|
||||
from the start (personal crafting, no craft_area, no level tag), same tier as the Spirit
|
||||
Stone, since its real power comes from playing the necromancer build over time (damage
|
||||
scales with kill count), not an expensive unlock. Say if this should change.
|
||||
|
||||
resourceZombieAsh (BACKLOG.md item 8) deliberately NOT added here, unlike the other
|
||||
necromancy recipes - per explicit user instruction 2026-08-29: ash only drops from
|
||||
zombies the knife itself has already marked as "Жертва", so requiring ash to craft the
|
||||
knife would make the knife uncraftable from a fresh start (circular dependency).
|
||||
|
||||
medicalBloodBag SWAPPED for resourceNecromancerBlood 2026-08-30, direct instruction
|
||||
("для ножа некроманта пусть используется кровь некроманта"). NOT a circular dependency
|
||||
like the ash case above, even though this item needs a knife to craft (see
|
||||
NecromancerBloodPatch.cs) - resourceNecromancerBlood's own requirement is "ANY knife"
|
||||
(ItemClass.DisplayType == "meleeKnife"), which vanilla's own starting/craftable knives
|
||||
(e.g. the plain bone knife) already satisfy - it does NOT require this specific
|
||||
necroWpnBladeNecroKnife to already exist. Count kept at 1, same as the medicalBloodBag it
|
||||
replaces - not inflated further, since the blood itself is now a genuinely costly
|
||||
ingredient (90% current HP + a knife + a jar per unit). -->
|
||||
<append xpath="/recipes">
|
||||
<recipe name="necroWpnBladeNecroKnife" count="1" tags="packMuleCrafting">
|
||||
<ingredient name="resourceBone" count="5"/>
|
||||
<ingredient name="resourceNecromancerBlood" count="1"/>
|
||||
<ingredient name="resourceYuccaFibers" count="10"/>
|
||||
</recipe>
|
||||
</append>
|
||||
|
||||
<!-- Three more summon-book recipes, BACKLOG.md item 4a. REPLACED 2026-08-29 - Griffin/Bear/
|
||||
Wolf instead of Stripper/Cop/Soldier (see entityclasses.xml). Same workbench shape as the
|
||||
Dog's own recipe; Griffin shares the Dog's gate (necroNecromancyApprentice, group 3
|
||||
"Ученик", level 2000), Bear/Wolf are one tier higher (necroNecromancyNecromancer, group 4
|
||||
"Некромант", level 3000, alongside the Banshee) - split REDISTRIBUTED 2026-08-30 across
|
||||
the real 5 skill tiers instead of one shared one-off level-200 tag (see progression.xml).
|
||||
Ingredient lists are a themed guess per creature (not specified by the user) -
|
||||
resourceFeather/foodRawMeat both verified to exist in vanilla items.xml before use, same
|
||||
lesson as the earlier wrong guesses. -->
|
||||
<append xpath="/recipes">
|
||||
<recipe name="bookSummonZombieGriffin" count="1" craft_area="workbench" tags="learnable,workbenchCrafting,necroNecromancyApprentice">
|
||||
<ingredient name="resourceFeather" count="30"/>
|
||||
<ingredient name="resourceBone" count="20"/>
|
||||
<ingredient name="casinoCoin" count="5"/>
|
||||
<ingredient name="resourceYuccaFibers" count="20"/>
|
||||
<ingredient name="resourceZombieAsh" count="10"/>
|
||||
</recipe>
|
||||
</append>
|
||||
<append xpath="/recipes">
|
||||
<recipe name="bookSummonZombieBear" count="1" craft_area="workbench" tags="learnable,workbenchCrafting,necroNecromancyNecromancer">
|
||||
<ingredient name="resourceLeather" count="20"/>
|
||||
<ingredient name="foodRawMeat" count="10"/>
|
||||
<ingredient name="resourceBone" count="20"/>
|
||||
<ingredient name="resourceYuccaFibers" count="20"/>
|
||||
<ingredient name="resourceZombieAsh" count="10"/>
|
||||
</recipe>
|
||||
</append>
|
||||
<append xpath="/recipes">
|
||||
<recipe name="bookSummonZombieWolf" count="1" craft_area="workbench" tags="learnable,workbenchCrafting,necroNecromancyNecromancer">
|
||||
<ingredient name="resourceLeather" count="15"/>
|
||||
<ingredient name="foodRawMeat" count="10"/>
|
||||
<ingredient name="resourceBone" count="15"/>
|
||||
<ingredient name="resourceYuccaFibers" count="20"/>
|
||||
<ingredient name="resourceZombieAsh" count="10"/>
|
||||
</recipe>
|
||||
</append>
|
||||
|
||||
<!-- "Петля вора" (Thief's Loop) recipe REMOVED 2026-08-30 along with the item itself - see
|
||||
items.xml for why. -->
|
||||
|
||||
<!-- "Пространственный браслет" (Spatial Bracelet) - gate moved 2026-08-30 from
|
||||
necroNecromancyLvl200 to necroNecromancyLvl20 per direct instruction ("нож, камень духов
|
||||
и хранилище - это база... хранилище, когда убито минимум 20 зомби"). craft_area/workbench
|
||||
left as-is (not specified either way) - still an "advanced" build, just unlocked much
|
||||
earlier than before. -->
|
||||
<append xpath="/recipes">
|
||||
<recipe name="braceletSpatialVault" count="1" craft_area="workbench" tags="learnable,workbenchCrafting,necroNecromancyLvl20">
|
||||
<ingredient name="resourceLeather" count="10"/>
|
||||
<ingredient name="resourceMechanicalParts" count="5"/>
|
||||
<ingredient name="resourceScrapIron" count="10"/>
|
||||
<ingredient name="resourceYuccaFibers" count="15"/>
|
||||
<ingredient name="resourceZombieAsh" count="10"/>
|
||||
</recipe>
|
||||
</append>
|
||||
|
||||
<!-- СТУПЕНЧАТАЯ РАЗБЛОКИРОВКА МОДОВ НОЖА. Изначально (2026-09-07) продиктовано: "механика
|
||||
скиллов даёт возможность сделать их доступными не сразу, а по мере набора количества
|
||||
убитых зомби. Т.е. грейд открылся, а рецепт ещё нет, на нём замочек. Еда/вода должны
|
||||
открываться на грейде сразу. Чутьё чуть позже, хватка ещё позже. Но всё в рамках грейда."
|
||||
|
||||
ПЕРЕСТАВЛЕНО 2026-09-09 - прямая правка баланса от пользователя: "Питьё важно в тот же
|
||||
день. Оно должно быть доступно после 30 убитых зомби. Еда - 60. Это самые важные для
|
||||
начала выживания модификации. Модификация на покой - 100 зомби. Модификацию на тёмное
|
||||
чутьё я бы сделал доступной после 300 убитых зомби. Дальше уже не так принципиально."
|
||||
|
||||
ЧТО БЫЛО НЕ ТАК. Первая раскладка держала все шесть модов внутри группы 2, то есть в
|
||||
диапазоне 500-1700 убийств. Ошибка не в коде (тег рецепта, RecipeTagUnlocked и
|
||||
unlock_tier сходились по всем шести), а в балансе: вода и еда нужны в ПЕРВЫЕ ДНИ, а к
|
||||
500 убийствам у игрока уже есть ферма, костёр, банки и фильтр - мод, дающий +2 воды с
|
||||
трупа, к этому моменту бесполезен. Теперь порог у мода стоит там, где мод реально нужен,
|
||||
а не там, где он "по силе" смотрится ровно.
|
||||
|
||||
Итоговая раскладка:
|
||||
30 - Слёзы мертвеца (вода) группа 1 "Адепт"
|
||||
60 - Пир падальщика (еда) группа 1
|
||||
100 - Могильный покой (тепло/холод) группа 1
|
||||
300 - Тёмное чутьё (радар) группа 1
|
||||
1400 - Хватка мертвеца (замедление) группа 2 "Подмастерье"
|
||||
1700 - Мёртвая буря (силовая) группа 2
|
||||
Первые четыре - выживание и информация, они переехали в группу 1 к самому ножу (он там и
|
||||
доступен с уровня 1). Последние два - чистый бой, остались в группе 2 на прежних порогах:
|
||||
пользователь про них сказал "дальше уже не так принципиально".
|
||||
|
||||
Двигать - тройка "тег в рецепте + RecipeTagUnlocked в progression.xml + unlock_tier в
|
||||
display_entry", все три должны совпадать, иначе замок на панели соврёт. -->
|
||||
<!-- Модификации Ножа некроманта (см. Config/item_modifiers.xml), 2026-09-07. Пользователь
|
||||
выбрал получение через "Крафт по скиллу Некромантии", поэтому все три идут обычными
|
||||
рецептами с тегом группы, а не через лут.
|
||||
|
||||
Пороги у каждого свои, см. комментарий выше - изначально все шесть сидели в группе 2
|
||||
("Подмастерье", 500 убийств), но 2026-09-09 четыре из них уехали в группу 1 к самому
|
||||
ножу. Распределение остальных тиров скилла эта правка не трогает - его пользователь уже
|
||||
правил вручную (см. progression.xml).
|
||||
|
||||
Ингредиенты - только из уже существующих ресурсов мода плюс ванильная база, как и просил
|
||||
("Ингредиенты из уже существующих ресурсов мода"). Прах зомби и Кожа жертвы обе падают
|
||||
из мешка "Жертва", то есть добываются этим же ножом - моды для ножа делаются из того, что
|
||||
нож добыл. Крафт личный (без craft_area), как у Камня духов и самого ножа: моды дешевле
|
||||
призывов и не должны требовать верстак. Тег learnable, как у остальных гейтованных
|
||||
рецептов мода, чтобы рецепт не светился в меню до открытия группы. -->
|
||||
<append xpath="/recipes">
|
||||
<recipe name="necroModKnifeTearsOfTheDead" count="1" tags="learnable,packMuleCrafting,necroNecromancyLvl30">
|
||||
<ingredient name="resourceZombieAsh" count="10"/>
|
||||
<ingredient name="resourceVictimSkin" count="1"/>
|
||||
<ingredient name="drinkJarEmpty" count="2"/>
|
||||
<ingredient name="resourceYuccaFibers" count="10"/>
|
||||
</recipe>
|
||||
<recipe name="necroModKnifeScavengersFeast" count="1" tags="learnable,packMuleCrafting,necroNecromancyLvl60">
|
||||
<ingredient name="resourceZombieAsh" count="10"/>
|
||||
<ingredient name="resourceVictimSkin" count="1"/>
|
||||
<ingredient name="foodRawMeat" count="5"/>
|
||||
<ingredient name="resourceYuccaFibers" count="10"/>
|
||||
</recipe>
|
||||
<recipe name="necroModKnifeDeadMansGrip" count="1" tags="learnable,packMuleCrafting,necroNecromancyLvl1400">
|
||||
<ingredient name="resourceZombieAsh" count="10"/>
|
||||
<ingredient name="resourceBone" count="15"/>
|
||||
<ingredient name="resourceNecromancerBlood" count="1"/>
|
||||
<ingredient name="resourceYuccaFibers" count="10"/>
|
||||
</recipe>
|
||||
<!-- "Могильный покой" (термозащита), добавлен 2026-09-07, порог 100 с 2026-09-09. Тот же
|
||||
личный крафт, что у остальных модов ножа. Кожа жертвы тут не косметика: мод по смыслу - обмотка
|
||||
рукояти, поэтому её взято 2 (больше всех), плюс перо как утеплитель - ванильный
|
||||
resourceFeather, существование проверено. -->
|
||||
<recipe name="necroModKnifeGravesRepose" count="1" tags="learnable,packMuleCrafting,necroNecromancyLvl100">
|
||||
<ingredient name="resourceZombieAsh" count="10"/>
|
||||
<ingredient name="resourceVictimSkin" count="2"/>
|
||||
<ingredient name="resourceFeather" count="10"/>
|
||||
<ingredient name="resourceYuccaFibers" count="10"/>
|
||||
</recipe>
|
||||
<!-- "Мёртвая буря" (переделка силовой атаки), 2026-09-07. Единственный мод ножа с
|
||||
электрическими деталями в рецепте - искрение берётся из ванильного buffShocked, того
|
||||
же, что у электродубинки, так что ингредиент тематически честный. Существование
|
||||
resourceElectricParts проверено. -->
|
||||
<!-- "Тёмное чутьё" (радар зомби), 2026-09-07. Глаз мертвеца как линза - отсюда кожа
|
||||
жертвы и прах. Стекло взято resourceBrokenGlass (обычный лут), а НЕ resourceScopeLens:
|
||||
та линза крафтится только в кузне и под перком perkAdvancedEngineering, то есть
|
||||
утащила бы рецепт ножа в зависимость от чужой ветки прокачки. resourceGlass, которое
|
||||
напрашивалось по названию, в игре не существует вовсе - проверено. -->
|
||||
<recipe name="necroModKnifeDarkSense" count="1" tags="learnable,packMuleCrafting,necroNecromancyLvl300">
|
||||
<ingredient name="resourceZombieAsh" count="15"/>
|
||||
<ingredient name="resourceVictimSkin" count="1"/>
|
||||
<ingredient name="resourceBrokenGlass" count="10"/>
|
||||
<ingredient name="resourceYuccaFibers" count="10"/>
|
||||
</recipe>
|
||||
|
||||
<recipe name="necroModKnifeDeadStorm" count="1" tags="learnable,packMuleCrafting,necroNecromancyLvl1700">
|
||||
<ingredient name="resourceZombieAsh" count="15"/>
|
||||
<ingredient name="resourceElectricParts" count="5"/>
|
||||
<ingredient name="resourceNecromancerBlood" count="1"/>
|
||||
<ingredient name="resourceYuccaFibers" count="10"/>
|
||||
</recipe>
|
||||
</append>
|
||||
</config>
|
||||
Reference in New Issue
Block a user