using HarmonyLib;
using UnityEngine;
namespace NecromancerTome
{
///
/// Обыск одержимой крысой (указание 08.10). Крыса - разведчик: «она может осмотреть ящики, и если там
/// что-то интересное, персонаж придёт и сам залутает». Поэтому - только смотреть.
///
/// Окно ванильное, ровно как у персонажа (таймер первого обыска, сетка, звуки): контейнер открывается
/// той же цепочкой LockManager.LockRequestLocal -> OnLockResponseLocal -> XUiC_LootWindowGroup.OpenLooting.
/// Отличия на время такого просмотра (Viewing):
/// - рюкзак персонажа не показывается («сумку персонажа вообще не показывать») - openContainer
/// открывает его сам, закрываем сразу же;
/// - ячейки контейнера заперты (XUiC_ItemStack.IsLocked), «Взять всё» (R) и сортировка не работают;
/// попытка взять - отказ, как при переполненном рюкзаке (указание: «делал бы вид, что сумки
/// переполнены»): звук ui_denied и xuiInventoryFullForPickup;
/// - окно не закрывается из-за дальности до ТЕЛА: XUiC_LootWindow.Update меряет её от персонажа.
/// Подсказку «E: обыскать» рисует управление игрока, а оно в одержимости выключено - выводим сами.
///
public static class RatLoot
{
/// Открыто (или открывается) окно просмотра крысой.
public static bool Viewing;
/// Окно открыл волк: щелчок и «Взять всё» кладут в его сумку (WolfBag), а не отказ.
public static bool Taking;
/// Контейнер, который осматривает крыса, и до какого времени после закрытия его не разрушать.
static Vector3i _sparePos;
static float _spareUntil;
/// Досягаемость контейнера от крысы, м.
const float Reach = 2.5f;
/// Контейнер перед крысой по лучу камеры: блок с хранилищем ближе Reach к крысе; иначе null.
public static TEFeatureStorage Find(World _world, EntityPlayerLocal _player, EntityAlive _rat, out Vector3i _pos, out BlockValue _bv)
{
_pos = Vector3i.zero;
_bv = BlockValue.Air;
Camera cam = _player.playerCamera;
if (cam == null)
{
return null;
}
Vector3 eye = _rat.getHeadPosition();
Vector3 from = cam.transform.position + Origin.position;
float dist = Vector3.Distance(from, eye) + Reach + 1f;
Ray ray = new Ray(from, cam.transform.forward);
if (!Voxel.Raycast(_world, ray, dist, -555266077, 69, 0f))
{
return null;
}
WorldRayHitInfo hit = Voxel.voxelRayHitInfo;
if ((hit.hit.pos - eye).sqrMagnitude > Reach * Reach)
{
return null;
}
Vector3i pos = hit.hit.blockPos;
BlockValue bv = hit.hit.blockValue;
Block block = bv.Block;
if (block == null || bv.isair)
{
return null;
}
if (bv.ischild && block.multiBlockPos != null)
{
pos = block.multiBlockPos.GetParentPos(pos, bv);
bv = _world.GetBlock(pos);
}
TileEntity te = _world.GetTileEntity(pos);
TEFeatureStorage st = te != null ? te.GetSelfOrFeature() : null;
if (st == null)
{
return null;
}
_pos = pos;
_bv = bv;
return st;
}
/// Каждый кадр одержимости крысой: подсказка и E - открыть.
public static void Tick(World _world, EntityPlayerLocal _player, EntityAlive _rat, bool _activate, bool _take = false)
{
LocalPlayerUI ui = _player.PlayerUI;
if (ui == null || Viewing || ui.windowManager.IsModalWindowOpen())
{
return;
}
TEFeatureStorage st = Find(_world, _player, _rat, out Vector3i pos, out BlockValue bv);
if (st == null)
{
return;
}
PlayerActionsLocal input = _player.playerInput;
string key = input.Activate.GetBindingXuiMarkupString() + input.PermanentActions.Activate.GetBindingXuiMarkupString();
string text = st.GetActivationText(_world, pos, bv, _rat, key, bv.Block.GetLocalizedBlockName());
if (!string.IsNullOrEmpty(text))
{
XUiC_InteractionPrompt.SetText(ui, text);
}
if (!_activate)
{
return;
}
bool locked = st.lockpickFeature != null && st.lockpickFeature.NeedsLockpicking() ||
st.lockFeature != null && st.lockFeature.IsLocked() && !st.lockFeature.IsUserAllowed(Platform.PlatformManager.InternalLocalUserIdentifier) ||
st.isJammed;
if (locked)
{
Audio.Manager.BroadcastPlayByLocalPlayer(pos.ToVector3() + Vector3.one * 0.5f, "Misc/locked");
return;
}
Viewing = true;
Taking = _take;
_sparePos = pos;
_spareUntil = float.MaxValue;
XUiC_InteractionPrompt.SetText(ui, null);
// Через отражение: у LockRequestLocal есть перегрузка с ReadOnlySpan, которой нет в справочных сборках проекта.
_lockRequest = _lockRequest ?? AccessTools.Method(typeof(LockManager), nameof(LockManager.LockRequestLocal), new[] { typeof(ILockTarget), typeof(ushort) });
_lockRequest.Invoke(LockManager.Instance, new object[] { st, (ushort)0 });
Debug.Log("[NecromancerTome] RatLoot: крыса осматривает " + bv.Block.GetBlockName() + " " + pos);
}
static System.Reflection.MethodInfo _lockRequest;
/// Отказ «рюкзак полон».
public static void Deny()
{
LocalPlayerUI ui = LocalPlayerUI.GetUIForPrimaryPlayer();
if (ui?.entityPlayer != null)
{
GameManager.ShowTooltip(ui.entityPlayer, Localization.Get("xuiInventoryFullForPickup"), string.Empty, "ui_denied");
}
}
/// Выход из одержимости при открытом окне - закрыть его.
public static void CloseIfViewing()
{
if (!Viewing)
{
return;
}
LocalPlayerUI ui = LocalPlayerUI.GetUIForPrimaryPlayer();
ui?.windowManager.CloseAllOpenModalWindows();
Viewing = false;
_spareUntil = Time.time + 5f;
}
static void LockSlots(XUiC_LootWindow _w, bool _lock)
{
XUiC_LootContainer grid = _w?.lootContainer;
if (grid?.itemControllers == null)
{
return;
}
foreach (XUiC_ItemStack s in grid.itemControllers)
{
if (s != null)
{
s.IsLocked = _lock;
}
}
}
[HarmonyPatch(typeof(XUiC_LootWindowGroup), nameof(XUiC_LootWindowGroup.openContainer))]
public static class Patch_openContainer
{
public static void Postfix(XUiC_LootWindowGroup __instance)
{
if (!Viewing)
{
return;
}
__instance.xui.playerUI.windowManager.Close("backpack");
LockSlots(__instance.lootWindow, true);
}
}
/// Контейнеры с destroy_on_close (loot.xml: «empty» - 94 вида, мусор, кучи, холодильники, рюкзаки...;
/// «true» - аэродроп и коробки Twitch, это сущности, крыса их не открывает) разрушаются при снятии замка
/// после закрытия окна. Осмотр крысой их не разрушает (указание 08.10).
[HarmonyPatch(typeof(GameManager), nameof(GameManager.CheckDestroyTileEntity))]
public static class Patch_CheckDestroyTileEntity
{
public static bool Prefix(Vector3i _blockPos)
{
if (_blockPos != _sparePos || Time.time > _spareUntil)
{
return true;
}
_spareUntil = 0f;
Debug.Log("[NecromancerTome] RatLoot: " + _blockPos + " после осмотра крысой не разрушен");
return false;
}
}
/// Таймер первого обыска прерван - просмотра нет.
[HarmonyPatch(typeof(XUiC_LootWindowGroup), nameof(XUiC_LootWindowGroup.openTimerClosedManually))]
public static class Patch_openTimerClosedManually
{
public static void Postfix()
{
if (Viewing)
{
Viewing = false;
_spareUntil = Time.time + 5f;
}
}
}
/// Отказ замка (контейнер занят другим) - просмотра нет.
[HarmonyPatch(typeof(TEFeatureStorage), nameof(TEFeatureStorage.OnLockResponseLocal))]
public static class Patch_OnLockResponseLocal
{
public static void Postfix(bool _success)
{
if (!_success && Viewing)
{
Viewing = false;
_spareUntil = 0f;
}
}
}
[HarmonyPatch(typeof(XUiC_LootWindowGroup), nameof(XUiC_LootWindowGroup.OnClose))]
public static class Patch_OnClose
{
public static void Postfix(XUiC_LootWindowGroup __instance)
{
LockSlots(__instance.lootWindow, false); // всегда: запертые ячейки не должны достаться персонажу
if (Viewing)
{
Viewing = false;
_spareUntil = Time.time + 5f; // снятие замка (и проверка разрушения) - следом, может прийти пакетом
}
}
}
/// Дальность окна - от крысы, не от тела: XUiC_LootWindow.Update меряет её от entityPlayer.position,
/// поэтому на время его Update персонаж «стоит» на месте крысы (поле, ничего больше не трогается), а
/// Finalizer возвращает позицию при любом исходе. (Тест 08.10: прежний вариант обнулял te - и закрытие окна
/// по E/ESC падало в XUiC_LootWindow.OnClose, окно застревало и после выхода из одержимости.)
[HarmonyPatch(typeof(XUiC_LootWindow), nameof(XUiC_LootWindow.Update))]
public static class Patch_LootWindow_Update
{
public static void Prefix(XUiC_LootWindow __instance, out Vector3? __state)
{
__state = null;
EntityPlayerLocal p = __instance.xui.playerUI.entityPlayer;
EntityAlive rat = Possession.Zombie;
if (!Viewing || p == null || rat == null)
{
return;
}
__state = p.position;
p.position = rat.position;
}
public static void Finalizer(XUiC_LootWindow __instance, Vector3? __state)
{
if (__state.HasValue)
{
__instance.xui.playerUI.entityPlayer.position = __state.Value;
}
}
}
[HarmonyPatch(typeof(XUiC_ContainerStandardControls), nameof(XUiC_ContainerStandardControls.MoveAll))]
public static class Patch_MoveAll
{
public static bool Prefix(XUiC_ContainerStandardControls __instance)
{
if (!Viewing)
{
return true;
}
TEFeatureStorage te = __instance.GetParentByType()?.te;
if (Taking && te != null)
{
WolfBag.TakeAll(te);
}
else
{
Deny();
}
return false;
}
}
[HarmonyPatch(typeof(XUiC_ContainerStandardControls), nameof(XUiC_ContainerStandardControls.Sort))]
public static class Patch_Sort
{
public static bool Prefix()
{
return !Viewing;
}
}
/// Щелчок по запертой ячейке контейнера - отказ «рюкзак полон».
[HarmonyPatch(typeof(XUiC_ItemStack), nameof(XUiC_ItemStack.Update))]
public static class Patch_ItemStack_Update
{
public static void Postfix(XUiC_ItemStack __instance)
{
if (!Viewing || !__instance.isOver || __instance.ItemStack == null || __instance.ItemStack.IsEmpty() ||
__instance.GetParentByType() == null)
{
return;
}
CursorControllerAbs cur = __instance.xui.playerUI.CursorController;
if (cur.GetMouseButtonDown(UICamera.MouseButton.LeftButton) || cur.GetMouseButtonDown(UICamera.MouseButton.RightButton))
{
XUiC_LootContainer grid = __instance.GetParentByType();
TEFeatureStorage te = __instance.GetParentByType()?.te;
int idx = grid?.itemControllers != null ? System.Array.IndexOf(grid.itemControllers, __instance) : -1;
if (Taking && te != null && idx >= 0)
{
WolfBag.TakeFrom(te, idx);
}
else
{
Deny();
}
}
}
}
}
}