Нож некроманта и Кровь некроманта получили собственные модели, а Пирамида духов - собственную поверхность. Три разных способа, каждый выбран по тому, как устроен сам предмет. НОЖ И КРОВЬ - свои префабы в бандлах мода. Форма записи "#@modfolder(...)?prefab" подтверждена живым примером; геометрия у обоих ванильная, меняются материал и текстура. Текстуры генерируются скриптами (см. _private/tools), а не рисуются: правка вида сводится к правке констант и одному запуску. Нож: состаренная кость, почти чёрная обмотка, пурпур во впадинах, плюс слой под ручную роспись рун - он подмешивается в альбедо и в эмиссию, поэтому руны светятся. Слой пользовательский, генератор его никогда не перезаписывает. Положение росписи посчитано по геометрии: развёртка выгружена из меша, и подобрано смещение, при котором вся роспись ложится на одну плоскую грань. Раньше она перегибалась через кромку, обращённую к игроку. Кровь: банка вместо мешка. Заодно чинится расхождение, жившее с самого начала - описание предмета говорило "Банка, наполненная кровью", а наследуемый medicalBloodBag показывал sackPrefab, обычный мешок. Жидкость перерисована по маскам мешей, а не перекрашена тинтом: тинт предмета на этот меш не действует вовсе, у шейдера Game_EntityTintMaskSSS выигрывает _Color материала. ПИРАМИДА - своя краска в атласе блоков через собственный Harmony-патч. Ваниль своих текстур блоков не умеет: Texture у блока это индекс в готовом атласе, а запись краски несёт только TextureId/PaintCost/Group/SortIndex и никогда путь к картинке. Поэтому CustomBlockPaintPatch дописывает наш слой в массивы атласа на лету. Путь через свою модель (Shape="ModelEntity") пробовался и отложен: там остались нерешёнными столкновения и маджента на дальних экземплярах. Краска лучше тем, что блок остаётся обычным Shape="New" - со всеми работающими столкновениями, наведением по E и правильной посадкой, - а поверхность у него своя. Побочно краска доступна кисточкой под именем "Некротический прах". Параметры атласа не угаданы, а замерены в игре (BlockAtlasProbePatch): 512x512, DXT1 для альбедо и DXT5 для нормалей со specular, 10 мип-уровней, массивы нечитаемые - отсюда GPU-копирование и явные настройки импорта текстур. Зонд оставлен намеренно: номер краски в blocks.xml (608) это длина ванильного uvMapping, и после обновления игры он может сдвинуться - зонд печатает фактические числа при каждом запуске. Патчи обёрнуты целиком: они работают внутри загрузки игры, где вылетевшее исключение срывает шаг загрузки. Resources/necropyramid в коммит не идёт - на него никто не ссылается. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W5F3AVwsusZHMqPBcSQcVJ
184 lines
16 KiB
XML
184 lines
16 KiB
XML
<config>
|
||
<!-- "Пирамида Ереси" (Pyramid of Heresy) - user request 2026-08-31: a placeable ward that
|
||
periodically applies the existing Deviator charm (see HarmonySrc/PyramidWardPatch.cs,
|
||
reusing HarmonySrc/CharmPatch.cs's buffNecroDeviatorCharm wholesale - no new charm logic
|
||
was written, this just triggers the same buff on a timer) to any un-charmed zombie that
|
||
wanders into its radius, turning it against other zombies instead of the player/base - a
|
||
"barrier" in effect, not in physics: it never pushes or physically blocks anything, it
|
||
flips allegiance.
|
||
|
||
SHAPE: Shape="New" + Model="@:Shapes/pyramid.fbx" - the same standalone pyramid mesh
|
||
vanilla itself ships and uses for imposterPyramid's own LOD model (Data/Config/blocks.xml,
|
||
"*** blockNextGen imposters" section) - a real, solid, always-loaded asset, not invented
|
||
for this mod. Checked the "tentBiohazardRoofXxx" tent-roof pieces first (they're the
|
||
nearest thing to a "block with a pyramid top" already in vanilla) but rejected them - all
|
||
of them are 3x1x1/3x2x3 multi-blocks meant as building-sized tent roofs, wrong shape/scale
|
||
for a single placeable ward. Red per the user's request via TintColor="8A4142" - the exact
|
||
hex vanilla's own tentBiohazardRoofRed/tentBiohazardRoofCenterRed use for their red tint,
|
||
reused here rather than inventing a new one.
|
||
|
||
FIXED 2026-08-31 (user report: placed fine but genuinely invisible - "нету префаба"):
|
||
missing `Texture` property. EVERY vanilla block using this exact Shape="New" + a custom
|
||
standalone Model="@:Shapes/....fbx" (imposterPyramid itself, imposterQuarter,
|
||
imposterCTREighth, poiFillerBlock - checked all of them directly) always pairs it with an
|
||
explicit `Texture="N"` atlas index; ours never had one, which is the real, simple root
|
||
cause. Also switched Material from plain "Msteel" to "Msteel_shapes" + Texture="356" -
|
||
the exact Material/Texture pair vanilla's own `steelShapes` block (the real "shapes=All"
|
||
steel building family) uses for this same "New" shape renderer - plain "Msteel" is meant
|
||
for non-shape steel blocks (doors, hatches) and was never a matched pair with Shape="New"
|
||
to begin with. TintColor="8A4142" still multiplies on top as before.
|
||
|
||
User specifically asked to reuse the "Пирамида 1 м" ("Pyramid 1m") shape/prefab -
|
||
genuinely tried, but that specific shape turned out to be a NAMED VARIANT of the engine's
|
||
procedural multi-shape building system (`shapes="All"` blocks like `steelShapes`/
|
||
`concreteShapes` - see those in vanilla blocks.xml), not a standalone asset with its own
|
||
file path at all: decompiled `BlockShapeNew` (the C# class behind every Shape="New" block)
|
||
directly and it contains NO "Pyramid" string anywhere - the mapping from a shape's display
|
||
name to its actual geometry lives in compiled/binary shape-node data this decompile
|
||
couldn't reach, not in anything grep-able or copyable as a `Model=` path. Reproducing that
|
||
exact shape would mean reverse-engineering that binary shape data, real extra work with an
|
||
uncertain payoff - what shipped instead is the closest already-proven-working equivalent:
|
||
the same "New"-shape pyramid mesh `imposterPyramid` itself uses, now actually configured
|
||
correctly (see the fix above) so it renders for real. If the proportions look wrong next to
|
||
the in-game "Pyramid 1m" (e.g. too tall/full-block instead of a short 1m cap), that's a
|
||
scale tweak once it's visible, not a rendering bug - say so and it can be resized.
|
||
|
||
REWRITTEN 2026-09-01, Class changed from none to Class="CompositeTileEntity" +
|
||
CompositeFeatures/TEFeaturePyramidWard - two direct user findings drove this:
|
||
1. "Навожу прицел, но подсказка про E не появляется" - the original plain-Block +
|
||
Harmony-patches-on-Block approach never showed the E-prompt at all, because a
|
||
SEPARATE gate method (Block.HasBlockActivationCommands) was never patched and always
|
||
said "nothing to activate" for a plain decorative block.
|
||
2. "Сделай TileEntity" - direct request for the effect/zone toggle state to actually
|
||
survive a save/reload, which the old static-Dictionary-in-Harmony-patch version could
|
||
not do at all (documented as a known caveat before, now actually fixed).
|
||
Same syntax vanilla's own keystoneBlock (Land Claim) already uses for this exact
|
||
Class="CompositeTileEntity" + CompositeFeatures pattern - see HarmonySrc/
|
||
PyramidWardPatch.cs's own class doc comment on TEFeaturePyramidWard for the full decompiled
|
||
reasoning (why this is the sanctioned extension point, not a hardcoded-switch hack; the
|
||
E-menu/persistence/per-tick mechanics all live there now, not in this XML file). -->
|
||
<append xpath="/blocks">
|
||
<block name="necroHeresyPyramid">
|
||
<property name="CreativeMode" value="Player" />
|
||
<property name="DescriptionKey" value="necroHeresyPyramidDesc" />
|
||
<property name="Class" value="CompositeTileEntity" />
|
||
<property class="CompositeFeatures">
|
||
<property class="TEFeaturePyramidWard" />
|
||
</property>
|
||
<property name="Material" value="Msteel_shapes" />
|
||
<!-- ВЕРНУТО НА Shape="New" 2026-09-10 по решению пользователя: «пока верни пирамиду,
|
||
которая была у нас изначально».
|
||
|
||
Своя модель со своей текстурой требует Shape="ModelEntity", и на этом пути осталось
|
||
два невылеченных дефекта - разбор и все замеры в BACKLOG.md, раздел про пирамиду:
|
||
- нет столкновений: при "New" их считает воксельная система блока, а "ModelEntity"
|
||
берёт от модели, и одного BoxCollider на корне в слое 16 не хватило;
|
||
- дальние экземпляры рисуются фиолетовым (маджента = не найден шейдер), ближние
|
||
при этом верны; добавленный LODGroup положения не исправил.
|
||
|
||
Всё, что сделано для того пути, сохранено и работает: генератор текстуры
|
||
_private/tools/make_pyramid_textures.py, сборка _private/Extracted/ShapesUnityProject,
|
||
бандл Resources/necropyramid. Чтобы вернуться к нему, достаточно заменить Shape на
|
||
ModelEntity и раскомментировать строку Model ниже. -->
|
||
<property name="Shape" value="New" />
|
||
<!-- Своя модель отключена вместе с возвратом на Shape="New" (см. комментарий выше).
|
||
Строка сохранена: она рабочая, ею подключается наш бандл. -->
|
||
<property name="Model" value="@:Shapes/pyramid.fbx" />
|
||
<!-- <property name="Model" value="#@modfolder(NecromancerTome):Resources/necropyramid?necroPyramidPrefab.prefab" /> -->
|
||
<!-- ModelOffset - ПОЛОЖЕНИЕ МОДЕЛИ, и задавать его надо именно здесь.
|
||
|
||
Меш формы из shapes-бандла сделан под воксельную систему: его границы лежат не вокруг
|
||
начала координат, а в углу (центр -0.50, 0.13, -0.50 при размере 0.51 x 0.26 x 0.50).
|
||
Как ModelEntity такая модель вылезает из своего куба - что и наблюдалось: «поднялась,
|
||
вершиной упирается в верхний угол».
|
||
|
||
Сначала это лечили сдвигом детей ВНУТРИ префаба. Модель встала верно, но по блоку
|
||
по-прежнему нельзя было попасть и не работало наведение по E: о сдвиге знал только
|
||
префаб, а игра держала свой объём для попаданий там, где модель была бы без сдвига.
|
||
Игрок целился в одно, а попадал мимо.
|
||
|
||
Ваниль двигает модель именно этим свойством (у верстака стоит "0,.5,0"), и его игра
|
||
учитывает целиком - и в отрисовке, и в попаданиях. Значение посчитано скриптом сборки
|
||
по фактическим границам меша: по X и Z в центр куба, по Y основанием на нижнюю грань.
|
||
Если меш заменят, скрипт напечатает новое значение в лог. -->
|
||
<!-- ModelOffset нужен только при ModelEntity; при "New" положение задаёт сама форма.
|
||
Значение верное, посчитано скриптом сборки - сохранено для возврата.
|
||
<property name="ModelOffset" value="0.5,-0.495,0.5" /> -->
|
||
<!-- ПОКРАСКА ВМЕСТО СВОЕЙ ТЕКСТУРЫ, 2026-09-10. Мысль пользователя: «в игре есть
|
||
кисточка, позволяющая перекрашивать блоки, можем ли мы наложить текстуру через
|
||
покраску, только заранее?» Можем - именно это свойство Texture и делает.
|
||
|
||
Data/Config/painting.xml сопоставляет краски из кисточки с номерами текстур атласа
|
||
(поле TextureId), а Texture у блока задаёт краску заранее. Доступно 156 красок;
|
||
выписаны с названиями в _private/Extracted/paints.txt.
|
||
|
||
Было 356 (txName_Steel_wall, «оцинкованная стальная стена») - выбор достался от
|
||
steelShapes вместе с парой Material/Texture и к праху отношения не имел.
|
||
Пробовали 11 (txName_Gravel, «гравий») - в игре прочиталось как земля.
|
||
Сейчас 552 (txName_GraniteBlack, «гранит чёрный»): тёмный камень ближе к
|
||
спрессованному праху, чем крупная осыпь гравия.
|
||
|
||
СВОЮ текстуру сюда подставить НЕЛЬЗЯ, проверено по коду игры: у записи краски есть
|
||
только TextureId, PaintCost, Group, SortIndex - поля с путём к своей картинке нет.
|
||
TextureId это индекс в уже собранном атласе, а сам атлас лежит в
|
||
blocktextureatlases_assets_all.bundle среди Addressables игры (рядом с
|
||
TerrainTextures), и крючка для подмены его модом не нашлось. Своя текстура
|
||
возможна только через путь ModelEntity со своим бандлом - см. BACKLOG.md.
|
||
|
||
Подбирать краску удобнее всего кисточкой прямо в игре: доступны все 156, перезапуск
|
||
не нужен. Найденный номер прописывается сюда.
|
||
|
||
СЕЙЧАС ЭТИ ДВА СВОЙСТВА НЕ РАБОТАЮТ. Блок снова на Shape="ModelEntity" со своей
|
||
моделью (2026-09-10, после того как префаб довели до вида ванильного): при
|
||
ModelEntity поверхность даёт материал префаба, а не атлас, и Texture с TintColor
|
||
не участвуют. Оставлены нетронутыми, чтобы возврат к покраске был ровно двумя
|
||
правками - Shape на "New" и переключить строку Model ниже.
|
||
|
||
TintColor - множитель поверх краски. Взят пепельно-серый с уходом в фиолет, в тон
|
||
остальному мод-набору. Прежний 8A4142 давал ржаво-красный.
|
||
|
||
Texture принимает и ШЕСТЬ значений через запятую - по одному на грань (в ванили есть
|
||
например value="195,570,570,570,570,570"). Если захочется основание отличать от
|
||
скатов - делается здесь же, без всякого бандла.
|
||
|
||
Хорошие запасные варианты из того же списка: 606 concrete_broken (потрескавшийся
|
||
бетон), 552 GraniteBlack (чёрный гранит), 443 Rust_black (чёрная ржавчина),
|
||
385 Green_rusty_metal (зелёная ржавчина, в тон вкраплениям). -->
|
||
<!-- СВОЯ КРАСКА, 2026-09-10. Это уже НЕ ванильная краска из атласа: текстуру пирамиды
|
||
добавляет в атлас наш собственный Harmony-патч, HarmonySrc/CustomBlockPaintPatch.cs.
|
||
Блок при этом остаётся обычным Shape="New" - со всеми работающими столкновениями,
|
||
наведением по E и правильной посадкой, - а поверхность у него своя.
|
||
|
||
608 - это НОМЕР ЗАПИСИ В uvMapping, а не номер краски. Патч печатает его в лог при
|
||
каждом запуске: «uvMapping entry 608 points at slice 407». Краска дополнительно
|
||
регистрируется под именем txName_NecroAsh («Некротический прах») и занимает слот 13,
|
||
но блоку нужен именно номер записи.
|
||
|
||
ХРУПКОЕ МЕСТО: 608 - это длина uvMapping ванильной игры, то есть наша запись просто
|
||
дописывается в конец. Число верно, пока атлас игры не изменился; после обновления игры
|
||
оно может сдвинуться. Патч печатает фактический номер в лог, поэтому расхождение видно
|
||
сразу - сверяться при обновлениях.
|
||
|
||
TintColor убран намеренно: цвет несёт сама текстура, а тинт - множитель, он бы её
|
||
исказил. Тот же вывод, что уже сделан по ножу и по банке крови.
|
||
<property name="TintColor" value="C8BCD2" /> -->
|
||
<property name="Texture" value="608" />
|
||
<!-- Real generated art (exch/pyramidOfSpirit.png, copied to
|
||
UIAtlases/ItemIconAtlas/) added 2026-09-02, per direct instruction - no
|
||
CustomIconTint alongside it (same lesson as every other real icon in this mod,
|
||
e.g. resourceZombieAsh: a real drawn icon already has its own correct color, tinting
|
||
it on top would recolor it unintentionally). TintColor above is unaffected - that's
|
||
the 3D block MESH's tint, a completely separate thing from the 2D inventory icon. -->
|
||
<property name="CustomIcon" value="pyramidOfSpirit" />
|
||
<property name="Group" value="Decoration" />
|
||
<property name="FilterTags" value="MC_building,SC_decor" />
|
||
<property name="Collide" value="movement,melee,bullet,arrow,rocket" />
|
||
<property name="StabilitySupport" value="false" />
|
||
<property name="MaxDamage" value="2000" />
|
||
<property name="EconomicValue" value="0" />
|
||
<property name="SellableToTrader" value="false" />
|
||
<drop event="Destroy" name="resourceScrapIron" count="10,20" />
|
||
</block>
|
||
</append>
|
||
</config>
|