Волк: сумка видна в окне рюкзака при обыске, E подбирает предметы с земли в сумку

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlexCube
2026-10-10 23:25:48 +03:00
co-authored by Claude Opus 5.5
parent eaf06e1cd9
commit 654b888d15
3 changed files with 185 additions and 4 deletions
+2 -1
View File
@@ -667,7 +667,8 @@ namespace NecromancerTome
// E: на контейнер или мешок с зомби - лутать в сумку (3 ячейки); у тела - выгрузить сумку в рюкзак;
// иначе - «багажник», окно сумки (указание 09.10).
bool looted = RatLoot.Tick(world, _player, z, detonate, true);
if (detonate && !looted && !RatLoot.Viewing && !WolfBag.TryUnload(_player, z))
// E на предмет на земле - в сумку (тест 10.10: «не могу подбирать выброшенные предметы»).
if (detonate && !looted && !RatLoot.Viewing && !WolfBag.TryPickup(world, _player, z) && !WolfBag.TryUnload(_player, z))
{
WolfBag.OpenWindow(_player, z);
}
+8 -2
View File
@@ -233,7 +233,10 @@ namespace NecromancerTome
{
return;
}
__instance.xui.playerUI.windowManager.Close("backpack");
if (!Taking)
{
__instance.xui.playerUI.windowManager.Close("backpack"); // у волка рюкзак показывает его сумку (WolfBagBackpack)
}
LockSlots(__instance.lootWindow, true);
}
}
@@ -337,7 +340,10 @@ namespace NecromancerTome
{
return;
}
__instance.xui.playerUI.windowManager.Close("backpack");
if (!Taking)
{
__instance.xui.playerUI.windowManager.Close("backpack");
}
LockSlots(__instance.containerWindow, true);
}
}
+175 -1
View File
@@ -16,7 +16,11 @@ namespace NecromancerTome
/// - «багажник» (указание 09.10: «почти как у пространственного хранилища»): E не на контейнер и не у тела -
/// окно сумки, то же ванильное окно, что у браслета (XUiC_BagStorageWindowGroup, шаблон roboticDrone), но
/// без рюкзака персонажа: внутри перекладывать, вытащить за окно - выбросить у волка (Patch_GetDropPosition);
/// - мешки с зомби волк лутает так же, как ящики (RatLoot, TakeFrom(EntityLootContainer)).
/// - мешки с зомби волк лутает так же, как ящики (RatLoot, TakeFrom(EntityLootContainer));
/// - тест 10.10: «волк - это ещё одно пространственное хранилище: берёт игрок, но положить может только в эти
/// 3 ячейки» - при обыске рядом с ящиком окно рюкзака показывает сумку волка (WolfBagBackpack: первые 3
/// ячейки, остальные скрыты, ячейки заперты - перекладка щелчком по ящику), а E на предмет на земле
/// кладёт его в сумку (TryPickup).
///
/// ХРАНЕНИЕ. Сумку сущности игра не сохраняет (Entity.bag нет в Entity.Write), поэтому она своя.
/// Упаковка - та же, что у пространственного хранилища (SpatialVaultPersistence.BuildPayload/ParsePayload,
@@ -35,6 +39,11 @@ namespace NecromancerTome
public const string WaypointIcon = "ui_game_symbol_backpack";
/// <summary>Открыто окно сумки («багажник»).</summary>
public static bool WindowOpen;
/// <summary>Счётчик изменений сумки - окно рюкзака перерисовывает её по нему.</summary>
public static int Version;
/// <summary>Предмет на земле - не дальше стольких метров от волка и градусов от луча камеры.</summary>
const float PickupReach = 2.5f;
const float PickupAimDeg = 25f;
static Bag _bag;
static string _dir;
@@ -77,6 +86,7 @@ namespace NecromancerTome
static void Save()
{
Version++;
try
{
if (_bag == null || _bag.IsEmpty())
@@ -181,6 +191,52 @@ namespace NecromancerTome
return rest.count > 0 ? rest : ItemStack.Empty.Clone();
}
/// <summary>E на предмет, лежащий на земле, - в сумку (как игрок подбирает: что не влезло, остаётся лежать).
/// True - предмет перед волком был (взят или «сумка полна»).</summary>
public static bool TryPickup(World _world, EntityPlayerLocal _player, EntityAlive _wolf)
{
Transform cam = _player.cameraTransform;
if (cam == null || _world.IsRemote())
{
return false;
}
Vector3 from = cam.position + Origin.position;
EntityItem best = null;
float bestDeg = PickupAimDeg;
for (int i = 0; i < _world.Entities.list.Count; i++)
{
if (!(_world.Entities.list[i] is EntityItem it) || it.IsDead() || it.IsMarkedForUnload() || it.itemStack == null ||
it.itemStack.IsEmpty() || !it.CanCollect() || (it.position - _wolf.position).sqrMagnitude > PickupReach * PickupReach)
{
continue;
}
float deg = Vector3.Angle(cam.forward, it.position - from);
if (deg < bestDeg)
{
bestDeg = deg;
best = it;
}
}
if (best == null)
{
return false;
}
ItemStack rest = Take(best.itemStack);
if (rest == null)
{
return true;
}
if (rest.IsEmpty())
{
_world.RemoveEntity(best.entityId, EnumRemoveEntityReason.Killed);
}
else
{
best.itemStack.count = rest.count;
}
return true;
}
/// <summary>«Взять всё» - по ячейкам, пока есть место.</summary>
public static void TakeAll(TEFeatureStorage _te)
{
@@ -423,6 +479,124 @@ namespace NecromancerTome
}
}
/// <summary>Окно рюкзака при обыске волком показывает сумку волка (указание 10.10). Рюкзак персонажа при этом
/// не читается и не пишется: UpdateBackend пропущен - в сейв игрока сумка не попадёт.</summary>
public static class WolfBagBackpack
{
public static bool On => RatLoot.Viewing && RatLoot.Taking && Possession.Active;
static int _seen = -1;
static readonly List<XUiC_ItemStack> _hidden = new List<XUiC_ItemStack>();
public static ItemStack[] Slots(int _n)
{
ItemStack[] a = new ItemStack[_n];
ItemStack[] b = WolfBag.Get().ItemGrid.items;
for (int i = 0; i < _n; i++)
{
a[i] = i < b.Length && b[i] != null ? b[i].Clone() : ItemStack.Empty.Clone();
}
return a;
}
public static void Tick(XUiC_Backpack _bp)
{
if (_seen != WolfBag.Version || _hidden.Count == 0)
{
_seen = WolfBag.Version;
_bp.RefreshBackpackSlots();
}
XUiC_ItemStack[] c = _bp.itemControllers;
for (int i = 0; c != null && i < c.Length; i++)
{
c[i].IsLocked = true;
if (i >= WolfBag.Slots && c[i].ViewComponent.IsVisible)
{
c[i].ViewComponent.IsVisible = false;
_hidden.Add(c[i]);
}
}
}
/// <summary>Обыск закончен: ячейки рюкзака снова видны и отперты, рюкзак - снова свой.</summary>
public static void Unlock(XUiC_Backpack _bp)
{
foreach (XUiC_ItemStack s in _hidden)
{
if (s != null)
{
s.ViewComponent.IsVisible = true;
}
}
bool was = _hidden.Count > 0 || _seen != -1;
_hidden.Clear();
_seen = -1;
if (_bp?.itemControllers == null || !was)
{
return;
}
foreach (XUiC_ItemStack s in _bp.itemControllers)
{
if (s != null)
{
s.IsLocked = false;
}
}
_bp.RefreshBackpackSlots();
}
}
[HarmonyPatch(typeof(XUiC_Backpack), nameof(XUiC_Backpack.GetSlots))]
public static class Patch_Backpack_GetSlots_WolfBag
{
public static bool Prefix(XUiC_Backpack __instance, ref ItemStack[] __result)
{
if (!WolfBagBackpack.On)
{
return true;
}
__result = WolfBagBackpack.Slots(__instance.itemControllers?.Length ?? WolfBag.Slots);
return false;
}
}
[HarmonyPatch(typeof(XUiC_Backpack), nameof(XUiC_Backpack.UpdateBackend))]
public static class Patch_Backpack_UpdateBackend_WolfBag
{
public static bool Prefix()
{
return !WolfBagBackpack.On;
}
}
[HarmonyPatch(typeof(XUiC_Backpack), nameof(XUiC_Backpack.Update))]
public static class Patch_Backpack_Update_WolfBag
{
static bool _was;
public static void Postfix(XUiC_Backpack __instance)
{
bool on = WolfBagBackpack.On;
if (on)
{
WolfBagBackpack.Tick(__instance);
}
else if (_was)
{
WolfBagBackpack.Unlock(__instance);
}
_was = on;
}
}
[HarmonyPatch(typeof(XUiC_Backpack), nameof(XUiC_Backpack.OnClose))]
public static class Patch_Backpack_OnClose_WolfBag
{
public static void Postfix(XUiC_Backpack __instance)
{
WolfBagBackpack.Unlock(__instance);
}
}
[HarmonyPatch(typeof(EntityAlive), nameof(EntityAlive.OnEntityDeath))]
public static class Patch_EntityAlive_OnEntityDeath_WolfBag
{