ИИ питомцев переписан, добавлен Дух крысы, пороги разнесены по шкале

Работа по указаниям 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>
This commit is contained in:
AlexCube
2026-09-18 20:51:42 +03:00
co-authored by Claude Opus 5
parent 15c59dd37a
commit d97f83e972
21 changed files with 2501 additions and 179 deletions
+123
View File
@@ -200,5 +200,128 @@
</effect_group>
</buff>
<!-- "Путы духа крысы" - укус Духа крысы, 2026-09-18.
БЫЛО НОКДАУНОМ, СТАЛО ЗАМЕДЛЕНИЕМ (в тот же день, по игровой проверке: "ронять зомби
при каждом ударе смотрится скорее смешно"). Первая версия ставила из кода
bodyDamage.CurrentStun = Prone на 5 секунд - механизм рабочий, но зомби валился на
землю от каждого укуса мелкого духа, и выглядело это комично.
ПОЧЕМУ ТЕПЕРЬ ХВАТАЕТ ЧИСТОГО XML, А РАНЬШЕ НЕ ХВАТАЛО. Открытым был ровно один вопрос:
доходят ли RunSpeed/WalkSpeed до ИИ, или это чисто игроцкие величины. Ответ нашёлся в
EntityAlive:
public virtual float GetMoveSpeedAggro() {
if (IsBloodMoon || world.IsDark())
return EffectManager.GetValue(PassiveEffects.RunSpeed, null, moveSpeedAggroMax, this);
return EffectManager.GetValue(PassiveEffects.WalkSpeed, null, moveSpeedAggro, this);
}
Скорость зомби берётся через EffectManager, а EntityMoveHelper.SetMoveTo читает её
оттуда же. Значит обычный пассивный эффект работает, и весь Harmony-код (RatGripPatch.cs)
оказался не нужен - удалён.
ОБЕ ВЕЛИЧИНЫ ОБЯЗАТЕЛЬНЫ. Ветка в GetMoveSpeedAggro выбирается по времени суток: ночью и
в кровавую луну читается RunSpeed, днём - WalkSpeed. Прописать одну - значит получить
замедление, которое молча отваливается на полсуток. CrouchSpeed добавлен за компанию,
для крадущихся спящих.
ЗАТУХАНИЕ, А НЕ РОВНАЯ ПОЛКА: value="0.7,.1" с duration="0,5" - это -70% в момент укуса,
плавно сходящие к -10% за 5 секунд, тем же приёмом, что у ванильного buffInjurySlow.
stack_type replace: каждый следующий укус начинает отсчёт заново, поэтому пока крыса
грызёт, зомби так и держится возле -70%, а стоит ей отвлечься - он разгоняется обратно.
Кулдаун, который был нужен нокдауну (иначе один питомец держал бы зомби лежачим вечно),
здесь не нужен и удалён вместе с ним: замедление, обновляемое укусами, - это ровно то
поведение, которое и требовалось. -->
<buff name="buffNecroRatGrip" name_key="buffNecroRatGripName" description_key="buffNecroRatGripDesc" icon="ui_game_symbol_twitch_slow" icon_color="140,200,255">
<stack_type value="replace"/>
<duration value="5"/>
<effect_group>
<passive_effect name="RunSpeed" operation="perc_subtract" value="0.7,.1" duration="0,5"/>
<passive_effect name="WalkSpeed" operation="perc_subtract" value="0.7,.1" duration="0,5"/>
<passive_effect name="CrouchSpeed" operation="perc_subtract" value="0.7,.1" duration="0,5"/>
</effect_group>
</buff>
<!-- "Метка духа" - вторая половина укуса Духа крысы, 2026-09-18 (указания "предложи дебаф
поинтереснее" -> выбраны варианты 2 и 3, метка и гниль, вместе).
ДВА ЭФФЕКТА, ОДИН БАФФ, И ЖИВЁТ ОН ДОЛЬШЕ ЗАМЕДЛЕНИЯ. Замедление (buffNecroRatGrip
выше) осмысленно только пока крыса грызёт, поэтому оно короткое и обновляется каждым
укусом. Метка - наоборот: её ценность в том, что она ПЕРЕЖИВАЕТ драку. Тридцать секунд
достаточно, чтобы добежать до помеченного и добить его, и мало, чтобы карта превратилась
в ёлку.
1. ВИДНО. SetNavObject на самого носителя - тот же механизм, которым сделано Тёмное
чутьё (necroModKnifeDarkSense ниже), только там selfAOE по площади, а здесь
target="self": метится ровно тот, кого укусили. Класс метки свой и зелёный, не
красный ванильный, - иначе добыча крысы была бы неотличима от зомби, подсвеченных
чутьём (см. комментарий в nav_objects.xml).
Снимается тремя триггерами, а не одним: по истечении баффа, по смерти носителя и на
всякий случай - тем же способом, что RemoveParticleEffectFromEntity у девиации выше.
Метка на трупе - это метка, которая не уйдёт уже никогда.
2. ГНИЁТ. GeneralDamageResist в МИНУС - помеченный получает на четверть больше урона от
всего подряд. Это не выдумка и не побочный эффект: сопротивление читается со стороны
ЖЕРТВЫ, в EntityAlive.DamageEntity:
float num = Utils.FastMin(1f, EffectManager.GetValue(PassiveEffects.GeneralDamageResist, null, 0f, this));
float num2 = (float)_strength * num + accumulatedDamageResisted;
int num3 = Utils.FastMin(_strength, (int)num2);
_strength -= num3;
при отрицательном сопротивлении num3 отрицателен, и вычитание его УВЕЛИЧИВАЕТ урон.
Ограничение FastMin(1f, ...) режет только верх (100% сопротивления), низ открыт.
Вместе это и есть роль Духа крысы: он не убивает сам (урон 5), он находит добычу и
делает её уязвимой для хозяина. -->
<buff name="buffNecroRatMark" name_key="buffNecroRatMarkName" description_key="buffNecroRatMarkDesc" icon="ui_game_symbol_skull" icon_color="150,220,70">
<stack_type value="replace"/>
<duration value="30"/>
<effect_group>
<passive_effect name="GeneralDamageResist" operation="base_subtract" value="0.25"/>
<triggered_effect trigger="onSelfBuffStart" action="SetNavObject" target="self" nav_object="necroRatSpiritMark" add="true"/>
<triggered_effect trigger="onSelfBuffRemove" action="SetNavObject" target="self" nav_object="necroRatSpiritMark" add="false"/>
<triggered_effect trigger="onSelfDied" action="SetNavObject" target="self" nav_object="necroRatSpiritMark" add="false"/>
</effect_group>
</buff>
<!-- "Кровоточащая рана" - кровотечение от укуса Духа крысы, 2026-09-18.
ВАНИЛЬНОЕ buffInjuryBleeding НА ЗОМБИ НЕ РАБОТАЕТ ВООБЩЕ, и стояло оно тут ошибочно.
Замечено пользователем по игре ("при такой слабой атаке байкера она била минут 5"),
подтверждено чтением Data/Config/buffs.xml. Урон там берётся не из самого баффа, а из
переменной игрока:
<triggered_effect trigger="onSelfBuffStart" action="ModifyCVar" cvar="$bleedAmount"
operation="set" value="@bleedCounter"/>
<passive_effect name="HealthChangeOT" operation="base_subtract" value="@$bleedAmount"/>
<triggered_effect trigger="onSelfBuffUpdate" action="RemoveBuff" buff="buffInjuryBleeding">
<requirement name="CVarCompare" cvar="bleedCounter" operation="Equals" value="0"/>
</triggered_effect>
bleedCounter накручивают ДРУГИЕ баффы-обёртки (buffInjuryBleedingOne/Two,
buffInjuryBleedingBarbedWire), и это часть игроцкой системы травм. Повесить
buffInjuryBleeding напрямую на зомби - значит получить счётчик 0: урон ноль, и бафф
снимает сам себя на первом же обновлении. Ровно ноль эффекта, молча.
Поэтому здесь своё, без единой внешней переменной: сколько написано, столько и капает.
ЧИСЛА. 5 в секунду на 15 секунд. Укус крысы даёт 5 урона и проходит примерно раз в
секунду, то есть кровотечение РОВНО УДВАИВАЕТ её вклад, а stack_type replace означает,
что пока она грызёт, рана не заживает. Отвлеклась - зомби живёт ещё 15 секунд и
перестаёт течь. Если в игре окажется мало или много, править надо эти две цифры и
больше ничего. -->
<buff name="buffNecroRatBleed" name_key="buffNecroRatBleedName" description_key="buffNecroRatBleedDesc" icon="ui_game_symbol_critical" icon_color="200,40,40" icon_blink="true">
<damage_type value="bloodloss"/>
<damage_source value="Internal"/>
<stack_type value="replace"/>
<duration value="15"/>
<update_rate value="1"/>
<effect_group>
<passive_effect name="HealthChangeOT" operation="base_subtract" value="5"/>
</effect_group>
</buff>
</append>
</config>