Сумка одержимого зомби: 2 ячейки, Tab; при выходе - мешком, при гибели - мешком с меткой

ZombieBag: своя сумка на одно вселение у зомби с видом от первого лица (число ячеек по виду, пока 2), окно как у сумки волка; пишется в отдельный файл сейва, остаток после вылета выпадает у тела при следующем вселении. Мешок и метка - общие с сумкой волка (WolfBag.DropSack/AddWaypoint). Подсказка Tab - 13 языков.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlexCube
2026-10-13 16:47:20 +03:00
co-authored by Claude Opus 5.5
parent c98e3f8b74
commit eb6c5fb4f1
6 changed files with 191 additions and 4 deletions
+3
View File
@@ -258,6 +258,9 @@ necroPossessHintWolfBag,items,Item,,,,"E at a container or loot bag - loot into
necroPossessSearchBag,items,Item,,,,{0} Search: {1},,{0} Durchsuchen: {1},{0} Registrar: {1},{0} Fouiller : {1},{0} Perquisisci: {1},{0} 調べる: {1},{0} 뒤지기: {1},{0} Przeszukaj: {1},{0} Vasculhar: {1},{0} Обыскать: {1},{0} Ara: {1},{0} 搜索:{1},{0} 搜索:{1}
necroWolfBagWindowTitle,items,Item,,,,Wolf bag,,Wolfstasche,Bolsa del lobo,Sac du loup,Borsa del lupo,狼の袋,늑대 가방,Torba wilka,Bolsa do lobo,Сумка волка,Kurt çantası,狼的包,狼的包
necroWolfBagWaypoint,items,Item,,,,Wolf bag,,Wolfstasche,Bolsa del lobo,Sac du loup,Borsa del lupo,狼の袋,늑대 가방,Torba wilka,Bolsa do lobo,Сумка волка,Kurt çantası,狼的包,狼的包
necroZombieBagWindowTitle,items,Item,,,,Zombie bag,,Zombietasche,Bolsa del zombi,Sac du zombie,Borsa dello zombi,ゾンビの袋,좀비 가방,Torba zombie,Bolsa do zumbi,Сумка зомби,Zombi çantası,僵尸的包,殭屍的包
necroZombieBagWaypoint,items,Item,,,,Zombie bag,,Zombietasche,Bolsa del zombi,Sac du zombie,Borsa dello zombi,ゾンビの袋,좀비 가방,Torba zombie,Bolsa do zumbi,Сумка зомби,Zombi çantası,僵尸的包,殭屍的包
necroPossessHintZombieBag,items,Item,,,,Tab - bag,,Tab - Tasche,Tab: bolsa,Tab : sac,Tab - borsa,Tab: 袋,Tab - 가방,Tab - torba,Tab - bolsa,Tab - сумка,Tab - çanta,Tab - 包,Tab - 包
necroPossessHintAfterFear,items,Item,,,,"after you leave, the zombie flees in chilling terror",,"nach dem Verlassen flieht der Zombie in eisigem Schrecken","al salir, el zombi huye presa de un terror helado","quand tu le quittes, le zombie fuit, glacé d'effroi","quando lo lasci, lo zombi fugge in preda al terrore","離れるとゾンビは凍てつく恐怖で逃げ出す","빠져나가면 좀비는 얼어붙는 공포에 질려 달아남","po wyjściu zombie ucieka w lodowatym przerażeniu","ao sair, o zumbi foge em terror gelado","после выхода зомби в леденящем ужасе убегает","çıktığında zombi dondurucu bir dehşetle kaçar","离开后,僵尸会在刺骨的恐惧中逃跑","離開後,殭屍會在刺骨的恐懼中逃跑"
necroWolfBagEmpty,items,Item,,,,"empty",,"leer","vacía","vide","vuota","空","비어 있음","pusta","vazia","пусто","boş","空","空"
necroWolfBagTook,items,Item,,,,"Wolf bag ({1}/{2}): {0}",,"Wolfstasche ({1}/{2}): {0}","Bolsa del lobo ({1}/{2}): {0}","Sac du loup ({1}/{2}) : {0}","Borsa del lupo ({1}/{2}): {0}","狼の袋({1}/{2}):{0}","늑대 가방 ({1}/{2}): {0}","Torba wilka ({1}/{2}): {0}","Bolsa do lobo ({1}/{2}): {0}","Сумка волка ({1}/{2}): {0}","Kurt çantası ({1}/{2}): {0}","狼的背包({1}/{2}):{0}","狼的背包({1}/{2}):{0}"
1 Key File Type UsedInMainMenu NoTranslate KeepLoaded english Context / Alternate Text german spanish french italian japanese koreana polish brazilian russian turkish schinese tchinese
258 necroPossessSearchBag items Item {0} Search: {1} {0} Durchsuchen: {1} {0} Registrar: {1} {0} Fouiller : {1} {0} Perquisisci: {1} {0} 調べる: {1} {0} 뒤지기: {1} {0} Przeszukaj: {1} {0} Vasculhar: {1} {0} Обыскать: {1} {0} Ara: {1} {0} 搜索:{1} {0} 搜索:{1}
259 necroWolfBagWindowTitle items Item Wolf bag Wolfstasche Bolsa del lobo Sac du loup Borsa del lupo 狼の袋 늑대 가방 Torba wilka Bolsa do lobo Сумка волка Kurt çantası 狼的包 狼的包
260 necroWolfBagWaypoint items Item Wolf bag Wolfstasche Bolsa del lobo Sac du loup Borsa del lupo 狼の袋 늑대 가방 Torba wilka Bolsa do lobo Сумка волка Kurt çantası 狼的包 狼的包
261 necroZombieBagWindowTitle items Item Zombie bag Zombietasche Bolsa del zombi Sac du zombie Borsa dello zombi ゾンビの袋 좀비 가방 Torba zombie Bolsa do zumbi Сумка зомби Zombi çantası 僵尸的包 殭屍的包
262 necroZombieBagWaypoint items Item Zombie bag Zombietasche Bolsa del zombi Sac du zombie Borsa dello zombi ゾンビの袋 좀비 가방 Torba zombie Bolsa do zumbi Сумка зомби Zombi çantası 僵尸的包 殭屍的包
263 necroPossessHintZombieBag items Item Tab - bag Tab - Tasche Tab: bolsa Tab : sac Tab - borsa Tab: 袋 Tab - 가방 Tab - torba Tab - bolsa Tab - сумка Tab - çanta Tab - 包 Tab - 包
264 necroPossessHintAfterFear items Item after you leave, the zombie flees in chilling terror nach dem Verlassen flieht der Zombie in eisigem Schrecken al salir, el zombi huye presa de un terror helado quand tu le quittes, le zombie fuit, glacé d'effroi quando lo lasci, lo zombi fugge in preda al terrore 離れるとゾンビは凍てつく恐怖で逃げ出す 빠져나가면 좀비는 얼어붙는 공포에 질려 달아남 po wyjściu zombie ucieka w lodowatym przerażeniu ao sair, o zumbi foge em terror gelado после выхода зомби в леденящем ужасе убегает çıktığında zombi dondurucu bir dehşetle kaçar 离开后,僵尸会在刺骨的恐惧中逃跑 離開後,殭屍會在刺骨的恐懼中逃跑
265 necroWolfBagEmpty items Item empty leer vacía vide vuota 空 비어 있음 pusta vazia пусто boş 空 空
266 necroWolfBagTook items Item Wolf bag ({1}/{2}): {0} Wolfstasche ({1}/{2}): {0} Bolsa del lobo ({1}/{2}): {0} Sac du loup ({1}/{2}) : {0} Borsa del lupo ({1}/{2}): {0} 狼の袋({1}/{2}):{0} 늑대 가방 ({1}/{2}): {0} Torba wilka ({1}/{2}): {0} Bolsa do lobo ({1}/{2}): {0} Сумка волка ({1}/{2}): {0} Kurt çantası ({1}/{2}): {0} 狼的背包({1}/{2}):{0} 狼的背包({1}/{2}):{0}
+21
View File
@@ -317,6 +317,10 @@ namespace NecromancerTome
_loggedCamera = false;
FirstPerson = false;
BeltSlot = 0;
if (ZombieBag.Has(_zombie, _pet))
{
ZombieBag.Begin(_player0, _zombie); // сумка зомби на это вселение (13.10)
}
// Пояс одержимому питомцу ни к чему (указание 06.10); открыться заново не даёт
// Patch_GUIWindowManager_openInternal_SnakeToolbelt, после выхода игра откроет его сама.
if (_pet)
@@ -372,6 +376,7 @@ namespace NecromancerTome
ShowPlayerBelt(true);
RatLoot.CloseIfViewing();
WolfBag.CloseWindow();
ZombieBag.End(Zombie); // сумка - мешком на месте зомби (13.10), погиб - с меткой
RatPhase.Stop(GameManager.Instance?.World, Zombie, "выход из одержимости");
DogSense.Clear();
WolfControl.Clear();
@@ -520,6 +525,7 @@ namespace NecromancerTome
DogSense.Clear();
WolfControl.Clear();
WolfBag.Forget();
ZombieBag.Forget();
_zoomCam = null;
_zoomBaseFov = -1f;
_zoom = 0f;
@@ -798,6 +804,17 @@ namespace NecromancerTome
// ПКМ (держать) - сквозь постройки, без полёта (BACKLOG, «Могильная крыса» п.4).
RatPhase.Tick(world, z, rangedHeld);
}
if (ZombieBag.Active)
{
// Tab в зомби - окно его сумки, а не рюкзак персонажа (указание 13.10), как у волка.
GUIWindowManager zwm = LocalPlayerUI.GetUIForPlayer(_player)?.windowManager;
if (zwm != null && zwm.IsWindowOpen("windowpaging") && !ZombieBag.WindowOpen && !RatLoot.Viewing)
{
zwm.CloseAllOpenModalWindows();
zwm.Close("windowpaging");
ZombieBag.OpenWindow(_player, z);
}
}
if (IsPet && WolfControl.Is(z))
{
// Tab в волке - окно его сумки, а не рюкзак персонажа (тест 10.10: «в волке Tab - вижу сумку игрока»).
@@ -1565,6 +1582,10 @@ namespace NecromancerTome
{
text += ", " + Localization.Get("necroPossessHintView");
}
if (ZombieBag.Has(_z, IsPet))
{
text += ", " + Localization.Get("necroPossessHintZombieBag");
}
if (Flying)
{
text += ", " + Localization.Get("necroPossessHintFly");
+7 -4
View File
@@ -497,7 +497,7 @@ namespace NecromancerTome
/// <summary>Метка на карте и компасе - как игра ставит метку транспорта (XUiC_MapArea.RefreshVehiclePositionWaypoint),
/// но обычная, не «последнее место сущности»: номер мешка после перезахода другой. Метка - в списке меток игрока,
/// сохраняется игрой вместе с ним; удалить можно и вручную.</summary>
static void AddWaypoint(Vector3 _at)
public static void AddWaypoint(Vector3 _at, string _name = WaypointName)
{
EntityPlayerLocal p = GameManager.Instance?.World?.GetPrimaryPlayer();
if (p?.Waypoints == null)
@@ -508,7 +508,7 @@ namespace NecromancerTome
w.pos = World.worldToBlockPos(_at);
w.icon = WaypointIcon;
w.ownerId = Platform.PlatformManager.InternalLocalUserIdentifier;
w.name.Update(WaypointName, Platform.PlatformManager.MultiPlatform.User.PlatformUserId);
w.name.Update(_name, Platform.PlatformManager.MultiPlatform.User.PlatformUserId);
w.bUsingLocalizationId = true;
p.Waypoints.Collection.Add(w);
w.navObject = NavObjectManager.Instance.RegisterNavObject("waypoint", w.pos.ToVector3(), w.icon);
@@ -530,7 +530,7 @@ namespace NecromancerTome
for (int i = list.Count - 1; i >= 0; i--)
{
Waypoint w = list[i];
if (w.name.Text == WaypointName && (w.pos.ToVector3() - _at).sqrMagnitude < 3f * 3f)
if ((w.name.Text == WaypointName || w.name.Text == ZombieBag.WaypointName) && (w.pos.ToVector3() - _at).sqrMagnitude < 3f * 3f)
{
p.Waypoints.Collection.Remove(w);
if (w.navObject != null)
@@ -542,6 +542,9 @@ namespace NecromancerTome
}
}
/// <summary>Мешок с предметами на земле (и для сумки одержимого зомби, ZombieBag).</summary>
public static void DropSack(Vector3 _at, List<ItemStack> _items) => Drop(_at, _items);
static void Drop(Vector3 _at, List<ItemStack> _items)
{
GameManager.Instance.DropContentInLootContainerServer(-1, "DroppedLootContainer", _at + Vector3.up * 0.5f, _items.ToArray());
@@ -574,7 +577,7 @@ namespace NecromancerTome
{
public static void Postfix(XUiC_BagStorageWindowGroup __instance)
{
bool wolf = WolfBag.WindowOpen && Possession.Active;
bool wolf = (WolfBag.WindowOpen || ZombieBag.WindowOpen) && Possession.Active;
if (wolf)
{
__instance.xui.playerUI.windowManager.Close("backpack");
+160
View File
@@ -0,0 +1,160 @@
using System.Collections.Generic;
using System.IO;
using UnityEngine;
namespace NecromancerTome
{
/// <summary>
/// Сумка одержимого зомби (указание 13.10: «всем одержимым зомби с видом от первого лица сумку и возможность вызывать
/// её по Tab»). Решения пользователя 13.10:
/// - своя, одна на всех зомби, число ячеек - по виду зомби (SlotsFor), пока у всех 2;
/// - при выходе из одержимости зомби сбрасывает рюкзаком всё, что было в сумке (и на поясе - когда в поясе
/// появятся предметы);
/// - гибель одержимого - мешок на месте и метка на карте, как у сумки волка.
/// Только зомби из списка вида от первого лица (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;
static string FilePath => Path.Combine(GameIO.GetSaveGameDir(), FileName);
public static bool Active => _bag != null;
/// <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 + ") - оставлен как есть");
}
_bag = new Bag(new Vector2i(SlotsFor(_z), 1), XUiC_ItemStack.StackLocationTypes.LootContainer, null);
Debug.Log("[NecromancerTome] ZombieBag: сумка " + _z.EntityName + " - ячеек " + _bag.SlotCount);
}
/// <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;
}
}
}
Binary file not shown.
Binary file not shown.