Добыча «Жертвы» переехала в сам труп: он красится, лежит час и обыскивается
Указание: «зомби после упокоения оставляет свой труп. Можем ли мы подменять этот труп точно таким же, но с другой текстурой и с функцией мешка жертвы?» ПОДМЕНЯТЬ НЕ ПРИШЛОСЬ НИЧЕГО. Труп - не отдельная модель, а та же скелетная модель зомби в позе смерти, так что «такой же, но другой текстуры» делается перекраской уже лежащего трупа. Ни нового префаба, ни новой сущности. Три части, каждая на своём ванильном механизме: НЕ ИСЧЕЗАЕТ. 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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b6a892cadd
commit
98ddcd7bce
@@ -0,0 +1,399 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NecromancerTome
|
||||
{
|
||||
/// <summary>
|
||||
/// ТРУП ПОМЕЧЕННОГО ЗОМБИ САМ СТАНОВИТСЯ ДОБЫЧЕЙ — указание 2026-09-18: «зомби после упокоения
|
||||
/// оставляет свой труп. Можем ли мы подменять этот труп в момент смерти точно таким же, но с
|
||||
/// другой текстурой и с функцией мешка жертвы (не исчезает, можно забрать лут)?»
|
||||
///
|
||||
/// ПОДМЕНЯТЬ НЕ ПРИШЛОСЬ НИЧЕГО, и это главное, что стоит знать. Труп — не отдельная модель, а
|
||||
/// та же скелетная модель зомби в позе смерти, поэтому «такой же, но с другой текстурой» — это
|
||||
/// перекраска материалов уже лежащего трупа, тем же приёмом, что призрачные питомцы и глаза
|
||||
/// собаки. Ни нового префаба, ни новой сущности.
|
||||
///
|
||||
/// ТРИ ЧАСТИ, И КАЖДАЯ ДЕРЖИТСЯ НА СВОЁМ ВАНИЛЬНОМ МЕХАНИЗМЕ:
|
||||
///
|
||||
/// 1. НЕ ИСЧЕЗАЕТ. `EntityAlive.timeStayAfterDeath` — обычное публичное поле экземпляра,
|
||||
/// которое при спавне заполняется из entity_class (`TimeStayAfterDeath * 20`). У зомби
|
||||
/// там 30 секунд, у ванильного мешка с лутом — 3600. Достаточно записать другое число.
|
||||
///
|
||||
/// 2. ЕГО МОЖНО ОБЫСКАТЬ. У сущностей есть своя система команд взаимодействия, отдельная от
|
||||
/// блочной: `Entity.GetActivationCommands()` собирает список из виртуального
|
||||
/// `InitLocalActivationCommands`, `AllowActivationCommand` решает, доступна ли команда
|
||||
/// сейчас, `OnEntityActivated` её исполняет. Мешок с лутом объявляет свою так
|
||||
/// (EntityItem, от которого он наследуется):
|
||||
///
|
||||
/// _addCallback(new EntityActivationCommand("search", "search"));
|
||||
///
|
||||
/// Мы объявляем ровно такую же — с тем же идентификатором и той же иконкой. Значит и
|
||||
/// подпись на экране, и значок берутся готовые, локализованные на все языки игры, и
|
||||
/// игроку это выглядит ровно как обыск мешка, потому что это и есть он.
|
||||
///
|
||||
/// 3. ВНУТРИ ЛУТ. А вот здесь сознательный обход. Само хранилище живёт внутри
|
||||
/// `EntityLootContainer`: его `bag`, его `Read`/`Write` для сейва, его сетевая
|
||||
/// синхронизация. Приделать всё это трупу зомби — работа масштаба Пространственного
|
||||
/// хранилища, с выходом на сейвы, то есть на риск номер один этого проекта.
|
||||
///
|
||||
/// Поэтому обыск НЕ открывает труп, а ПРЕВРАЩАЕТ его в мешок «Жертва»: мешок спавнится
|
||||
/// на месте трупа, труп убирается. Снаружи читается как «обыскал и нашёл», внутри —
|
||||
/// обычный ванильный контейнер, который сам себя сохраняет, синхронизирует и открывает.
|
||||
/// Ни одной новой сущности с состоянием.
|
||||
///
|
||||
/// ОБЫЧНЫЙ МЕШОК ЭТО НЕ ОТМЕНЯЕТ. Бросок вероятности в VictimPatch.cs остаётся как был: выпал
|
||||
/// — обычный мешок лежит рядом, не выпал — только труп. «Жертва» переехала из «падает сразу» в
|
||||
/// «лежит внутри трупа», и больше ничего не изменилось.
|
||||
///
|
||||
/// ПОЧЕМУ СПИСОК КОМАНД СБРАСЫВАЕТСЯ. `GetActivationCommands` кеширует результат в
|
||||
/// `activationCommands` при первом обращении — а первое обращение случается, пока зомби ещё
|
||||
/// жив и командой не обладает. Обнуление поля на смерти заставляет пересобрать список; тем же
|
||||
/// приёмом пользуется сама игра в CopyPropertiesFromEntityClass.
|
||||
/// </summary>
|
||||
public static class CorpseLoot
|
||||
{
|
||||
/// <summary>Сколько секунд лежит помеченный труп. У ванильного мешка с лутом 3600, и
|
||||
/// причин быть скромнее нет: труп и есть мешок.</summary>
|
||||
public const float CorpseSecondsAfterDeath = 3600f;
|
||||
|
||||
/// <summary>Во что красится труп. Тёмно-багровый: издалека видно, что этот не как все, и
|
||||
/// при этом не выглядит подсветкой из другой игры.</summary>
|
||||
public static readonly Color CorpseTint = new Color(0.55f, 0.12f, 0.16f);
|
||||
|
||||
/// <summary>Идентификатор берётся у ванильного мешка (EntityItem), чтобы подпись досталась
|
||||
/// готовой и локализованной на все языки игры. ИКОНКА - "hand", а не "search", по указанию
|
||||
/// 2026-09-18 («при наведении на зомби пиктограмма руки»); имена иконок к идентификатору
|
||||
/// команды не привязаны, у ванильного "take" стоит ровно эта.</summary>
|
||||
public const string SearchCommand = "search";
|
||||
public const string SearchIcon = "hand";
|
||||
|
||||
/// <summary>Секунды удержания. Круговой индикатор рисует сама игра - XUiC_Timer, тот же,
|
||||
/// что крутится при снятии верстака.</summary>
|
||||
public const float SearchSeconds = 3f;
|
||||
|
||||
/// <summary>Трупы, которые ещё не обысканы. Ключ — entityId. Чистится сама: запись
|
||||
/// снимается при обыске, а остальные уходят вместе с миром.</summary>
|
||||
public static readonly HashSet<int> SearchableCorpses = new HashSet<int>();
|
||||
|
||||
/// <summary>Трупы, которые ещё не удалось перекрасить: модель бывает не собрана на том
|
||||
/// кадре, когда сущность умирает. Добивается односекундным тиком PetFollowPatch.</summary>
|
||||
public static readonly HashSet<int> PendingTint = new HashSet<int>();
|
||||
|
||||
/// <summary>Повторная попытка перекраски. Зовётся из общего тика раз в секунду.</summary>
|
||||
public static void RetryPendingTints(World _world)
|
||||
{
|
||||
if (PendingTint.Count == 0 || _world == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
List<int> done = new List<int>();
|
||||
foreach (int id in PendingTint)
|
||||
{
|
||||
Entity corpse = _world.GetEntity(id);
|
||||
if (corpse == null || TintCorpse(corpse))
|
||||
{
|
||||
done.Add(id);
|
||||
}
|
||||
}
|
||||
foreach (int id in done)
|
||||
{
|
||||
PendingTint.Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsSearchable(Entity _entity)
|
||||
{
|
||||
return _entity != null && SearchableCorpses.Contains(_entity.entityId);
|
||||
}
|
||||
|
||||
/// <summary>Зовётся из VictimPatch, когда помеченный зомби умер.</summary>
|
||||
public static void MarkCorpse(EntityAlive _victim)
|
||||
{
|
||||
if (_victim == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
SearchableCorpses.Add(_victim.entityId);
|
||||
|
||||
// timeStayAfterDeath хранится в ТИКАХ: игра заполняет его как TimeStayAfterDeath * 20
|
||||
// (EntityAlive.CopyPropertiesFromEntityClass), то есть двадцать тиков на секунду.
|
||||
_victim.timeStayAfterDeath = (int)(CorpseSecondsAfterDeath * 20f);
|
||||
|
||||
// Список команд уже собран и закеширован, пока зомби был жив и обыскивать его было
|
||||
// нельзя. Обнуляем, чтобы собрался заново - уже с "search".
|
||||
_victim.activationCommands = null;
|
||||
|
||||
if (!TintCorpse(_victim))
|
||||
{
|
||||
// Модель на этом кадре может быть ещё не готова - ровно тем же болели призрачные
|
||||
// торговцы и разведение коллайдеров у питомцев. Кладём в очередь, тик добьёт.
|
||||
PendingTint.Add(_victim.entityId);
|
||||
}
|
||||
Debug.Log("[NecromancerTome] CorpseLoot: труп " + _victim.entityId +
|
||||
" помечен как обыскиваемый, лежит " + CorpseSecondsAfterDeath + " с");
|
||||
}
|
||||
|
||||
/// <summary>Перекраска трупа. ПЕРЕПИСАНА 2026-09-18, первая версия не работала вообще:
|
||||
/// пользователь сообщил «труп не перекрасился», и причина нашлась в нашем же логе.
|
||||
///
|
||||
/// Первая версия ставила `_Color` на материалах - ровно как сделано у глаз собаки. Но у
|
||||
/// собаки глаз сидит на шейдере `Standard`, а тело зомби и шкура зверей - на собственных
|
||||
/// шейдерах игры, и проба, которую печатает GhostTraderPatch, по ним говорит прямо:
|
||||
///
|
||||
/// shader 'Game/Character' ... tint property: <none>
|
||||
/// shader 'Game/Animal/Fur' ... tint property: <none>
|
||||
///
|
||||
/// Свойства цвета у них НЕТ ВООБЩЕ. `HasProperty("_Color")` возвращал false, цикл не делал
|
||||
/// ни одной итерации, и метод честно печатал «материалов 0» - которое никто не прочитал.
|
||||
///
|
||||
/// Поэтому красится САМА ТЕКСТУРА, тем же приёмом, которым мод обесцвечивает торговцев:
|
||||
/// прогон через RenderTexture (игровые текстуры сжаты и с isReadable=false, напрямую
|
||||
/// GetPixels по ним падает), затем попиксельное умножение на цвет. Разница с
|
||||
/// обесцвечиванием ровно в одной строке арифметики, всё остальное - тот же путь, уже
|
||||
/// проверенный на шести торговцах.
|
||||
///
|
||||
/// Результат кешируется по исходной текстуре: они 2048x2048 и больше, а помеченных трупов
|
||||
/// за игру будет много - пересчитывать на каждый незачем.</summary>
|
||||
public static readonly Dictionary<Texture, Texture2D> TintedTextures = new Dictionary<Texture, Texture2D>();
|
||||
|
||||
public static bool TintCorpse(Entity _corpse)
|
||||
{
|
||||
Renderer[] renderers = _corpse.GetComponentsInChildren<Renderer>(true);
|
||||
if (renderers == null || renderers.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int tinted = 0;
|
||||
foreach (Renderer renderer in renderers)
|
||||
{
|
||||
if (renderer == null || renderer is ParticleSystemRenderer)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Material[] shared = renderer.sharedMaterials;
|
||||
if (shared == null || shared.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Material[] instances = renderer.materials;
|
||||
for (int i = 0; i < instances.Length && i < shared.Length; i++)
|
||||
{
|
||||
Material material = instances[i];
|
||||
if (material == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
string albedo = GhostTraderPatch.FindAlbedoProperty(material);
|
||||
if (albedo == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Texture2D repainted = Tint(material.GetTexture(albedo));
|
||||
if (repainted == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
material.SetTexture(albedo, repainted);
|
||||
tinted++;
|
||||
}
|
||||
}
|
||||
Debug.Log("[NecromancerTome] CorpseLoot: труп " + _corpse.entityId + " перекрашен, материалов " + tinted);
|
||||
return tinted > 0;
|
||||
}
|
||||
|
||||
/// <summary>Копия GhostTraderPatch.Desaturate с одной изменённой строкой: вместо сведения
|
||||
/// каналов к яркости - умножение на CorpseTint. Прогон через RenderTexture обязателен и там
|
||||
/// и здесь по одной причине: игровые текстуры сжаты и не читаются процессором напрямую.</summary>
|
||||
public static Texture2D Tint(Texture _source)
|
||||
{
|
||||
if (_source == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (TintedTextures.TryGetValue(_source, out Texture2D cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
Texture2D painted = null;
|
||||
RenderTexture rt = null;
|
||||
RenderTexture previous = RenderTexture.active;
|
||||
try
|
||||
{
|
||||
rt = RenderTexture.GetTemporary(_source.width, _source.height, 0,
|
||||
RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
|
||||
Graphics.Blit(_source, rt);
|
||||
RenderTexture.active = rt;
|
||||
|
||||
painted = new Texture2D(_source.width, _source.height, TextureFormat.RGBA32, false);
|
||||
painted.ReadPixels(new Rect(0f, 0f, _source.width, _source.height), 0, 0);
|
||||
|
||||
Color32[] pixels = painted.GetPixels32();
|
||||
for (int i = 0; i < pixels.Length; i++)
|
||||
{
|
||||
Color32 p = pixels[i];
|
||||
p.r = (byte)(p.r * CorpseTint.r);
|
||||
p.g = (byte)(p.g * CorpseTint.g);
|
||||
p.b = (byte)(p.b * CorpseTint.b);
|
||||
pixels[i] = p;
|
||||
}
|
||||
painted.SetPixels32(pixels);
|
||||
painted.Apply(false, false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError("[NecromancerTome] CorpseLoot: не удалось перекрасить текстуру '" +
|
||||
_source.name + "': " + e.Message);
|
||||
painted = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
RenderTexture.active = previous;
|
||||
if (rt != null)
|
||||
{
|
||||
RenderTexture.ReleaseTemporary(rt);
|
||||
}
|
||||
}
|
||||
|
||||
TintedTextures[_source] = painted;
|
||||
return painted;
|
||||
}
|
||||
|
||||
/// <summary>Обыск: труп превращается в мешок «Жертва» на своём же месте.</summary>
|
||||
public static void SearchCorpse(EntityAlive _corpse)
|
||||
{
|
||||
SearchableCorpses.Remove(_corpse.entityId);
|
||||
Patch_EntityAlive_dropItemOnDeath_VictimBag.SpawnVictimBag(_corpse, false);
|
||||
Debug.Log("[NecromancerTome] CorpseLoot: труп " + _corpse.entityId + " обыскан, заменён мешком");
|
||||
if (_corpse.world != null)
|
||||
{
|
||||
_corpse.world.RemoveEntity(_corpse.entityId, EnumRemoveEntityReason.Despawned);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Добавляет команду «обыскать» в список. Постфикс, а не префикс: ванильные команды
|
||||
/// сущности (у EntityAlive это «подобрать» для тех, у кого задан PickupItem) должны остаться.</summary>
|
||||
[HarmonyPatch(typeof(EntityAlive), "InitLocalActivationCommands")]
|
||||
public static class Patch_EntityAlive_InitLocalActivationCommands_Corpse
|
||||
{
|
||||
public static void Postfix(EntityAlive __instance, Action<EntityActivationCommand> _addCallback)
|
||||
{
|
||||
if (!CorpseLoot.IsSearchable(__instance))
|
||||
{
|
||||
return;
|
||||
}
|
||||
_addCallback(new EntityActivationCommand(CorpseLoot.SearchCommand, CorpseLoot.SearchIcon));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Разрешает команду только помеченному трупу. Без этого «обыскать» досталось бы
|
||||
/// любому существу, у которого список команд пересобрался бы по любой причине.</summary>
|
||||
[HarmonyPatch(typeof(EntityAlive), "AllowActivationCommand")]
|
||||
public static class Patch_EntityAlive_AllowActivationCommand_Corpse
|
||||
{
|
||||
public static void Postfix(EntityAlive __instance, ReadOnlySpan<char> _commandName, ref bool __result)
|
||||
{
|
||||
if (!MemoryExtensions.Equals(_commandName, CorpseLoot.SearchCommand.AsSpan(), StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
__result = CorpseLoot.IsSearchable(__instance);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>ПОДСКАЗКА ПРИ НАВЕДЕНИИ. Баг-репорт 2026-09-18: «при наведении пиктограмма руки так
|
||||
/// и не появилась. А без неё интуитивно непонятно что тут можно лутать».
|
||||
///
|
||||
/// Оказалось, я настроил не то место. Иконка у EntityActivationCommand рисуется в РАДИАЛЬНОМ
|
||||
/// МЕНЮ (XUiC_Radial.SetCurrentEntityData -> CreateRadialEntry со спрайтом
|
||||
/// $"ui_game_symbol_{icon}"), то есть только когда игрок ЗАЖМЁТ кнопку взаимодействия. А
|
||||
/// подпись, которая появляется просто при наведении, берётся совсем из другого места - из
|
||||
/// виртуального Entity.GetActivationText(), и у EntityAlive он для обычного существа
|
||||
/// возвращает пустую строку.
|
||||
///
|
||||
/// Здесь возвращается ровно то же, что у ванильного лутаемого объекта (EntityItem):
|
||||
///
|
||||
/// string arg = playerInput.Activate.GetBindingXuiMarkupString()
|
||||
/// + playerInput.PermanentActions.Activate.GetBindingXuiMarkupString();
|
||||
/// return string.Format(Localization.Get("lootTooltipNew"), arg, имя);
|
||||
///
|
||||
/// `lootTooltipNew` - штатная строка лутаемых объектов, «{0} Search {1} (Untouched)» /
|
||||
/// «{0}: обыскать {1} (нетронуто)», уже переведённая на все языки игры. Значок кнопки
|
||||
/// подставляется разметкой [action:...] из первого аргумента, поэтому у игрока он будет ровно
|
||||
/// такой же, как на любом мешке, и подпишется его собственной раскладкой.
|
||||
///
|
||||
/// Имя берётся у самого зомби (LocalizedEntityName), так что подсказка читается как
|
||||
/// «обыскать Арлин», а не «обыскать мешок».</summary>
|
||||
[HarmonyPatch(typeof(EntityAlive), "GetActivationText")]
|
||||
public static class Patch_EntityAlive_GetActivationText_Corpse
|
||||
{
|
||||
public static void Postfix(EntityAlive __instance, ref string __result)
|
||||
{
|
||||
if (!CorpseLoot.IsSearchable(__instance))
|
||||
{
|
||||
return;
|
||||
}
|
||||
EntityPlayerLocal player = GameManager.Instance?.World?.GetPrimaryPlayer();
|
||||
if (player == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
PlayerActionsLocal input = player.playerInput;
|
||||
string key = input.Activate.GetBindingXuiMarkupString() +
|
||||
input.PermanentActions.Activate.GetBindingXuiMarkupString();
|
||||
__result = string.Format(Localization.Get("lootTooltipNew"), key,
|
||||
Localization.Get(__instance.LocalizedEntityName));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Запускает обыск - НЕ мгновенно, а с трёхсекундным круговым индикатором (указание
|
||||
/// 2026-09-18). Индикатор рисует сама игра: XUiC_Timer.OpenTimer, тот же механизм, что крутится
|
||||
/// при снятии верстака, и знакомый игроку без объяснений.
|
||||
///
|
||||
/// ПОЧЕМУ НЕ ЧЕРЕЗ activateTime У КОМАНДЫ. У EntityActivationCommand есть поле activateTime, и
|
||||
/// Entity.ActivateEntityCommand действительно открывает по нему таймер - но по завершении он
|
||||
/// дёргает ИГРОВОЕ СОБЫТИЕ из entity_class (`onActivateEvent`), а не наш код, и запускается
|
||||
/// только если у команды заполнено eventName. Нам нужен свой обработчик в конце, поэтому таймер
|
||||
/// открывается вручную, а не описанием команды.
|
||||
///
|
||||
/// CloseOnHit=true: если по игроку ударили посреди обыска, полоса закрывается и труп остаётся
|
||||
/// нетронутым. Обыскивать труп посреди боя не должно быть бесплатно.</summary>
|
||||
[HarmonyPatch(typeof(EntityAlive), "OnEntityActivated")]
|
||||
public static class Patch_EntityAlive_OnEntityActivated_Corpse
|
||||
{
|
||||
public static void Postfix(EntityAlive __instance, EntityActivationCommand _command,
|
||||
EntityPlayerLocal _playerFocusing)
|
||||
{
|
||||
if (_command.commandId != CorpseLoot.SearchCommand || !CorpseLoot.IsSearchable(__instance))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (_playerFocusing == null || _playerFocusing.PlayerUI == null)
|
||||
{
|
||||
CorpseLoot.SearchCorpse(__instance);
|
||||
return;
|
||||
}
|
||||
|
||||
EntityAlive corpse = __instance;
|
||||
TimerEventData timer = new TimerEventData
|
||||
{
|
||||
Data = corpse,
|
||||
CloseOnHit = true,
|
||||
};
|
||||
timer.FullTimeFinishEvent += delegate
|
||||
{
|
||||
// Повторная проверка на финише: за три секунды труп мог истлеть, а игрок - уйти.
|
||||
if (CorpseLoot.IsSearchable(corpse))
|
||||
{
|
||||
CorpseLoot.SearchCorpse(corpse);
|
||||
}
|
||||
};
|
||||
XUiC_Timer.OpenTimer(_playerFocusing.PlayerUI.xui, CorpseLoot.SearchSeconds, timer);
|
||||
Debug.Log("[NecromancerTome] CorpseLoot: начат обыск трупа " + corpse.entityId +
|
||||
", " + CorpseLoot.SearchSeconds + " с");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user