using System.Collections.Generic; using System.IO; using HarmonyLib; using UnityEngine; namespace NecromancerTome { /// /// Сумка одержимого лютоволка на 3 ячейки (решено 09.10, BACKLOG «Медведь и лютоволк в одержимости»): /// - волк лутает: окно контейнера то же, что у крысы (RatLoot, рюкзак персонажа не показывается), /// но щелчок по предмету (и «Взять всё») кладёт его в сумку волка; места нет - «Сумка полна»; /// - выгрузка - принести к телу: рядом с телом E перекладывает сумку в рюкзак персонажа, что не /// влезло - мешком на землю рядом; /// - волк погиб или отозван - сумка выпадает мешком на месте. /// /// ХРАНЕНИЕ. Сумку сущности игра не сохраняет (Entity.bag нет в Entity.Write), поэтому она своя. /// Упаковка - та же, что у пространственного хранилища (SpatialVaultPersistence.BuildPayload/ParsePayload, /// проверена на сейвах 3.2 и 3.3), но в ОТДЕЛЬНОМ файле папки сейва, как у Чертогов разума /// (NecromancerTome_palaces.txt): файл игрока не меняется (сейвы - главный риск мода). Пишется сразу /// при каждом изменении - переживает и сохранение, и вылет. Волк у игрока один (один спутник за раз), /// поэтому сумка одна, без привязки к номеру сущности. /// public static class WolfBag { public const int Slots = 3; const string FileName = "NecromancerTome_wolfbag.bin"; public const float UnloadRange = 3f; static Bag _bag; static string _dir; static string FilePath => Path.Combine(GameIO.GetSaveGameDir(), FileName); /// Сумка текущего сейва (из файла при первом обращении в этом сейве). public static Bag Get() { string dir = GameIO.GetSaveGameDir(); if (_bag != null && _dir == dir) { return _bag; } _dir = dir; _bag = null; try { if (File.Exists(FilePath)) { _bag = SpatialVaultPersistence.ParsePayload(File.ReadAllBytes(FilePath)); } } catch (System.Exception e) { Debug.LogWarning("[NecromancerTome] WolfBag: файл сумки не прочитан (" + e.Message + ") - оставлен как есть, сумка пустая"); _bag = new Bag(new Vector2i(Slots, 1), XUiC_ItemStack.StackLocationTypes.LootContainer, null); return _bag; } if (_bag == null) { _bag = new Bag(new Vector2i(Slots, 1), XUiC_ItemStack.StackLocationTypes.LootContainer, null); } else if (_bag.SlotCount < Slots) { _bag.Resize(new Vector2i(Slots, 1)); } Debug.Log("[NecromancerTome] WolfBag: сумка загружена - " + Describe()); return _bag; } static void Save() { try { if (_bag == null || _bag.IsEmpty()) { if (File.Exists(FilePath)) { File.Delete(FilePath); } return; } File.WriteAllBytes(FilePath, SpatialVaultPersistence.BuildPayload(_bag)); } catch (System.Exception e) { Debug.LogWarning("[NecromancerTome] WolfBag: сумка не записана - " + e.Message); } } public static string Describe() { Bag b = Get(); List parts = new List(); foreach (ItemStack s in b.ItemGrid.items) { if (s != null && !s.IsEmpty()) { parts.Add(s.itemValue.ItemClass.GetLocalizedItemName() + (s.count > 1 ? " x" + s.count : "")); } } return parts.Count == 0 ? Localization.Get("necroWolfBagEmpty") : string.Join(", ", parts); } static void Show(string _key) { EntityPlayerLocal p = GameManager.Instance?.World?.GetPrimaryPlayer(); if (p != null) { GameManager.ShowTooltip(p, string.Format(Localization.Get(_key), Describe(), Get().GetUsedSlotCount(), Slots)); } } /// Переложить ячейку контейнера в сумку. Без раздвоения: сперва в неполные стопки, остаток - /// в пустую ячейку; в контейнере остаётся ровно то, что не влезло. public static bool TakeFrom(TEFeatureStorage _te, int _idx) { ItemStack src = _te.ItemGrid.GetItem(_idx); if (src == null || src.IsEmpty()) { return false; } Bag b = Get(); ItemStack moving = src.Clone(); int before = moving.count; b.TryStackItem(0, moving); if (moving.count > 0 && b.AddItem(moving.Clone())) { moving.count = 0; } int moved = before - moving.count; if (moved <= 0) { Audio.Manager.PlayInsidePlayerHead("ui_denied"); Show("necroWolfBagFull"); return false; } ItemStack rest = src.Clone(); rest.count = moving.count; _te.UpdateSlot(_idx, rest.count > 0 ? rest : ItemStack.Empty.Clone()); _te.SetModified(); _te.Parent?.NotifyListeners(); // окно контейнера перерисует ячейку (как ванильная сортировка) Save(); Audio.Manager.PlayInsidePlayerHead("UseActions/takeall1"); Show("necroWolfBagTook"); return true; } /// «Взять всё» - по ячейкам, пока есть место. public static void TakeAll(TEFeatureStorage _te) { bool any = false; for (int i = 0; i < _te.ItemGrid.items.Length; i++) { if (!_te.ItemGrid.items[i].IsEmpty() && Get().CanTakeItem(_te.ItemGrid.items[i])) { any |= TakeFrom(_te, i); } } if (!any) { Audio.Manager.PlayInsidePlayerHead("ui_denied"); Show("necroWolfBagFull"); } } /// E рядом с телом: сумка - в рюкзак, что не влезло - мешком у тела. public static bool TryUnload(EntityPlayerLocal _body, EntityAlive _wolf) { if (_body == null || (_body.position - _wolf.position).sqrMagnitude > UnloadRange * UnloadRange) { return false; } Bag b = Get(); if (b.IsEmpty()) { Show("necroWolfBagShow"); return true; } List left = new List(); ItemStack[] slots = b.ItemGrid.items; for (int i = 0; i < slots.Length; i++) { ItemStack s = slots[i]?.Clone(); if (s == null || s.IsEmpty()) { continue; } _body.bag.TryStackItem(0, s); if (s.count > 0 && _body.bag.AddItem(s.Clone())) { s.count = 0; } if (s.count > 0) { left.Add(s); } } b.Clear(); Save(); if (left.Count > 0) { Drop(_body.position + _body.GetForwardVector() * 0.8f, left); } Audio.Manager.PlayInsidePlayerHead("UseActions/takeall1"); GameManager.ShowTooltip(_body, Localization.Get(left.Count > 0 ? "necroWolfBagUnloadedPart" : "necroWolfBagUnloaded")); Debug.Log("[NecromancerTome] WolfBag: выгружена у тела" + (left.Count > 0 ? ", не влезло " + left.Count + " - мешком" : "")); return true; } /// Волк погиб или отозван - сумка мешком на месте. public static void DropFrom(EntityAlive _wolf, string _why) { if (_wolf == null || !WolfControl.Is(_wolf)) { return; } Bag b = Get(); if (b.IsEmpty()) { return; } List items = new List(); foreach (ItemStack s in b.ItemGrid.items) { if (s != null && !s.IsEmpty()) { items.Add(s.Clone()); } } b.Clear(); Save(); Drop(_wolf.position, items); Debug.Log("[NecromancerTome] WolfBag: волк " + _why + " - сумка выпала мешком (" + items.Count + ")"); } static void Drop(Vector3 _at, List _items) { GameManager.Instance.DropContentInLootContainerServer(-1, "DroppedLootContainer", _at + Vector3.up * 0.5f, _items.ToArray()); } /// Выход из мира - забыть (следующий сейв прочитает свой файл). public static void Forget() { _bag = null; _dir = null; } } [HarmonyPatch(typeof(EntityAlive), nameof(EntityAlive.OnEntityDeath))] public static class Patch_EntityAlive_OnEntityDeath_WolfBag { public static void Prefix(EntityAlive __instance) { if (!__instance.world.IsRemote() && WolfControl.Is(__instance)) { WolfBag.DropFrom(__instance, "погиб"); } } } }