- Пояс - нижний ряд сумки зомби; оружие ближнего боя в руку, если этот тип зомби им владеет (ZombieWeapons: таблица умений, утверждена 25.10). Удар как у персонажа: ItemActionAttack.Hit, урон предмета без перков зомби, износ, темп оружия. Модель на кость RightWeapon, хват WeaponHoldType; иконки и износ на поясе HUD. Неумелое или сломанное - кулаки и подсказка (13 языков). Выключатель necroarms weapon on|off; dev-команда necropossess для проверок без мыши. - M в зомби и волке открывал сумку вместо карты (перехват windowpaging) - исправлено; туман карты и карта на одержимом - у любого одержимого зомби, как у стервятника. - Глава «Одержимость» (EN/RU): пояс - оружие, M - карта. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
199 lines
8.3 KiB
C#
199 lines
8.3 KiB
C#
using System.Collections.Generic;
|
||
using System.IO;
|
||
using UnityEngine;
|
||
|
||
namespace NecromancerTome
|
||
{
|
||
/// <summary>
|
||
/// Сумка одержимого зомби (указание 13.10: «всем одержимым зомби с видом от первого лица сумку и возможность вызывать
|
||
/// её по Tab»). Решения пользователя 13.10:
|
||
/// - своя, одна на всех зомби, число ячеек - по виду зомби (SlotsFor), пока у всех 2;
|
||
/// - при выходе из одержимости зомби сбрасывает рюкзаком всё, что было в сумке (и на поясе - когда в поясе
|
||
/// появятся предметы);
|
||
/// - гибель одержимого - мешок на месте и метка на карте, как у сумки волка.
|
||
/// ПОЯС (#37, 25.10) - нижний ряд той же сумки: Possession.BeltSlots ячеек под верхним рядом, оружие в руке берётся
|
||
/// оттуда (ZombieWeapons). Хранение, выброс мешком и метка - общие с сумкой.
|
||
/// Лут (указание 13.10: «раз у них E свободно, подключай лутание») - как у волка: E на ящик или мешок - обыск с
|
||
/// переносом в сумку, E на предмет на земле - подобрать (WolfBag.Target - сумка зомби, пока она есть).
|
||
/// Только зомби из списка вида от первого лица (FpArms.Allows), не питомцы. Окно - то же, что у сумки волка
|
||
/// (XUiC_BagStorageWindowGroup, без рюкзака персонажа), Tab в зомби открывает его вместо рюкзака.
|
||
///
|
||
/// ХРАНЕНИЕ. Сумка живёт одно вселение, но пишется в отдельный файл сейва при каждом изменении (как сумка волка,
|
||
/// сейв игрока не трогается): вылет посреди вселения вещи не теряет - при следующем вселении то, что осталось в
|
||
/// файле, выпадает мешком у тела.
|
||
/// </summary>
|
||
public static class ZombieBag
|
||
{
|
||
const string FileName = "NecromancerTome_zombiebag.bin";
|
||
public const int DefaultSlots = 2;
|
||
/// <summary>Метка выпавшей сумки - ключ локализации (снимается вместе с меткой сумки волка, когда мешок исчез).</summary>
|
||
public const string WaypointName = "necroZombieBagWaypoint";
|
||
public static bool WindowOpen;
|
||
static Bag _bag;
|
||
/// <summary>Ячеек в ряду: верхний ряд - сумка, нижний - пояс.</summary>
|
||
static int _cols;
|
||
|
||
static string FilePath => Path.Combine(GameIO.GetSaveGameDir(), FileName);
|
||
|
||
public static bool Active => _bag != null;
|
||
public static Bag Current => _bag;
|
||
|
||
/// <summary>Ячейка пояса (0..BeltSlots-1) - нижний ряд сумки; нет сумки - null.</summary>
|
||
public static ItemStack Belt(int _i)
|
||
{
|
||
ItemStack[] items = _bag?.ItemGrid?.items;
|
||
int idx = _cols + _i;
|
||
return items != null && _i >= 0 && _i < Possession.BeltSlots && idx < items.Length && !items[idx].IsEmpty() ? items[idx] : null;
|
||
}
|
||
|
||
/// <summary>necropossess belt: положить предмет в ячейку пояса (проверка без мыши).</summary>
|
||
public static bool DevSetBelt(int _i, ItemStack _s)
|
||
{
|
||
if (_bag == null || _i < 0 || _i >= Possession.BeltSlots)
|
||
{
|
||
return false;
|
||
}
|
||
_bag.SetSlot(_cols + _i, _s);
|
||
Save();
|
||
return true;
|
||
}
|
||
|
||
/// <summary>Предмет пояса изменился на месте (износ) - окно и файл.</summary>
|
||
public static void BeltChanged()
|
||
{
|
||
_bag?.onBackpackChanged();
|
||
Save();
|
||
}
|
||
|
||
/// <summary>Записать после лута (WolfBag.SaveTarget).</summary>
|
||
public static void SaveNow() => Save();
|
||
|
||
/// <summary>У кого сумка: зомби с видом от первого лица, не питомцы.</summary>
|
||
public static bool Has(EntityAlive _z, bool _pet) => !_pet && FpArms.Allows(_z);
|
||
|
||
/// <summary>Ячеек у этого вида зомби (указание: «каким-то добавим, каким-то наоборот») - пока у всех DefaultSlots.</summary>
|
||
public static int SlotsFor(EntityAlive _z) => DefaultSlots;
|
||
|
||
/// <summary>Вселение началось: то, что осталось в файле после вылета, - мешком у тела; новая пустая сумка.</summary>
|
||
public static void Begin(EntityPlayerLocal _body, EntityAlive _z)
|
||
{
|
||
try
|
||
{
|
||
if (File.Exists(FilePath))
|
||
{
|
||
Bag old = SpatialVaultPersistence.ParsePayload(File.ReadAllBytes(FilePath));
|
||
List<ItemStack> left = Items(old);
|
||
if (left.Count > 0 && _body != null)
|
||
{
|
||
WolfBag.DropSack(_body.position + _body.GetForwardVector() * 0.8f, left);
|
||
Debug.Log("[NecromancerTome] ZombieBag: в файле осталась сумка прошлого вселения - мешком у тела (" + left.Count + ")");
|
||
}
|
||
File.Delete(FilePath);
|
||
}
|
||
}
|
||
catch (System.Exception e)
|
||
{
|
||
Debug.LogWarning("[NecromancerTome] ZombieBag: файл сумки не прочитан (" + e.Message + ") - оставлен как есть");
|
||
}
|
||
_cols = Mathf.Max(SlotsFor(_z), Possession.BeltSlots);
|
||
_bag = new Bag(new Vector2i(_cols, 2), XUiC_ItemStack.StackLocationTypes.LootContainer, null);
|
||
Debug.Log("[NecromancerTome] ZombieBag: сумка " + _z.EntityName + " - ячеек " + _bag.SlotCount + ", из них пояс " + Possession.BeltSlots);
|
||
}
|
||
|
||
/// <summary>Вселение кончилось: всё из сумки - мешком на месте зомби; погиб - ещё и метка на карте.</summary>
|
||
public static void End(EntityAlive _z)
|
||
{
|
||
if (_bag == null)
|
||
{
|
||
return;
|
||
}
|
||
CloseWindow();
|
||
List<ItemStack> items = Items(_bag);
|
||
_bag = null;
|
||
Save();
|
||
if (items.Count == 0 || _z == null)
|
||
{
|
||
return;
|
||
}
|
||
bool died = _z.IsDead();
|
||
WolfBag.DropSack(_z.position, items);
|
||
if (died)
|
||
{
|
||
WolfBag.AddWaypoint(_z.position, WaypointName);
|
||
}
|
||
Debug.Log("[NecromancerTome] ZombieBag: " + (died ? "одержимый погиб" : "выход из одержимости") + " - сумка мешком (" + items.Count + ")");
|
||
}
|
||
|
||
static List<ItemStack> Items(Bag _b)
|
||
{
|
||
List<ItemStack> items = new List<ItemStack>();
|
||
if (_b?.ItemGrid?.items == null)
|
||
{
|
||
return items;
|
||
}
|
||
foreach (ItemStack s in _b.ItemGrid.items)
|
||
{
|
||
if (s != null && !s.IsEmpty())
|
||
{
|
||
items.Add(s.Clone());
|
||
}
|
||
}
|
||
return items;
|
||
}
|
||
|
||
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] ZombieBag: сумка не записана - " + e.Message);
|
||
}
|
||
}
|
||
|
||
/// <summary>Tab в зомби - окно сумки, как «багажник» волка (WolfBag.OpenWindow).</summary>
|
||
public static void OpenWindow(EntityPlayerLocal _player, EntityAlive _z)
|
||
{
|
||
LocalPlayerUI ui = LocalPlayerUI.GetUIForPlayer(_player);
|
||
if (ui == null || _bag == null)
|
||
{
|
||
return;
|
||
}
|
||
WindowOpen = true;
|
||
XUiC_BagStorageWindowGroup.Open(ui.xui, _z, _bag, LootContainer.GetLootContainer("roboticDrone"),
|
||
Localization.Get("necroZombieBagWindowTitle"), Save, () =>
|
||
{
|
||
WindowOpen = false;
|
||
Save();
|
||
});
|
||
Debug.Log("[NecromancerTome] ZombieBag: окно сумки");
|
||
}
|
||
|
||
public static void CloseWindow()
|
||
{
|
||
if (WindowOpen)
|
||
{
|
||
LocalPlayerUI.GetUIForPrimaryPlayer()?.windowManager.CloseAllOpenModalWindows();
|
||
WindowOpen = false;
|
||
}
|
||
}
|
||
|
||
/// <summary>Выход из мира - забыть (файл остаётся: при следующем вселении выпадет мешком у тела).</summary>
|
||
public static void Forget()
|
||
{
|
||
_bag = null;
|
||
WindowOpen = false;
|
||
}
|
||
}
|
||
}
|