diff --git a/HarmonySrc/Possession.cs b/HarmonySrc/Possession.cs
index 84c7fc2..832b11e 100644
--- a/HarmonySrc/Possession.cs
+++ b/HarmonySrc/Possession.cs
@@ -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);
}
diff --git a/HarmonySrc/RatLoot.cs b/HarmonySrc/RatLoot.cs
index 12c6b33..2f22183 100644
--- a/HarmonySrc/RatLoot.cs
+++ b/HarmonySrc/RatLoot.cs
@@ -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);
}
}
diff --git a/HarmonySrc/WolfBag.cs b/HarmonySrc/WolfBag.cs
index 9c009c8..06eecd8 100644
--- a/HarmonySrc/WolfBag.cs
+++ b/HarmonySrc/WolfBag.cs
@@ -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";
/// Открыто окно сумки («багажник»).
public static bool WindowOpen;
+ /// Счётчик изменений сумки - окно рюкзака перерисовывает её по нему.
+ public static int Version;
+ /// Предмет на земле - не дальше стольких метров от волка и градусов от луча камеры.
+ 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();
}
+ /// E на предмет, лежащий на земле, - в сумку (как игрок подбирает: что не влезло, остаётся лежать).
+ /// True - предмет перед волком был (взят или «сумка полна»).
+ 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;
+ }
+
/// «Взять всё» - по ячейкам, пока есть место.
public static void TakeAll(TEFeatureStorage _te)
{
@@ -423,6 +479,124 @@ namespace NecromancerTome
}
}
+ /// Окно рюкзака при обыске волком показывает сумку волка (указание 10.10). Рюкзак персонажа при этом
+ /// не читается и не пишется: UpdateBackend пропущен - в сейв игрока сумка не попадёт.
+ public static class WolfBagBackpack
+ {
+ public static bool On => RatLoot.Viewing && RatLoot.Taking && Possession.Active;
+ static int _seen = -1;
+ static readonly List _hidden = new List();
+
+ 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]);
+ }
+ }
+ }
+
+ /// Обыск закончен: ячейки рюкзака снова видны и отперты, рюкзак - снова свой.
+ 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
{