Files
necromants-tome-7d2d-3-2/HarmonySrc/VictimPatch.cs
T
AlexCubeandClaude Opus 5.5 e25544521d Труп «Жертвы» открывается сам, как ящик: без мешка
Обыск больше не превращает труп в мешок. Труп получает сумку со списком добычи zPackVictim
и открывается игровым путём (LockRequestLocal -> LootBagOpened -> окно добычи с игровым
таймером обыска). Запрет открывать мёртвых у EntityAlive снят префиксами только для таких
трупов. Открытый и не опустошённый труп лежит час, опустошённый убирается сразу.
После загрузки труп узнаётся по сохранённой сумке (баффы зомби в сейв не пишутся).
LockRequestLocal вызывается из TEPersistence (перегрузка с ReadOnlySpan не компилируется
в основном проекте). Класс EntityLootContainerVictim оставлен ради старых сейвов.
Описание ножа исправлено на всех языках, README - текущее описание.

Проверено в игре на 3.3: открытие, таймер, уборка опустошённых, восстановление после загрузки.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 19:33:52 +03:00

129 lines
9.2 KiB
C#

using HarmonyLib;
using UnityEngine;
namespace NecromancerTome
{
/// <summary>
/// Нож некроманта (BACKLOG.md, п.5): зомби, задетый ножом, носит buffNecroVictim (buffs.xml) до
/// самой смерти. На смерти его труп становится обыскиваемым контейнером с добычей «Жертвы»
/// (CorpseLootPatch.cs; до 1.5.0 обыск превращал труп в мешок EntityLootContainerVictim).
///
/// ПОВЕДЕНИЕ ИСПРАВЛЕНО 2026-09-18, И ЭТО ВОЗВРАТ К ТОМУ, О ЧЁМ ДОГОВАРИВАЛИСЬ. Баг-репорт
/// пользователя: «мы договаривались, что он выпадает только если не должен был выпасть мешок с
/// лутом. Но похоже падает только мешок жертвы».
///
/// Так и было. Прежняя версия состояла из двух префиксов, и оба возвращали false:
///
/// 1. на dropItemOnDeath - при дебафе звала DropBagServer() НАПРЯМУЮ, минуя ванильный
/// бросок вероятности, и обрывала оригинал;
/// 2. на DropBagServer - при дебафе спавнила «Жертву» ВМЕСТО обычного мешка.
///
/// Вместе это означало: помеченный зомби роняет «Жертву» ВСЕГДА и обычный мешок НИКОГДА. То
/// есть нож не добавлял добычу, а подменял её - и в редких 4% случаев, когда игроку и так
/// причитался нормальный мешок, он его терял.
///
/// СТАЛО (уточнено пользователем в тот же день - «а можно сделать так, чтобы в случае когда
/// положен лут, падало два мешка»): «Жертва» не заменяет обычный мешок и не ждёт его отсутствия,
/// а ДОБАВЛЯЕТСЯ. Ванильный бросок делается как есть (формула скопирована из
/// EntityAlive.dropItemOnDeath дословно, включая модификатор от оружия убийцы и множитель
/// LootContainer.LootBagChance), и дальше:
///
/// бросок прошёл -> обычный мешок рядом И добыча в трупе;
/// бросок не прошёл -> только добыча в трупе.
///
/// То есть помеченный ножом зомби никогда не оставляет игрока ни с чем, а в удачном случае
/// даёт вдвое больше обычного. Это осознанно щедро: метку ещё надо поставить ножом, которым
/// по зомби надо попасть.
///
/// УТОЧНЕНО В ТОТ ЖЕ ДЕНЬ: «Жертва» больше не падает мешком в момент смерти - она остаётся
/// ВНУТРИ ТРУПА, который перекрашивается, перестаёт исчезать и получает команду «обыскать»
/// (CorpseLootPatch.cs). Мешок появляется только по обыску. Сам бросок и вся арифметика ниже
/// от этого не изменились: они по-прежнему решают судьбу ОБЫЧНОГО мешка.
///
/// Второй префикс (на DropBagServer) УДАЛЁН ЦЕЛИКОМ - он теперь вреден: подменял бы и тот
/// обычный мешок, который мы как раз хотим оставить.
///
/// С 1.5.0 мешка «Жертва» нет совсем: сам труп открывается как ящик (CorpseLootPatch.cs), и
/// метод SpawnVictimBag удалён. Класс EntityLootContainerVictim в entityclasses.xml ОСТАВЛЕН
/// ради сейвов: в мирах игроков могут лежать уже выпавшие мешки, и без класса они не загрузятся.
///
/// ПОЧЕМУ ВООБЩЕ НУЖЕН ПРЕФИКС, А НЕ ПОСТФИКС. Узнать «выпал ли мешок» после оригинала нечем:
/// dropItemOnDeath ничего не возвращает и следов не оставляет. Поэтому бросок повторяется
/// здесь, а оригинал не запускается. Ветка оригинала, которая при этом теряется, - выброс
/// собственного инвентаря для сущностей БЕЗ ИИ (`if (!hasAI)`), и к зомби она не относится
/// никогда; на всякий случай она всё равно отдана оригиналу проверкой hasAI ниже.
///
/// LootContainer.NoLoot УВАЖАЕТСЯ: если в настройках мира мешки выключены, «Жертва» тоже не
/// падает. Это мировая настройка игрока, а не место мода спорить с ней.
/// </summary>
[HarmonyPatch(typeof(EntityAlive), "dropItemOnDeath")]
public static class Patch_EntityAlive_dropItemOnDeath_VictimBag
{
public const string VictimBuffName = "buffNecroVictim";
public static bool Prefix(EntityAlive __instance)
{
if (__instance.Buffs == null || !__instance.Buffs.HasBuff(VictimBuffName))
{
return true;
}
if (!__instance.hasAI)
{
// Не наш случай: у оригинала для таких сущностей своя работа (выброс инвентаря),
// и дублировать её здесь незачем. Зомби сюда не попадают.
return true;
}
if (!SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer)
{
return true;
}
// Ванильный расчёт, скопированный из EntityAlive.dropItemOnDeath дословно.
float prob = __instance.lootDropProb;
if ((bool)__instance.entityThatKilledMe)
{
prob = EffectManager.GetValue(PassiveEffects.LootDropProb,
__instance.entityThatKilledMe.inventory.holdingItemItemValue, prob,
__instance.entityThatKilledMe);
}
prob *= LootContainer.LootBagChance;
if (LootContainer.NoLoot)
{
Debug.Log("[NecromancerTome] VictimPatch: " + __instance.entityId +
" - мешки выключены настройками мира, «Жертва» не падает");
return false;
}
// Сам бросок печатается ВСЕГДА, и это не отладочный мусор, а ответ на вопрос, который
// уже задавали: «на 25 Арлин ни одного мешка с обычным лутом». У ванильного зомби
// LootDropProb = .04, то есть ноль мешков на два десятка убийств - исход с
// вероятностью около 40%, совершенно рядовой. Без этой строки отличить «не повезло»
// от «ролл сломан» можно только гаданием.
float roll = __instance.rand.RandomFloat;
Debug.Log("[NecromancerTome] VictimPatch: " + __instance.entityId + " - бросок на обычный мешок: шанс " +
prob.ToCultureInvariantString("0.000") + " против " + roll.ToCultureInvariantString("0.000") +
" -> " + (prob > roll ? "ВЫПАЛ" : "не выпал"));
if (prob > roll)
{
// Поле пишется ПЕРЕД вызовом намеренно, ровно как это делает сам оригинал:
// DropBagServer читает lootDropProb (`if (lootDropProb != 0f)`) и по нему решает,
// брать ли добычу из entityClass.lootDrops или из инвентаря. Посчитай мы в
// локальную и не запиши - редкий зомби с нулевым собственным lootDropProb, чей
// убийца поднял шанс оружием, пошёл бы не по той ветке.
__instance.lootDropProb = prob;
__instance.DropBagServer();
Debug.Log("[NecromancerTome] VictimPatch: " + __instance.entityId +
" - обычный мешок выпал по ролу; «Жертва» ждёт в трупе");
}
// «Жертва» БОЛЬШЕ НЕ ПАДАЕТ МЕШКОМ СРАЗУ (указание 2026-09-18): она остаётся внутри
// трупа, а труп становится обыскиваемым. Разбор целиком - в CorpseLootPatch.cs.
// Обычный мешок при этом по-прежнему выпадает по своему ролу и лежит рядом, так что
// прежняя договорённость «выпал рол - два источника добычи» сохраняется.
CorpseLoot.MarkCorpse(__instance);
return false;
}
}
}