Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbd58ad018 | ||
|
|
e71cd69d1e | ||
|
|
d8f06cd53a | ||
|
|
6301a62a54 | ||
|
|
156e046cc2 | ||
|
|
90d8fe331b | ||
|
|
03b4b8f164 | ||
|
|
c832f734d7 | ||
|
|
8c5636b523 | ||
|
|
229b436420 | ||
|
|
e890999391 | ||
|
|
5a512260cc | ||
|
|
896501dcd8 | ||
|
|
e362c627e7 | ||
|
|
20af2bbe6c |
+20
-1
File diff suppressed because one or more lines are too long
+2
-2
@@ -11,7 +11,7 @@
|
||||
|
||||
<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">
|
||||
<buff name="buffNecroZombieKillTrackerDisplay" icon="ui_game_symbol_skull" icon_color="150,0,0" name_key="buffNecroZombieKillTrackerDisplayName" description_key="buffNecroZombieKillTrackerDisplayDesc">
|
||||
<stack_type value="ignore"/>
|
||||
<duration value="0"/>
|
||||
<update_rate value=".1"/>
|
||||
@@ -172,7 +172,7 @@
|
||||
состояние кадра верное, но пульсация иконки может подёргиваться раз в секунду. Если
|
||||
будет заметно - убрать строку снятия из onSelfBuffUpdate и оставить чистку только на
|
||||
onSelfBuffRemove, ценой залипших меток на ушедших зомби. -->
|
||||
<buff name="buffNecroDarkSense" name_key="buffNecroDarkSenseName" description_key="buffNecroDarkSenseDesc" icon="ui_game_symbol_zombie" icon_color="150,0,255">
|
||||
<buff name="buffNecroDarkSense" name_key="buffNecroDarkSenseName" description_key="buffNecroDarkSenseDesc" icon="ui_game_symbol_skull" icon_color="150,0,255">
|
||||
<stack_type value="replace"/>
|
||||
<duration value="0"/>
|
||||
<update_rate value="1"/>
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
<config>
|
||||
<!-- ВКЛАДКА ИСПЫТАНИЙ "Некромантия" (указание 2026-09-16: "Сделать вкладку квестов для
|
||||
некромантии. Типа тех вкладок, где дают опыт за установку первой двери, за установку
|
||||
клеймблока, за повешение факела и сбор ресурсов").
|
||||
|
||||
ОБА ЗАКАЗАННЫХ ИСПЫТАНИЯ НА МЕСТЕ с 2026-09-16. Сначала тут было одно ("Первым делом -
|
||||
нож"): challenges.xml мод не трогал ни разу, и сперва надо было увидеть в игре, что вкладка
|
||||
вообще появляется. Нож выбран для этой проверки потому, что крафтится сразу и без верстака,
|
||||
то есть проверка занимала минуту, а не сессию. Вкладка отобразилась, испытание закрылось,
|
||||
лог чистый - после чего добавлен "Безумный донор".
|
||||
|
||||
РАЗРОЗНЕННО, НЕ ЦЕПОЧКОЙ (указание: "цепочку ивентов там делать не нужно. Пусть будет
|
||||
разрозненно. Что сделано то и отмечаем"). Поэтому у группы НЕТ ни link_challenges="true",
|
||||
ни hidden_by - оба атрибута стоят у ванильных Basics/Homesteading именно затем, чтобы
|
||||
открывать записи по очереди. Без них испытания видны сразу и закрываются в любом порядке.
|
||||
Побочная польза: снимается противоречие порядка - Кровь некроманта нельзя скрафтить без
|
||||
ножа в инвентаре, так что "донор" первым в цепочке был бы невыполним.
|
||||
|
||||
ОТКУДА ИКОНКИ - разобрано 2026-09-16, и вывод ограничивающий: ТОЛЬКО из символьного
|
||||
атласа игры, свой спрайт туда положить нельзя.
|
||||
- В Data/Config/XUi_InGame/templates.xml оба места, где эти иконки рисуются, объявлены
|
||||
БЕЗ атрибута atlas: строка испытания (challenge_entry, sprite="{iconname}") и кнопка
|
||||
вкладки (category_icon, sprite="{categoryicon}"). Нет atlas - значит атлас по
|
||||
умолчанию, тот, где живут все ui_game_symbol_*.
|
||||
- Подтверждение с другой стороны: в ванильном challenges.xml ВСЕ 215 иконок имеют вид
|
||||
ui_game_symbol_* без единого исключения (проверено вычитанием).
|
||||
- Свой атлас мода (UIAtlases/NecroFinal) тут не поможет: механизм UIAtlases/<Имя>
|
||||
создаёт НОВЫЙ атлас, и обратиться к нему можно только явным atlas="NecroFinal" на
|
||||
спрайте, которого в этих шаблонах нет.
|
||||
- Дописать atlas= в сам шаблон НЕЛЬЗЯ: шаблон один на все испытания, и тогда ванильные
|
||||
215 иконок начнут искаться в нашем атласе и пропадут.
|
||||
Выбирали из 359 доступных ui_game_symbol_*, собранных по ванильным конфигам. -->
|
||||
<append xpath="/challenges">
|
||||
|
||||
<!-- ВКЛАДКА. Череп, и это ОДНО РЕШЕНИЕ С ИКОНКОЙ САМОГО СКИЛЛА "Некромантия" в
|
||||
progression.xml: вкладка и скилл обязаны читаться как одно целое, поэтому меняются
|
||||
только парой. Разойдутся - и панель скилла с вкладкой испытаний перестанут выглядеть
|
||||
как одна система.
|
||||
|
||||
Сначала (16.09) здесь стоял ui_game_symbol_zombie - по тому же принципу совпадения со
|
||||
скиллом, у которого он тогда и стоял. В игре вкладка отобразилась и испытание
|
||||
сработало, после чего пользователь заменил символ на череп: "Для некроманта это более
|
||||
атмосферно". Заменено разом во всех четырёх живых местах мода (здесь, скилл в
|
||||
progression.xml и два баффа в buffs.xml), разбор - в BACKLOG.md.
|
||||
|
||||
Годные альтернативы, если череп когда-нибудь разонравится:
|
||||
ui_game_symbol_specters_grace, ui_game_symbol_7th_curse,
|
||||
ui_game_symbol_near_death_trauma. -->
|
||||
<challenge_category name="NecroNecromancy" title_key="challengeCatNecroNecromancy" icon="ui_game_symbol_skull"/>
|
||||
|
||||
<!-- ГРУППА. Сознательно БЕЗ reward_text_key и reward_event.
|
||||
|
||||
Это осознанный риск скелета, а не забывчивость: у всех 20 ванильных групп они есть, но
|
||||
корень <challenges> объявлен с default_reward="challenge_reward_default" и
|
||||
default_reward_text_key="challenge_reward_100xp", и мы добавляемся именно в этот
|
||||
корень. Если умолчания на группы не распространяются, игра скажет об этом в логе - и
|
||||
это ровно то, ради чего скелет и запускается. Заводить своё событие награды вслепую
|
||||
дороже, чем один раз посмотреть в лог. -->
|
||||
<challenge_group category="NecroNecromancy" name="NecroPath" title_key="challengeGroupNecroPath"/>
|
||||
|
||||
<!-- ИСПЫТАНИЕ. objective type="Craft" без count - ножа нужен один.
|
||||
hint не задан: он необязателен - в ванили одно испытание из 188 обходится без него, а
|
||||
тут подсказка и не нужна, название говорит само за себя.
|
||||
|
||||
500 XP, А НЕ УМОЛЧАНИЕ. Сначала тут не стояло награды вовсе, то есть работал
|
||||
challenge_reward_default (100 XP), по аналогии с ванильным craftStoneAxe: один крафт в
|
||||
самом начале. Аналогия оказалась неверной, и это выяснилось только при чтении рецепта:
|
||||
в ножа входит 1 Кровь некроманта, а её собственный крафт снимает 90% ТЕКУЩЕГО здоровья.
|
||||
То есть нож стоит одного захода на порог смерти, чего у каменного топора нет и близко.
|
||||
500 - ванильный тир простых, но небесплатных дел (craftCharredMeat, huntAnimals). -->
|
||||
<challenge name="necroCraftKnife" title_key="challengeNecroCraftKnifeTitle" icon="ui_game_symbol_knife" group="NecroPath"
|
||||
short_description_key="challengeNecroCraftKnifeShort" description_key="challengeNecroCraftKnifeDesc"
|
||||
reward_text_key="challenge_reward_500xp" reward_event="challenge_reward_500">
|
||||
<objective type="Craft" item="necroWpnBladeNecroKnife"/>
|
||||
</challenge>
|
||||
|
||||
<!-- ИСПЫТАНИЕ 2: "Безумный донор" (указание: "добыть 15 флаконов крови некроманта").
|
||||
Кровь не добывается из мира, она крафтится, поэтому тип Craft, а не Gather.
|
||||
|
||||
count="15" - у Craft счётчик поддерживается, и это стоило отдельной проверки: в самых
|
||||
наглядных ванильных примерах (craftStoneAxe, craftClothes) он не указан, и легко
|
||||
решить, что его нет. Есть - 18 ванильных Craft-целей его несут, вплоть до
|
||||
resourceForgedIron count="100" и thrownAmmoPipeBomb count="20".
|
||||
|
||||
ИМЯ ОПРАВДАНО МЕХАНИКОЙ, А НЕ ПРОСТО КРАСИВОЕ: каждый крафт Крови некроманта снимает
|
||||
90% ТЕКУЩЕГО здоровья (NecromancerBloodPatch.cs) и требует нож в инвентаре. Пятнадцать
|
||||
раз подряд - это пятнадцать раз довести себя до полусмерти, и "Безумный донор" описывает
|
||||
это точно.
|
||||
|
||||
Зависимости от первого испытания нет и быть не должно (указание: "разрозненно"), хотя
|
||||
фактически кровь без ножа не скрафтить. Порядка у группы нет, так что игрок сам
|
||||
упрётся в нож, когда попробует начать с крови, - и это честнее замка.
|
||||
|
||||
icon="ui_game_symbol_siphoning_strikes" - "высасывающие удары", символ выкачивания
|
||||
жизни. Найден при разборе всех 359 доступных ui_game_symbol_*; под механику "плати
|
||||
собственной кровью" попадает точнее любого медицинского символа. -->
|
||||
<challenge name="necroMadDonor" title_key="challengeNecroMadDonorTitle" icon="ui_game_symbol_siphoning_strikes" group="NecroPath"
|
||||
short_description_key="challengeNecroMadDonorShort" description_key="challengeNecroMadDonorDesc"
|
||||
reward_text_key="challenge_reward_2500xp" reward_event="challenge_reward_2500">
|
||||
<objective type="Craft" item="resourceNecromancerBlood" count="15"/>
|
||||
</challenge>
|
||||
|
||||
<!-- ИСПЫТАНИЕ 3: "Первый подданный" - Камень духов (указание 2026-09-17: "И первый камень
|
||||
духов. Сам посчитай и придумай").
|
||||
|
||||
Имя про механику: Камень духов подчиняет зомби, и подчинённый бьётся за игрока. Это
|
||||
первое существо, которое слушается некроманта, - отсюда "подданный".
|
||||
|
||||
100 XP, то есть challenge_reward_default, награда НЕ ЗАДАНА СОЗНАТЕЛЬНО. Рецепт -
|
||||
1 камень + 20 волокон травы, ни праха, ни крови. Это самая дешёвая вещь мода и
|
||||
буквальный аналог ванильного craftStoneAxe, за который ваниль платит ровно умолчание.
|
||||
Здесь 100 - не занижение, а единственное честное число: поднять его выше значило бы
|
||||
платить за десять минут игры больше, чем ваниль платит за то же самое.
|
||||
|
||||
icon="ui_game_symbol_specters_grace" ("милость призрака") - предмет называется Камнем
|
||||
ДУХОВ и подчиняет мёртвого, так что символ совпадает и по названию, и по смыслу. -->
|
||||
<challenge name="necroSpiritStone" title_key="challengeNecroSpiritStoneTitle" icon="ui_game_symbol_specters_grace" group="NecroPath"
|
||||
short_description_key="challengeNecroSpiritStoneShort" description_key="challengeNecroSpiritStoneDesc">
|
||||
<objective type="Craft" item="thrownStoneSpirit"/>
|
||||
</challenge>
|
||||
|
||||
<!-- ИСПЫТАНИЕ 4: "Короткая дорога домой" - Синий портальный камень (указание 2026-09-17).
|
||||
|
||||
Имя про механику: камень телепортирует к спальному мешку за десятисекундный канал и не
|
||||
расходуется. Это в буквальном смысле короткий путь домой, и никакой другой предмет мода
|
||||
этого не делает.
|
||||
|
||||
2000 XP - и это САМОЕ ДОРОГОЕ ИСПЫТАНИЕ ВКЛАДКИ ПО ГРАЙНДУ, что видно только из
|
||||
рецепта: 1 камень + 3 минералки + 15 волокон + 150 ПРАХА ЗОМБИ. Прах падает с Жертвы,
|
||||
помеченной Ножом некроманта, по 1-4 с вероятностью 0.4 (loot.xml), то есть в среднем
|
||||
около одной единицы за убийство - значит 150 праха это порядка ста пятидесяти
|
||||
ритуальных убийств ножом. Для сравнения, ванильный craftForgedIron (сто крафтов
|
||||
кованого железа) платит 2000, и это чистый грайнд без риска - ровно тот же случай.
|
||||
Выше не ставим: 2500 у "Безумного донора" оплачивает смертельный риск, которого здесь
|
||||
нет, а 5000 ваниль держит за химстанцией и тирами квестов.
|
||||
|
||||
icon="ui_game_symbol_map_house" - дом, к которому камень и возвращает. -->
|
||||
<challenge name="necroPortalStoneBlue" title_key="challengeNecroPortalBlueTitle" icon="ui_game_symbol_map_house" group="NecroPath"
|
||||
short_description_key="challengeNecroPortalBlueShort" description_key="challengeNecroPortalBlueDesc"
|
||||
reward_text_key="challenge_reward_2000xp" reward_event="challenge_reward_2000">
|
||||
<objective type="Craft" item="thrownStonePortalBlue"/>
|
||||
</challenge>
|
||||
|
||||
</append>
|
||||
</config>
|
||||
+25
-13
@@ -1,17 +1,29 @@
|
||||
<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>
|
||||
<!-- СЧЁТ УБИЙСТВ ПЕРЕЕХАЛ В КОД 2026-09-16. Здесь СОЗНАТЕЛЬНО ничего нет, и вернуть это
|
||||
обратно нельзя - см. HarmonySrc/NecromancyKillCreditPatch.cs.
|
||||
|
||||
Тут стоял append на zombieTemplateMale с двумя onOtherKilledSelf-эффектами
|
||||
(ModifyCVar necroZombieKillsCVar и AddProgressionLevel craftingNecroNecromancy) под общим
|
||||
требованием EntityTagCompare target="other" tags="player". Он был сломан дважды:
|
||||
|
||||
1. Один класс - не все зомби. Пять зомби-ЗВЕРЕЙ наследуют животную ветку и до
|
||||
zombieTemplateMale не доходят вовсе (animalZombieBear extends animalBear,
|
||||
animalZombieBoar extends animalBoar, animalZombieDog extends animalWolf,
|
||||
animalZombieVulture extends animalTemplateHostile, animalZombieVultureRadiated).
|
||||
Убийство зомбопса, зомбомедведя, зомбокабана и зомбоворона не считалось никак.
|
||||
2. target="other" - это БУКВАЛЬНЫЙ убийца, а не тот, кому ваниль зачла убийство.
|
||||
Робомолот, кровотечение и питомцы требование tags="player" не проходят, хотя опыт
|
||||
игрок за них получает: ваниль определяет получателя по DamageSource, в
|
||||
EntityAlive.AwardKillXPServer.
|
||||
|
||||
Требование было одно на оба эффекта, поэтому вместе со скиллом недосчитывался и
|
||||
necroZombieKillsCVar - то есть занижался урон Ножа некроманта (items.xml: Damage =
|
||||
necroZombieKillsCVar / 10).
|
||||
|
||||
ОБА эффекта теперь делает Postfix на EntityPlayer.AddKillXP - единственной точке, где
|
||||
ваниль уже решила, чей это фраг. ЕСЛИ ВЕРНУТЬ ЭТОТ 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
|
||||
|
||||
@@ -324,4 +324,197 @@
|
||||
</item_modifier>
|
||||
|
||||
</append>
|
||||
|
||||
<!-- "Кровавая сфера" (Blood Sphere) - расходный заряд Пространственного браслета.
|
||||
Название и рецепт продиктованы 2026-09-15: «Кровавая сфера. Доступна на первом грейде.
|
||||
Станки не нужны. Ингридиенты: Кровь некроманта, 5 праха зомби. По одному рецепту
|
||||
изготавливается две сферы. Прочность сферы 500.»
|
||||
|
||||
ЗАЧЕМ ОНА ВООБЩЕ СУЩЕСТВУЕТ - и почему это НЕ «кровь, вставляемая в браслет». 15.09.2026
|
||||
кровь перенесли в этот файл, чтобы она вставлялась в браслет, и это уничтожило персонажа в
|
||||
сейве вместе с бэкапом: класс предмета определяет байтовую раскладку его стака
|
||||
(ItemValue.Read:1094 / Write:1228 - обычный предмет пишет байт числа модификаций,
|
||||
ItemClassModifier не пишет), и старый сейв стал нечитаемым. Полный разбор - в BACKLOG.md и
|
||||
в большом предупреждении у крови в items.xml.
|
||||
|
||||
Сфера обходит это тем, что она НОВЫЙ предмет: в старых сейвах её нет, значит нет и ни
|
||||
одного стака, который читался бы по другой раскладке. Это общее правило, а не уловка:
|
||||
нужна модификация - заводи новый предмет, никогда не переводи существующий.
|
||||
|
||||
Атрибуты - по образцу модов ножа:
|
||||
installable_tags="necroBracelet" - иначе модификация лезла бы в ЛЮБОЙ предмет
|
||||
(CanSwap короткозамыкается на InstallableTags.IsEmpty);
|
||||
modifier_tags="necroBraceletSphere" - свой, чтобы не конкурировать с будущим Кровавым
|
||||
камнем через MaxModsAllowed;
|
||||
blocked_tags НЕ задан - у браслета в тегах "noMods", объявить его тут значило бы
|
||||
заблокировать самому себе установку;
|
||||
type="attachment" - сферу можно вынуть обратно. -->
|
||||
<append xpath="/item_modifiers">
|
||||
<item_modifier name="resourceBloodSphere" installable_tags="necroBracelet" modifier_tags="necroBraceletSphere" type="attachment">
|
||||
<!-- Extends на modGeneralMaster - та же база, что у шести модов ножа: даёт Group
|
||||
"Mods", звуки mod_grab/mod_place, Stacknumber 1 и CreativeMode None.
|
||||
param1="CustomIcon" исключает наследование родительского missingIcon.
|
||||
|
||||
РАНЬШЕ ЗДЕСЬ СТОЯЛ Extends="resourceRockSmall", И ЭТО БЫЛА ОШИБКА. Камень нужен
|
||||
был только ради вида, а вместе с ним приезжали Action0 Class="ThrowAway",
|
||||
ThrowableDecoy="true" и DistractionTags - то есть сферу можно было бы метать как
|
||||
отвлекающий камень. Меш берётся строкой Meshfile ниже; наследовать ради него
|
||||
весь предмет не нужно. -->
|
||||
<property name="Extends" value="modGeneralMaster" param1="CustomIcon"/>
|
||||
<property name="DescriptionKey" value="resourceBloodSphereDesc"/>
|
||||
|
||||
<!-- В РУКЕ - КАМЕНЬ С АЛЫМ ТИНТОМ (указание 2026-09-15: «в руке и сфера и кровавый
|
||||
камень пусть будут как камень с алым тинтом»).
|
||||
|
||||
Это уже проверенный в этом моде путь, а не догадка: Камень духов, Синий и Чёрный
|
||||
порталы (items.xml) сидят ровно на этом меше с ровно таким тинтом и в игре
|
||||
работают - зелёный, синий и чёрный камни соответственно. Поэтому взят их набор
|
||||
целиком: HoldType 40 плюс ВСЕ ТРИ меша. Три, а не один, потому что это три разные
|
||||
ситуации - Meshfile общий, HandMeshfile в руке, DropMeshfile лежащим на земле, и
|
||||
у тех трёх предметов они выписаны явно именно поэтому.
|
||||
|
||||
TintColor - ТРИПЛЕТ "R, G, B", а НЕ hex. Это другая ручка, чем CustomIconTint
|
||||
выше (там hex): ItemClass парсит их разными путями - Color32 через запятые против
|
||||
ParseHexColor. Перепутать легко, и на модах ножа это уже стоило круга.
|
||||
|
||||
ПОЧЕМУ ЗДЕСЬ ТИНТ РАБОТАЕТ, А НА БАНКЕ КРОВИ НЕ СРАБОТАЛ. 10.09 тинт предмета на
|
||||
меш чая из золотарника не подействовал вообще: у шейдера Game_EntityTintMaskSSS
|
||||
выигрывает собственный _Color материала. У rock_smallPrefab такого конфликта нет -
|
||||
доказательство лежит в самом моде, три перекрашенных камня в игре видны. -->
|
||||
<property name="HoldType" value="40"/>
|
||||
<property name="Meshfile" value="@:Other/Items/Crafting/rock_smallPrefab.prefab"/>
|
||||
<property name="HandMeshfile" value="@:Other/Items/Crafting/rock_smallPrefab.prefab"/>
|
||||
<property name="DropMeshfile" value="@:Other/Items/Crafting/rock_smallPrefab.prefab"/>
|
||||
<property name="TintColor" value="220, 30, 45"/>
|
||||
|
||||
<!-- Своя рисованная иконка, получена 2026-09-15 (exch/bloodSphere.png, 160x160 RGBA,
|
||||
как все остальные 28). Заглушка со спрайтом Камня духов и багровым тинтом,
|
||||
стоявшая тут несколько часов, снята.
|
||||
|
||||
CustomIconTint НЕ ЗАДАЁТСЯ, и это тот же принцип, что у всех рисованных иконок
|
||||
мода (шесть модов ножа, Кровь, Жертвенная кожа): тинт существует, чтобы
|
||||
заимствованный ванильный спрайт не читался как предмет, из которого он взят. На
|
||||
готовой работе он бы просто её затемнил. -->
|
||||
<property name="CustomIcon" value="BloodSphere"/>
|
||||
|
||||
<!-- Прочность 500 (указание). Две ручки, обе обязательны - тот же разбор, что у крови
|
||||
в items.xml: ShowQuality рисует полоску, а само число идёт пассивкой
|
||||
DegradationMax. Без пассивки прочность равна нулю, а полоска при MaxUseTimes == 0
|
||||
рисуется ПОЛНОЙ, то есть забытый эффект выглядит как "всё работает".
|
||||
|
||||
tiered="false" обязателен: HasQuality читается как Effects.IsOwnerTiered(), и
|
||||
тированная группа превратила бы сферу в предмет с качеством - тиры, рамка.
|
||||
|
||||
Гейта по тегу здесь, в отличие от крови, НЕ НУЖНО. У модификации effect_group
|
||||
применяется к предмету-хозяину, и у крови пассивку приходилось гейтить, чтобы
|
||||
1000 прочности не досталась браслету. Сфера же и есть расходник браслета: пусть
|
||||
он её и тратит. Браслету от DegradationMax ничего не будет - ему эту прочность
|
||||
никто не списывает (SpendCharge трогает только сферу), а полоски у него нет. -->
|
||||
<property name="ShowQuality" value="true"/>
|
||||
<!-- true: опустевшая сфера должна исчезать, а не лежать "сломанной" в ожидании
|
||||
ремонта. Само удаление из слота делает SpendCharge - см. SpatialVaultPickupPatch. -->
|
||||
<property name="DegradationBreaksAfter" value="true"/>
|
||||
<effect_group name="resourceBloodSphere" tiered="false">
|
||||
<passive_effect name="DegradationMax" operation="base_set" value="500"/>
|
||||
</effect_group>
|
||||
|
||||
<!-- Stacknumber 1 приходит из modGeneralMaster, своя строка не нужна. -->
|
||||
<property name="EconomicValue" value="0"/>
|
||||
<property name="SellableToTrader" value="false"/>
|
||||
</item_modifier>
|
||||
</append>
|
||||
|
||||
<!-- КРОВАВЫЙ КАМЕНЬ (Blood Stone), продиктовано 2026-09-15, сделано 2026-09-16: «По сути это
|
||||
будет тот же флакон, но с бесконечной прочностью и иконкой камня с алым тинтом.»
|
||||
Эндгейм-версия расходника браслета: то же, что Кровавая сфера, только не тратится.
|
||||
|
||||
ПОЧЕМУ ЭТО БЫЛО СРОЧНО, А НЕ «ФИЧА ИЗ СПИСКА». Сообщение о пустом слоте
|
||||
(braceletSpatialVaultNoMod) и описание браслета с 15.09 обещают игроку «кровавую сферу
|
||||
ИЛИ кровавый камень» на всех 13 языках. Пока камня не было, отгруженный текст отправлял
|
||||
за предметом, которого в игре нет. Так что эта правка - ещё и починка текста.
|
||||
|
||||
ПОЧЕМУ ЭТО БЕЗОПАСНО ДЛЯ СЕЙВОВ, и это здесь главный вопрос, а не побочный.
|
||||
15.09 перенос Крови некроманта из items.xml сюда уничтожил персонажа в сейве: ItemValue
|
||||
гейтит блок модификаций одним условием на чтении (Read, ~1094) и на записи (Write, ~1228)
|
||||
- "!(itemClass is ItemClassModifier)", то есть КЛАСС ПРЕДМЕТА ОПРЕДЕЛЯЕТ БАЙТОВУЮ
|
||||
РАСКЛАДКУ КАЖДОГО ЕГО СТАКА В СЕЙВЕ. Обычный предмет пишет байт "сколько модификаций",
|
||||
ItemClassModifier не пишет ничего; после переезда читатель этот байт пропустил, поток
|
||||
съехал, ближайший ReadString() упал, и .ttp с .ttp.bak умерли оба.
|
||||
resourceBloodStone - НОВОЕ имя. В сейвах, записанных до этой правки, нет ни одного его
|
||||
стака, значит нет и ни одной записи, которую пришлось бы читать по другой раскладке.
|
||||
Именно поэтому камень заведён новым предметом, а не переводом уже существующего - тем же
|
||||
решением, что и Кровавая сфера. Правило целиком: раздел про аварию в BACKLOG.md.
|
||||
|
||||
Атрибуты - по образцу сферы:
|
||||
- installable_tags="necroBracelet" - обязательно. У CanSwap есть короткое замыкание
|
||||
"InstallableTags.IsEmpty || ...", то есть модификация БЕЗ этого атрибута лезет в любой
|
||||
предмет игры.
|
||||
- modifier_tags СВОЙ (necroBraceletStone, не ...Sphere). Мод с совпадающим modifier_tags
|
||||
считается против ItemClass.MaxModsAllowed - разбор в шапке этого файла. Слот у
|
||||
браслета сейчас один, так что практической разницы нет, но правило "свой тег на
|
||||
каждую модификацию" в моде записано, и ломать его незачем.
|
||||
- blocked_tags НЕ задаётся: у браслета в тегах noMods, отрицательную половину он
|
||||
обеспечивает сам.
|
||||
-->
|
||||
<append xpath="/item_modifiers">
|
||||
<item_modifier name="resourceBloodStone" installable_tags="necroBracelet" modifier_tags="necroBraceletStone" type="attachment">
|
||||
<!-- Та же база, что у сферы и у шести модов ножа: Group "Mods", звуки
|
||||
mod_grab/mod_place, Stacknumber 1, CreativeMode None. param1="CustomIcon"
|
||||
исключает наследование родительского missingIcon.
|
||||
НЕ наследовать resourceRockSmall ради вида камня: вместе с видом приедут
|
||||
Action0 Class="ThrowAway", ThrowableDecoy="true" и DistractionTags, то есть
|
||||
камень можно будет метать как отвлекающий. Меш берётся строками ниже. На сфере
|
||||
эта ошибка уже была и была исправлена 15.09. -->
|
||||
<property name="Extends" value="modGeneralMaster" param1="CustomIcon"/>
|
||||
<property name="DescriptionKey" value="resourceBloodStoneDesc"/>
|
||||
|
||||
<!-- В РУКЕ - КАМЕНЬ С АЛЫМ ТИНТОМ (указание 2026-09-15: «в руке и сфера и кровавый
|
||||
камень пусть будут как камень с алым тинтом»). Набор скопирован со сферы, а та -
|
||||
с трёх камней мода (Камень духов, Синий и Чёрный порталы), которые на этом же
|
||||
меше с этим же тинтом в игре видны зелёным, синим и чёрным.
|
||||
|
||||
Три меша, а не один, потому что это три разные ситуации: Meshfile общий,
|
||||
HandMeshfile в руке, DropMeshfile лежащим на земле.
|
||||
|
||||
TintColor - ТРИПЛЕТ "R, G, B", а не hex; hex берёт CustomIconTint, и это другая
|
||||
ручка (Color32 через запятые против ParseHexColor). Перепутать легко, на модах
|
||||
ножа это уже стоило круга. -->
|
||||
<property name="HoldType" value="40"/>
|
||||
<property name="Meshfile" value="@:Other/Items/Crafting/rock_smallPrefab.prefab"/>
|
||||
<property name="HandMeshfile" value="@:Other/Items/Crafting/rock_smallPrefab.prefab"/>
|
||||
<property name="DropMeshfile" value="@:Other/Items/Crafting/rock_smallPrefab.prefab"/>
|
||||
<property name="TintColor" value="220, 30, 45"/>
|
||||
|
||||
<!-- Своя рисованная иконка, лежит с 15.09 в обеих папках атласа
|
||||
(UIAtlases/ItemIconAtlas/BloodStone.png + ItemIconAtlasGreyscale/BloodStone.png).
|
||||
Серая копия обязательна: заблокированная запись в панели скилла рисуется из
|
||||
greyscale-атласа, без неё у записи не было бы картинки вообще.
|
||||
CustomIconTint НЕ ЗАДАЁТСЯ - тинт нужен заимствованным ванильным спрайтам, на
|
||||
готовой работе он бы её просто затемнил. Тот же принцип, что у сферы. -->
|
||||
<property name="CustomIcon" value="BloodStone"/>
|
||||
|
||||
<!-- БЕСКОНЕЧНАЯ ПРОЧНОСТЬ СДЕЛАНА ОТСУТСТВИЕМ СТРОК, А НЕ БОЛЬШИМ ЧИСЛОМ.
|
||||
Здесь СОЗНАТЕЛЬНО нет ни ShowQuality, ни DegradationBreaksAfter, ни effect_group
|
||||
с DegradationMax - сравнить со сферой выше, у которой все три есть.
|
||||
|
||||
Почему этого достаточно: SpendCharge (HarmonySrc/SpatialVaultPickupPatch.cs)
|
||||
списывает прочность ТОЛЬКО у модификации, чьё имя совпадает с ChargeItemName
|
||||
("resourceBloodSphere"). Камень под это условие не попадает по определению -
|
||||
цикл делает continue, UseTimes камня не трогает никто и никогда. Кода менять не
|
||||
пришлось вообще; комментарий в SpendCharge это заранее и обещает («a mod that is
|
||||
not the charge pays nothing, is not emptied, and the pull still happens»).
|
||||
|
||||
Почему НЕ выставлять огромный DegradationMax: полоска прочности, которая никогда
|
||||
не двигается, хуже её отсутствия. Плюс ловушка, уже разобранная на флаконе крови:
|
||||
при MaxUseTimes == 0 полоска рисуется ПОЛНОЙ, то есть забытая пассивка выглядит
|
||||
как "всё работает".
|
||||
|
||||
Тег necroBloodFlask здесь тоже не нужен, но уже по другой причине, чем было
|
||||
записано в спеке: после полного отката Крови некроманта (15.09) этого тега в моде
|
||||
не осталось совсем - он существовал только чтобы гейтить пассивку прочности
|
||||
флакона. Пункт снят сам собой. -->
|
||||
<property name="EconomicValue" value="0"/>
|
||||
<property name="SellableToTrader" value="false"/>
|
||||
</item_modifier>
|
||||
</append>
|
||||
</config>
|
||||
|
||||
+78
-46
@@ -1288,63 +1288,79 @@
|
||||
its mesh/material/hold/pickup-sound (reused wholesale, thematically it IS a bag of blood,
|
||||
just a darker/necromantic one) - CustomIcon still set explicitly even though Extends is
|
||||
used, same lesson as every other item in this mod (Extends alone never gives a working
|
||||
icon, confirmed originally on the Knife). Reuses the REAL vanilla sprite named
|
||||
"medicalBloodBag" itself (that item has no CustomIcon of its own, so its sprite name
|
||||
equals its item name) rather than a generated-art file, per direct instruction ("Иконка
|
||||
такая же как и у обычной крови, но тинт затемнённый") - only TintColor differs (dark,
|
||||
near-black red vs. no tint on the vanilla bag).
|
||||
icon, confirmed originally on the Knife).
|
||||
|
||||
ЭТОТ ПРЕДМЕТ НИКОГДА НЕ ДОЛЖЕН СТАТЬ <item_modifier>. ЭТО НЕ СТИЛЬ, ЭТО СЕЙВЫ.
|
||||
15.09.2026 он был перенесён в item_modifiers.xml, чтобы вставляться в Пространственный
|
||||
браслет, и это уничтожило персонажа в тестовом мире - вместе с бэкапом. Разбор целиком в
|
||||
BACKLOG.md, здесь суть, потому что соблазн повторить велик:
|
||||
|
||||
ItemValue.Read строка 1094: if ((version > 4 || HasQuality) && !(itemClass is ItemClassModifier))
|
||||
ItemValue.Write строка 1228: if (!(ItemClass is ItemClassModifier))
|
||||
|
||||
Класс предмета решает БАЙТОВУЮ РАСКЛАДКУ каждого его стака в сейве: обычный предмет пишет
|
||||
байт числа модификаций, ItemClassModifier - не пишет. Значит любой сейв, записанный до
|
||||
переноса, после переноса читается со сдвигом: поток съезжает, ближайший ReadString()
|
||||
получает мусор, PlayerDataFile.Load падает с "output char buffer is too small", и игра
|
||||
откатывается на NewGame. Бэкап .ttp.bak умирает вместе с основным файлом - он старого
|
||||
формата ровно так же. Кровь лежит в сейвах у всех, кто поставил 1.1.0 с Nexus.
|
||||
|
||||
Правило на будущее: предмет, который уже мог попасть в чужой инвентарь, нельзя переводить
|
||||
между ItemClass и ItemClassModifier ни в какую сторону. Нужна модификация - это НОВЫЙ
|
||||
предмет с новым именем, которого в старых сейвах нет. Проверка "айди не поедут"
|
||||
(assignIdsFromMapping) к этому отношения не имеет и ничего тут не гарантирует - именно на
|
||||
неё я и посмотрел вместо раскладки.
|
||||
|
||||
Crafting rules ("для создания нужна пустая банка и наличие любого ножа. При крафте нужно
|
||||
отнимать у персонажа 90% имеющегося ХП") - the jar is a normal recipe ingredient (see
|
||||
recipes.xml), but "any knife present, not consumed" and "cost 90% of current HP" have NO
|
||||
vanilla XML equivalent (recipes.xml has no per-ingredient "required but not consumed" flag,
|
||||
and crafting a resource has no HP-cost hook at all) - both enforced in
|
||||
HarmonySrc/NecromancerBloodPatch.cs instead. See that file for the exact decompiled
|
||||
mechanism and an important caveat about ingredient-refund timing that's flagged there, not
|
||||
glossed over. -->
|
||||
HarmonySrc/NecromancerBloodPatch.cs instead. -->
|
||||
<append xpath="/items">
|
||||
<item name="resourceNecromancerBlood">
|
||||
<property name="Extends" value="medicalBloodBag"/>
|
||||
<property name="DescriptionKey" value="resourceNecromancerBloodDesc"/>
|
||||
<!-- Custom art delivered 2026-08-30 (exch/NecromantsBlood.png, 160x160, copied to
|
||||
UIAtlases/ItemIconAtlas/) - replaces the earlier placeholder that reused the
|
||||
vanilla medicalBloodBag sprite with a darkened tint. No CustomIconTint here,
|
||||
same reasoning as every other hand-drawn icon in this mod (Dog/Insect summon
|
||||
books, etc.) - don't recolor finished art. -->
|
||||
<!-- Своя рисованная иконка, 2026-08-30. CustomIcon задаётся явно даже при Extends. -->
|
||||
<property name="CustomIcon" value="NecromantsBlood"/>
|
||||
|
||||
<!-- СВОЯ БАНКА С КРОВЬЮ, 2026-09-10 (указание: «берём чай из золотарника, и жёлтое
|
||||
заменяем на кровавый цвет, с фиолетовыми оттенками»).
|
||||
<!-- Своя банка с кровью, 2026-09-10 (указание: «берём чай из золотарника, и жёлтое
|
||||
заменяем на кровавый цвет, с фиолетовыми оттенками»). Заодно чинилось расхождение
|
||||
текста и модели: описание говорит «Банка, наполненная кровью», а наследуемый
|
||||
medicalBloodBag показывал sackPrefab - обычный мешок.
|
||||
|
||||
Заодно чинится расхождение текста и модели: описание предмета
|
||||
(resourceNecromancerBloodDesc) с самого начала говорит «Банка, наполненная кровью
|
||||
самого некроманта», а наследуемый medicalBloodBag показывает
|
||||
@:Other/Items/Misc/sackPrefab.prefab - обычный мешок. Банка вернее и по механике:
|
||||
рецепт и так требует пустую банку (recipes.xml, NecromancerBloodPatch.cs).
|
||||
ПОЧЕМУ НЕ ХВАТИЛО ТИНТА - ПРОВЕРЕНО В ИГРЕ. Ванильный префаб чая плюс TintColor:
|
||||
банка осталась чаем из золотарника, тинт предмета на этот меш НЕ ПОДЕЙСТВОВАЛ
|
||||
ВООБЩЕ - у шейдера Game_EntityTintMaskSSS выигрывает собственный _Color материала.
|
||||
Поэтому TintColor здесь не задаётся совсем.
|
||||
|
||||
ПОЧЕМУ НЕ ХВАТИЛО ТИНТА - ПРОВЕРЕНО В ИГРЕ. Сначала пробовали дёшево, без бандла:
|
||||
ванильный префаб чая плюс TintColor. Проверка 2026-09-10 показала, что банка
|
||||
осталась чаем из золотарника - тинт предмета на этот меш НЕ ПОДЕЙСТВОВАЛ ВООБЩЕ.
|
||||
У шейдера Game_EntityTintMaskSSS выигрывает собственный _Color материала (у чая
|
||||
жёлтый, 166,133,37), и свойство предмета его не перебивает. Поэтому TintColor здесь
|
||||
не задаётся совсем: он ничего не даёт и только вводил бы в заблуждение.
|
||||
И по сути: кровь отличается от чая не цветом, а тем, что она непрозрачная, тёмная
|
||||
и густая, с плёнкой на стекле. Жидкость ПЕРЕРИСОВАНА по яркости, а не перекрашена
|
||||
множителем - генератор _private/tools/make_necroblood_textures.py.
|
||||
|
||||
И по сути: кровь отличается от чая не цветом, а тем, что она непрозрачная, тёмная и
|
||||
густая, с плёнкой на стекле. Поэтому жидкость ПЕРЕРИСОВАНА по яркости, а не
|
||||
перекрашена множителем - генератор _private/tools/make_necroblood_textures.py.
|
||||
|
||||
HoldType 3 - хват банки вместо 45 (мешок), Material Mglass - стекло вместо ткани
|
||||
(звук удара и осколки при разбитии). Без них банка держалась бы как мешок.
|
||||
|
||||
ЧТО СМОТРЕТЬ ГЛАЗАМИ. Материал собран на встроенном Standard в режиме Fade: родной
|
||||
шейдер переиспользовать нельзя, AssetRipper выгрузил шейдеры заглушками. У Standard
|
||||
альфа текстуры - это прозрачность, поэтому она задана осознанно: стекло
|
||||
полупрозрачное, жидкость плотная. Банка должна читаться как стекло с густой кровью,
|
||||
а не как матовый сосуд. -->
|
||||
HoldType 3 - хват банки вместо 45 (мешок), Material Mglass - стекло вместо ткани. -->
|
||||
<property name="Meshfile" value="#@modfolder(NecromancerTome):Resources/necroblood?necroBloodPrefab.prefab"/>
|
||||
<property name="HoldType" value="3"/>
|
||||
<property name="Material" value="Mglass"/>
|
||||
|
||||
<!-- НИ Stacknumber, НИ ПРОЧНОСТИ ЗДЕСЬ НЕТ, И ЭТО НАМЕРЕННО.
|
||||
|
||||
15.09.2026 крови на один заход выдали и то, и другое: стак по одной банке и
|
||||
прочность 1000. Обе правки существовали ради одного - кровь должна была стать
|
||||
расходником, вставляемым в Пространственный браслет. Эта затея откачена (она
|
||||
ломала сейвы, см. предупреждение выше), расходником стала Кровавая сфера, и по
|
||||
прямому указанию пользователя кровь возвращена к тому, чем была:
|
||||
|
||||
- стак снова 15 - наследуется от medicalBloodBag, своей строки Stacknumber
|
||||
больше нет. Своя строка была нужна только чтобы перебить наследуемое 15 на 1;
|
||||
- прочности нет вовсе: ни ShowQuality, ни DegradationBreaksAfter, ни
|
||||
effect_group с DegradationMax. Тратил её браслет; тратить стало некому, а
|
||||
полоска, которая никогда не двигается, хуже, чем её отсутствие.
|
||||
|
||||
Кровь снова то, чем была с самого начала: ингредиент рецептов, и только.
|
||||
Если прочность когда-нибудь понадобится - разбор обеих ручек (пассивка
|
||||
DegradationMax плюс отдельное свойство ShowQuality, и ловушка с полной полоской
|
||||
при MaxUseTimes == 0) лежит в BACKLOG.md, повторно раскапывать не нужно. -->
|
||||
<property name="EconomicValue" value="0"/>
|
||||
</item>
|
||||
</append>
|
||||
@@ -1398,7 +1414,8 @@
|
||||
<append xpath="/items">
|
||||
<item name="braceletSpatialVault">
|
||||
<!-- MOD SLOTS ADDED 2026-09-13 ("добавь хранилищу 4 слота под модификации. Сами
|
||||
модификации реализуем потом"). Two tags, exactly the scheme necroWpnBladeNecroKnife
|
||||
модификации реализуем потом"), УБАВЛЕНЫ ДО ОДНОГО 2026-09-15 - число стоит в
|
||||
effect_group в самом низу этого предмета, здесь только теги. Two tags, exactly the scheme necroWpnBladeNecroKnife
|
||||
already proved on 2026-09-07 - see that item's own comment for the full
|
||||
decompiled reasoning:
|
||||
|
||||
@@ -1536,14 +1553,29 @@
|
||||
bar), and it is deliberately left unset here. The item behaves as tiered for the
|
||||
mod system and still reads as a plain bracelet in the UI.
|
||||
|
||||
FOR THE MODS THEMSELVES, WHEN THEY GET WRITTEN: give each one its OWN
|
||||
modifier_tags. XUiC_ItemPartStack.CanSwap counts already-installed mods whose
|
||||
modifier_tags intersect the one being installed and refuses at
|
||||
num >= ItemClass.MaxModsAllowed, which defaults to 1 - so a shared tag like
|
||||
"necroBraceletMod" across all four would leave exactly one of these four slots
|
||||
usable. -->
|
||||
FOR THE MODS THEMSELVES: give each one its OWN modifier_tags.
|
||||
XUiC_ItemPartStack.CanSwap counts already-installed mods whose modifier_tags
|
||||
intersect the one being installed and refuses at num >= ItemClass.MaxModsAllowed,
|
||||
which defaults to 1. With a single slot this no longer costs slots - but it still
|
||||
matters, because a shared tag would ALSO make two different bracelet mods mutually
|
||||
exclusive in ways nothing in the UI explains. Keep them distinct.
|
||||
|
||||
ОДИН СЛОТ, указание 2026-09-15 («убавь у пространственного браслета количество
|
||||
слотов под модификации до одного»). Было 4, поставленные 2026-08-30 по прежнему
|
||||
выбору пользователя («4 фиксированно»).
|
||||
|
||||
Что это меняет по сути: слот из «набора улучшений» превратился в ВЫБОР. Сейчас
|
||||
единственный кандидат - флакон Крови некроманта (item_modifiers.xml, переехал туда
|
||||
2026-09-15), так что выбирать пока не из чего; но каждая следующая модификация
|
||||
браслета теперь конкурирует за один слот, а не добавляется к остальным. Это стоит
|
||||
держать в голове при их придумывании - иначе получится набор, из которого всегда
|
||||
берут одну и ту же.
|
||||
|
||||
Число только здесь. ModSlots - пассивка, а не свойство, и никакой другой файл на
|
||||
него не смотрит; менять обратно - эта же строка. У Ножа некроманта свои 4 слота
|
||||
(выше в этом файле, ~строка 1056) - их указание НЕ трогало. -->
|
||||
<effect_group name="braceletSpatialVault">
|
||||
<passive_effect name="ModSlots" operation="base_set" value="4"/>
|
||||
<passive_effect name="ModSlots" operation="base_set" value="1"/>
|
||||
</effect_group>
|
||||
</item>
|
||||
</append>
|
||||
|
||||
+26
-3
@@ -87,7 +87,7 @@
|
||||
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">
|
||||
<crafting_skill name="craftingNecroNecromancy" max_level="5000" parent="attCrafting" name_key="craftingNecroNecromancyName" desc_key="craftingNecroNecromancyDesc" long_desc_key="craftingNecroNecromancyLongDesc" icon="ui_game_symbol_skull">
|
||||
|
||||
<!-- 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,
|
||||
@@ -102,7 +102,19 @@
|
||||
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"/>
|
||||
<!-- resourceBloodSphere ПЕРЕЕХАЛА СЮДА ИЗ tier 1, 2026-09-16.
|
||||
Сначала (15.09) она стояла на tier 1 по указанию «доступна на первом грейде»,
|
||||
то есть с уровня 1. Но вставляется она в Пространственный браслет, а тот
|
||||
открывается на tier 2 (уровень 20) - ровно этой же строкой. Девятнадцать
|
||||
уровней игрок мог крафтить расходник к предмету, которого у него нет.
|
||||
По записанному правилу мода «пороги - по нужде, а не по силе» порог ставится
|
||||
туда, где предмет впервые нужен, - здесь это момент появления браслета.
|
||||
Парная правка в recipes.xml: рецепту добавлены теги learnable и
|
||||
necroNecromancyLvl20. Оба уровня ОБЯЗАНЫ совпадать: RecipeTagUnlocked для
|
||||
necroNecromancyLvl20 стоит на level="20,5000" ниже, а tier 2 в
|
||||
unlock_level="1,20,30,60,100,300" - это те же 20. Разойдись они, замок на
|
||||
панели скилла разошёлся бы с реальной доступностью рецепта. -->
|
||||
<unlock_entry item="braceletSpatialVault,resourceBloodSphere" unlock_tier="2"/>
|
||||
<!-- ЧЕТЫРЕ МОДА НОЖА ПЕРЕЕХАЛИ СЮДА ИЗ ГРУППЫ 2, 2026-09-09. Продиктовано:
|
||||
"Питьё важно в тот же день. Оно должно быть доступно после 30 убитых зомби.
|
||||
Еда - 60. Это самые важные для начала выживания модификации. Модификация на
|
||||
@@ -159,7 +171,18 @@
|
||||
<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"/>
|
||||
<!-- resourceBloodStone добавлен 2026-09-16 (указание «доступен на третьем грейде
|
||||
некромантии, в самом его начале»). Тир 3 - это и есть третий грейд, группа
|
||||
«Ученик», а его единственный unlock_level равен 2000, то есть unlock_tier="1"
|
||||
здесь и означает «в самом начале грейда, без своего смещения».
|
||||
|
||||
ЭТА СТРОКА - ТОЛЬКО ОТОБРАЖЕНИЕ. Открытие рецепта делает тег
|
||||
necroNecromancyApprentice в recipes.xml вместе с RecipeTagUnlocked
|
||||
level="2000,5000" ниже; display_entry о тегах не знает и рисует замок сам по
|
||||
себе, по unlock_tier. Поэтому 2000 здесь и 2000 там ОБЯЗАНЫ совпадать -
|
||||
иначе замок на панели разойдётся с реальной доступностью рецепта. Совпадают:
|
||||
один порог, одно значение. -->
|
||||
<unlock_entry item="bookSummonZombieDog,bookSummonInsectSwarm,bookSummonZombieGriffin,resourceBloodStone" 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"/>
|
||||
|
||||
@@ -376,4 +376,79 @@
|
||||
<ingredient name="resourceYuccaFibers" count="10"/>
|
||||
</recipe>
|
||||
</append>
|
||||
|
||||
<!-- Кровавая сфера, продиктовано 2026-09-15: «Станки не нужны. Ингридиенты: Кровь некроманта,
|
||||
5 праха зомби. По одному рецепту изготавливается две сферы.»
|
||||
|
||||
craft_area нет - крафт личный, как у Камня духов и самого ножа: сфера дешевле призывов и
|
||||
верстака не требует.
|
||||
|
||||
ПОРОГ ПЕРЕНЕСЁН С УРОВНЯ 1 НА 20, 2026-09-16. Изначально (15.09) рецепт стоял вовсе без
|
||||
тега разблокировки по указанию «доступна на первом грейде» - а рецепт без тега доступен
|
||||
всегда. Но сфера вставляется в Пространственный браслет, который открывается только на 20
|
||||
убийствах (necroNecromancyLvl20), то есть девятнадцать уровней её можно было крафтить в
|
||||
пустоту. Теперь порог общий с браслетом, по записанному правилу мода «пороги - по нужде, а
|
||||
не по силе».
|
||||
|
||||
Тег learnable добавлен вместе с necroNecromancyLvl20 - как у всех остальных гейтованных
|
||||
рецептов мода, чтобы рецепт не светился в меню до открытия. Парная правка в
|
||||
progression.xml: сфера переехала в unlock_entry с unlock_tier="2", там же, где браслет.
|
||||
|
||||
Цена реальная, а не по списку: одна кровь некроманта стоит ещё и 90% текущего ХП на её
|
||||
собственный крафт (NecromancerBloodPatch.cs). Две сферы за один заход это и учитывают. -->
|
||||
<append xpath="/recipes">
|
||||
<recipe name="resourceBloodSphere" count="2" tags="learnable,packMuleCrafting,necroNecromancyLvl20">
|
||||
<ingredient name="resourceNecromancerBlood" count="1"/>
|
||||
<ingredient name="resourceZombieAsh" count="5"/>
|
||||
</recipe>
|
||||
</append>
|
||||
|
||||
<!-- Кровавый камень, продиктовано 2026-09-15: «Рецепт: 50 праха зомби, 20 костей, 4 флакона
|
||||
обычной крови, 1 кровь некроманта. Рецепт будет доступен на третьем грейде некромантии
|
||||
(в самом его начале)... Рабочее место - химическая станция.»
|
||||
|
||||
Все четыре имени проверены по файлам, а не по памяти:
|
||||
resourceZombieAsh - свой предмет мода (Config/items.xml)
|
||||
resourceBone - ванильный (Data/Config/items.xml:23781)
|
||||
medicalBloodBag - ванильный (Data/Config/items.xml:19196)
|
||||
resourceNecromancerBlood - свой предмет мода (Config/items.xml:1321). После отката
|
||||
15.09 он снова обычный <item> в items.xml, а не модификация.
|
||||
|
||||
ЦЕНА ВЫШЕ, ЧЕМ ЧИТАЕТСЯ ПО СПИСКУ: одна кровь некроманта стоит ещё и 90% текущего ХП на
|
||||
её собственный крафт (NecromancerBloodPatch.cs).
|
||||
|
||||
«ТРЕТИЙ ГРЕЙД, В САМОМ ЕГО НАЧАЛЕ» = ровно порог группы 3 «Ученик», без собственного
|
||||
смещения, то есть тег necroNecromancyApprentice и уровень 2000 (progression.xml,
|
||||
RecipeTagUnlocked level="2000,5000"). Тег уже заведён и уже работает - свой
|
||||
necroNecromancyLvl*-тег здесь не нужен, такие одноразовые уровни в моде заведены ровно
|
||||
один раз (necroNecromancyLvl20 для браслета) и считаются исключением, а не приёмом.
|
||||
В progression.xml камень добавлен в display_entry третьего тира (unlock_level="2000")
|
||||
строкой скилл-панели - это чисто отображение, механику открытия делает тег.
|
||||
|
||||
ДВА ИМЕНИ, КОТОРЫЕ ЛЕГКО ПЕРЕПУТАТЬ, выписаны проверенными по ванили (образцы ammoGasCan,
|
||||
carBattery в Data/Config/recipes.xml): craft_area="chemistryStation" - ПОЛНОЕ слово, а
|
||||
тег - chemStationCrafting, СОКРАЩЁННОЕ "chem", не "chemistry".
|
||||
|
||||
Тег learnable - как у остальных гейтованных рецептов мода, чтобы рецепт не светился в
|
||||
меню до открытия группы.
|
||||
|
||||
ПОБОЧНОЕ СЛЕДСТВИЕ, замеченное заранее: химстанция добавляет камню ВТОРОЙ порог поверх
|
||||
уровня 2000 - она открывается своим путём (перк/схема), с Некромантией не связанным.
|
||||
Практически к 2000 уровню она у игрока почти наверняка есть, но формально «третий грейд»
|
||||
перестаёт быть единственным условием. Ручка одна: убрать craft_area и вернуть личный
|
||||
крафт.
|
||||
|
||||
БАЛАНСОВАЯ ЗАМЕТКА: слот у браслета один, значит камень с бесконечной прочностью не
|
||||
дополняет Кровавую сферу, а ВЫТЕСНЯЕТ её в тот момент, когда игрок его получает. Судя по
|
||||
формулировке («тот же флакон, но с бесконечной прочностью») это и задумано - камень это
|
||||
эндгейм-версия расходника, уровень 2000 как раз про это. Записано, чтобы потом не
|
||||
удивляться, что сфера перестала расходоваться. -->
|
||||
<append xpath="/recipes">
|
||||
<recipe name="resourceBloodStone" count="1" craft_area="chemistryStation" tags="learnable,chemStationCrafting,necroNecromancyApprentice">
|
||||
<ingredient name="resourceZombieAsh" count="50"/>
|
||||
<ingredient name="resourceBone" count="20"/>
|
||||
<ingredient name="medicalBloodBag" count="4"/>
|
||||
<ingredient name="resourceNecromancerBlood" count="1"/>
|
||||
</recipe>
|
||||
</append>
|
||||
</config>
|
||||
|
||||
@@ -148,7 +148,12 @@ namespace NecromancerTome
|
||||
/// <summary>Current value plus what it actually reached, in both units, and which way the
|
||||
/// body is being faded. The count is the half that answers "did it do anything": 0
|
||||
/// materials means no trader has been converted yet - they stream in on approach - not
|
||||
/// that the number was refused.</summary>
|
||||
/// that the number was refused.
|
||||
///
|
||||
/// The trader count is "held as a ghost RIGHT NOW", not "seen this session": since the
|
||||
/// 2026-09-15 fix, Ghosted is keyed by entity id but re-entered when a trader is rebuilt,
|
||||
/// and a trader whose chunk has unloaded keeps his entry only until the next sweep finds
|
||||
/// his model gone. So the number falls as well as rises, and that is correct.</summary>
|
||||
public static void Report(string _prefix)
|
||||
{
|
||||
float alpha = GhostTraderPatch.GhostAlpha;
|
||||
@@ -157,7 +162,7 @@ namespace NecromancerTome
|
||||
_prefix + ": " + ((1f - alpha) * 100f).ToString("0.#") + "% transparent (alpha " +
|
||||
alpha.ToString("0.###") + "), body mode " + GhostTraderPatch.BodyMode +
|
||||
", applied to " + applied + " live material(s) across " +
|
||||
GhostTraderPatch.Ghosted.Count + " trader(s) converted this session.");
|
||||
GhostTraderPatch.Ghosted.Count + " trader(s) currently held as ghosts.");
|
||||
}
|
||||
|
||||
/// <summary>Percent out of what the user typed. StringParsers is the game's own parser and
|
||||
|
||||
@@ -67,6 +67,11 @@ namespace NecromancerTome
|
||||
/// Polling with ModEvents.UnityUpdate - the same approach PetFollowPatch.cs already uses here -
|
||||
/// avoids guessing at the right moment inside someone else's character pipeline. A trader with
|
||||
/// no renderers yet is simply not marked done and is picked up on the next sweep.
|
||||
///
|
||||
/// The same tick is what puts a trader BACK once the game has rebuilt him - see Ghosted, and
|
||||
/// the bug of 2026-09-15 that taught this file the difference between an entity id and a
|
||||
/// model. A spawn hook would not have helped there either: the entity was never re-created as
|
||||
/// far as its id is concerned.
|
||||
/// </summary>
|
||||
public static class GhostTraderPatch
|
||||
{
|
||||
@@ -135,6 +140,16 @@ namespace NecromancerTome
|
||||
public Material[] Originals;
|
||||
}
|
||||
|
||||
/// <summary>What one trader was actually given, kept so the sweep can ask "is he STILL a
|
||||
/// ghost" instead of only "have I seen this id". The renderers are the answer: a trader
|
||||
/// that streams out and back in is rebuilt from scratch - new GameObject, new renderers,
|
||||
/// the game's own materials - while keeping the id he was saved under, so an id on its own
|
||||
/// says nothing about the model standing there now. See the Ghosted comment.</summary>
|
||||
public struct GhostBody
|
||||
{
|
||||
public Renderer[] Renderers;
|
||||
}
|
||||
|
||||
/// <summary>Every renderer taken over, in the order it was found. Pruned of destroyed
|
||||
/// renderers as they are walked; dropped wholesale when the world unloads.</summary>
|
||||
public static readonly List<GhostRenderer> Converted = new List<GhostRenderer>();
|
||||
@@ -192,8 +207,29 @@ namespace NecromancerTome
|
||||
/// beyond doubt: 1 = solid, 0 = gone, exactly like an alpha.</summary>
|
||||
public static readonly string[] FadeNameHints = { "_Fade" };
|
||||
|
||||
/// <summary>Entity ids already converted. Cleared when the world unloads.</summary>
|
||||
public static readonly HashSet<int> Ghosted = new HashSet<int>();
|
||||
/// <summary>Traders already converted, by entity id, WITH the renderers each was given.
|
||||
/// Cleared when the world unloads.
|
||||
///
|
||||
/// THE VALUE IS NOT DECORATION - it is the fix for "the trader stopped being a ghost the
|
||||
/// next morning" (2026-09-15). This was a HashSet of ids, and an id is not enough:
|
||||
/// EntityFactory restores `entity.entityId = ecd.id` from the save, so a trader who is
|
||||
/// streamed out while the player is away (they are streamed IN on approach in the first
|
||||
/// place - see the class comment) comes back as a BRAND NEW GameObject carrying the SAME
|
||||
/// id, with the game's own materials on it. The set still held the id, the sweep skipped
|
||||
/// him, and he stayed an ordinary living person for the rest of the session.
|
||||
///
|
||||
/// It is NOT the restock, which was the first guess and is worth writing down as ruled
|
||||
/// out: TraderData's reset rewrites PrimaryInventory and lastInventoryUpdate and touches
|
||||
/// no renderer, and TraderArea.SetClosed - the whole open/close cycle - only works doors,
|
||||
/// lights and speakers. Nothing on the shop's clock ever reaches the model. What does is
|
||||
/// the chunk the shop sits in, which is why the symptom looks like it follows the morning:
|
||||
/// the player is away for the night, the trader unloads with his chunk, and he is rebuilt
|
||||
/// when they walk back.
|
||||
///
|
||||
/// Holding the renderers makes the question answerable: Unity's destroyed objects compare
|
||||
/// equal to null, so a trader whose model is gone is visible as such, and the same check
|
||||
/// covers any other rebuild of the model for free.</summary>
|
||||
public static readonly Dictionary<int, GhostBody> Ghosted = new Dictionary<int, GhostBody>();
|
||||
|
||||
/// <summary>Source shader names already described in the log, so the probe says each
|
||||
/// distinct thing once rather than once per trader per part.</summary>
|
||||
@@ -252,21 +288,84 @@ namespace NecromancerTome
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (Ghosted.Contains(trader.entityId))
|
||||
if (Ghosted.TryGetValue(trader.entityId, out GhostBody body))
|
||||
{
|
||||
if (IsIntact(body))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (ApplyGreyscale(trader))
|
||||
// His model was destroyed and rebuilt under him. Drop what is known about the
|
||||
// old one before building the new, or Converted and TintedMaterials keep
|
||||
// entries for renderers and materials that no longer exist.
|
||||
Debug.Log("[NecromancerTome] GhostTraderPatch: entity " + trader.entityId +
|
||||
" came back with a new model - ghosting him again");
|
||||
Ghosted.Remove(trader.entityId);
|
||||
Prune();
|
||||
}
|
||||
if (ApplyGreyscale(trader, out GhostBody fresh))
|
||||
{
|
||||
Ghosted.Add(trader.entityId);
|
||||
Ghosted[trader.entityId] = fresh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Is this trader still wearing what we put on him? False the moment any part of
|
||||
/// the model we converted has been destroyed - which is what a stream-out and back in
|
||||
/// looks like from here, and equally what any other rebuild of the model would look like.
|
||||
///
|
||||
/// Deliberately NOT "does he have renderers we have not converted": a trader gains and
|
||||
/// loses renderers in normal play (a held item, worn equipment), and treating that as a
|
||||
/// rebuild would re-run the conversion on renderers already carrying our materials - whose
|
||||
/// sharedMaterials hand back OUR clones, so the "originals" kept for the next mode switch
|
||||
/// would be re-shaded ones with no way back. The known gap that leaves is a part of the
|
||||
/// model built AFTER the first sweep reached him: it stays in colour until he next
|
||||
/// reloads. Nothing like that has been seen on the six traders.</summary>
|
||||
public static bool IsIntact(GhostBody _body)
|
||||
{
|
||||
if (_body.Renderers == null || _body.Renderers.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
foreach (Renderer renderer in _body.Renderers)
|
||||
{
|
||||
if (renderer == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Drops every entry whose Unity object the game has destroyed. Both lists are
|
||||
/// session-long and keyed by nothing - without this they grow by one trader's worth of
|
||||
/// renderers and materials every time a trader is rebuilt, and Retint/Reapply would be
|
||||
/// walking the wreckage. A material assigned through renderer.materials is owned by that
|
||||
/// renderer and dies with it, so one pass settles both.</summary>
|
||||
public static void Prune()
|
||||
{
|
||||
for (int i = Converted.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (Converted[i].Renderer == null)
|
||||
{
|
||||
Converted.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
for (int i = TintedMaterials.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (TintedMaterials[i].Material == null)
|
||||
{
|
||||
TintedMaterials.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>False when there is nothing to work on yet (model not built), so the caller
|
||||
/// leaves this trader unmarked and tries again on the next sweep.</summary>
|
||||
public static bool ApplyGreyscale(EntityTrader _trader)
|
||||
/// leaves this trader unmarked and tries again on the next sweep. On true, _body carries
|
||||
/// the renderers taken over, which is how the next sweep tells this trader from a rebuilt
|
||||
/// one standing under the same entity id.</summary>
|
||||
public static bool ApplyGreyscale(EntityTrader _trader, out GhostBody _body)
|
||||
{
|
||||
_body = default(GhostBody);
|
||||
Renderer[] renderers = _trader.GetComponentsInChildren<Renderer>(true);
|
||||
if (renderers == null || renderers.Length == 0)
|
||||
{
|
||||
@@ -279,6 +378,7 @@ namespace NecromancerTome
|
||||
|
||||
int converted = 0;
|
||||
int leversBefore = TintedMaterials.Count;
|
||||
List<Renderer> taken = new List<Renderer>(renderers.Length);
|
||||
foreach (Renderer renderer in renderers)
|
||||
{
|
||||
if (renderer == null || renderer is ParticleSystemRenderer)
|
||||
@@ -293,11 +393,13 @@ namespace NecromancerTome
|
||||
}
|
||||
|
||||
Converted.Add(new GhostRenderer { Renderer = renderer, Originals = sources });
|
||||
taken.Add(renderer);
|
||||
if (Convert(renderer, sources))
|
||||
{
|
||||
converted++;
|
||||
}
|
||||
}
|
||||
_body.Renderers = taken.ToArray();
|
||||
|
||||
// The lever count is the half that answers "will the console command reach him":
|
||||
// desaturation and opacity come from different properties, and the body had the first
|
||||
|
||||
@@ -5,8 +5,19 @@ namespace NecromancerTome
|
||||
{
|
||||
/// <summary>
|
||||
/// "Кровь некроманта" (Necromancer's Blood) - dictated 2026-08-30. See items.xml
|
||||
/// (resourceNecromancerBlood) for the item, recipes.xml for the base recipe (an empty jar,
|
||||
/// like any other resource conversion). Two rules the user asked for have NO vanilla XML
|
||||
/// (resourceNecromancerBlood) for the item and recipes.xml for the base recipe (an empty jar,
|
||||
/// like any other resource conversion).
|
||||
///
|
||||
/// IT LIVES IN items.xml, AND THAT IS NOT AN ACCIDENT. On 2026-09-15 it was moved into
|
||||
/// item_modifiers.xml so it could be installed in the Spatial Bracelet, and that move
|
||||
/// DESTROYED a character in a save: ItemValue.Read/Write gate the modification block on
|
||||
/// !(itemClass is ItemClassModifier), so the item's CLASS decides the byte layout of every
|
||||
/// stack of it in the save, and an existing save read one byte off from the first blood stack
|
||||
/// onward. It was rolled back the same day, the bracelet's charge became a brand-new item
|
||||
/// (resourceBloodSphere, and later resourceBloodStone) instead, and the rule stands: an item
|
||||
/// that could already be in someone's inventory must not change class in either direction.
|
||||
/// The full account is in BACKLOG.md; the earlier wording of this comment claimed the modifier
|
||||
/// home as current and outlived the code by a day. Two rules the user asked for have NO vanilla XML
|
||||
/// equivalent at all, so both are enforced here instead:
|
||||
/// 1. "нужна... наличие любого ножа" - a knife must be present (in the toolbelt or
|
||||
/// backpack) to craft this, but is NOT consumed. recipes.xml has no "required but not
|
||||
@@ -50,6 +61,73 @@ namespace NecromancerTome
|
||||
public const string BloodItemName = "resourceNecromancerBlood";
|
||||
public const float HealthCostFraction = 0.9f;
|
||||
|
||||
/// <summary>Damage above which the player's BIG pain grunt is used instead of the small
|
||||
/// one. Not invented: it is vanilla's own threshold, read out of
|
||||
/// EntityPlayer.GetSoundHurt(DamageSource, int) - the override that every spike and every
|
||||
/// strand of barbed wire goes through. Its IL is
|
||||
///
|
||||
/// ldarg.2 // _damageStrength
|
||||
/// ldc.i4.s 15
|
||||
/// bgt.s -> GetSoundHurt() // strictly MORE than 15 -> soundHurt (…painlg)
|
||||
/// call GetSoundHurtSmall() // 15 or less -> soundHurtSmall (…painsm)
|
||||
///
|
||||
/// so the comparison is strictly greater-than, and 15 exactly still counts as small. The
|
||||
/// same method has an earlier branch for damage type 16 that returns GetSoundDrownPain();
|
||||
/// that one is the drowning case and has nothing to do with us.</summary>
|
||||
public const int PainSoundBigDamage = 15;
|
||||
|
||||
/// <summary>Plays the player's own pain grunt, picking the big or the small one by the
|
||||
/// same rule vanilla uses for spikes and barbed wire (user request 2026-09-16: "в игре
|
||||
/// есть звук боли (когда персонаж напарывается на колья или на колючую проволоку). Пусть
|
||||
/// этот звук воспроизводится при создании крови некроманта").
|
||||
///
|
||||
/// WHY THE SOUND NAME IS ASKED FOR AND NOT SPELLED OUT. The clip differs by gender -
|
||||
/// playerMale carries SoundHurt="player1painlg"/SoundHurtSmall="player1painsm" and
|
||||
/// playerFemale overrides both to player2pain* (Data/Config/entityclasses.xml). Hardcoding
|
||||
/// "player1painlg" would have given every female character a male grunt. GetSoundHurt() and
|
||||
/// GetSoundHurtSmall() are public on EntityAlive and are plain field reads (verified: each
|
||||
/// one's whole body is "ldarg.0; ldfld soundHurt|soundHurtSmall; ret"), so they return
|
||||
/// whatever this entity's own class declared and cost nothing.
|
||||
///
|
||||
/// WHY NOT GetSoundHurt(DamageSource, int), which would pick for us: it needs a
|
||||
/// DamageSource, and this is not damage from a source - the HP here is spent by AddHealth,
|
||||
/// deliberately (see the comment at the call site). Its selection rule is three lines, so
|
||||
/// it is reproduced instead of faked with a synthetic DamageSource.
|
||||
///
|
||||
/// The null guard is vanilla's too: EntityAlive.OnUpdateEntity stores the result and skips
|
||||
/// the call on null (brfalse right after the stloc) rather than handing PlayOneShot a null
|
||||
/// clip name. The fallback to the big grunt covers an entity that declares SoundHurt but
|
||||
/// not SoundHurtSmall - again exactly what vanilla's override does when
|
||||
/// GetSoundHurtSmall() comes back empty.
|
||||
///
|
||||
/// PlayOneShot(name) with no further arguments is byte-for-byte what vanilla passes here:
|
||||
/// its optional parameters default to sound_in_head:false, serverSignalOnly:false,
|
||||
/// isUnique:false, _animEvent:null, volumeScale:1f, and OnUpdateEntity's own call pushes
|
||||
/// exactly those five constants. So the grunt comes out of the character, not "in the
|
||||
/// head", same as being spiked.</summary>
|
||||
public static void PlayPainSound(EntityPlayerLocal _player, int _damage)
|
||||
{
|
||||
if (_player == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string clip = _damage > PainSoundBigDamage
|
||||
? _player.GetSoundHurt()
|
||||
: _player.GetSoundHurtSmall();
|
||||
|
||||
if (string.IsNullOrEmpty(clip))
|
||||
{
|
||||
clip = _player.GetSoundHurt();
|
||||
}
|
||||
if (string.IsNullOrEmpty(clip))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_player.PlayOneShot(clip);
|
||||
}
|
||||
|
||||
public static bool HasAnyKnife(EntityPlayerLocal player)
|
||||
{
|
||||
return ContainsKnife(player.inventory?.GetSlots()) || ContainsKnife(player.bag?.GetSlots());
|
||||
@@ -131,6 +209,13 @@ namespace NecromancerTome
|
||||
return;
|
||||
}
|
||||
__state.AddHealth(-amount);
|
||||
|
||||
// The grunt goes AFTER the HP is actually gone, so a craft that somehow bailed out
|
||||
// above never makes a sound the player cannot account for. At the 90% cost this is the
|
||||
// big pain clip in every normal case (amount > 15 unless the player is already down to
|
||||
// about 17 HP), and drops to the small one exactly where vanilla would drop it too.
|
||||
NecromancerBloodPatch.PlayPainSound(__state, amount);
|
||||
|
||||
Debug.Log("[NecromancerTome] NecromancerBloodPatch: crafted blood, deducted " + amount + " HP from owner=" + __state.entityId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NecromancerTome
|
||||
{
|
||||
/// <summary>
|
||||
/// Счёт убийств для скилла "Некромантия" (user report 2026-09-16: "Почему-то наш скилл
|
||||
/// некроманта не всегда засчитывает убийство зомби... если робомолот убьёт зомбака, то игрок
|
||||
/// получает за это опыт. Если зомби умрёт от кровотечения, которое навесил игрок, то игрок
|
||||
/// получит опыт. У нас скилл некроманта в этих случаях не прибавляется. Это баг.").
|
||||
///
|
||||
/// WHAT WAS BROKEN, AND IT WAS TWO SEPARATE THINGS.
|
||||
///
|
||||
/// Until this patch the whole count was four lines of XML appended to ONE entity class in
|
||||
/// Config/entityclasses.xml:
|
||||
///
|
||||
/// <append xpath="/entity_classes/entity_class[@name='zombieTemplateMale']">
|
||||
/// <requirement name="EntityTagCompare" target="other" tags="player"/>
|
||||
/// <triggered_effect trigger="onOtherKilledSelf" action="ModifyCVar" target="other" .../>
|
||||
/// <triggered_effect trigger="onOtherKilledSelf" action="AddProgressionLevel" target="other" .../>
|
||||
///
|
||||
/// 1. ONE CLASS IS NOT EVERY ZOMBIE. Humanoids were fine - effect_group DOES inherit through
|
||||
/// extends on entity_class (unlike items.xml, see progression.xml's header), and every
|
||||
/// zombie template chains back to zombieTemplateMale. But the five zombie ANIMALS inherit
|
||||
/// the animal branch and never reach it:
|
||||
/// animalZombieBear extends animalBear, animalZombieBoar extends animalBoar,
|
||||
/// animalZombieDog extends animalWolf, animalZombieVulture extends animalTemplateHostile,
|
||||
/// animalZombieVultureRadiated extends animalZombieVulture
|
||||
/// Killing a zombie dog, bear, boar or vulture counted for nothing at all. Zombie vultures
|
||||
/// are everywhere on roads, which is most of what "не всегда засчитывает" was.
|
||||
///
|
||||
/// 2. target="other" IS THE LITERAL KILLER, NOT THE PLAYER WHO EARNED IT. With
|
||||
/// trigger="onOtherKilledSelf" plus a requirement that "other" be tagged player, anything
|
||||
/// that kills on the player's behalf fails the requirement: a robotic sledge (the turret is
|
||||
/// "other"), a bleed the player applied (no direct killer at the moment of death), a summoned
|
||||
/// pet (the pet is "other"). Vanilla still awards XP in all of these because it does NOT use
|
||||
/// the literal killer - it resolves the crediting player from the DamageSource, in
|
||||
/// EntityAlive.AwardKillXPServer(DamageSource, EntityAlive), whose body reads BuffClass
|
||||
/// (DoT damage) and a dedicated bTrapKillXP flag (trap kills) before calling AddKillXP.
|
||||
///
|
||||
/// BOTH EFFECTS SHARED ONE REQUIREMENT, so every missed kill also failed to raise
|
||||
/// necroZombieKillsCVar - and that CVar is the Necromancer's Knife's damage (items.xml: "Damage
|
||||
/// = necroZombieKillsCVar / 10", recomputed continuously in buffs.xml). The bug was quietly
|
||||
/// underpowering the knife too, which is why the fix keeps both effects together.
|
||||
///
|
||||
/// WHY THIS HOOK AND NOT A WIDER XML PATCH. Adding the five animal classes by XML would have
|
||||
/// fixed cause 1 and left cause 2 untouched. EntityPlayer.AddKillXP is the single point where
|
||||
/// vanilla has ALREADY decided which player gets the kill - it is called from exactly one place
|
||||
/// in the whole assembly, AwardKillXPServer, after all the DamageSource resolution is done.
|
||||
/// Hooking it means our count agrees with the XP number the player sees on screen by
|
||||
/// construction, for every case vanilla handles, including ones nobody has thought of yet.
|
||||
/// Verified by metadata scan: AwardKillXPServer is the only caller of AddKillXP.
|
||||
///
|
||||
/// THE XML TRIGGERS ARE GONE, NOT LEFT ALONGSIDE. Config/entityclasses.xml no longer carries
|
||||
/// the effect_group - if it stayed, a kill by the player's own hand would satisfy both it and
|
||||
/// this patch and count TWICE. That was the one trap of moving the count into code, and it is
|
||||
/// the first thing to check if levels ever start rising two at a time.
|
||||
///
|
||||
/// PETS ARE NOT GUARANTEED BY THIS PATCH. The user also asked that summoned creatures count.
|
||||
/// They will count if and only if vanilla itself credits the owner for a pet kill - this patch
|
||||
/// follows vanilla's decision, it does not make it. Whether it does is NOT verified and is the
|
||||
/// specific thing to watch for in game; if pets turn out not to be credited, that is a separate
|
||||
/// piece of work (giving the pet's DamageSource an owner), not a bug in this file.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(EntityPlayer), "AddKillXP")]
|
||||
public static class Patch_EntityPlayer_AddKillXP_NecromancyCount
|
||||
{
|
||||
public const string NecromancySkillName = "craftingNecroNecromancy";
|
||||
public const string KillsCVarName = "necroZombieKillsCVar";
|
||||
|
||||
/// <summary>The tag every zombie carries, humanoid and animal alike. Checked against the
|
||||
/// real data rather than assumed: zombieBiker/zombieArlene/zombieBoe/zombieSpider all
|
||||
/// declare "entity,zombie,..." and the five zombie animals declare
|
||||
/// "entity,animal,zombie,zombieAnimal,...". Note that entity Tags do NOT inherit through
|
||||
/// extends (entityclasses.xml says so in a comment right on the property), which is exactly
|
||||
/// why this works: every concrete, spawnable zombie spells its own tags out, and the bare
|
||||
/// templates that do not are never spawned.
|
||||
///
|
||||
/// A tag test also ages better than the class list it replaces: any zombie added by a
|
||||
/// future game version or another mod counts the moment it calls itself a zombie.</summary>
|
||||
private static readonly FastTags<TagGroup.Global> ZombieTag =
|
||||
FastTags<TagGroup.Global>.Parse("zombie");
|
||||
|
||||
public static void Postfix(EntityPlayer __instance, EntityAlive killedEntity)
|
||||
{
|
||||
if (__instance == null || killedEntity == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!killedEntity.HasAnyTags(ZombieTag))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AddKillsCVar(__instance);
|
||||
AddNecromancyLevel(__instance);
|
||||
}
|
||||
|
||||
/// <summary>necroZombieKillsCVar += 1 - the same thing the removed ModifyCVar action did,
|
||||
/// and the reason it is here rather than left in XML is that it shared the broken
|
||||
/// requirement with the progression effect. GetCVar/SetCVar are public on EntityAlive and
|
||||
/// are the same storage the buffs.xml formula reads.</summary>
|
||||
private static void AddKillsCVar(EntityPlayer _player)
|
||||
{
|
||||
_player.SetCVar(KillsCVarName, _player.GetCVar(KillsCVarName) + 1f);
|
||||
}
|
||||
|
||||
/// <summary>+1 level of Necromancy, replicating MinEventActionAddProgressionLevel.Execute
|
||||
/// step for step rather than inventing a shorter version of it - its IL was read for this:
|
||||
/// GetProgressionValue, Level + amount, clamp to ProgressionClass.MaxLevel, then (for a
|
||||
/// crafting skill) the level-up toast and HandleCheckCrafting, then the two dirty flags.
|
||||
///
|
||||
/// HandleCheckCrafting is the part that would be easy to drop and expensive to miss: it is
|
||||
/// what the game calls on a crafting-skill level change, and skipping it risks recipes not
|
||||
/// noticing they became available. Both it and AddCraftingSkillNotification are public.
|
||||
///
|
||||
/// The clamp matters for a different reason than it looks: max_level is 5000, and without
|
||||
/// the clamp Level would keep climbing past it forever, because nothing else limits it.</summary>
|
||||
private static void AddNecromancyLevel(EntityPlayer _player)
|
||||
{
|
||||
Progression progression = _player.Progression;
|
||||
if (progression == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ProgressionValue pv = progression.GetProgressionValue(NecromancySkillName);
|
||||
if (pv == null || pv.ProgressionClass == null)
|
||||
{
|
||||
// Not a crash, and not silent either: this means the skill did not load, which is a
|
||||
// config problem worth seeing once in the log rather than a reason to throw inside
|
||||
// a kill handler.
|
||||
Debug.LogWarning("[NecromancerTome] NecromancyKillCredit: progression '" +
|
||||
NecromancySkillName + "' not found - kill not counted");
|
||||
return;
|
||||
}
|
||||
|
||||
int oldLevel = pv.Level;
|
||||
int maxLevel = pv.ProgressionClass.MaxLevel;
|
||||
int newLevel = oldLevel + 1;
|
||||
if (newLevel > maxLevel)
|
||||
{
|
||||
newLevel = maxLevel;
|
||||
}
|
||||
if (newLevel == oldLevel)
|
||||
{
|
||||
// Already at 5000. The CVar above still went up on purpose - the knife's damage is
|
||||
// not capped by the skill's max_level, and the player who is past the cap should
|
||||
// keep getting stronger knives.
|
||||
return;
|
||||
}
|
||||
pv.Level = newLevel;
|
||||
|
||||
EntityPlayerLocal local = _player as EntityPlayerLocal;
|
||||
if (pv.ProgressionClass.IsCrafting && local != null)
|
||||
{
|
||||
// true = add the notification only if one is not already up, so a horde night does
|
||||
// not stack a fresh toast per corpse.
|
||||
local.PlayerUI?.xui?.CollectedItemList?.AddCraftingSkillNotification(pv, true);
|
||||
pv.ProgressionClass.HandleCheckCrafting(local, oldLevel, newLevel);
|
||||
}
|
||||
|
||||
// isEntityRemote guards these in vanilla too: a remote player's stats are the server's
|
||||
// business, and marking them dirty here would be claiming an authority we do not have.
|
||||
if (!_player.isEntityRemote)
|
||||
{
|
||||
progression.bProgressionStatsChanged = true;
|
||||
_player.bPlayerStatsChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,7 +105,9 @@ namespace NecromancerTome
|
||||
// пространственного браслета не делает ничего"), after the knockback+slow version did not
|
||||
// visibly do anything in testing. ShoveZombieAtCrosshair is kept below, unused,
|
||||
// because that abandoned version was never shown to be WRONG - only invisible.
|
||||
SpatialVaultPickup.Begin(player);
|
||||
// The ItemValue goes with it: the pickup refuses outright when this particular
|
||||
// bracelet has nothing in its mod slot, and mods live on the instance.
|
||||
SpatialVaultPickup.Begin(player, _actionData.invData.itemValue);
|
||||
}
|
||||
|
||||
// Skip ItemActionEat's own logic entirely - the click has been fully handled here.
|
||||
|
||||
@@ -130,10 +130,27 @@ namespace NecromancerTome
|
||||
public const string MsgChanneling = "braceletSpatialVaultPickupChanneling";
|
||||
public const string MsgTraderArea = "braceletSpatialVaultTraderArea";
|
||||
public const string MsgIndestructible = "braceletSpatialVaultIndestructible";
|
||||
public const string MsgNoMod = "braceletSpatialVaultNoMod";
|
||||
|
||||
/// <summary>The denial sound vanilla plays with these tooltips.</summary>
|
||||
public const string DeniedSound = "ui_denied";
|
||||
|
||||
/// <summary>What the bracelet burns to pull a block: the Кровавая сфера, dictated
|
||||
/// 2026-09-15. Its own definition is in Config/item_modifiers.xml.
|
||||
///
|
||||
/// WHY A SEPARATE ITEM AND NOT THE BLOOD ITSELF - this is the scar of the 15.09 accident
|
||||
/// and the reason not to "simplify" it back. The blood was moved into item_modifiers.xml
|
||||
/// so it could be installed here, and that destroyed a character's save along with its
|
||||
/// backup: an item's CLASS decides the byte layout of every stack of it
|
||||
/// (ItemValue.Read:1094 / Write:1228), so a save written before the move became
|
||||
/// unreadable. The sphere is a NEW name that no old save contains, which is what makes it
|
||||
/// safe. Full write-up in BACKLOG.md.
|
||||
///
|
||||
/// The Кровавый камень, when it exists, goes in the same slot and is NOT charged here -
|
||||
/// it is the infinite one. Nothing to add for it: this check names the sphere, so
|
||||
/// anything else in the slot simply pays nothing.</summary>
|
||||
public const string ChargeItemName = "resourceBloodSphere";
|
||||
|
||||
/// <summary>Unscaled time at which a cancel last opened the vault, or -1. Exists to stop
|
||||
/// ONE press from opening the vault TWICE: the cancel reacts to the button going down,
|
||||
/// while the bracelet's ordinary power attack reacts to it coming back up, and those are
|
||||
@@ -169,12 +186,52 @@ namespace NecromancerTome
|
||||
public EntityPlayerLocal Player;
|
||||
public Vector3i Position;
|
||||
public BlockValue Expected;
|
||||
|
||||
/// <summary>The bracelet this pull was started with, so the blood that pays for it is
|
||||
/// taken from the flask in THAT bracelet. Kept as the live ItemValue rather than
|
||||
/// looked up again at the end: mods live on the instance, and ten seconds is long
|
||||
/// enough for the player to have scrolled to another slot.</summary>
|
||||
public ItemValue Bracelet;
|
||||
|
||||
/// <summary>What this particular pull costs, in seconds - computed once when the
|
||||
/// channel starts (user request 2026-09-15: "пусть количество секунд требуемое для
|
||||
/// поглощения блока записывается в отдельную переменную"). It is the same number the
|
||||
/// timer counts down and the same number the flask pays, and that is the point of
|
||||
/// storing it instead of recomputing: by the time the channel ends the player may
|
||||
/// have turned away, the ray is gone, and a second call to ChannelSecondsFor would
|
||||
/// quietly charge for a different block than the one that was taken.</summary>
|
||||
public float ChannelSeconds;
|
||||
}
|
||||
|
||||
/// <summary>Regular attack on the bracelet. Every refusal happens here, before the player
|
||||
/// is asked to stand still for ten seconds.</summary>
|
||||
public static void Begin(EntityPlayerLocal _player)
|
||||
public static void Begin(EntityPlayerLocal _player, ItemValue _bracelet)
|
||||
{
|
||||
// AN EMPTY MOD SLOT REFUSES THE WHOLE ACTION (user request 2026-09-15: "пусть обычная
|
||||
// атака (поглощение блока) у пространственного хранилища не работает, если у хранилища
|
||||
// в слоте модификаций пусто"). FIRST, deliberately, ahead of every other check in this
|
||||
// method: the others are about the TARGET (no block, wrong kind of block, vault full),
|
||||
// and telling the player "no block there" when the real problem is his empty bracelet
|
||||
// would send him looking in the wrong place. This one is about the tool, so it is
|
||||
// answered before the tool is even pointed at anything.
|
||||
//
|
||||
// ItemValue.HasMods() is the game's own test and the right one: it walks Modifications
|
||||
// only, skipping both nulls and IsEmpty() slots, and does NOT count CosmeticMods - a
|
||||
// dye would otherwise have read as "the bracelet is loaded". The bracelet has no
|
||||
// cosmetic slot anyway (canHaveCosmetic is deliberately absent from its Tags, see
|
||||
// items.xml), so this is belt and braces rather than a live case - but the next item
|
||||
// that reuses this pattern may well have one.
|
||||
//
|
||||
// The ItemValue is handed in rather than read from the player, because the caller
|
||||
// already holds the exact instance the click came from (_actionData.invData.itemValue)
|
||||
// and mods live on the INSTANCE, not on the ItemClass. Two bracelets in the same
|
||||
// inventory can legitimately disagree about whether they are loaded.
|
||||
if (_bracelet == null || !_bracelet.HasMods())
|
||||
{
|
||||
Deny(_player, MsgNoMod);
|
||||
return;
|
||||
}
|
||||
|
||||
World world = GameManager.Instance != null ? GameManager.Instance.World : null;
|
||||
if (world == null)
|
||||
{
|
||||
@@ -249,9 +306,20 @@ namespace NecromancerTome
|
||||
return;
|
||||
}
|
||||
|
||||
// Computed HERE, while the ray still exists, and carried in the job from this point
|
||||
// on - see PickupJob.ChannelSeconds.
|
||||
float channelSeconds = ChannelSecondsFor(hitInfo);
|
||||
|
||||
TimerEventData timerData = new TimerEventData
|
||||
{
|
||||
Data = new PickupJob { Player = _player, Position = position, Expected = blockValue },
|
||||
Data = new PickupJob
|
||||
{
|
||||
Player = _player,
|
||||
Position = position,
|
||||
Expected = blockValue,
|
||||
Bracelet = _bracelet,
|
||||
ChannelSeconds = channelSeconds
|
||||
},
|
||||
// Vanilla's own two escapes: taking a hit stops the channel, and so does the
|
||||
// activate key. Neither is built here - both are fields XUiC_Timer.Update reads.
|
||||
CloseOnHit = true,
|
||||
@@ -267,7 +335,6 @@ namespace NecromancerTome
|
||||
ChannelVision.End(_player);
|
||||
};
|
||||
|
||||
float channelSeconds = ChannelSecondsFor(hitInfo);
|
||||
LocalPlayerUI playerUI = LocalPlayerUI.GetUIForPlayer(_player);
|
||||
XUiC_Timer.OpenTimer(playerUI.xui, channelSeconds, timerData, -1f, Localization.Get(MsgChanneling));
|
||||
// After the window is up, so a channel that somehow fails to open never leaves the
|
||||
@@ -344,13 +411,119 @@ namespace NecromancerTome
|
||||
}
|
||||
|
||||
world.SetBlockRPC(job.Position, BlockValue.Air);
|
||||
|
||||
// AFTER the block is gone and the item is in the vault, never before: every refusal
|
||||
// above returns early, and blood paid for a pull that was then refused would be blood
|
||||
// charged for nothing. This is the only place the flask is spent.
|
||||
float spent = SpendCharge(job);
|
||||
|
||||
// The vault lives in memory and is written out with the player's own save data; this
|
||||
// is the same commit point closing the vault window uses, so a block taken and then
|
||||
// left alone is not waiting on the next autosave to become real.
|
||||
// left alone is not waiting on the next autosave to become real. The flask's UseTimes
|
||||
// rides along in the same save - it lives on the bracelet in the player's inventory.
|
||||
GameManager.Instance.SaveLocalPlayerData();
|
||||
|
||||
Debug.Log("[NecromancerTome] SpatialVaultPickup: owner=" + job.Player.entityId + " took " +
|
||||
blockValue.Block.GetBlockName() + " at " + job.Position + " into the vault");
|
||||
blockValue.Block.GetBlockName() + " at " + job.Position + " into the vault for " +
|
||||
spent.ToString("0.#") + " of charge");
|
||||
}
|
||||
|
||||
/// <summary>Charges this pull to the Кровавая сфера in the bracelet's mod slot, one point
|
||||
/// of durability per second of channel (user request 2026-09-15). Returns what was
|
||||
/// actually taken.
|
||||
///
|
||||
/// WHY THE NAME CHECK AND NOT "whatever is in the slot". The slot is meant to take the
|
||||
/// Кровавый камень too, and that one is explicitly the infinite version - it must pay
|
||||
/// nothing. Naming the sphere here gets that for free: anything else installed is simply
|
||||
/// not charged, and the pull still happens.
|
||||
///
|
||||
/// NOT ENOUGH BLOOD IS NOT A REFUSAL (user request 2026-09-15: "пусть поглощение всё-равно
|
||||
/// сработает, но флакон крови некроманта после этого пусть исчезнет из слота", clarified to
|
||||
/// "если прочность 0 или меньше, пусть флакон исчезнет из слота"). So the last pull is
|
||||
/// always free of charge in the sense that matters - it completes - and the flask simply
|
||||
/// does not survive it. The charge is therefore NOT clamped: UseTimes is allowed to go past
|
||||
/// MaxUseTimes, because the only thing that then reads it is the emptiness test right
|
||||
/// below, and a clamp would have made "spent exactly to zero" and "overdrawn" look the
|
||||
/// same at the moment the difference stopped mattering anyway.
|
||||
///
|
||||
/// The test is "durability 0 or less", not "could not cover the cost", and those are not
|
||||
/// the same rule: a flask with exactly enough left is also gone afterwards. That is the
|
||||
/// user's own correction and it closes the hole the first version would have left - a
|
||||
/// flask sitting at 0/1000 in the slot, counting as "something is installed" for the empty-
|
||||
/// slot gate in Begin, and pulling blocks for free forever.
|
||||
///
|
||||
/// WHY THE NAME CHECK AND NOT "whatever is in the slot" - see above; a mod that is not
|
||||
/// blood pays nothing, is not emptied, and the pull still happens.</summary>
|
||||
public static float SpendCharge(PickupJob _job)
|
||||
{
|
||||
ItemValue bracelet = _job.Bracelet;
|
||||
if (bracelet == null || bracelet.Modifications == null || _job.ChannelSeconds <= 0f)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
float spent = 0f;
|
||||
bool emptied = false;
|
||||
for (int i = 0; i < bracelet.Modifications.Length; i++)
|
||||
{
|
||||
ItemValue mod = bracelet.Modifications[i];
|
||||
if (mod == null || mod.IsEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (mod.ItemClass == null || mod.ItemClass.Name != ChargeItemName)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int max = mod.MaxUseTimes;
|
||||
mod.UseTimes += _job.ChannelSeconds;
|
||||
spent += _job.ChannelSeconds;
|
||||
|
||||
// max <= 0 means this flask has no durability at all - a DegradationMax that did
|
||||
// not resolve. Draining something with no capacity would delete it on the first
|
||||
// pull, which is a config bug eating the player's item, so it is left alone and
|
||||
// said out loud instead.
|
||||
if (max <= 0)
|
||||
{
|
||||
Debug.LogWarning("[NecromancerTome] SpatialVaultPickup: sphere in slot " + i +
|
||||
" has MaxUseTimes 0 - nothing to spend, flask kept. Check the " +
|
||||
"DegradationMax passive_effect in Config/item_modifiers.xml");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mod.UseTimes >= max)
|
||||
{
|
||||
// ItemValue.None is what an empty mod slot holds - type 0, which is exactly
|
||||
// what IsEmpty() tests for, so the slot reads as free to every other piece of
|
||||
// code including the gate in Begin.
|
||||
bracelet.Modifications[i] = ItemValue.None;
|
||||
emptied = true;
|
||||
Debug.Log("[NecromancerTome] SpatialVaultPickup: sphere in slot " + i +
|
||||
" ran out (" + mod.UseTimes.ToString("0.#") + "/" + max +
|
||||
") - sphere removed from the bracelet");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("[NecromancerTome] SpatialVaultPickup: sphere in slot " + i + " now " +
|
||||
mod.UseTimes.ToString("0.#") + "/" + max + " used");
|
||||
}
|
||||
}
|
||||
|
||||
if (emptied)
|
||||
{
|
||||
// Vanilla's own answer to "the thing you were using is gone" - the same cue
|
||||
// ItemAction.HandleItemBreak plays. An item vanishing out of a slot in silence is
|
||||
// the one outcome here the player could miss entirely.
|
||||
_job.Player.PlayOneShot("itembreak");
|
||||
}
|
||||
if (spent > 0f && _job.Player.inventory != null)
|
||||
{
|
||||
// Without this the number is right and the bar on the toolbelt icon is stale
|
||||
// until something else happens to redraw it.
|
||||
_job.Player.inventory.CallOnToolbeltChangedInternal();
|
||||
}
|
||||
return spent;
|
||||
}
|
||||
|
||||
/// <summary>How long this particular pull takes. See the class comment for why the ray's
|
||||
|
||||
+1
-1
@@ -4,6 +4,6 @@
|
||||
<DisplayName value="Necromancer's Tome" />
|
||||
<Description value="A dark necromancy progression for 7 Days to Die 3.2: a kill-count-driven skill tree with cursed weapons, charm/deviation magic, summonable undead pets, base-defence wards, and a story-ending Black Portal ritual. Fully localized into 13 languages. Single-player; requires EAC off." />
|
||||
<Author value="Alex Cube" />
|
||||
<Version value="1.1.0" />
|
||||
<Version value="1.2.0" />
|
||||
<Website value="https://www.alexcube.ru/7-days-to-die-moi-mody/kniga-nekromanta-necromancer-s-tome/" />
|
||||
</xml>
|
||||
|
||||
@@ -20,10 +20,10 @@ To uninstall, delete the folder. The mod adds items and one block, so a save tha
|
||||
[b]A skill that levels from kills, not perk points.[/b] Necromancy counts every zombie you put down. Five tiers, 5000 kills to the top, each threshold opening part of the arsenal:
|
||||
|
||||
[list]
|
||||
[*][b]Adept[/b] (from the start) - Spirit Stone, Necromancer's Knife, Blue Portal Stone, Pyramid of Spirits
|
||||
[*][b]Adept[/b] (from the start) - Spirit Stone, Necromancer's Knife, Blue Portal Stone, Pyramid of Spirits, Blood Sphere
|
||||
[*][b]Adept[/b] (20 / 30 / 60 / 100 / 300) - Spatial Bracelet, then four knife mods: Tears of the Dead, Scavenger's Feast, Grave's Repose, Dark Sense
|
||||
[*][b]Journeyman[/b] (500 / 1400 / 1700) - Scroll of Deviation, knife mods Dead Man's Grip and Dead Storm
|
||||
[*][b]Apprentice[/b] (2000) - Summon Zombie Dog, Beetles of the Lord, Summon Zombie Griffin
|
||||
[*][b]Apprentice[/b] (2000) - Summon Zombie Dog, Beetles of the Lord, Summon Zombie Griffin, Blood Stone
|
||||
[*][b]Necromancer[/b] (3000) - Summon Zombie Bear, Summon Zombie Wolf, Banshee's Scroll
|
||||
[*][b]Master[/b] (5000) - Black Portal Stone
|
||||
[/list]
|
||||
@@ -36,7 +36,7 @@ To uninstall, delete the folder. The mod adds items and one block, so a save tha
|
||||
|
||||
[b]Base defence that converts instead of killing.[/b] The [b]Pyramid of Spirits[/b] is a deployable block. While you stand in its radius it charms any uncharmed zombie nearby on its own and sets it alight with cold purple flame, turning it against the rest instead of your walls. Its block menu toggles the effect and shows the radius.
|
||||
|
||||
[b]Necromancer's tools.[/b] The [b]Blue Portal Stone[/b] teleports you to your bedroll after a ten-second channel that any damage interrupts, and is never consumed. The [b]Spatial Bracelet[/b] opens a personal storage rift that grows with your Necromancy level - and holding its regular attack on a block pulls that block straight into the rift, ten seconds up close and one more per block of distance, while the world drains to black and white around you. [b]Necromancer's Blood[/b] is paid for in your own health. [b]Tin cans[/b] add a reusable water cycle - fill, boil on a campfire without a pot, drink, keep the can.
|
||||
[b]Necromancer's tools.[/b] The [b]Blue Portal Stone[/b] teleports you to your bedroll after a ten-second channel that any damage interrupts, and is never consumed. The [b]Spatial Bracelet[/b] opens a personal storage rift that grows with your Necromancy level - and holding its regular attack on a block pulls that block straight into the rift, ten seconds up close and one more per block of distance, while the world drains to black and white around you. That pull runs on a charge in the bracelet's mod slot: the [b]Blood Sphere[/b] is spent a point per second and crumbles when empty, while the [b]Blood Stone[/b] from the third grade is never spent at all. [b]Necromancer's Blood[/b] is paid for in your own health. [b]Tin cans[/b] add a reusable water cycle - fill, boil on a campfire without a pot, drink, keep the can.
|
||||
|
||||
[b]The dead keep shop.[/b] Every trader in the world is rendered in black and white, half-transparent and matte. Nothing about the trade changes - but the necromancer deals with the dead, and the only people still doing business out here are not quite alive.
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -2,7 +2,7 @@
|
||||
|
||||
*English version below — scroll past the Russian half.*
|
||||
|
||||
**Версия 1.1.0** — для 7 Days to Die 3.2. Автор: Alex Cube.
|
||||
**Версия 1.2.0** — для 7 Days to Die 3.2. Автор: Alex Cube.
|
||||
|
||||
- Страница мода: https://www.alexcube.ru/7-days-to-die-moi-mody/kniga-nekromanta-necromancer-s-tome/
|
||||
- Nexus Mods: https://www.nexusmods.com/7daystodie/mods/12547
|
||||
@@ -24,16 +24,18 @@
|
||||
## Прогрессия
|
||||
|
||||
Отдельный скилл **"Некромантия"** растёт не от опыта, а от счётчика упокоенных зомби — свой
|
||||
счётчик, своя механика. Пять тиров, каждый открывает часть арсенала:
|
||||
счётчик, своя механика. Засчитывается любое убийство, за которое игра начисляет вам опыт: добитые
|
||||
ловушкой, сгоревшие, умершие от наложенного вами кровотечения, и зомби-звери наравне с
|
||||
человекоподобными. Пять тиров, каждый открывает часть арсенала:
|
||||
|
||||
| Тир | Порог | Что открывается |
|
||||
|---|---|---|
|
||||
| Адепт | сразу | Камень духов, Нож некроманта, Синий портальный камень, Пирамида духов |
|
||||
| Адепт | сразу | Камень духов, Нож некроманта, Синий портальный камень, Пирамида духов, Кровавая сфера |
|
||||
| Адепт (доп.) | 20 зомби | Пространственный браслет |
|
||||
| Адепт (доп.) | 30 / 60 / 100 / 300 зомби | Моды ножа: Слёзы мертвеца, Пир падальщика, Могильный покой, Тёмное чутьё |
|
||||
| Подмастерье | 500 зомби | Свиток девиации |
|
||||
| Подмастерье (доп.) | 1400 / 1700 зомби | Моды ножа: Хватка мертвеца, Мёртвая буря |
|
||||
| Ученик | 2000 зомби | Призыв зомбособаки, Жуки Властелина, Призыв зомбогрифа |
|
||||
| Ученик | 2000 зомби | Призыв зомбособаки, Жуки Властелина, Призыв зомбогрифа, Кровавый камень |
|
||||
| Некромант | 3000 зомби | Призыв зомбомедведя, Призыв зомбоволка, Свиток банши |
|
||||
| Мастер | 5000 зомби | Чёрный портальный камень |
|
||||
|
||||
@@ -67,6 +69,14 @@
|
||||
круглым индикатором, что и у разбора верстака. Мир на это время обесцвечивается. Не поддаются
|
||||
повреждённые блоки, контейнеры с содержимым, территория торговца и неразрушимое вроде дна мира —
|
||||
каждый отказ со своим сообщением. Силовая атака прерывает утаскивание и открывает хранилище.
|
||||
Утаскивание питается зарядом в слоте модификаций браслета — с пустым слотом обычная атака
|
||||
отказывает.
|
||||
- **Кровавая сфера** — заряд браслета. 500 прочности, каждая секунда утаскивания блока тратит
|
||||
единицу; опустевшая сфера рассыпается. Доступна с самого начала и делается без верстака, по две
|
||||
за раз, из крови некроманта и праха зомби.
|
||||
- **Кровавый камень** — тот же слот, но не тратится вовсе: эндгейм-замена сфере. Открывается на
|
||||
2000 упокоенных и варится на химической станции из праха зомби, костей, обычной крови и крови
|
||||
некроманта.
|
||||
- **Консервные банки** (пустая / с речной водой / с кипячёной) — расходный цикл вместо
|
||||
одноразовых банок: наполняются водой, кипятятся прямо на костре без кастрюли, выпиваются, банка
|
||||
возвращается пустой. Речная вода из банки может вызвать дизентерию, как обычная мутная вода;
|
||||
@@ -117,7 +127,7 @@
|
||||
|
||||
**13 языков полностью:** русский, английский, немецкий, испанский, французский, итальянский,
|
||||
японский, корейский, польский, португальский (Бразилия), турецкий, китайский упрощённый и
|
||||
традиционный. Все 130 ключей `Config/Localization.csv` заполнены, пустых ячеек нет.
|
||||
традиционный. Все 149 ключей `Config/Localization.csv` заполнены, пустых ячеек нет.
|
||||
|
||||
## Установка
|
||||
|
||||
@@ -129,17 +139,38 @@
|
||||
|
||||
## Статус
|
||||
|
||||
Версия 1.1.0 — Пространственный браслет научился забирать блоки прямо в хранилище: зажатая на
|
||||
блоке обычная атака утаскивает его туда через десять секунд плюс секунда за каждый блок
|
||||
расстояния, с обесцвечиванием мира на время ожидания (оно же теперь висит и на каналах обоих
|
||||
порталов). Не поддаются повреждённые блоки, контейнеры с содержимым, территория торговца и
|
||||
неразрушимое вроде дна мира. Плюс все торговцы стали чёрно-белыми, полупрозрачными и матовыми.
|
||||
Версия 1.2.0 — у Пространственного браслета появился расходник. Забор блоков теперь питается
|
||||
зарядом в слоте модификаций: **Кровавая сфера** тратит единицу прочности за секунду поглощения и,
|
||||
опустев, рассыпается; **Кровавый камень** с третьего грейда занимает тот же слот и не тратится
|
||||
вовсе. С пустым слотом обычная атака отказывает.
|
||||
|
||||
Предыдущая версия 1.0.1 закрывала первый баг-репорт с Nexus: содержимое браслета больше не
|
||||
пропадает после выхода из игры (хранилище сохраняется в файле игрока, рядом с рюкзаком).
|
||||
В этой же версии починен счёт убийств для Некромантии. Раньше скилл рос только от убийств своей
|
||||
рукой и только от человекоподобных зомби — мимо проходили зомбопёс, зомбомедведь, зомбокабан и
|
||||
зомбоворон, а также всё, что убивало за вас: ловушки, огонь, кровотечение. Теперь засчитывается
|
||||
всё, за что игра начисляет вам опыт. Тот же счётчик задаёт урон Ножа некроманта, так что нож
|
||||
заодно перестал недобирать. Плюс торговец больше не теряет призрачность после выгрузки чанка, а
|
||||
создание Крови некроманта сопровождается криком боли — оно и раньше стоило 90% здоровья, просто
|
||||
молча.
|
||||
|
||||
Весь заявленный контент реализован и проходит тесты в игре. Из запланированного не сделана
|
||||
только часть фирменных звуков. Текст описания для сайта (RU + EN) — в
|
||||
Версия 1.1.0 научила Пространственный браслет забирать блоки прямо в хранилище: зажатая на блоке
|
||||
обычная атака утаскивает его туда через десять секунд плюс секунда за каждый блок расстояния, с
|
||||
обесцвечиванием мира на время ожидания (оно же висит и на каналах обоих порталов). Не поддаются
|
||||
повреждённые блоки, контейнеры с содержимым, территория торговца и неразрушимое вроде дна мира.
|
||||
Плюс все торговцы стали чёрно-белыми, полупрозрачными и матовыми.
|
||||
|
||||
Версия 1.0.1 закрывала первый баг-репорт с Nexus: содержимое браслета больше не пропадает после
|
||||
выхода из игры (хранилище сохраняется в файле игрока, рядом с рюкзаком).
|
||||
|
||||
Весь заявленный контент реализован. Основное проверено в игре: заряд браслета и расход Кровавой
|
||||
сферы, счёт убийств Некромантии (включая зомби-зверей, кровотечение и огонь), призрачность
|
||||
торговцев, крик при создании Крови некроманта.
|
||||
|
||||
> **Эта сборка проверена не полностью.** Вкладка испытаний, оба первых испытания и счёт убийств
|
||||
> в игре подтверждены. Ещё не проверены: **Кровавый камень** (нужен уровень Некромантии 2000 и
|
||||
> химстанция), **новый порог Кровавой сферы** (переехала с уровня 1 на 20, туда же, где открывается
|
||||
> Пространственный браслет), а также **награды испытаний и два новых испытания** — на Камень духов
|
||||
> и Синий портальный камень. Если что-то поведёт себя не так, это ожидаемые места для сюрприза. Из запланированного не
|
||||
сделана только часть фирменных звуков. Текст описания для сайта (RU + EN) — в
|
||||
`SITE_DESCRIPTION.html` (разметка блоков WordPress). Полная техническая история разработки и текст финала лежат рядом с модом
|
||||
в `BACKLOG.md` и `FINAL_TEXT.md` — в репозиторий они не входят (спойлеры и внутренняя кухня).
|
||||
|
||||
@@ -147,7 +178,7 @@
|
||||
|
||||
# Necromancer's Tome (English)
|
||||
|
||||
**Version 1.1.0** - for 7 Days to Die 3.2. By Alex Cube.
|
||||
**Version 1.2.0** - for 7 Days to Die 3.2. By Alex Cube.
|
||||
|
||||
- Mod page: https://www.alexcube.ru/7-days-to-die-moi-mody/kniga-nekromanta-necromancer-s-tome/
|
||||
- Nexus Mods: https://www.nexusmods.com/7daystodie/mods/12547
|
||||
@@ -169,16 +200,17 @@ survival in spite of death, but power over it.
|
||||
## Progression
|
||||
|
||||
A dedicated **"Necromancy"** skill grows not from experience but from a count of zombies put to
|
||||
rest - its own counter, its own mechanic. Five tiers, each opening part of the arsenal:
|
||||
rest - its own counter, its own mechanic. Anything the game gives you XP for counts: kills finished
|
||||
by a trap, by fire, by a bleed you applied, and zombie animals alongside the humanoids. Five tiers, each opening part of the arsenal:
|
||||
|
||||
| Tier | Threshold | What unlocks |
|
||||
|---|---|---|
|
||||
| Adept | from the start | Spirit Stone, Necromancer's Knife, Blue Portal Stone, Pyramid of Spirits |
|
||||
| Adept | from the start | Spirit Stone, Necromancer's Knife, Blue Portal Stone, Pyramid of Spirits, Blood Sphere |
|
||||
| Adept (extra) | 20 zombies | Spatial Bracelet |
|
||||
| Adept (extra) | 30 / 60 / 100 / 300 zombies | Knife mods: Tears of the Dead, Scavenger's Feast, Grave's Repose, Dark Sense |
|
||||
| Journeyman | 500 zombies | Scroll of Deviation |
|
||||
| Journeyman (extra) | 1400 / 1700 zombies | Knife mods: Dead Man's Grip, Dead Storm |
|
||||
| Apprentice | 2000 zombies | Summon Zombie Dog, Beetles of the Lord, Summon Zombie Griffin |
|
||||
| Apprentice | 2000 zombies | Summon Zombie Dog, Beetles of the Lord, Summon Zombie Griffin, Blood Stone |
|
||||
| Necromancer | 3000 zombies | Summon Zombie Bear, Summon Zombie Wolf, Banshee's Scroll |
|
||||
| Master | 5000 zombies | Black Portal Stone |
|
||||
|
||||
@@ -212,7 +244,14 @@ rest - its own counter, its own mechanic. Five tiers, each opening part of the a
|
||||
workbench pickup uses. The world drains to black and white while it runs. Damaged blocks,
|
||||
containers with anything inside, a trader's ground and indestructible things like the world's
|
||||
floor all refuse, each with its own message. The power attack interrupts the pull and opens the
|
||||
rift instead.
|
||||
rift instead. The pull runs on a charge in the bracelet's mod slot - with that slot empty, the
|
||||
regular attack refuses.
|
||||
- **Blood Sphere** - the bracelet's charge. 500 points of durability, one spent per second of a
|
||||
pull; emptied, the sphere crumbles. Available from the very start and hand-crafted without a
|
||||
workbench, two at a time, from Necromancer's Blood and Zombie Ash.
|
||||
- **Blood Stone** - the same slot, but never spent at all: the endgame replacement for the sphere.
|
||||
Opens at 2000 zombies put to rest and is brewed at a chemistry station from zombie ash, bones,
|
||||
ordinary blood and Necromancer's Blood.
|
||||
- **Tin cans** (empty / with river water / with boiled water) - a reusable cycle instead of
|
||||
single-use jars: fill them with water, boil it right on a campfire without a pot, drink, and the
|
||||
can comes back empty. River water from a can can cause dysentery, like any murky water; boiled
|
||||
@@ -262,7 +301,7 @@ deliberately not retold here: a README gets read before the playthrough.
|
||||
## Localization
|
||||
|
||||
**13 languages, complete:** Russian, English, German, Spanish, French, Italian, Japanese, Korean,
|
||||
Polish, Brazilian Portuguese, Turkish, Simplified and Traditional Chinese. All 130 keys in
|
||||
Polish, Brazilian Portuguese, Turkish, Simplified and Traditional Chinese. All 149 keys in
|
||||
`Config/Localization.csv` are filled in, with no empty cells.
|
||||
|
||||
## Installation
|
||||
@@ -276,19 +315,38 @@ story scenes play without stopping time.
|
||||
|
||||
## Status
|
||||
|
||||
Version 1.1.0 - the Spatial Bracelet learned to take blocks straight into the vault: hold its
|
||||
regular attack on a block and it is pulled in after ten seconds, plus one more per block of
|
||||
distance, with the world draining to black and white for the wait (which now also covers both
|
||||
portal channels). Damaged blocks, containers with anything inside, a trader's ground and
|
||||
indestructible things like the world's floor all refuse. Every trader is now rendered in black and
|
||||
white, half-transparent and matte.
|
||||
Version 1.2.0 - the Spatial Bracelet now runs on a charge. Pulling blocks draws on whatever sits
|
||||
in its mod slot: the **Blood Sphere** spends a point of durability per second of the pull and
|
||||
crumbles once empty, while the **Blood Stone** from the third grade takes the same slot and is never
|
||||
spent at all. With the slot empty, the regular attack refuses.
|
||||
|
||||
The previous version, 1.0.1, closed the first bug report from Nexus: the bracelet's contents no
|
||||
longer disappear after leaving the game (the storage is saved in the player's own file, next to
|
||||
the backpack).
|
||||
The same version fixes Necromancy's kill count. The skill used to rise only from kills by your own
|
||||
hand, and only from humanoid zombies - zombie dogs, bears, boars and vultures never counted, and
|
||||
neither did anything that killed on your behalf: traps, fire, bleeding. Now everything the game
|
||||
gives you XP for counts. The same counter drives the Necromancer's Knife's damage, so the knife
|
||||
stopped coming up short as well. On top of that, a trader no longer loses its ghostly look after
|
||||
its chunk is unloaded, and crafting Necromancer's Blood now comes with a cry of pain - it always
|
||||
cost 90% of your health, it was just silent about it.
|
||||
|
||||
All the announced content is implemented and passes testing in game. Of what was planned, only
|
||||
part of the mod's own sound effects is missing. The description text for the website
|
||||
Version 1.1.0 taught the Spatial Bracelet to take blocks straight into the vault: hold its regular
|
||||
attack on a block and it is pulled in after ten seconds, plus one more per block of distance, with
|
||||
the world draining to black and white for the wait (which also covers both portal channels).
|
||||
Damaged blocks, containers with anything inside, a trader's ground and indestructible things like
|
||||
the world's floor all refuse. Every trader is now rendered in black and white, half-transparent and
|
||||
matte.
|
||||
|
||||
Version 1.0.1 closed the first bug report from Nexus: the bracelet's contents no longer disappear
|
||||
after leaving the game (the storage is saved in the player's own file, next to the backpack).
|
||||
|
||||
All the announced content is implemented. The core of it is confirmed in game: the bracelet's
|
||||
charge and the Blood Sphere's spending, Necromancy's kill count (zombie animals, bleeding and fire
|
||||
included), the traders' ghostly look, and the cry when Necromancer's Blood is made.
|
||||
|
||||
> **This build has not been fully tested.** The challenges tab, its first two challenges and the
|
||||
> kill count are confirmed in game. Not yet checked: the **Blood Stone** (it needs Necromancy level
|
||||
> 2000 and a chemistry station), the **Blood Sphere's new threshold** (it moved from level 1 to 20,
|
||||
> where the Spatial Bracelet itself unlocks), and the **challenge rewards plus two new challenges**
|
||||
> for the Spirit Stone and the Blue Portal Stone. If anything misbehaves, that is where to look. Of what was planned, only part of the mod's own sound effects is missing. The description text for the website
|
||||
(RU + EN) is in `SITE_DESCRIPTION.html` (WordPress block markup). The full technical history of
|
||||
development and the text of the finale sit next to the mod in `BACKLOG.md` and `FINAL_TEXT.md` -
|
||||
they are not part of the repository (spoilers and back-of-house).
|
||||
|
||||
+10
-6
@@ -8,7 +8,7 @@
|
||||
|
||||
Отдельный навык <strong>«Некромантия»</strong> качается не за очки перков и не за книги, а по счётчику упокоенных зомби. Пять тиров, 5000 убийств до вершины, каждый порог открывает часть арсенала:
|
||||
|
||||
<table><thead><tr><th>Тир</th><th>Порог</th><th>Что открывается</th></tr></thead><tbody><tr><td>Адепт</td><td>сразу</td><td>Камень духов, Нож некроманта, Синий портальный камень, Пирамида духов</td></tr><tr><td>Адепт</td><td>20 / 30 / 60 / 100 / 300</td><td>Пространственный браслет, затем моды ножа: Слёзы мертвеца, Пир падальщика, Могильный покой, Тёмное чутьё</td></tr><tr><td>Подмастерье</td><td>500 / 1400 / 1700</td><td>Свиток девиации, моды ножа Хватка мертвеца и Мёртвая буря</td></tr><tr><td>Ученик</td><td>2000</td><td>Призыв зомбособаки, Жуки Властелина, Призыв зомбогрифа</td></tr><tr><td>Некромант</td><td>3000</td><td>Призыв зомбомедведя, Призыв зомбоволка, Свиток банши</td></tr><tr><td>Мастер</td><td>5000</td><td>Чёрный портальный камень</td></tr></tbody></table>
|
||||
<table><thead><tr><th>Тир</th><th>Порог</th><th>Что открывается</th></tr></thead><tbody><tr><td>Адепт</td><td>сразу</td><td>Камень духов, Нож некроманта, Синий портальный камень, Пирамида духов, Кровавая сфера</td></tr><tr><td>Адепт</td><td>20 / 30 / 60 / 100 / 300</td><td>Пространственный браслет, затем моды ножа: Слёзы мертвеца, Пир падальщика, Могильный покой, Тёмное чутьё</td></tr><tr><td>Подмастерье</td><td>500 / 1400 / 1700</td><td>Свиток девиации, моды ножа Хватка мертвеца и Мёртвая буря</td></tr><tr><td>Ученик</td><td>2000</td><td>Призыв зомбособаки, Жуки Властелина, Призыв зомбогрифа, Кровавый камень</td></tr><tr><td>Некромант</td><td>3000</td><td>Призыв зомбомедведя, Призыв зомбоволка, Свиток банши</td></tr><tr><td>Мастер</td><td>5000</td><td>Чёрный портальный камень</td></tr></tbody></table>
|
||||
|
||||
<h2>Арсенал</h2>
|
||||
|
||||
@@ -20,7 +20,9 @@
|
||||
<li><strong>Кровь некроманта</strong> — ритуальный ресурс: пустая банка, любой нож в руках и 90% текущего здоровья за одну порцию. Ингредиент для самых тёмных рецептов.</li>
|
||||
<li><strong>Кожа жертвы</strong> и <strong>Прах зомби</strong> — падают с зомби, помеченного ножом как Жертва. Основа книг призыва и большинства некромантских рецептов.</li>
|
||||
<li><strong>Синий портальный камень</strong> — держите кнопку использования 10 секунд, чтобы телепортироваться к своему спальнику. Любой урон прерывает переход. Не расходуется.</li>
|
||||
<li><strong>Пространственный браслет</strong> — силовая атака открывает личный разлом-хранилище, размер которого растёт вместе с уровнем Некромантии. Обычная атака, зажатая на блоке, утаскивает этот блок прямо в хранилище: десять секунд вплотную и ещё по секунде за каждый блок расстояния, мир на это время обесцвечивается. Повреждённые блоки, контейнеры с содержимым, территория торговца и неразрушимое вроде дна мира не поддаются. Силовая атака прерывает утаскивание и открывает хранилище.</li>
|
||||
<li><strong>Пространственный браслет</strong> — силовая атака открывает личный разлом-хранилище, размер которого растёт вместе с уровнем Некромантии. Обычная атака, зажатая на блоке, утаскивает этот блок прямо в хранилище: десять секунд вплотную и ещё по секунде за каждый блок расстояния, мир на это время обесцвечивается. Повреждённые блоки, контейнеры с содержимым, территория торговца и неразрушимое вроде дна мира не поддаются. Силовая атака прерывает утаскивание и открывает хранилище. Утаскивание питается зарядом в слоте модификаций браслета — с пустым слотом обычная атака отказывает.</li>
|
||||
<li><strong>Кровавая сфера</strong> — заряд браслета: 500 прочности, каждая секунда утаскивания блока тратит единицу, опустевшая сфера рассыпается. Доступна с самого начала и делается без верстака, по две за раз, из крови некроманта и праха зомби.</li>
|
||||
<li><strong>Кровавый камень</strong> — тот же слот, но не тратится вовсе: эндгейм-замена сфере. Открывается на 2000 упокоенных и варится на химической станции из праха зомби, костей, обычной крови и крови некроманта.</li>
|
||||
<li><strong>Консервные банки</strong> — расходный цикл вместо одноразовых: наполнить водой, вскипятить прямо на костре без кастрюли, выпить, банка остаётся. Речная вода из банки может вызвать дизентерию, кипячёная безопасна. Вмещают меньше стеклянных.</li>
|
||||
</ul>
|
||||
|
||||
@@ -54,7 +56,7 @@
|
||||
|
||||
<h2>Локализация</h2>
|
||||
|
||||
<strong>13 языков полностью:</strong> русский, английский, немецкий, испанский, французский, итальянский, японский, корейский, польский, португальский (Бразилия), турецкий, китайский упрощённый и традиционный. Все 130 строк переведены, пустых ячеек нет.
|
||||
<strong>13 языков полностью:</strong> русский, английский, немецкий, испанский, французский, итальянский, японский, корейский, польский, португальский (Бразилия), турецкий, китайский упрощённый и традиционный. Все 149 строк переведены, пустых ячеек нет.
|
||||
|
||||
<h2>Установка</h2>
|
||||
|
||||
@@ -84,7 +86,7 @@ Necromancy here is an answer to a curse, not a side branch of crafting. Instead
|
||||
|
||||
A dedicated <strong>Necromancy</strong> skill levels not from perk points and not from books, but from your zombie kill count. Five tiers, 5000 kills to the top, each threshold opening part of the arsenal:
|
||||
|
||||
<table><thead><tr><th>Tier</th><th>Threshold</th><th>What unlocks</th></tr></thead><tbody><tr><td>Adept</td><td>from the start</td><td>Spirit Stone, Necromancer's Knife, Blue Portal Stone, Pyramid of Spirits</td></tr><tr><td>Adept</td><td>20 / 30 / 60 / 100 / 300</td><td>Spatial Bracelet, then the knife mods: Tears of the Dead, Scavenger's Feast, Grave's Repose, Dark Sense</td></tr><tr><td>Journeyman</td><td>500 / 1400 / 1700</td><td>Scroll of Deviation, knife mods Dead Man's Grip and Dead Storm</td></tr><tr><td>Apprentice</td><td>2000</td><td>Summon Zombie Dog, Beetles of the Lord, Summon Zombie Griffin</td></tr><tr><td>Necromancer</td><td>3000</td><td>Summon Zombie Bear, Summon Zombie Wolf, Banshee's Scroll</td></tr><tr><td>Master</td><td>5000</td><td>Black Portal Stone</td></tr></tbody></table>
|
||||
<table><thead><tr><th>Tier</th><th>Threshold</th><th>What unlocks</th></tr></thead><tbody><tr><td>Adept</td><td>from the start</td><td>Spirit Stone, Necromancer's Knife, Blue Portal Stone, Pyramid of Spirits, Blood Sphere</td></tr><tr><td>Adept</td><td>20 / 30 / 60 / 100 / 300</td><td>Spatial Bracelet, then the knife mods: Tears of the Dead, Scavenger's Feast, Grave's Repose, Dark Sense</td></tr><tr><td>Journeyman</td><td>500 / 1400 / 1700</td><td>Scroll of Deviation, knife mods Dead Man's Grip and Dead Storm</td></tr><tr><td>Apprentice</td><td>2000</td><td>Summon Zombie Dog, Beetles of the Lord, Summon Zombie Griffin, Blood Stone</td></tr><tr><td>Necromancer</td><td>3000</td><td>Summon Zombie Bear, Summon Zombie Wolf, Banshee's Scroll</td></tr><tr><td>Master</td><td>5000</td><td>Black Portal Stone</td></tr></tbody></table>
|
||||
|
||||
<h3>Arsenal</h3>
|
||||
|
||||
@@ -96,7 +98,9 @@ A dedicated <strong>Necromancy</strong> skill levels not from perk points and no
|
||||
<li><strong>Necromancer's Blood</strong> — a ritual resource: an empty jar, any knife in hand and 90% of your current health per portion. An ingredient for the darkest recipes.</li>
|
||||
<li><strong>Victim's Skin</strong> and <strong>Zombie Ash</strong> — dropped by a zombie marked as a Victim. The basis of the summoning books and most necromantic recipes.</li>
|
||||
<li><strong>Blue Portal Stone</strong> — hold the use button for 10 seconds to teleport to your bedroll. Any damage interrupts the channel. Not consumed.</li>
|
||||
<li><strong>Spatial Bracelet</strong> — a power attack opens a personal storage rift whose size grows with your Necromancy level. Hold the regular attack on a block and it is pulled straight into the rift: ten seconds up close, one more per block of distance, with the world draining to black and white while it runs. Damaged blocks, containers with anything inside, a trader's ground and indestructible things like the world's floor all refuse. The power attack interrupts the pull and opens the rift instead.</li>
|
||||
<li><strong>Spatial Bracelet</strong> — a power attack opens a personal storage rift whose size grows with your Necromancy level. Hold the regular attack on a block and it is pulled straight into the rift: ten seconds up close, one more per block of distance, with the world draining to black and white while it runs. Damaged blocks, containers with anything inside, a trader's ground and indestructible things like the world's floor all refuse. The power attack interrupts the pull and opens the rift instead. The pull runs on a charge in the bracelet's mod slot - with that slot empty, the regular attack refuses.</li>
|
||||
<li><strong>Blood Sphere</strong> — the bracelet's charge: 500 points of durability, one spent per second of a pull, and the emptied sphere crumbles. Available from the very start and hand-crafted without a workbench, two at a time, from Necromancer's Blood and Zombie Ash.</li>
|
||||
<li><strong>Blood Stone</strong> — the same slot, but never spent at all: the endgame replacement for the sphere. Opens at 2000 zombies put to rest and is brewed at a chemistry station from zombie ash, bones, ordinary blood and Necromancer's Blood.</li>
|
||||
<li><strong>Tin cans</strong> — a reusable cycle instead of single-use jars: fill with water, boil it right on a campfire without a pot, drink, keep the can. River water from a can can cause dysentery, boiled water is safe. They hold less than glass jars.</li>
|
||||
</ul>
|
||||
|
||||
@@ -130,7 +134,7 @@ Activating it asks for confirmation, stops the game and unfolds a full-screen fi
|
||||
|
||||
<h3>Localization</h3>
|
||||
|
||||
<strong>13 languages, complete:</strong> English, German, Spanish, French, Italian, Japanese, Korean, Polish, Brazilian Portuguese, Russian, Turkish, Simplified and Traditional Chinese. All 130 strings are translated, with no empty cells.
|
||||
<strong>13 languages, complete:</strong> English, German, Spanish, French, Italian, Japanese, Korean, Polish, Brazilian Portuguese, Russian, Turkish, Simplified and Traditional Chinese. All 149 strings are translated, with no empty cells.
|
||||
|
||||
<h3>Installation</h3>
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
Reference in New Issue
Block a user