- Главы питомцев с чертежами (Fig. III-VI), команда necroobj для выгрузки моделей
- Книги призыва питомцев переименованы в гримуары (13 языков)
- Чертоги: второй этаж; таблички necroDoorNumber (только в редакторе) нумеруют двери,
дверь с номером закрыта до события (SetDoorOpen, necropalace door N open|close),
с начала открыты 2, 4, 5, 6
- Дух: плавный шаг по ступенькам, проход в проёмы с порогом
- Стервятник: снижение только C, без Ctrl-переключателя
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Книга некроманта: главы «Нож некроманта» и «Могильная крыса» - лист-чертёж сверху (Textures/,
три вида из Blender, подписи на эсперанто), управление и лор; окно книги непрозрачное, текст с контуром
- «Дух крысы» переименован в «Могильную крысу» на 13 языках (ключи прежние)
- Блоки (Dev): плакат-синька ножа (necroPosterKnife, 3:2 под полотно рамки), грифельная доска
necroChalkboard (надписи - TEFeatureSignable, писать только в редакторе/творческом; шрифт Neucha OFL,
свой шейдер текста с тестом глубины), дверь-книжный шкаф necroBookDoor (своя модель с аниматором,
хитбокс створки через RootTransformRefParent)
- Чертоги: префаб 25x8x25 (промежуточный); открываются только межкомнатные двери; надписи на досках
по дням игры: «Отомсти архимагу!», с 5-го дня «Берегись излучения красной луны»
- Нож: силовая атака стоит 20 здоровья, с «Мёртвой бурей» 40; не хватает - itembreak, «Недостаточно
здоровья» и обычный удар; описания на 13 языках
- Консольная команда necromodel - устройство модели блока в лог
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Прицел всех одержимых - всегда перекрестие (свой guiDrawCrosshair: точка бралась из проверки
камеры от 3-го лица у тела); краснеет на враге: у стервятника - цель рывка, у наземных - враг
на луче прицела до 30 м, не за стеной
- Падаль: отхил вдвое меньше (EatHealFrac 0.125); ест на F стервятник, медведь, собака, волк и
одержимые зомби
- E - действие: стервятник - клич (был F), крыса/волк - обыск, медведь - на дыбы (враги до 3.5 м
сбиты с ног и отброшены, откат 6 с)
- Рёв медведя: надписи «на медведя идут N» и откат, мимо очереди подсказок
- Крыса и волк обыскивают мешки с зомби (EntityLootContainer), волк берёт в сумку
- Багажник волка: окно сумки как у браслета, без рюкзака; выброс - у волка
- Отзыв волка: мешок на месте и метка «Сумка волка» на карте, снимается с мешком
- Тексты на 13 языках
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Могильная крыса: своя модель без волчьей шерсти; стоит на земле (центр капсулы);
E - осмотр контейнеров без права взять (окно игры, рюкзак скрыт, отказ «инвентарь
заполнен», контейнер не разрушается); ПКМ - проход сквозь постройки, полупрозрачная.
- Стервятник: курица в когтях - слепок с обвисшими крыльями и шеей, без хитбокса.
- Зомбособака: нюх - все существа рядом на компасе и карте.
- Медведь: толстая шкура (вдвое меньше урона), рёв-провокация, удар лапой по блокам.
- Лютоволк: бросок на цель с опрокидыванием, след крови раненых, сумка на 3 ячейки
(лут, выгрузка у тела, выпадает мешком при гибели и отзыве; отдельный файл сейва).
- Череп (останки): 2% при обычном обыске красного трупа вместо браслета.
- Прицел книги не рисуется в одержимости; подсказки вселения на 13 языках.
- Замер EntityLeakProbe: удалённые, но живые в памяти сущности - в лог.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Одержимость питомца: ПКМ с книгой - вселиться, E - отозвать (и изнутри питомца),
пояс скрыт, надпись о враге у тела.
- Одержимый стервятник: полёт, рывок на ЛКМ с 10 м с прямым уроном, звуком и
кровотечением, обычный прицел краснеет на цели; посадка в позу 0 и взлёт на пробел;
ПКМ - клич-приманка (паникующая курица, откат 0.5 с); F - питаться трупами и
блоками-падалью, доеденное исчезает.
- Дух змеи атакует только нежить; фиолетовая метка на карте у змеи и одержимого.
- Череп из красного трупа (этап 1 цинерария); глава книги «Письмо архимага» с видео.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HoldType 14 (консерва) не имеет анимации набора воды, банка набирала
воду без движения рук. Ставим 87 - хват drinkJarEmpty.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Кровавые семена (50 семян травы + кровь некроманта), три этапа роста на грядке:
росток -> бутоны-черепа -> куст роз с черепом в сердцевине (бандл necrorose).
Урожай - чёрные розы (3 базово, бонусы фермерства через ванильные теги).
- Ступка (верстак, 1 куб, руны на боку; бандл necromortar) - на Подмастерье.
- Сок чёрной розы: 4 розы + пустая банка на ступке; банка - копия банки крови
с чернильно-чёрным содержимым (в бандле necroblood).
- Копии всех рецептов на крови некроманта с соком: банка сока = две банки крови
(при одной крови - одна), из сока 3 кровавые сферы вместо 2.
- Иконки семян, ступки, сока и чёрной розы; тексты на 13 языках.
- DecorColliderTagPatch: хитбокс ступки и куста роз.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Змея: проверка «внутри текстуры» лучами (земля игры - поверхность), каждый кадр выброс
на ближайшее свободное место (на земле - из любых текстур, в полёте - из грунта);
шаг и прыжок в текстуры не пускают. Полёт пока зажата ПКМ: 1.1 м/с (половина шага
зомби), сквозь постройки можно, в грунт нельзя; Пробел вверх, C/Ctrl вниз.
E - аварийный выход. Детализация зданий и область тела - как у одержимого.
- Одержимый: будит спящих (комнаты зданий и сам вид одержимого), приседание на C и Ctrl,
удар по точке под прицелом с наведением на грудь ближнего врага.
- Хваты: толчок руки к прицелу на ЛКМ; «пиф-паф» вселяет на пике толчка.
«Отрицание» стало «Козой» (13 языков): средний и безымянный поджаты, большой прижат;
звук - бросок змеи.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- tinCanRiverWater/tinCanBoiledWater: HoldType 14 -> 3 (хват drinkJar*),
питьё сразу, без фазы вскрытия консервы.
- necrocan: диск воды расширен с r 0.0389 до 0.0425 - край уходит в
толщину двойной стенки, щели у стенки больше нет. Проверено в игре.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Чертоги разума: дух в префабе chertogi под землёй, книга памяти, нож и плакат;
кровать префаба - мебель necroPalaceBed (иначе становилась точкой возрождения);
охрана выбрасывает и игроков (их нет в EntityAlives).
- Хваты пустой руки по R: «отрицание» и «пиф-паф» - поза рук костями поверх аниматора,
остальные кости заморожены, ЛКМ - заклинание, прицел краснеет на зомби.
- «Отрицание»: зомби в ужасе бежит дальше 40 м и теряет игрока как цель.
- «Пиф-паф»: одержимость зомби-гуманоидом - бег, прыжок, удар, дальняя атака,
подрыв, крик крикуньи; тело цепенеет, зомби танкуют одержимого; область
вокруг тела сжата; полоска здоровья; спавн и прорисовка у одержимого.
- Книга с оглавлением (окно necroTome), глава «Некроэнергия».
- Прорисовка дверей у змеи и одержимого (CheckLODs от камеры, а не от тела).
- Почтовый ящик консоли и команды necrobones/necropose/necroshot для разработки.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Радиация: проверка раз в 2 с, в поясе змея сразу развеивается (таймер 30 с убран).
- Леденящий ужас: учуянные змеёй зомби (30 м) бегут, ломая препятствия;
очередь испуга, отметка места змеи для поиска пути, выключен в кровавую луну.
«Шум» (зомби шли к змее) удалён. Диагностика бегства в логе пока оставлена.
- Стиратель (zombieEraser): в орде игрока-змеи с шансом 0.5; взрывается на любой
атаке и при разрыве змеёй, отбрасывает змею; тело - чёрный разрыв. Вид: своя
раскраска на модели гавайца (Resources/Eraser), свечение маджента, звуки спайдера.
- Фиолетовая змея на карте (necroSnakeSpirit).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- пирамида: огонь убран; включённая модель светится ярко (кристалл, глазницы),
выключенная тускло; граница зоны - светящееся фиолетовое кольцо по земле
(LineRenderer, точки опущены на рельеф) вместо огоньков; старые огни снимаются
- банка крови: снято зеркалирование - стоит как ванильная, в руке
- нож: руны перенесены на противоположную плоскость клинка
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- модель: ступени из костей, черепа с тлеющими глазницами, костяная ось насквозь,
наверху цветок из рваной плоти на коротком шипастом стебле из скрученных жил,
в нём фиолетовый кристалл; бандл necroward (копия префаба оссуария)
- блок на Shape="ModelEntity" как оссуарий; шейдер и теги коллайдера ставит
GeoOssuaryPatch (теперь для обоих префабов); свой коллайдер-коробка
- «Пирамида духов» переименована в «Пирамиду отрицания» (13 языков, и в описаниях);
внутреннее имя блока прежнее - уже поставленные пирамиды в сейвах сохранятся
- иконка - рендер модели
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- стекло, крышка и кровь построены заново по размерам игровой банки
- крышка - свернувшаяся кровь: бугристая, пористая, тёмно-бордовая с почти чёрными
пятнами и прожилками фибрина
- на стекле процарапаны руны: «SANGO DE NEKROMANTO» и «NE TUŠU» (эсперанто,
старший футарк); потёки крови по стеклу над уровнем
- кровь тёмная с фиолетовым отливом, слабо светится, со светящимися искрами
- стекло - отдельный прозрачный материал, крышка и кровь - непрозрачный со свечением;
ванильные меши masonJar* и masonJar_n из бандла убраны
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- кость и клинок построены заново по профилю прежнего ножа - длина и положение
навершия, рукояти, гарды и острия те же
- облик костяного кинжала: навершие-сустав, рукоять со скруглённо-прямоугольным
сечением и рифлением как у рифлёного листа, крестообразная гарда, клинок
обоюдоострый ромбического сечения, у гарды во всю ширину; проволочная обмотка
убрана; резьба по кости на навершии и у основания клинка
- текстуры свои (кость, трещины с пурпуром, резьба); руны пользователя перенесены
со старой модели запеканием; ванильные boneShiv_n и перекраска boneShiv_d убраны
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- скакун: меш коллайдеров колёс - свой цилиндр тех же размеров; у материалов
выключенных ореола и стекла фары сняты ванильные текстуры
- оссуарий: коллайдер - своя коробка по габаритам ванильного меша
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Префабы necroBookDogPrefab и necroBookVulturePrefab в бандле necrobooks: модель
книги крысы, свои текстуры - рисунки z-dog и z-bird, кожа в тон шкуре зомбопса
(серо-бурая) и перьям зомбостервятника (красно-бурая).
- bookSummonZombieDog и bookSummonZombieGriffin переведены на них, тинт снят.
- Иконки SummonZombieDog и SummonZombieGriffin - рендер моделей, как у книги крысы.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Камень в любой ячейке скакуна - вечно полный бак, сферы рядом не тратятся.
- Камень в отсеке топлива гео-оссуария - работает вечно (FuelTypes + removeFuel).
- Кровавая сфера чинится пакетом крови: 250 прочности за пакет (2 пакета - полная).
- Описания сферы, камня, оссуария и скакуна дополнены (13 языков); в описании
скакуна по-корейски и по-китайски название сферы приведено к принятому в моде.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Мотор не заводится без сфер и глохнет, когда кровь кончилась (ваниль заводила
его и с пустым баком - ехал на полной тяге); вставили сферу в седле - заводится.
- Звуки мотора у скакуна молчат; на ходу - стук педалей bicycle_pedal,
гудок и колёса - велосипедные.
- Частицы дыма/огня убраны (розовые кубы - ванильные частицы без материалов).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Топливо скакуна - кровавые сферы в 4 ячейках модификаций, тратятся по очереди
(одна сфера = 120 ед., бак 480); уровень бака считается по прочности сфер,
выработанная сфера исчезает. Заправка канистрой убрана. В ячейки - только сферы.
- Причина «не едет» и «-2147483648%»: Extends не наследует effect_group, у предмета
скакуна не было ни ячеек, ни прочности. Своя группа: ModSlots 4, DegradationMax 4000;
здоровье закреплено кодом (DegradationMax сфер в ячейках его перебивал бы).
- Ремонт - 50 костей за ход вместо ремкомплекта; подсказка при нехватке, 13 языков.
- Модель скакуна: сиденье и рама прогнуты по ванили, рога по оси руля,
череп меньше и короче, свет фары в глазнице, крыло над передним колесом,
зубы под черепом убраны.
- Книга крысы: повёрнута как ванильная (палец на корешке), без металлических уголков.
В игре не проверено.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Костяной скакун (necroBoneSteed): мотоцикл из костей, сухожилий и сосудов
с черепом-циклопом вместо фары; своя модель (бандл necrobonesteed),
ванильная система предмета-транспорта (necroBoneSteedPlaceable, предпросмотр
при установке); топливо - кровавая сфера, одна сфера = полный бак; теги
коллайдеров и шейдер копируются с ванильного мотоцикла; рецепт на верстаке,
Подмастерье
- Книга Духа крысы: своя модель (бандл necrobooks) - чёрная кожа, светящийся
узор со скелетом крысы и семиконечной звездой, ржавые уголки, ремешок
- Турели не стреляют по существам мода (питомцы, рой, змея)
- Гео-оссуарий тратит 1/10 прочности сферы на стак вместо целой сферы
В игре не проверено.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Питомцы:
- охрана хозяина: враг ближе 20 м (в режиме змеи - к телу) атакуется сам,
поводок 40 м, охраняют все кроме крысы; приказ прицелом охрана не перебивает
- IsFoe исключает существ мода (necro*): питомец выбирал целью себя
- призыв нового спутника отзывает прежнего
- прицел книг призыва - как у арбалета, краснеет на годной цели
Гео-оссуарий (necroGeoOssuary): станция добычи на кровавой сфере
- основа - коллектор росы; в 3 ячейки модов любой из 5 ресурсов
(камень, уголь, селитра, железо, сланец), 1 сфера = стак 6000 за 5-8 ч
- работает без открытого неба; верстак, Подмастерье (ур. 25)
- своя модель (бандл necroossuary): кости, корни, сосуды, клыки, черепа
с глазами-углями; шейдер и теги коллайдера берутся у ванильного коллектора
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
В первые RagdollDelay секунд после входа игра ещё обрабатывает ввод:
можно было взять ствол, и R разом перезаряжало его и выводило из змеи.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FPS сползал 60 -> 15-30 за 10-15 минут в любом мире; перезаход сбрасывал, в режиме
змеи (пояс скрыт) тормозов не было. Замер показал: группа окон toolbelt 2 -> 23 мс/кадр
при неизменном числе элементов. Причина - ошибка игры: BindingNcalcFunctions.cvar при
каждом вычислении регистрирует новый VariableStateCVar, RegisterVariable добавляет без
проверки, новая переменная стартует с 0 и в следующем кадре снова вызывает пересчёт.
Шкала fill="{% cvar('necroNecromancyProgressCVar') / 20 }" (с 1.3.0) набирала по одной
переменной за кадр. CVarBindingLeakPatch: одна CVar регистрируется в привязке один раз.
Проверено: 15 мин, 25 убийств - FPS 55-58 ровно, toolbelt ~1 мс/кадр.
Пирамида: на Texture Quality ниже Full атлас блоков игры меньше нашей текстуры 512, и
CustomBlockPaint её пропускал. Теперь берётся мип под размер атласа; бандл necroatlas
пересобран с ignoreMipmapLimit (иначе CopyTexture отказывает и пирамида чёрная).
Проверено на Half.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Серые копии были 4096x4096 RGBA32 без сжатия (64 МБ видеопамяти каждая плюс CPU-копия) - на стриме 28.09 это добавило к переполнению 8 ГБ видеокарты. Теперь как в CorpseLoot: уменьшение до MaxTintSize, Compress, Apply(makeNoLongerReadable). При выходе из мира копии уничтожаются, а не просто забываются.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Вместо «Everything else works» - честно: мод изначально под одиночную
игру, переделывает механики игры (трупы, добыча, поведение зомби), и в
мультиплеере часть вещей может не работать, например трупы «Жертвы».
README (RU/EN), сайт (RU/EN), описание Nexus.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Префикс EntityPlayer.AddKillXP пропускает начисление опыта для жертв
змеи. Некромантия и урон ножа растут как прежде: их счёт - постфикс на
том же методе, Harmony вызывает его и при отменённом оригинале.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ночью по R с ножом - дух змеи: тело падает (-95% HP), игрок управляет
призрачной змеёй от первого лица. Свободный ход по миру (свой наблюдатель
чанков, отрисовка, звук, спавн и спящие считаются от змеи), прыжок,
атака на ЛКМ - рывок сквозь препятствия и чёрный разрыв, ПКМ - выбраться
из текстур. Тёмное чутьё, тревога у тела, отсчёт в радиации края карты,
пурпурный фильтр зрения (necrovision). Утром змея развеивается.
Чёрный разрыв (NecroBurst.cs): клон частиц confetti в чёрном, команда
necroburst. Проверено в игре 28.09.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ModInfo 1.5.0. README (обе половины), описание для сайта и для Nexus: мод для 3.3,
для игры 3.2 остаётся 1.4.2; Нож метит Жертву и с одного удара; красный труп
открывается как ящик, без мешка. Тексты для Nexus: NEXUS_FILE_SUMMARY_1.5.0.txt
(<=255 знаков) и NEXUS_CHANGELOG_1.5.0.txt (строка = пункт).
DLL пересобраны против 3.3 из текущих исходников.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Обыск больше не превращает труп в мешок. Труп получает сумку со списком добычи zPackVictim
и открывается игровым путём (LockRequestLocal -> LootBagOpened -> окно добычи с игровым
таймером обыска). Запрет открывать мёртвых у EntityAlive снят префиксами только для таких
трупов. Открытый и не опустошённый труп лежит час, опустошённый убирается сразу.
После загрузки труп узнаётся по сохранённой сумке (баффы зомби в сейв не пишутся).
LockRequestLocal вызывается из TEPersistence (перегрузка с ReadOnlySpan не компилируется
в основном проекте). Класс EntityLootContainerVictim оставлен ради старых сейвов.
Описание ножа исправлено на всех языках, README - текущее описание.
Проверено в игре на 3.3: открытие, таймер, уборка опустошённых, восстановление после загрузки.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Метка висела на onSelfAttackedOther, которое приходит после DamageEntity; при смертельном
ударе добыча успевала разыграться без неё. Перенесена на onSelfPrimary/SecondaryActionRayHit
(до урона, как лечение ножом), с гейтом Health GT 0 - трупы больше не метятся.
Отзыв sanovskiy на Nexus. Проверено в игре на 3.3: три убийства с одного удара дали «Жертву».
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Пирамида: StreamModeWrite/Read теперь глобальные перечисления (в 3.2 были внутри TileEntity).
- Хранилище: Bag поверх ItemStackGrid (конструктор с местом хранения, Resize, ItemGrid),
запись и чтение через PooledBinaryWriter/Reader. Формат хранилища 2; формат 1 из сейвов 3.2
читается. Незнакомый номер формата больше не считается «хранилища нет» - байты сохраняются.
- Сфера на браслете: ModificationCount/GetModification/SetModification.
- Непустой сундук: TEFeatureStorage.IsEmpty (ITileEntityLootable в 3.3 нет).
- Поиск ножа в инвентаре через ItemGrid.
- GameVersionGuard пускает только 3.3.
Проверено в игре на V 3.3.0 b14: хранилище из сейва 3.2 (24 стопки) прочиталось, сохранилось
и перечиталось; Пирамида включается; «Жертва», обыск трупа и мешки работают.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Повод: 1.4.1 на игре 3.3.0 experimental не смогла прочитать Пространственное
хранилище (Bag переписан) и при сохранении записала пустой маркер - вещи
пропали навсегда.
GameVersionGuard: версия из Constants.cVersionInformation (не из const
cVersionMinor - та вшивается в DLL), поддерживается Major 3 и Minor/10 == 2,
то есть любая 3.2.x. На другой версии в главном меню окно «мод несовместим,
обновите» с единственной кнопкой «Выйти из игры» (решение пользователя), а
вместо PatchAll ставятся по отдельности только окно и четыре патча
сохранения хранилища. Всё, чем пользуется защита, сверено с декомпиляцией 3.3.
Хранилище: непрочитанные байты (чужая версия или упавший разбор) проходят
Read -> ToPlayer -> FromPlayer -> Write нетронутыми, ключ - entityId, сброс
при открытии главного меню (последнее сохранение игрока делается раньше,
внутри SaveAndCleanupWorld - проверено по коду игры). Пока байты не
прочитаны, хранилище не открывается и не пополняется, с подсказкой.
Проверено в игре на 3.2 через отладочные файлы FORCE_VAULT_UNREAD и
FORCE_VERSION_GUARD (в .gitignore, в архив не попадают): 603 байта
хранилища прошли сохранение и прочитались обратно совпадающими с исходными
(26 ячеек, 24 предмета); окно появляется, Esc его не закрывает, кнопка
закрывает игру. На настоящей 3.3 не проверено.
Localization: 4 новых ключа на 13 языках, всего 165; README - статус и
число ключей (было 149).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Было: CorpseLootPatch перекрашивал труп через renderer.materials и Tint(),
и ничего за собой не убирал. Копии материалов Unity с объектом не удаляет,
перекрашенная текстура была несжатой RGBA32 (16-64 МБ) и читаемой (копия
ещё и в RAM), кеш только рос. Лог игры: Texture.currentTextureMemory
1,2 -> 7,4 ГБ за два часа при desired ~3,5 ГБ, FPS 60 -> 11. Те же жалобы
на Nexus (sanovskiy, Kriloko, Megaladon43).
Стало: каждый перекрашенный труп записан (рендереры, материалы, текстуры),
тик раз в секунду удаляет всё за трупом, которого нет в мире, текстура
удаляется со счётчиком пользователей. Текстура не больше 1024, DXT,
makeNoLongerReadable. Проверено в игре: обыск и истечение срока доводят
счёт до «трупов 0, текстур 0».
Необысканный труп лежит 300 с вместо 3600 (указание пользователя).
buffNecroZombieKillTrackerDisplay: update_rate .1 -> 1 (отзыв zomb1);
урон ножа растёт после убийства, проверено.
Документы: версия 1.4.1, README (статус + несовместимость с 3.3
experimental), описания Nexus и сайта (пять минут вместо часа),
NEXUS_CHANGELOG/FILE_SUMMARY_1.4.1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Пользователь проверил: поднятая и заново поставленная кровать становится точкой
возрождения, финальный вид ножа (гарда над кулаком, светящиеся руны) и иконки на месте.
README (обе половины): кровати перенесены в проверенное, во врезке из новшеств 1.4.0
осталось только поведение питомцев в кровавую луну и на транспорте. Changelog: «should
become ... Not tested in game yet» -> утверждение.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
НОЖ ПОЛУЧИЛ СВОЮ ФОРМУ. До сих пор в бандле necroknife стояла ванильная геометрия
костяной заточки с нашей текстурой - на Nexus это заметили (Scarlettdruid: нож не похож
на свою иконку). Форма сделана в Blender поверх ванильного меша: головка и шейка бедра
срезаны, срез закрыт; лезвие выпрямлено в ампутационный нож с клиновым сечением;
костяная гарда стоит над кулаком (место выбрано по игре: девятый виток обмотки от
прежней гарды); рукоять обмотана верёвкой. Весь нож повёрнут на 180° вокруг оси -
режущая кромка смотрела не в ту сторону, "получается, что я обухом режу". Поворот, а
не зеркало: так руны на клинке не отражаются.
Текстура осталась от ножа некроманта. Верёвка нарисована в свободном углу её же атласа
(второй материал импорт в Unity отбрасывает), старые руны заменены светящимися
рублеными знаками, одинаковыми на обеих сторонах клинка. Меш 2823 вершины / 4360
треугольников. Сама цепочка сборки (скрипты Blender и сборки текстур) - в _private,
в репозиторий не входит.
ИКОНКИ нарисованы пользователем: NecromancersKnife.png - под новую модель, RatBook.png -
первая своя у Свитка духа крысы. У свитка снят CustomIconTint: он множится на иконку
и гасил бы нарисованные цвета, он был нужен только перекрашенной волчьей иконке.
ДОКУМЕНТЫ. README (обе половины), SITE_DESCRIPTION.html, NEXUS_DESCRIPTION.bbcode:
версия 1.4.0, новый вид ножа, добыча Жертвы в самом трупе (краснеет, лежит час, при
обыске оставляет мешок - проверено в игре), подбор кроватей-точек возрождения (в игре
НЕ проверено, так и написано). В "Статусе" - абзац 1.4.0 и напоминание о шкале 1.3.0:
на Nexus 21.09 игрок принял уровень "1 из 250" за застывший счётчик и решил, что
прогресс сбросился - уровень растёт раз в 20 убийств, а с 1.0.1 он пересчитывается и
становится в 20 раз меньше.
Тексты для Nexus в принятых сайтом форматах: NEXUS_FILE_SUMMARY_1.4.0.txt (254 знака
из 255) и NEXUS_CHANGELOG_1.4.0.txt (20 строк = 20 пунктов, без номера версии в начале).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Указание: «зомби после упокоения оставляет свой труп. Можем ли мы подменять
этот труп точно таким же, но с другой текстурой и с функцией мешка жертвы?»
ПОДМЕНЯТЬ НЕ ПРИШЛОСЬ НИЧЕГО. Труп - не отдельная модель, а та же скелетная
модель зомби в позе смерти, так что «такой же, но другой текстуры» делается
перекраской уже лежащего трупа. Ни нового префаба, ни новой сущности.
Три части, каждая на своём ванильном механизме:
НЕ ИСЧЕЗАЕТ. EntityAlive.timeStayAfterDeath - обычное публичное поле в тиках,
у зомби 30 секунд, у ванильного мешка 3600. Пишем второе.
ОБЫСКИВАЕТСЯ. У сущностей своя система команд, отдельная от блочной:
InitLocalActivationCommands / AllowActivationCommand / OnEntityActivated.
Объявляем ту же команду "search", что и ванильный мешок (EntityItem), поэтому
подпись достаётся готовой и локализованной. Список команд кешируется при
первом обращении - пока зомби жив, - поэтому на смерти он обнуляется.
ВНУТРИ ЛУТ. Обход сознательный: хранилище живёт внутри EntityLootContainer
вместе со своим сейвом и сетевой синхронизацией, и приделывать это трупу
значит трогать сейвы. Поэтому обыск ПРЕВРАЩАЕТ труп в мешок «Жертва».
ПЕРЕКРАСКА ЧЕРЕЗ ТЕКСТУРУ, А НЕ ЧЕРЕЗ _Color. Первая версия не работала вовсе:
у шейдеров Game/Character и Game/Animal/Fur свойства цвета НЕТ (видно в пробе,
которую печатает GhostTraderPatch: "tint property: <none>"). Красится сама
текстура - прогоном через RenderTexture, как обесцвечиваются торговцы, только
вместо сведения к яркости умножение на цвет. Результат кешируется по исходной
текстуре. Если модель на кадре смерти не собрана, труп встаёт в очередь и
докрашивается односекундным тиком.
ПОДСКАЗКА ПРИ НАВЕДЕНИИ - отдельный механизм, и сначала я настроил не тот.
Иконка EntityActivationCommand рисуется в РАДИАЛЬНОМ меню, то есть на зажатии.
А то, что видно при простом наведении, приходит из Entity.GetActivationText(),
и у EntityAlive он для существа возвращает пустую строку. Теперь возвращает то
же, что ванильный лутаемый объект: строку lootTooltipNew со значком клавиши из
разметки [action:...]. Имя берётся у зомби, так что читается «обыскать Арлин».
ОБЫСК НЕ МГНОВЕННЫЙ: три секунды с круговым индикатором (XUiC_Timer, тот же,
что при снятии верстака). Поле activateTime у команды не подошло - по нему игра
дёргает игровое событие из entity_class, а не наш код.
Обычный мешок это не отменяет: бросок вероятности остался как был, и теперь он
печатается в лог целиком (шанс против броска) - чтобы «не повезло» отличалось
от «сломано» без гадания. У ванильного зомби шанс 4%, то есть ноль мешков на
два десятка убийств - исход с вероятностью около 40%.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Исправление промаха по цели. Кровати класса SleepingBag (bed02*, bunkBed*) были
исключены из подбора НАМЕРЕННО, с формулировкой "это respawn-anchor блоки, не
декорация, трогать не просил" - и подбирались только декоративные
bedMadeNoFrame*/bedMessyNoFrame*.
Пользователь объяснил, что смысл подбора кроватей ровно обратный: "поглощённые
блоки сохраняют состояние, которое было до поглощения. Кровати до поглощения не
являются точкой возрождения. Точкой возрождения они могли бы стать при подборе".
То есть мы подбирали ровно те кровати, которые не нужны, и не подбирали те, ради
которых всё затевалось.
Проверено декомпиляцией BlockSleepingBag: PlaceBlock ставит игроку
selectedSpawnPointKey и запоминает BedrollPos - поставленная своими руками
кровать действительно становится точкой возрождения, а стоящая в POI нет.
Значит подбор и поглощение браслетом решают РАЗНЫЕ задачи.
Новых патчей не понадобилось, и это проверялось до правки: BlockSleepingBag
переопределяет только CanPlaceBlockAt, PlaceBlock, GetActivationText и
ЧЕТЫРЁХаргументный OnBlockActivated. Наши три патча висят на
HasBlockActivationCommands, GetBlockActivationCommands и ПЯТИаргументном
OnBlockActivated - ни одного из них он не перекрывает. С BlockCompositeTileEntity
(кулеры, коробки) в своё время так не вышло и понадобился отдельный набор.
Добавлены два префикса: bed02 и bunkBed. Полсотни цветовых вариантов наследуют
Class="SleepingBag" через Extends и покрываются ими же. bedroll НЕ включён:
собственный предмет игрока, ставится и снимается штатно, а bedrollPOI от него не
наследуется и точкой возрождения не является.
В ИГРЕ НЕ ПРОВЕРЕНО. Смотреть надо одно: становится ли поднятая и заново
поставленная кровать точкой возрождения на самом деле.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Работа по указаниям 2026-09-18. В релиз пока не выходит.
НОВЫЙ ПИТОМЕЦ "Дух крысы" (necroRatSpirit), Некромантия 3 (60 убийств):
модель зомбоволка в масштабе 0.28, урон 5, кроличьи звуки. Сам не нападает
никогда, держится справа-сзади в двух блоках, упёршись в препятствие проходит
СКВОЗЬ него и блоков не ломает. Укус замедляет, оставляет метку духа (видна на
компасе, +25% получаемого урона) и рвёт жилу. Обгрызая труп, лечится.
СВОЯ ЗАДАЧА ИИ. Задачи "иди за сущностью" в игре нет вовсе - проверены все 32
типа EAI*. Написана NecroFollowOwnerTask по образцу EAIApproachSpot: сектор
"3-6 часов" от хозяина, FindPath с canBreak:false, проход сквозь препятствие
через Entity.IsStuck. Вешается в рантайме, минуя Type.GetType.
ПРИКАЗ АТАКОВАТЬ. Повторное применение свитка при живом питомце натравливает
его на цель под прицелом: EntityPlayerLocal.HitInfo + ItemActionAttack.
GetEntityFromHit. Торговцы и игроки отсеяны. Нет цели - "Нет цели для атаки".
ЗОМБОЖИВОТНЫЕ ПРИВЕДЕНЫ К ТОМУ ЖЕ ОБРАЗЦУ. Сняты BreakBlock, Territorial,
ApproachSpot, Wander, BlockingTargetTask и поедание трупов; цель они больше не
выбирают сами. Лестница урона 20/35/45/60 плюс расчленение у Пса, Медведя и
Волка. Кровотечение всем, метка и ослабление - только у крысы.
УБИЙСТВА ПИТОМЦЕМ ЗАСЧИТЫВАЮТСЯ ВЛАДЕЛЬЦУ, включая добивание кровотечением.
Префикс на AwardKillXPServer подменяет убийцу владельцем; для смерти от баффа
заведена память укусов, потому что в DamageSource от баффа нет того, кто его
наложил. Зомби под Камнем духов это не задело - решение от 17.09 в силе.
ГРИФ откачен на летающую ветку EntityVulture и переименован в Могильного
стервятника: модель наконец соответствует имени. Держится у игрока сам, через
собственный механизм "дома" (setHomeArea), на время погони дом отвязывается.
Попытка натянуть птичий префаб на наземный класс провалилась и записана - так
делать нельзя.
ПРОЧЕЕ: призрачный вид распространён с торговцев на питомцев (у Пса, Медведя и
Волка выключен по указанию), у Пса светятся фиолетовые глаза, белая иконка
книги снята со всех свитков призыва, "Жуки Властелина" переименованы в "Рой
фараона" на всех 13 языках.
ПОРОГИ: крыса 60, стервятник 500, пёс 1300, медведь 2000, волк 4000.
ИСПРАВЛЕНО ПО ХОДУ ИГРОВЫХ ПРОВЕРОК: питомцы не призывались обычным кликом
(AnimWait требовал удержания), отзыв срабатывал не с первого раза (автомат
состояний ItemActionSpawnEntity), крыса проваливалась сквозь мир (IsStuck
отключает и пол), питомец подбрасывал хозяина (коллайдеры разводились до
появления модели).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Пользователь: "У тебя там много переносов строк, и каждый перенос отображается
как отдельное изменение."
Так и есть, проверено по разметке живой записи: Nexus заворачивает КАЖДУЮ строку
в свой <li> внутри ul.arrowlist. Мой текст был разбит по ширине в 90 символов,
поэтому на странице получилось 62 пункта со стрелками - половина из них обрывки
фраз вроде "skill's level in a single byte, so everything above 255 was cut".
Текст переписан: 16 строк, каждая - законченный пункт, переносов по ширине нет.
Содержание сохранено, только плотнее: откат уровня и новая шкала, самопочинка
сейвов, два индикатора, мир между подчинёнными, счёт убийств с благодарностью
Derizor и Savion, непочиненная модель кинжала для Scarlettdruid, изменения
порогов и блок "если приходишь с 1.0.1".
Строки длинные (до 288 знаков) - это нормально, пункт переносится сам по ширине
колонки, оставаясь одним пунктом.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nexus рисует "Version 1.3.0" над записью сам, из отдельного поля, а текст
начинался с той же строки - на странице номер задваивался. Проверено на живой
записи: "Version 1.3.0 1.3.0 FIXED: ...".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Пользователь: "Описание файлов не bb-код и всего 255 знаков."
Описание ФАЙЛА на Nexus - обычный текст с жёстким пределом 255 знаков, разметка
там не работает. Мой NEXUS_FILE_DESCRIPTION_1.3.0.bbcode был написан в неверном
формате и втрое длиннее предела, поэтому удалён, а не оставлен путаться под
рукой. Задним числом это объясняет и живую страницу: у файла 1.0.1 на сайте
висит короткая заметка, а подробный NEXUS_FILE_DESCRIPTION_1.0.1.bbcode из
репозитория туда никогда и не вставлялся - он физически не мог туда влезть.
Вместо него два файла по назначению:
NEXUS_FILE_SUMMARY_1.3.0.txt - 251 знак, в поле описания файла;
NEXUS_CHANGELOG_1.3.0.txt - полные заметки во вкладку Changelogs, тоже
обычным текстом.
Содержание длинного текста сохранено целиком, включая поимённый ответ Derizor,
Savion и Scarlettdruid.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Страницу мода удалось прочитать впервые (Chrome со своим профилем и отладочным
портом - curl и WebFetch там получают 403 от Cloudflare). В комментариях висит
жалоба Derizor (14.09) и Savion (15.09): убийство не засчитывается, если добил не
игрок своим оружием. Это ровно тот баг, который закрыт 16.09 в 229b436 - завели
его по своему наблюдению, не зная про тред.
В описание версии добавлен блок с ответом обоим поимённо и честная строка про
непочиненное: модель кинжала (Scarlettdruid) ждёт своего 3D-префаба.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Пользователь: "Ссылка на ютуб у тебя неверная. Должно быть @alex_cube".
Правка во всех четырёх текстах, где ссылка встречалась (README RU+EN,
SITE_DESCRIPTION, NEXUS_DESCRIPTION и старое описание версии 1.0.1) - и в самом
URL, и в подписи. Заодно описание версии 1.3.0 получило тот же футер со
ссылками, что и 1.0.1: исходники, страница на сайте, канал.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NEXUS_FILE_DESCRIPTION_1.3.0.bbcode - текст на страницу файла: переполнение
уровня и чем оно выглядело для игрока, новая шкала, починка старых сейвов, два
индикатора, мир между подчинёнными, переезд мода на воду с 30 на 20. Отдельным
блоком то, что войдёт в публичную линию, если у игрока стоит 1.0.1: расходник
браслета, починенный счёт убийств, забор блоков, вкладка испытаний, торговцы.
В NEXUS_DESCRIPTION.bbcode инструкция по установке обещала скилл "from level
one" - теперь он появляется с нулевого уровня и растёт по одному за 20 зомби.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Уровень навыка игра хранит ОДНИМ БАЙТОМ (ProgressionValue.Write/Read), поэтому
старая шкала "одно убийство - один уровень" при max_level=5000 на каждом
сохранении откатывала уровень на 256 назад. Снаружи это выглядело как
самопроизвольно закрывающиеся рецепты (жалоба со стрима: Слёзы мертвеца открыты,
Пир падальщика под замком при 250+ убитых), а тиры 500/2000/3000/5000 были
недостижимы в принципе. Подтверждено на двух живых сейвах: sezon8 - 384 убийства
при уровне 129, test8 - 303 при уровне 48.
Шкала переведена на 20 убийств = 1 уровень, max_level=250 - влезает в байт с
запасом. Уровень больше не накапливается, а вычисляется из necroZombieKillsCVar
(float, сохраняется честно) и на каждом убийстве, и постфиксом на
PlayerDataFile.ToPlayer - последнее чинит старые сейвы само, без команд и новой
игры. Открытое при этом не теряется: в старой шкале уровень всегда был не больше
счётчика, так что пересчёт может только вернуть украденное переполнением.
Отдельно закрыт случай "счётчик пуст, а уровень есть" - счётчик восстанавливается
из уровня по старой шкале.
Все пороги пересчитаны в уровни, числа убийств не тронуты, кроме воды: 30 на
сетку шагом 20 не ложится, по указанию пользователя мод переехал на 20 - туда же,
где браслет и Кровавая сфера. Тег necroNecromancyLvl30 удалён. Сходимость всех 18
рецептов (тег -> RecipeTagUnlocked -> unlock_tier) проверена скриптом.
Два индикатора: череп в статус-баре показывает уровень (раньше - общее число
убийств), фиолетовая шкала над полосой опыта - продвижение внутри уровня, 0..20.
Шкала сделана вёрсткой, а не баффом (бафф умеет число, но не полосу), заполнение
привязано через XUi-выражение cvar(). Закрывающая скобка у выражения - одна "}",
а не "%}": лишний "%" NCalc читает как остаток от деления и ждёт правый операнд,
на чём первая проверка в игре и споткнулась.
Третья правка: подчинённые зомби больше не дерутся между собой. Девиация выдаёт
приказ "бей зомби", а подчинённый сам EntityZombie, и в targetClasses выражается
только тип. Постфикс на EAITarget.check вычёркивает подчинённого из кандидатов
(зомби выбирает следующего, настоящего врага), префикс на
EntityAlive.SetAttackTarget гасит цель на путях мимо выбора - прежде всего месть,
когда подчиняют уже дерущихся.
Тексты на 13 языках, README (RU+EN), описания для сайта и Nexus приведены к новой
шкале.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Врезка называла два пункта, а их стало больше. Теперь перечислено и то, что
подтверждено в игре (вкладка испытаний, два первых испытания, счёт убийств), и
то, что нет: Кровавый камень, новый порог Кровавой сферы, награды испытаний и два
новых испытания на Камень духов и Синий портальный камень.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Мои были описательные ("Первый подданный", "Короткая дорога домой") - верные по
смыслу и скучные. Пользователь предложил свои:
Камень духов "Не бейте меня" подчинённый зомби бьётся за
игрока, по игроку не бьют
Синий портальный камень "Сам себе G.L.A.D.O.S." отсылка к Portal
Нож некроманта "Основной инструмент" каламбур на "Основной инстинкт"
"Безумный донор" не тронут - он и был авторский, и уже в этом стиле.
КАК РЕШАЛСЯ ПЕРЕВОД, потому что шутки переводятся плохо:
"Не бейте меня" - буквально на все 13, смысл не зависит от культуры.
"Сам себе G.L.A.D.O.S." - имя ИИ сохранено латиницей во всех языках (имя
собственное, в локализациях Portal оно тоже латиницей), переводится только
обёртка.
"Основной инструмент" - каламбур на названии фильма, живёт только там, где
название созвучно. Сохранён: en Basic Instinct -> Basic Instrument, es Instinto
basico -> Instrumento basico, pl Nagi instynkt -> Nagi instrument, ru Основной
инстинкт -> Основной инструмент. Где фильм известен под английским названием или
созвучия нет (de/fr/it/ja/ko/tr/zh) - взят прямой смысл "главный инструмент".
Мёртвую шутку туда не подставляли: каламбур, который не звучит, читается как
ошибка переводчика, а не как юмор.
Описания не переписаны, остались в серьёзно-атмосферном тоне. Контраст выглядит
скорее намеренным; свести тон - отдельная правка на 4 ключа x 13 языков.
Ключей по-прежнему 149, менялись значения, не состав.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Пользователь: "за кровь 100 XP маловато". Награды не задавались вовсе, поэтому
все испытания падали на challenge_reward_default (100 XP, gameevents.xml:602).
Своих событий заводить не нужно - готовы 500/1000/2000/2500/5000/10000.
ИСПРАВЛЕНА МОЯ ЖЕ ОЦЕНКА. Я предложил оставить ножу 100, обосновав это тем, что
он "один крафт, без риска", - и сказал это ДО чтения рецепта. В нож входит
1 Кровь некроманта, а её крафт снимает 90 процентов ТЕКУЩЕГО здоровья. Нож стоит
захода на порог смерти, аналогия с craftStoneAxe была неверной. Поставлено 500.
Итоговая лестница, каждое число обосновано ценой:
Камень духов 1 камень + 20 волокон 100
Нож некроманта 5 костей + 10 волокон + 1 кровь 500
Синий портальный камень 150 праха зомби + 3 минералки 2000
Безумный донор 15 крафтов крови = 15 x 90% ХП 2500
Портальный камень: 2000 при одном крафте, потому что в рецепте 150 праха зомби.
Прах падает с помеченной Жертвы по 1-4 с вероятностью 0.4 (loot.xml), в среднем
около единицы за убийство - то есть порядка 150 ритуальных убийств ножом.
Ванильный craftForgedIron (100 крафтов) платит 2000 за такой же грайнд без риска.
Донор: 2500, выше портального камня, потому что оплачивает не грайнд, а
смертельный риск. Выше не пошли - 5000 ваниль держит за химстанцией и тирами
квестов.
Камень духов: 100 и это не занижение - самая дешёвая вещь мода и буквальный
аналог craftStoneAxe, за который ваниль платит ровно умолчание.
ДВА НОВЫХ ИСПЫТАНИЯ, названия под механику:
"Первый подданный" - Камень духов подчиняет зомби, и тот бьётся за игрока; это
первое существо, которое слушается некроманта. Иконка specters_grace совпадает
и с названием предмета, и со смыслом.
"Короткая дорога домой" - камень телепортирует к спальному мешку и не
расходуется. Иконка map_house.
Локализация: 6 ключей x 13 языков, ключей стало 149. Счётчик в README и
SITE_DESCRIPTION обновлён. Текст наград заводить не пришлось - ключи
challenge_reward_*xp ванильные.
НЕ ПРОВЕРЕНО В ИГРЕ.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
В скелет сознательно клали одно испытание, чтобы сперва убедиться, что вкладка
появляется. Убедились в игре - добавлено второе, оба заказанных на месте.
<objective type="Craft" item="resourceNecromancerBlood" count="15"/>
Craft, а не Gather: кровь не добывается из мира, она крафтится. count у Craft
работает - проверено заранее, потому что в самых наглядных ванильных примерах
(craftStoneAxe, craftClothes) счётчика нет и легко решить, что его не бывает; на
деле 18 ванильных Craft-целей его несут, вплоть до resourceForgedIron count=100.
Иконка ui_game_symbol_siphoning_strikes ("высасывающие удары") - найдена при
разборе всех 359 доступных символов. Под механику "плати собственной кровью"
попадает точнее любого медицинского символа.
Имя оправдано механикой, а не просто звучит: каждый крафт снимает 90 процентов
ТЕКУЩЕГО здоровья (NecromancerBloodPatch.cs) и требует нож в инвентаре.
Пятнадцать раз подряд - пятнадцать раз до полусмерти.
Зависимости от первого испытания нет и не будет (указание "разрозненно"), хотя
фактически кровь без ножа не скрафтить: игрок сам упрётся в нож, если начнёт с
крови, и это честнее замка.
Локализация: 3 ключа x 13 языков, ключей стало 143. Счётчик в README (RU+EN) и
SITE_DESCRIPTION.html (RU+EN) обновлён той же правкой. Шапка challenges.xml
переписана - она описывала состояние "одно испытание из двух".
НЕ ПРОВЕРЕНО В ИГРЕ.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Вкладка испытаний подтверждена в игре ("вкладка видна, нож сработал"), лог
чистый - ERR за весь запуск ноль, challenges загрузились за 0.08. Заодно
подтвердилось, что осознанный риск скелета сыграл: у группы нет reward_event и
reward_text_key, и игра не сказала ни слова - умолчания корня challenges на
группы распространяются.
По указанию пользователя ui_game_symbol_zombie заменён на ui_game_symbol_skull
во всех четырёх живых местах мода:
progression.xml:90 crafting_skill craftingNecroNecromancy - сам скилл
challenges.xml:41 challenge_category NecroNecromancy - вкладка
buffs.xml:14 buffNecroZombieKillTrackerDisplay - счётчик убийств
buffs.xml:175 buffNecroDarkSense - Тёмное чутьё
Причина пользователя: "Значок зомби - протокольная рожа, как у уголовника. Череп
будет более некромантским."
Вкладка и скилл меняются ТОЛЬКО ПАРОЙ - они обязаны читаться как одно целое. Это
записано комментарием в challenges.xml, чтобы позже их не развели по одному.
СОЗНАТЕЛЬНО НЕ ТРОНУТО: комментарий buffs.xml:150 упоминает ui_game_symbol_zombie,
но описывает ВАНИЛЬНЫЙ класс nav_object="zombie" и его собственную иконку на
карте и компасе. Это не наша иконка, текст не устарел.
Чистый XML, пересборка не нужна.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Новый Config/challenges.xml - мод его раньше не имел. Категория (вкладка),
группа и ОДНО испытание "Первым делом - нож".
Одно, а не два заказанных, намеренно: файл трогается впервые, сначала надо
увидеть в игре, что вкладка вообще появляется. Нож крафтится сразу и без
верстака, то есть проверка занимает минуту. "Безумный донор" добавится одной
записью, когда скелет подтвердится.
РАЗРОЗНЕННО, НЕ ЦЕПОЧКОЙ (по указанию): у группы нет ни link_challenges, ни
hidden_by. Побочная польза - снялось противоречие порядка: Кровь некроманта
нельзя скрафтить без ножа, так что "донор" первым в цепочке был бы невыполним.
ИКОНКИ ТОЛЬКО ИЗ СИМВОЛЬНОГО АТЛАСА ИГРЫ. В templates.xml оба места, где они
рисуются (challenge_entry и category_icon), объявлены БЕЗ атрибута atlas, то есть
читают атлас по умолчанию; в ванильном challenges.xml все 215 иконок имеют вид
ui_game_symbol_* без исключений. Свой атлас мода (NecroFinal) не поможет -
обратиться к нему можно только явным atlas= на спрайте, а дописать его в шаблон
нельзя: шаблон один на все испытания, ванильные иконки пропадут.
Из 359 собранных символов нашлись некромантские, череп не понадобился. Вкладка -
ui_game_symbol_zombie (тот же символ, что у скилла в progression.xml, чтобы
вкладка и скилл читались как одно целое), нож - ui_game_symbol_knife. Для
"Безумного донора" отложен ui_game_symbol_siphoning_strikes.
Локализация: 5 ключей x 13 языков, ключей стало 140. Название вкладки взято слово
в слово из craftingNecroNecromancyName. Счётчик в README и SITE_DESCRIPTION
обновлён 135 -> 140.
ОСОЗНАННЫЙ РИСК: у группы нет reward_text_key и reward_event, хотя у всех 20
ванильных они есть. Ставка на умолчания корня challenges (default_reward,
default_reward_text_key). Если не сработает - будет в логе, и это дешевле, чем
заводить событие награды вслепую.
НЕ ПРОВЕРЕНО В ИГРЕ.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
По указанию пользователя перед пушем. Текст коммитов про непроверенное видит
только тот, кто читает историю; README видно сразу.
В обеих половинах (RU и EN) раздел "Статус" теперь перечисляет, что подтверждено
в игре, и отдельной врезкой - что нет: Кровавый камень (нужен уровень 2000 и
химстанция) и новый порог Кровавой сферы (переехала с уровня 1 на 20). Указано,
что это ожидаемое место для сюрприза.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Сфера вставляется в Пространственный браслет, но открывалась с уровня 1,
тогда как сам браслет - с 20. Девятнадцать уровней её можно было крафтить в
пустоту. По записанному правилу мода "пороги - по нужде, а не по силе" порог
ставится туда, где предмет впервые нужен.
progression.xml: resourceBloodSphere убрана из unlock_entry с unlock_tier="1" и
дописана в строку браслета (unlock_tier="2").
recipes.xml: теги рецепта packMuleCrafting -> learnable,packMuleCrafting,
necroNecromancyLvl20. learnable как у всех гейтованных рецептов мода, чтобы
рецепт не светился в меню до открытия.
Два числа обязаны совпадать и совпадают: RecipeTagUnlocked для
necroNecromancyLvl20 стоит на level="20,5000", а unlock_tier="2" в группе с
unlock_level="1,20,30,60,100,300" - это те же 20. Разойдись они, замок на панели
скилла разошёлся бы с реальной доступностью рецепта.
Чистый XML, пересборка не нужна. Комментарии в обоих файлах переписаны - там
стояло "ЗАМЕЧЕНО, НЕ ИСПРАВЛЕНО".
НЕ ПРОВЕРЕНО В ИГРЕ: на свежем персонаже рецепта быть не должно, после 20
убийств должны открыться браслет и сфера одной строкой.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Проверка в игре прошла, версия поднята с 1.1.0 до 1.2.0, описания доведены до
текущего состояния мода.
ПОЧЕМУ МИНОРНАЯ, А НЕ ПАТЧ. Тег v1.1.0 указывает на 7172681. Заряд браслета
(Кровавая сфера и Кровавый камень), починка счёта убийств, кряк боли при крафте
крови и фикс призрачности торговца появились ПОСЛЕ него и в выпущенную 1.1.0 не
входили. Это новая функциональность, а не исправления к выпущенному.
ЧТО ПОДТВЕРЖДЕНО В ИГРЕ:
- Кровавая сфера исчезает при обнулении прочности, звук разбивания играет;
- кряк боли при создании Крови некроманта играет;
- торговец остаётся призраком (на одном торговце; на трёх будет проверено
отдельно);
- зомбопёс засчитывается в скилл - причина 1 (пять зомби-зверей вне
zombieTemplateMale) закрыта, фильтр по тегу zombie работает;
- зомби, умерший от кровотечения, засчитывается - причина 2 (DamageSource
вместо буквального убийцы) закрыта;
- зомби, сгоревшие от Пирамиды духов, засчитываются. Этого случая не было ни в
указании, ни в плане - он достался бесплатно, потому что хук стоит на
EntityPlayer.AddKillXP, то есть следует решению ванили, а не перечисляет
причины смерти по одной.
ПИТОМЦЫ: ВОПРОС ЗАКРЫТ ОТРИЦАТЕЛЬНО. Зомби под контролем Камня духов, убивая
другого зомби, в скилл не идёт - это и есть случай "убил питомец", и ваниль
владельцу такое убийство не зачисляет. Патч следует её решению, как и написано в
его комментарии. Пункт исходного указания про призванных существ требует
отдельной работы (проставить владельца в DamageSource питомца); пользователь
решил оставить как есть.
ОПИСАНИЯ (README.md, RU и EN):
- шапка версии в обеих половинах;
- новый абзац 1.2.0 в "Статусе", 1.1.0 и 1.0.1 сдвинуты в историю. Заодно
исправлена фактическая ошибка: раздел приписывал сферу и камень версии 1.1.0,
хотя их там не было;
- описание скилла теперь говорит, что засчитывается любое убийство, за которое
игра даёт опыт - ловушка, огонь, кровотечение, зомби-звери наравне с
человекоподобными. Раньше было просто "счётчик упокоенных зомби", и это
вводило в заблуждение ровно в тех случаях, что чинились;
- снята оговорка про непроверенную сферу, вместо неё честная оговорка про
непроверенный Кровавый камень.
SITE_DESCRIPTION.html и NEXUS_DESCRIPTION.bbcode номера версии не содержат, сферу
и камень получили раньше - правки не потребовалось.
НЕ ПРОВЕРЕН В ИГРЕ Кровавый камень: собран и на месте, но требует уровня
Некромантии 2000 и химстанции. В README это сказано прямым текстом.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Чинит баг: скилл не засчитывал часть убийств зомби. Причин было две, и обе
закрываются одним ходом.
ПРИЧИНА 1: ОДИН КЛАСС - НЕ ВСЕ ЗОМБИ. Счёт висел append-ом на zombieTemplateMale.
Человекоподобные покрыты (effect_group у entity_class наследуется через extends, и
все шаблоны зомби сходятся к Male), но пять зомби-ЗВЕРЕЙ наследуют животную ветку
и до него не доходят вовсе:
animalZombieBear extends animalBear
animalZombieBoar extends animalBoar
animalZombieDog extends animalWolf
animalZombieVulture extends animalTemplateHostile
animalZombieVultureRadiated extends animalZombieVulture
Убийство зомбопса, зомбомедведя, зомбокабана и зомбоворона не считалось никак.
Вороны попадаются постоянно - это и была бОльшая часть "не всегда засчитывает".
ПРИЧИНА 2: target="other" - БУКВАЛЬНЫЙ УБИЙЦА. Триггер onOtherKilledSelf с
требованием EntityTagCompare tags="player" начислял только за убийство своей
рукой. Робомолот, кровотечение и питомцы требование не проходят, хотя опыт игрок
за них получает: ваниль определяет получателя не по убийце, а по DamageSource - в
EntityAlive.AwardKillXPServer, где для этого есть обращение к BuffClass (DoT) и
отдельный флаг bTrapKillXP (ловушки).
Требование было ОДНО на оба эффекта, поэтому недосчитывался и
necroZombieKillsCVar, а это урон Ножа некроманта (items.xml: Damage = CVar / 10).
Баг тихо занижал ещё и нож.
РЕШЕНИЕ: Postfix на EntityPlayer.AddKillXP. Этот метод вызывается ровно из одного
места во всей сборке - из AwardKillXPServer, то есть уже ПОСЛЕ того, как ваниль
разобрала DamageSource и решила, чей это фраг (проверено сканированием IL).
Мы не повторяем её логику и не угадываем владельца турели или автора
кровотечения - забираем готовый ответ. Наш счёт совпадает с опытом на экране по
построению, включая случаи, о которых мы не подумали.
ФИЛЬТР ПО ТЕГУ zombie, А НЕ ПО КЛАССУ. Проверено по данным: zombieBiker,
zombieArlene, zombieBoe, zombieSpider несут "entity,zombie,...", зомби-звери -
"entity,animal,zombie,zombieAnimal,...". Тег есть у всех. Работает это благодаря
тому, что Tags у entity_class НЕ наследуется через extends: каждый реально
спавнящийся зомби выписывает теги сам, а безтеговые шаблоны не спавнятся. Тег
переживёт и новых зомби из патчей игры, и чужие моды.
Повышение уровня воспроизводит MinEventActionAddProgressionLevel.Execute шаг в
шаг по его IL: GetProgressionValue, Level+1, кламп по MaxLevel, для крафтового
скилла AddCraftingSkillNotification и HandleCheckCrafting, затем
bProgressionStatsChanged и bPlayerStatsChanged под !isEntityRemote.
HandleCheckCrafting легко выбросить и дорого потерять - без него рецепты рискуют
не заметить, что открылись. Уведомление с _bAddOnlyIfNotExisting=true, чтобы в
орду не всплывал тост на каждый труп.
Config/entityclasses.xml: append снят ЦЕЛИКОМ, на его месте комментарий, почему
возвращать нельзя - XML-триггер рядом с патчем засчитает убийство своей рукой
ДВАЖДЫ. Это единственная ловушка переезда.
Сборка: 0 ошибок (4 прежних MSB3277). Проверено рефлексией по собранной DLL:
атрибут нацелен верно, перегрузка AddKillXP ровно одна, имя параметра killedEntity
совпадает с ванильным (Harmony инжектит по имени - опечатка дала бы молчаливо
неработающий патч), PatchAll подхватывает файл сам.
НЕ ПРОВЕРЕНО В ИГРЕ. Отдельно: питомцы НЕ гарантированы - патч следует решению
ванили, а не принимает его. Если ваниль не зачисляет владельцу убийство
питомцем, не зачислит и он; это отдельная работа, а не ошибка здесь.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Крафт крови и так снимает 90% текущего ХП, но делал это молча. Теперь на том же
месте играет штатный звук боли игрока - тот, что слышно при накалывании на кол
или на колючую проволоку.
ИМЯ КЛИПА СПРАШИВАЕТСЯ У СУЩНОСТИ, А НЕ ВПИСАНО СТРОКОЙ. Звук боли зависит от
пола персонажа (Data/Config/entityclasses.xml):
playerMale SoundHurt="player1painlg" SoundHurtSmall="player1painsm"
playerFemale SoundHurt="player2painlg" SoundHurtSmall="player2painsm"
playerFemale extends playerMale и переопределяет обе строки, так что
захардкоженный "player1painlg" выдал бы женскому персонажу мужской кряк. Вместо
этого зовутся GetSoundHurt() и GetSoundHurtSmall() - публичные методы
EntityAlive, каждый из которых целиком есть чтение поля (проверено по IL:
"ldarg.0; ldfld soundHurt|soundHurtSmall; ret", токены разрешены через
Module.ResolveField).
ПОРОГ ВЗЯТ У САМОЙ ИГРЫ. EntityPlayer переопределяет
GetSoundHurt(DamageSource, int) - тот самый метод, через который проходят колья
и колючая проволока. Его IL разобран целиком, и правило такое: урон СТРОГО
больше 15 -> большой кряк (soundHurt), 15 и меньше -> малый (soundHurtSmall),
плюс фолбэк на большой, если малый не объявлен. Воспроизведено один в один,
включая строгое "больше" и фолбэк; константа названа PainSoundBigDamage и несёт
этот разбор в комментарии.
Сам GetSoundHurt(DamageSource, int) не вызывается: ему нужен DamageSource, а
здесь урона от источника нет - ХП снимается через AddHealth, осознанно (см.
комментарий на месте вызова). Правило выбора - три строки, поэтому оно повторено,
а не подделано синтетическим DamageSource.
PLAYONESHOT ВЫЗВАН РОВНО КАК В ВАНИЛИ. Единственный вызывающий -
EntityAlive.OnUpdateEntity, IL 348: там PlayOneShot получает
sound_in_head:false, serverSignalOnly:false, isUnique:false, _animEvent:null,
volumeScale:1f. Значения по умолчанию у Entity.PlayOneShot - те же самые
(сверено через RawDefaultValue), так что вызов одним аргументом поведенчески
идентичен ванильному: кряк идёт ОТ персонажа, а не "в голове". Оттуда же взят
null-guard - ваниль пропускает вызов при пустом имени клипа, а не отдаёт его
PlayOneShot.
Вызов стоит ПОСЛЕ AddHealth(-amount), а не до: крафт, который свалился бы выше,
не должен издавать звук, которому игрок не найдёт объяснения. При цене 90% это
практически всегда большой кряк - малый включится только на остатке около 17 ХП,
там же, где его сделала бы малым ваниль.
ЗАОДНО: устаревшая шапка NecromancerBloodPatch.cs. Комментарий утверждал, что
кровь живёт в item_modifiers.xml, "not in items.xml, since 2026-09-15". Откат
15.09 это отменил, но тогда правились только комментарии в items.xml, а .cs
пропустили. Переписано: кровь в items.xml, и рядом коротко, почему её оттуда
нельзя двигать.
Сборка: 0 ошибок (4 прежних MSB3277 про версии System.Runtime, к правке
отношения не имеют). Скомпилированный PlayPainSound проверен рефлексией по
готовой DLL - вызовы идут в ожидаемом порядке.
НЕ ПРОВЕРЕНО В ИГРЕ. Проверять обоими полами персонажа - ради этого имя клипа и
спрашивается у сущности.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Эндгейм-версия расходника Пространственного браслета: то же место в слоте, что у
Кровавой сферы, но не тратится вовсе. Кода менять не пришлось - только конфиги.
ЗАОДНО ЭТО ПОЧИНКА УЖЕ ОТГРУЖЕННОГО ТЕКСТА. Сообщение о пустом слоте
(braceletSpatialVaultNoMod) и описание браслета обещают игроку "кровавую сферу
ИЛИ кровавый камень" на всех 13 языках с 15.09. Пока камня не было, оба текста
отправляли за предметом, которого в игре не существует.
СОВМЕСТИМОСТЬ СЕЙВОВ - ПЕРВОЕ, ЧТО ПРОВЕРЕНО. resourceBloodStone - НОВОЕ имя, в
существующих сейвах его нет, значит нет и ни одного стака, который читался бы по
другой байтовой раскладке. Это ровно то условие, нарушение которого в e362c62
убило персонажа: ItemValue.Read/Write гейтят блок модификаций одним условием
!(itemClass is ItemClassModifier), и класс предмета определяет раскладку каждого
стака в сейве. Камень заведён новым предметом, а не переводом существующего -
тем же решением, что и Кровавая сфера. Добавление нового <item_modifier>
раскладку уже записанных стаков не меняет: айди новых предметов дописываются в
name->id мэппинг сейва, байты старых стаков остаются как были.
БЕСКОНЕЧНАЯ ПРОЧНОСТЬ СДЕЛАНА ОТСУТСТВИЕМ СТРОК, А НЕ БОЛЬШИМ ЧИСЛОМ. У камня
сознательно нет ни ShowQuality, ни DegradationBreaksAfter, ни effect_group с
DegradationMax - сравнить со сферой, у которой все три есть. SpendCharge
(SpatialVaultPickupPatch.cs) списывает прочность только у модификации, чьё имя
равно ChargeItemName ("resourceBloodSphere"); камень под условие не попадает,
цикл делает continue, его UseTimes не трогает никто. Большой DegradationMax был
бы хуже: неподвижная полоска прочности бесполезна, а при MaxUseTimes == 0 она
рисуется ПОЛНОЙ, то есть забытая пассивка выглядела бы как "всё работает".
Три места в коде сверены на то, что камень пройдёт весь путь:
1. Гейт пустого слота в Begin - это !_bracelet.HasMods(), то есть "в слоте
что-нибудь есть", а НЕ "есть сфера по имени". Камень гейт проходит. Это был
главный риск правки: искал бы гейт сферу по имени, камень не работал бы.
2. SpendCharge вызывается ПОСЛЕ SetBlockRPC(..., Air) и bag.AddItem, а его
результат только логируется - ветки "не заплатил -> откажи" нет. Блок с
камнем уходит в хранилище, в логе "took ... into the vault for 0 of charge".
3. Ветка max <= 0 в SpendCharge камня не касается: до неё доходят только
предметы, прошедшие проверку имени.
ФАЙЛЫ
Config/item_modifiers.xml - новый item_modifier resourceBloodStone,
installable_tags="necroBracelet", свой modifier_tags="necroBraceletStone".
Extends="modGeneralMaster" param1="CustomIcon" - НЕ resourceRockSmall: с ним
приехали бы Action0 ThrowAway и ThrowableDecoy, и камень можно было бы метать
как отвлекающий (на сфере эта ошибка уже была и была исправлена). Вид в руке -
HoldType 40 плюс все три меша rock_smallPrefab плюс TintColor "220, 30, 45", как
у сферы и трёх камней мода. CustomIcon="BloodStone", без CustomIconTint:
иконка своя, рисованная, лежит в обеих папках атласа с 15.09.
Config/recipes.xml - рецепт на химстанции, craft_area="chemistryStation",
tags="learnable,chemStationCrafting,necroNecromancyApprentice": 50 праха зомби,
20 костей, 4 обычной крови, 1 кровь некроманта. Все четыре имени проверены по
файлам. Цена выше, чем читается по списку: крафт самой крови некроманта снимает
90% текущего ХП.
Config/progression.xml - камень добавлен в unlock_entry третьего display_entry
(unlock_level="2000", unlock_tier="1"). Это только строка в панели скилла;
открытие рецепта делает тег. 2000 здесь и 2000 у RecipeTagUnlocked обязаны
совпадать, иначе замок на панели разойдётся с доступностью рецепта.
Config/Localization.csv - два ключа, resourceBloodStone и
resourceBloodStoneDesc, 13 языков; ключей стало 135. Названия камня взяты из уже
переведённого braceletSpatialVaultNoMod, так что сообщение об отказе и имя в
инвентаре совпадают слово в слово. braceletSpatialVaultDesc дополнено на всех 13
языках: камень не тратится. Файл переписан через csv-модуль, round-trip без
правок даёт побайтово тот же файл - кавычки и CRLF остальных строк не поехали.
ОПИСАНИЯ. README.md, SITE_DESCRIPTION.html и NEXUS_DESCRIPTION.bbcode доведены
до текущего состояния мода: сфера и камень добавлены в таблицы тиров и в список
арсенала, у браслета сказано, что забор блоков питается зарядом в слоте,
счётчик ключей 130 -> 135. Заодно снято утверждение "весь контент проходит тесты
в игре" - оно перестало быть верным: расход сферы в игре подтверждён, камень в
игру ещё не грузился.
НЕ ПРОВЕРЕНО В ИГРЕ: камень целиком. Всё выше - сверка по файлам и по
декомпиляции. Сценарий проверки в BACKLOG.md; главное, на что смотреть, -
отсутствие полоски прочности у камня в слоте и "for 0 of charge" в логе.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Чинит поломку сейвов, внесённую предыдущим коммитом e362c62, и заменяет её
механику на безопасную.
ЧТО БЫЛО СЛОМАНО. e362c62 перенёс resourceNecromancerBlood из items.xml в
item_modifiers.xml, чтобы кровь вставлялась в Пространственный браслет. Класс
предмета определяет БАЙТОВУЮ РАСКЛАДКУ каждого его стака в сейве:
ItemValue.Read строка 1094: if ((version > 4 || HasQuality) && !(itemClass is ItemClassModifier))
ItemValue.Write строка 1228: if (!(ItemClass is ItemClassModifier))
Обычный предмет пишет байт числа модификаций и байт числа косметических слотов;
ItemClassModifier не пишет ни того, ни другого. Сейв, записанный до переноса,
после переноса читается со сдвигом - поток съезжает на первом же стаке крови, и
PlayerDataFile.Load падает. Бэкап .ttp.bak умирает вместе с основным файлом: он
того же формата. В тестовом мире персонаж потерян вместе с бэкапом, игра
откатилась на Respawning: NewGame.
В коммите e362c62 написано "сейв цел" со ссылкой на assignIdsFromMapping. Айди
действительно берутся из сохранённого name->id мэппинга - но ломается не айди, а
раскладка байтов, и к мэппингу это отношения не имеет. Была проверена не та вещь.
ОТКАТ. Кровь вернулась в items.xml обычным <item>. Набор её свойств сверен с
7172681 и совпадает посимвольно: убраны и Stacknumber=1, и прочность
(ShowQuality + DegradationBreaksAfter + effect_group DegradationMax). Обе правки
были безвредны для сейва, но существовали ради отменённой механики - по указанию
пользователя откат доведён до "как было", а не до "как было плюс безвредное".
Стак снова 15, наследуется от medicalBloodBag.
В комментарии у предмета оставлено предупреждение с номерами строк Read/Write -
единственное, что вынесено из аварии, и единственное, ради чего стоит читать этот
комментарий целиком.
КРОВАВАЯ СФЕРА. Расходник браслета теперь отдельный предмет, и это ровно то, что
делает правку безопасной: resourceBloodSphere - НОВОЕ имя, в старых сейвах его
нет, значит нет и ни одного стака, который читался бы по другой раскладке. Общее
правило, выведенное из аварии: предмет, который уже мог попасть в чужой
инвентарь, нельзя переводить между ItemClass и ItemClassModifier ни в какую
сторону - нужна модификация, заводи новый предмет.
Продиктовано: доступна на первом грейде, без станка, 1 кровь некроманта + 5 праха
зомби дают две сферы, прочность 500. В руке - камень с алым тинтом.
- item_modifiers.xml: installable_tags="necroBracelet", свой modifier_tags,
type="attachment", DegradationMax 500 в tiered="false" группе.
Extends=modGeneralMaster, а НЕ resourceRockSmall: вместе с камнем приезжали бы
Action0 ThrowAway и ThrowableDecoy, то есть сферу можно было бы метать.
Меш берётся строкой Meshfile, наследовать ради него весь предмет не нужно.
- Вид в руке: HoldType 40 и все три меша (Meshfile/HandMeshfile/DropMeshfile) на
rock_smallPrefab плюс TintColor "220, 30, 45". Путь проверенный - на этом же
меше с таким же тинтом в моде живут Камень духов и оба портальных камня.
- recipes.xml: count=2, без craft_area и без тега разблокировки (группа 1
открыта с уровня 1, рецепт без тега доступен всегда - как у Камня духов).
- progression.xml: сфера в unlock_entry группы 1, unlock_tier="1".
- SpatialVaultPickupPatch: SpendBlood -> SpendCharge, имя из новой константы
ChargeItemName. Гейт пустого слота в Begin снова включён - после отката он был
временно закомментирован, потому что вставлять было нечего.
ИКОНКИ. Свои, от пользователя: BloodSphere.png и BloodStone.png, 160x160 RGBA, в
ItemIconAtlas и ItemIconAtlasGreyscale. Серая копия обязательна - без неё у
заблокированной записи в скиллах не будет картинки вообще. Способ её получения
подобран сверкой с существующими файлами и совпал ПОБИТОВО: convert('L') на RGB
без альфы, альфа приклеивается обратно отдельным каналом. Записано в BACKLOG.md,
раньше это нигде не было зафиксировано. Иконка камня положена заранее - самого
предмета ещё нет, он запланирован.
ПРОВЕРЕНО, ЧТО НИЧЕГО БОЛЬШЕ НЕ ЕДЕТ. Сверка с 7172681: набор <item> в items.xml
не изменился, в item_modifiers.xml единственное добавление - resourceBloodSphere,
и ни у одного существующего предмета не менялись Tags, ShowQuality и Stacknumber.
То есть ни один предмет не сменил класс и не сменил раскладку.
В игре: мир грузится без ошибок, сфера крафтится и тратится - в логе
"sphere in slot 0 now 12/500 used" за 12-секундный канал.
---
Revert the blood, and a Blood Sphere in its place
Fixes the save corruption introduced by e362c62 and replaces the mechanic behind
it with a safe one.
WHAT WAS BROKEN. e362c62 moved resourceNecromancerBlood from items.xml into
item_modifiers.xml so it could be installed in the Spatial Bracelet. An item's
CLASS decides the BYTE LAYOUT of every stack of it in a save:
ItemValue.Read line 1094: if ((version > 4 || HasQuality) && !(itemClass is ItemClassModifier))
ItemValue.Write line 1228: if (!(ItemClass is ItemClassModifier))
A plain item writes a modifications count and a cosmetic-slot count; an
ItemClassModifier writes neither. A save written before the move reads out of
step after it - the stream slips on the first blood stack and PlayerDataFile.Load
throws. The .ttp.bak backup dies with the main file, being the same format. In the
test world the character was lost along with its backup and the game fell back to
Respawning: NewGame.
e362c62's message claimed "saves are safe", citing assignIdsFromMapping. Item ids
really do come from the stored name->id mapping - but what breaks is not the id,
it is the byte layout, and the mapping has nothing to do with it. The wrong thing
was verified.
THE REVERT. The blood is a plain <item> in items.xml again. Its property set was
diffed against 7172681 and matches character for character: both Stacknumber=1 and
the durability (ShowQuality + DegradationBreaksAfter + the DegradationMax
effect_group) are gone. Both were harmless to the save format, but both existed
only to serve the cancelled mechanic - on the user's instruction the revert goes
back to "as it was", not "as it was plus whatever I judged harmless". The stack is
15 again, inherited from medicalBloodBag.
A warning carrying the Read/Write line numbers stays in the item's comment - the
one thing worth keeping out of this accident.
THE BLOOD SPHERE. The bracelet's charge is its own item now, and that is precisely
what makes this safe: resourceBloodSphere is a NEW name, absent from every
existing save, so no stack of it can be read under the wrong layout. The general
rule the accident produced: an item that may already sit in someone's inventory
must never be moved between ItemClass and ItemClassModifier in either direction -
if a modifier is wanted, make a new item.
Dictated: available at the first grade, no workstation, 1 Necromancer's Blood +
5 zombie ash makes two spheres, durability 500. Held, it is a stone with a
scarlet tint.
- item_modifiers.xml: installable_tags="necroBracelet", its own modifier_tags,
type="attachment", DegradationMax 500 in a tiered="false" group.
Extends=modGeneralMaster, NOT resourceRockSmall: the rock would have brought
Action0 ThrowAway and ThrowableDecoy with it, making the sphere throwable.
The mesh comes from the Meshfile line; inheriting a whole item for it is not
needed.
- Held look: HoldType 40 and all three meshes (Meshfile/HandMeshfile/
DropMeshfile) on rock_smallPrefab, plus TintColor "220, 30, 45". A proven
path - the Spirit Stone and both portal stones already live on that mesh with
that same kind of tint.
- recipes.xml: count=2, no craft_area and no unlock tag (group 1 opens at level
1, and a recipe with no tag is simply always available, as with the Spirit
Stone).
- progression.xml: the sphere joins group 1's unlock_entry at unlock_tier="1".
- SpatialVaultPickupPatch: SpendBlood -> SpendCharge, the name coming from a new
ChargeItemName constant. The empty-slot gate in Begin is switched back on - it
was commented out during the revert because nothing could be installed.
ICONS. The user's own art: BloodSphere.png and BloodStone.png, 160x160 RGBA, in
both ItemIconAtlas and ItemIconAtlasGreyscale. The greyscale copy is mandatory -
without it a locked skill entry has no picture at all. How those copies are made
was worked out by diffing against the existing files and matched BIT FOR BIT:
convert('L') over RGB without the alpha, with the alpha merged back as its own
channel. Written up in BACKLOG.md; it had never been recorded anywhere. The
stone's icon is filed ahead of the item, which is still only planned.
VERIFIED THAT NOTHING ELSE SHIFTS. Diffed against 7172681: the set of <item>
entries in items.xml is unchanged, the only addition to item_modifiers.xml is
resourceBloodSphere, and no existing item had its Tags, ShowQuality or Stacknumber
changed. No item changed class, and no item changed layout.
In game: the world loads clean, and the sphere crafts and drains - the log shows
"sphere in slot 0 now 12/500 used" for a 12-second channel.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MnwP2Dt1vk8bUPJ452EoVL
Шесть указаний одного захода, которые сложились в одну механику: поглощение блока
больше не бесплатно. Браслет требует модификацию в слоте, кровь ею стала, и она
на это тратится.
СТАК ПО ОДНОЙ БАНКЕ. Наследуемый medicalBloodBag даёт Stacknumber 15, и это надо
перебивать явно - Extends копирует свойство целиком, а не "если не задано иначе".
Дорого и так задумано: Чёрный портал просит десять банок, то есть десять ячеек.
ПРОЧНОСТЬ 1000. Свойства с именем вроде Durability в игре нет; ручек две, и обе
обязательны. Число - пассивный эффект DegradationMax
(ItemValue.MaxUseTimesBase -> EffectManager.GetValue), полоска - отдельное
свойство ShowQuality (XUiC_ItemStack.ShowDurability -> ItemClass.ShowQualityBar).
Без первой прочность равна нулю, а полоска при MaxUseTimes == 0 рисуется ПОЛНОЙ,
то есть забытый эффект выглядит как "всё работает". tiered="false" обязателен:
ItemClass.HasQuality читается как Effects.IsOwnerTiered(), и тированная группа
превратила бы банку в предмет с качеством, с тирами и рамкой.
КРОВЬ ПЕРЕЕХАЛА В item_modifiers.xml. Это не настройка, а смена класса предмета:
XUiC_ItemPartStack.CanSwap открывается строкой
`if (!(stack.itemValue.ItemClass is ItemClassModifier ...)) return false;` - слот
модификации не смотрит ни на теги, ни на свойства, пока предмет не
ItemClassModifier, а этот класс создаётся только из <item_modifier>. Свойства
вида CanBeInstalled не существует; остаться ресурсом в items.xml и вставляться в
браслет физически нельзя. На старом месте оставлен комментарий-указатель.
Что при этом проверено, а не понадеялось: рецепты резолвятся (ItemClassModifier
наследует ItemClass, имена лежат в общем ItemClass.nameToItem, Recipe ищет через
GetItemClass по тому же словарю); сейв цел (ItemClass.assignIdsFromMapping берёт
айди из сохранённого name->id мэппинга, перестановка в конфигах предмет не
подменит); Harmony-патч и ключи локализации ходят по имени, имя не менялось.
ЛОВУШКА ПЕРЕЕЗДА: у модификации effect_group применяется к предмету, В КОТОРЫЙ её
вставили - прочность 1000 начала бы выдаваться БРАСЛЕТУ. Пассивка гейтована
tags="necroBloodFlask", тег добавлен в Tags флакона: MaxUseTimesBase зовёт
GetValue с ItemTags того предмета, для которого считает.
СЛОТОВ У БРАСЛЕТА 1, было 4. Слот из набора улучшений стал выбором.
ПУСТОЙ СЛОТ ОТКАЗЫВАЕТ. Проверка стоит первой строкой Begin, впереди всех
остальных отказов: прочие про ЦЕЛЬ (нет блока, не тот блок, хранилище полно), эта
про ИНСТРУМЕНТ, и сказать "здесь нет блока", когда пуст браслет, значит отправить
игрока искать не там. Тест - ItemValue.HasMods(), игровой собственный: обходит
только Modifications, пропуская null и IsEmpty, и не считает CosmeticMods, иначе
краска читалась бы как "браслет заряжен". Звук отказа достался бесплатно -
Deny() в этом файле уже играет ванильный ui_denied.
РАСХОД. Цена пула считается ОДИН раз, при старте, и едет в PickupJob.ChannelSeconds
вместе с самим браслетом. Не потому, что так короче: ChannelSecondsFor меряет луч
игрока, а за десять секунд игрок успевает отвернуться - второй вызов насчитал бы
цену за другой блок, а не за тот, который забрали. Браслет хранится экземпляром по
той же причине: моды живут на ItemValue, а колесо прокручивается.
Списывается в SpendBlood, ПОСЛЕ SetBlockRPC и после того, как предмет лёг в
хранилище: все отказы выходят раньше через return, так что кровь за отменённое
поглощение невозможна по построению. Имя предмета берётся из
NecromancerBloodPatch.BloodItemName, а не вторым литералом, чтобы не разъехались.
Мод, который не кровь, не платит ничего и поглощению не мешает - слот задуман под
другие вещи.
КОНЧИЛАСЬ - РАЗБИВАЕТСЯ. Правило именно "прочность 0 или меньше", а не "не хватило
на пул", и разница не косметическая: по второй формулировке флакон, которому
хватило впритык, остался бы в слоте с нулём, HasMods() видел бы "что-то вставлено",
и браслет работал бы бесплатно до конца света. Поэтому зажим по MaxUseTimes убран,
а слот обнуляется через ItemValue.None - это type 0, ровно то, что проверяет
IsEmpty(). Последнее поглощение проходит всегда, флакон его просто не переживает.
Звук - ванильный itembreak, тот же, что играет ItemAction.HandleItemBreak.
Защита: если MaxUseTimes окажется 0 (снесли passive_effect или тег), флакон НЕ
удаляется, а в лог идёт предупреждение с указанием, где чинить. Без этой ветки
ошибка в XML съедала бы игроку предмет на первом же поглощении, и выглядело бы это
багом механики.
Локализация: новый ключ braceletSpatialVaultNoMod в 13 языках, плюс описания
флакона и браслета - механика стала условной и платной, и оба текста без этого
стали бы враньём.
Известное и намеренное: кровавого камня, который обещает сообщение о пустом слоте,
ещё нет - он запланирован, разбор в BACKLOG.md. В игре ничего из этого не
проверено.
---
Necromancer's Blood is the Spatial Bracelet's fuel
Six instructions from one session that add up to one mechanic: pulling a block
into the vault is no longer free. The bracelet needs a mod in its slot, the blood
became that mod, and it is spent doing the work.
ONE JAR PER STACK. The inherited medicalBloodBag sets Stacknumber 15 and it has to
be overridden explicitly - Extends copies a property wholesale, not "unless set".
Expensive on purpose: the Black Portal asks for ten jars, so ten slots.
DURABILITY 1000. There is no property called anything like Durability; there are
two knobs and both are required. The number is a DegradationMax passive effect
(ItemValue.MaxUseTimesBase -> EffectManager.GetValue); the bar is a separate
ShowQuality property (XUiC_ItemStack.ShowDurability -> ItemClass.ShowQualityBar).
Without the first, durability is zero - and the bar at MaxUseTimes == 0 draws
FULL, so a forgotten effect looks exactly like success. tiered="false" is
mandatory: ItemClass.HasQuality is Effects.IsOwnerTiered(), and a tiered group
would have turned the jar into a quality item with tiers and a frame.
THE BLOOD MOVED TO item_modifiers.xml. Not a setting but a change of item class:
XUiC_ItemPartStack.CanSwap opens with
`if (!(stack.itemValue.ItemClass is ItemClassModifier ...)) return false;` - a mod
slot looks at neither tags nor properties until the item is an ItemClassModifier,
and that class is only created from <item_modifier>. No CanBeInstalled property
exists; staying a resource in items.xml and going into the bracelet is impossible.
A pointer comment was left where it used to live.
Checked rather than hoped: recipes still resolve (ItemClassModifier extends
ItemClass, names live in the shared ItemClass.nameToItem, Recipe looks them up
through GetItemClass); saves are safe (ItemClass.assignIdsFromMapping takes ids
from the stored name->id mapping, so shuffling configs cannot swap the item); the
Harmony patch and the localization keys go by name, and the name did not change.
THE TRAP IN MOVING IT: a modifier's effect_group applies to the item it is
INSTALLED IN - the 1000 durability would have been granted to the BRACELET. The
passive is gated with tags="necroBloodFlask" and the tag added to the flask's own
Tags: MaxUseTimesBase calls GetValue with the ItemTags of whatever it is
computing for.
THE BRACELET HAS 1 MOD SLOT, down from 4. The slot stopped being a set of
upgrades and became a choice.
AN EMPTY SLOT REFUSES. The check is the first line of Begin, ahead of every other
refusal: the others are about the TARGET (no block, wrong block, vault full), this
one is about the TOOL, and saying "no block there" when the real problem is an
empty bracelet sends the player looking in the wrong place. The test is
ItemValue.HasMods(), the game's own: it walks Modifications only, skipping nulls
and IsEmpty, and does not count CosmeticMods - a dye would otherwise have read as
"loaded". The refusal sound came free: Deny() in this file already plays vanilla's
ui_denied.
THE COST. The price of a pull is computed ONCE, at the start, and carried in
PickupJob.ChannelSeconds along with the bracelet itself. Not for brevity:
ChannelSecondsFor measures the player's ray, and ten seconds is long enough to
turn away - a second call would charge for a different block than the one taken.
The bracelet is kept as an instance for the same reason: mods live on the
ItemValue and the hotbar scrolls.
It is charged in SpendBlood, AFTER SetBlockRPC and after the item is in the vault:
every refusal returns earlier, so blood charged for a cancelled pull is impossible
by construction. The item name comes from NecromancerBloodPatch.BloodItemName
rather than a second literal, so the two cannot drift. A mod that is not blood
pays nothing and does not block the pull - the slot is meant for other things.
RUNS OUT, SHATTERS. The rule is "durability 0 or less", not "could not cover the
pull", and the difference is not cosmetic: under the second wording a flask with
exactly enough left would sit in the slot at zero, HasMods() would see "something
installed", and the bracelet would work for free forever. So the MaxUseTimes clamp
is gone and the slot is cleared with ItemValue.None - type 0, exactly what
IsEmpty() tests. The last pull always completes; the flask simply does not survive
it. The sound is vanilla's itembreak, the same cue ItemAction.HandleItemBreak
plays.
A guard: if MaxUseTimes comes out 0 (the passive effect or the tag removed), the
flask is NOT deleted and a warning naming the fix goes to the log. Without that
branch a config error would eat the player's item on the first pull and look like
a bug in the mechanic.
Localization: a new braceletSpatialVaultNoMod key in 13 languages, plus the flask
and bracelet descriptions - the mechanic became conditional and paid, and both
texts would have been lies without it.
Known and deliberate: the Blood Stone the empty-slot message promises does not
exist yet - it is planned, written up in BACKLOG.md. None of this is tested in
game.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MnwP2Dt1vk8bUPJ452EoVL
Баг со стрима: торговец, ставший чёрно-белым и полупрозрачным, наутро снова
обычный человек. Догадка была про обновление ассортимента - не подтвердилась, и
это стоит записать, потому что по часам лавки модель не трогает ВООБЩЕ ничего:
TraderData при сбросе переписывает только PrimaryInventory и lastInventoryUpdate,
а TraderArea.SetClosed ходит по блокам TraderOnOff - двери, замки, BlockLight,
динамик - и до сущности торговца не дотягивается.
Настоящая причина в EntityFactory.CreateEntityOperation.CompleteEntity:
`entity.entityId = ecd.id`, то есть сохранённый айди ВОССТАНАВЛИВАЕТСЯ. Торговцы
стримятся на подходе и точно так же выгружаются, когда игрок уходит на ночь.
Возвращается он - торговец собран заново: новый GameObject, новые рендереры,
ванильные материалы, ТОТ ЖЕ entityId. А в патче стоял HashSet<int> Ghosted -
"этот айди я уже обработал". Айди в наборе есть, свип проходит мимо, торговец
остаётся живым человеком до конца сессии. Отсюда и "на следующее утро": симптом
идёт не за часами лавки, а за чанком, в котором лавка стоит.
Ghosted стал Dictionary<int, GhostBody>, где GhostBody держит массив рендереров,
которые патч взял себе. IsIntact проверяет их: у Unity уничтоженный объект
сравнивается с null, так что подмена модели видна прямо, и та же проверка
бесплатно закрывает любую другую пересборку, не только выгрузку чанка.
Prune чистит Converted и TintedMaterials от уничтоженных объектов. Без него оба
списка росли бы на одного торговца за каждую пересборку, а Retint/Reapply ходили
бы по обломкам. Материал, выданный через renderer.materials, принадлежит
рендереру и умирает вместе с ним - одного прохода хватает на оба списка.
В лог добавлена строка "entity <id> came back with a new model" - ровно та,
которой не хватало, чтобы найти это за один заход вместо разбора в декомпиляторе.
Счётчик в necroghost переименован: со словарём он означает "торговцев под
присмотром сейчас", а не "id, которые когда-либо видели" - он теперь падает и
растёт.
Не проверено в игре: нужен заход к торговцу, выгрузка лавки и возвращение.
---
Traders go back to being ghosts after a chunk unload
Reported from the stream: a trader who had gone black-and-white and translucent
was an ordinary person again the next morning. The guess was the restock - it was
wrong, and that is worth recording, because nothing on the shop's clock touches
the model at all: TraderData's reset rewrites PrimaryInventory and
lastInventoryUpdate only, and TraderArea.SetClosed walks TraderOnOff blocks -
doors, locks, BlockLight, speaker - and never reaches the trader entity.
The real cause is in EntityFactory.CreateEntityOperation.CompleteEntity:
`entity.entityId = ecd.id`, so the saved id is RESTORED. Traders are streamed in
on approach and streamed out the same way when the player leaves for the night.
On return the trader is rebuilt from scratch - new GameObject, new renderers, the
game's own materials - carrying THE SAME entityId. The patch held a
HashSet<int> Ghosted, meaning "this id is done". The id was still in the set, the
sweep skipped him, and he stayed an ordinary person for the rest of the session.
Hence "the next morning": the symptom follows the chunk the shop sits in, not the
shop's clock.
Ghosted is now a Dictionary<int, GhostBody>, the value holding the renderers the
patch took over. IsIntact tests them: Unity's destroyed objects compare equal to
null, so a swapped model is directly visible, and the same check covers any other
rebuild for free.
Prune drops destroyed entries from Converted and TintedMaterials. Without it both
lists would grow by one trader's worth per rebuild 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.
A log line was added - "entity <id> came back with a new model" - the exact line
that was missing to find this in one visit rather than in a decompiler.
The necroghost counter was reworded: with a dictionary it means "traders held as
ghosts right now", not "ids ever seen" - it now falls as well as rises.
Not tested in game: needs a visit, a shop unload and a return.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MnwP2Dt1vk8bUPJ452EoVL
Номер по публичной линии Nexus: после 1.0.1 идёт 1.1.0, а не 1.0.2 - вышла не
починка, а две новые механики.
Описания приведены в соответствие с модом во всех четырёх местах, где они живут:
обе половины README, обе половины SITE_DESCRIPTION.html и NEXUS_DESCRIPTION.bbcode.
- Забор блоков дописан в пункт про Пространственный браслет: десять секунд
вплотную плюс секунда за блок расстояния, тот же круглый индикатор, что у
разбора верстака, обесцвечивание мира на время ожидания, перечень того, что не
поддаётся, и прерывание силовой атакой. До этой правки там стояло "обычная
атака пока ничего не делает".
- Торговцы - отдельным пунктом в "По мелочи", а на Nexus своим абзацем в тон
остальному тексту: чёрно-белые, полупрозрачные и матовые, торговля не меняется.
- Счётчик строк 123 -> 130 в трёх местах. Он врал и до сегодня: ключей было 124.
ЗАОДНО ЭТО ЧИСЛО СТАЛО ЧЕСТНЫМ. Рядом со счётчиком все описания утверждают "пустых
ячеек нет", и это было неправдой: у txName_NecroAsh пустовали семь языков
(японский, корейский, польский, бразильский, турецкий и оба китайских).
Заполнены - теперь в файле 130 ключей и ноль пустых ячеек перевода.
Раздел "Статус" переписан под 1.1.0, прежний текст про 1.0.1 сохранён абзацем
ниже: он не устарел, он просто стал предысторией.
NEXUS_FILE_DESCRIPTION_1.0.1.bbcode намеренно не тронут - он описывает конкретную
уже залитую загрузку. Новой версии понадобится свой файл.
---
Version 1.1.0: descriptions updated for the block pull and the traders
The number follows the public Nexus line: after 1.0.1 comes 1.1.0 rather than
1.0.2, because what shipped is two new mechanics, not a fix.
The descriptions now match the mod in all four places they live: both halves of
the README, both halves of SITE_DESCRIPTION.html, and NEXUS_DESCRIPTION.bbcode.
- The block pull was added to the Spatial Bracelet entry: ten seconds up close
plus one per block of distance, the same circular indicator a workbench pickup
uses, the world draining to black and white for the wait, what refuses to be
taken, and the power attack as the interrupt. Until this change the text still
said the regular attack does nothing.
- The traders got their own bullet under "Small things", and on Nexus their own
paragraph in the tone of the rest: black and white, half-transparent and matte,
with the trading itself unchanged.
- The string count went 123 -> 130 in three places. It was already wrong before
today: there were 124 keys.
AND THAT NUMBER IS NOW HONEST. Next to the count, every description claims there
are no empty cells, which was untrue: txName_NecroAsh was missing seven languages
(Japanese, Korean, Polish, Brazilian Portuguese, Turkish and both Chinese). They
are filled in - the file now has 130 keys and zero empty translation cells.
The "Status" section is rewritten for 1.1.0, with the old 1.0.1 text kept as a
paragraph below it: it did not become wrong, it became backstory.
NEXUS_FILE_DESCRIPTION_1.0.1.bbcode is deliberately untouched - it describes one
specific upload that already happened. The new version will need its own file.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XN8J75vnum2qAVrtRUMKf7