Одержимость: прицел-перекрестие с покраснением, кнопки E/F, лут мешков и багажник волка, медведь на дыбы
- Прицел всех одержимых - всегда перекрестие (свой guiDrawCrosshair: точка бралась из проверки камеры от 3-го лица у тела); краснеет на враге: у стервятника - цель рывка, у наземных - враг на луче прицела до 30 м, не за стеной - Падаль: отхил вдвое меньше (EatHealFrac 0.125); ест на F стервятник, медведь, собака, волк и одержимые зомби - E - действие: стервятник - клич (был F), крыса/волк - обыск, медведь - на дыбы (враги до 3.5 м сбиты с ног и отброшены, откат 6 с) - Рёв медведя: надписи «на медведя идут N» и откат, мимо очереди подсказок - Крыса и волк обыскивают мешки с зомби (EntityLootContainer), волк берёт в сумку - Багажник волка: окно сумки как у браслета, без рюкзака; выброс - у волка - Отзыв волка: мешок на месте и метка «Сумка волка» на карте, снимается с мешком - Тексты на 13 языках Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
31aa99860c
commit
e94e967b8e
+187
-8
@@ -11,7 +11,12 @@ namespace NecromancerTome
|
||||
/// но щелчок по предмету (и «Взять всё») кладёт его в сумку волка; места нет - «Сумка полна»;
|
||||
/// - выгрузка - принести к телу: рядом с телом E перекладывает сумку в рюкзак персонажа, что не
|
||||
/// влезло - мешком на землю рядом;
|
||||
/// - волк погиб или отозван - сумка выпадает мешком на месте.
|
||||
/// - волк погиб или отозван - сумка выпадает мешком на месте; при отзыве (указание 09.10) на карте метка
|
||||
/// «Сумка волка», чтобы за ним прийти; мешок подобран или истлел - метка снята (Patch_removeBackpack);
|
||||
/// - «багажник» (указание 09.10: «почти как у пространственного хранилища»): E не на контейнер и не у тела -
|
||||
/// окно сумки, то же ванильное окно, что у браслета (XUiC_BagStorageWindowGroup, шаблон roboticDrone), но
|
||||
/// без рюкзака персонажа: внутри перекладывать, вытащить за окно - выбросить у волка (Patch_GetDropPosition);
|
||||
/// - мешки с зомби волк лутает так же, как ящики (RatLoot, TakeFrom(EntityLootContainer)).
|
||||
///
|
||||
/// ХРАНЕНИЕ. Сумку сущности игра не сохраняет (Entity.bag нет в Entity.Write), поэтому она своя.
|
||||
/// Упаковка - та же, что у пространственного хранилища (SpatialVaultPersistence.BuildPayload/ParsePayload,
|
||||
@@ -25,6 +30,11 @@ namespace NecromancerTome
|
||||
public const int Slots = 3;
|
||||
const string FileName = "NecromancerTome_wolfbag.bin";
|
||||
public const float UnloadRange = 3f;
|
||||
/// <summary>Метка выпавшей сумки: имя - ключ локализации, по нему и находим свою метку среди меток игрока.</summary>
|
||||
public const string WaypointName = "necroWolfBagWaypoint";
|
||||
public const string WaypointIcon = "ui_game_symbol_backpack";
|
||||
/// <summary>Открыто окно сумки («багажник»).</summary>
|
||||
public static bool WindowOpen;
|
||||
static Bag _bag;
|
||||
static string _dir;
|
||||
|
||||
@@ -112,11 +122,41 @@ namespace NecromancerTome
|
||||
/// в пустую ячейку; в контейнере остаётся ровно то, что не влезло.</summary>
|
||||
public static bool TakeFrom(TEFeatureStorage _te, int _idx)
|
||||
{
|
||||
ItemStack src = _te.ItemGrid.GetItem(_idx);
|
||||
if (src == null || src.IsEmpty())
|
||||
ItemStack rest = Take(_te.ItemGrid.GetItem(_idx));
|
||||
if (rest == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_te.UpdateSlot(_idx, rest);
|
||||
_te.SetModified();
|
||||
_te.Parent?.NotifyListeners(); // окно контейнера перерисует ячейку (как ванильная сортировка)
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>То же из мешка, упавшего с зомби. Окно мешка перерисуется по событию сумки (SetSlot).</summary>
|
||||
public static bool TakeFrom(EntityLootContainer _src, int _idx)
|
||||
{
|
||||
if (_idx >= _src.bag.SlotCount)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ItemStack rest = Take(_src.bag.ItemGrid.items[_idx]);
|
||||
if (rest == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_src.bag.SetSlot(_idx, rest);
|
||||
_src.OnBagModified(); // на клиенте - отправить сумку мешка серверу, как делает окно
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Переложить стопку в сумку; что осталось в источнике (пустая стопка, если всё), или null - не влезло.</summary>
|
||||
static ItemStack Take(ItemStack src)
|
||||
{
|
||||
if (src == null || src.IsEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Bag b = Get();
|
||||
ItemStack moving = src.Clone();
|
||||
int before = moving.count;
|
||||
@@ -130,17 +170,15 @@ namespace NecromancerTome
|
||||
{
|
||||
Audio.Manager.PlayInsidePlayerHead("ui_denied");
|
||||
Show("necroWolfBagFull");
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
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;
|
||||
Debug.Log("[NecromancerTome] WolfBag: в сумку " + src.itemValue.ItemClass.GetItemName() + " x" + moved + " - " + Describe());
|
||||
return rest.count > 0 ? rest : ItemStack.Empty.Clone();
|
||||
}
|
||||
|
||||
/// <summary>«Взять всё» - по ячейкам, пока есть место.</summary>
|
||||
@@ -161,6 +199,54 @@ namespace NecromancerTome
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>«Взять всё» из мешка - по ячейкам, пока есть место.</summary>
|
||||
public static void TakeAll(EntityLootContainer _src)
|
||||
{
|
||||
bool any = false;
|
||||
ItemStack[] slots = _src.bag.ItemGrid.items;
|
||||
for (int i = 0; i < slots.Length; i++)
|
||||
{
|
||||
if (!slots[i].IsEmpty() && Get().CanTakeItem(slots[i]))
|
||||
{
|
||||
any |= TakeFrom(_src, i);
|
||||
}
|
||||
}
|
||||
if (!any)
|
||||
{
|
||||
Audio.Manager.PlayInsidePlayerHead("ui_denied");
|
||||
Show("necroWolfBagFull");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>«Багажник»: окно сумки, как у пространственного браслета (шаблон дрона), без рюкзака персонажа.
|
||||
/// Перекладка внутри пишется сразу (Save на каждое изменение и на закрытие).</summary>
|
||||
public static void OpenWindow(EntityPlayerLocal _player, EntityAlive _wolf)
|
||||
{
|
||||
LocalPlayerUI ui = LocalPlayerUI.GetUIForPlayer(_player);
|
||||
if (ui == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
WindowOpen = true;
|
||||
XUiC_BagStorageWindowGroup.Open(ui.xui, _wolf, Get(), LootContainer.GetLootContainer("roboticDrone"),
|
||||
Localization.Get("necroWolfBagWindowTitle"), Save, () =>
|
||||
{
|
||||
WindowOpen = false;
|
||||
Save();
|
||||
});
|
||||
Debug.Log("[NecromancerTome] WolfBag: окно сумки - " + Describe());
|
||||
}
|
||||
|
||||
/// <summary>Выход из одержимости при открытом окне сумки - закрыть его.</summary>
|
||||
public static void CloseWindow()
|
||||
{
|
||||
if (WindowOpen)
|
||||
{
|
||||
LocalPlayerUI.GetUIForPrimaryPlayer()?.windowManager.CloseAllOpenModalWindows();
|
||||
WindowOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>E рядом с телом: сумка - в рюкзак, что не влезло - мешком у тела.</summary>
|
||||
public static bool TryUnload(EntityPlayerLocal _body, EntityAlive _wolf)
|
||||
{
|
||||
@@ -228,9 +314,61 @@ namespace NecromancerTome
|
||||
b.Clear();
|
||||
Save();
|
||||
Drop(_wolf.position, items);
|
||||
if (_why == "отозван")
|
||||
{
|
||||
AddWaypoint(_wolf.position);
|
||||
}
|
||||
Debug.Log("[NecromancerTome] WolfBag: волк " + _why + " - сумка выпала мешком (" + items.Count + ")");
|
||||
}
|
||||
|
||||
/// <summary>Метка на карте и компасе - как игра ставит метку транспорта (XUiC_MapArea.RefreshVehiclePositionWaypoint),
|
||||
/// но обычная, не «последнее место сущности»: номер мешка после перезахода другой. Метка - в списке меток игрока,
|
||||
/// сохраняется игрой вместе с ним; удалить можно и вручную.</summary>
|
||||
static void AddWaypoint(Vector3 _at)
|
||||
{
|
||||
EntityPlayerLocal p = GameManager.Instance?.World?.GetPrimaryPlayer();
|
||||
if (p?.Waypoints == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Waypoint w = new Waypoint();
|
||||
w.pos = World.worldToBlockPos(_at);
|
||||
w.icon = WaypointIcon;
|
||||
w.ownerId = Platform.PlatformManager.InternalLocalUserIdentifier;
|
||||
w.name.Update(WaypointName, Platform.PlatformManager.MultiPlatform.User.PlatformUserId);
|
||||
w.bUsingLocalizationId = true;
|
||||
p.Waypoints.Collection.Add(w);
|
||||
w.navObject = NavObjectManager.Instance.RegisterNavObject("waypoint", w.pos.ToVector3(), w.icon);
|
||||
w.navObject.IsActive = false;
|
||||
w.navObject.name = w.name.Text;
|
||||
w.navObject.usingLocalizationId = true;
|
||||
Debug.Log("[NecromancerTome] WolfBag: метка сумки на карте " + w.pos);
|
||||
}
|
||||
|
||||
/// <summary>Мешок исчез (опустел или истлел) - снять метку сумки, стоящую рядом с ним.</summary>
|
||||
public static void RemoveWaypointAt(Vector3 _at)
|
||||
{
|
||||
EntityPlayerLocal p = GameManager.Instance?.World?.GetPrimaryPlayer();
|
||||
if (p?.Waypoints == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
List<Waypoint> list = p.Waypoints.Collection.list;
|
||||
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)
|
||||
{
|
||||
p.Waypoints.Collection.Remove(w);
|
||||
if (w.navObject != null)
|
||||
{
|
||||
NavObjectManager.Instance.UnRegisterNavObject(w.navObject);
|
||||
}
|
||||
Debug.Log("[NecromancerTome] WolfBag: мешок сумки подобран - метка снята " + w.pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void Drop(Vector3 _at, List<ItemStack> _items)
|
||||
{
|
||||
GameManager.Instance.DropContentInLootContainerServer(-1, "DroppedLootContainer", _at + Vector3.up * 0.5f, _items.ToArray());
|
||||
@@ -244,6 +382,47 @@ namespace NecromancerTome
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Мешок исчез (пустой после обыска - «empty», или истлел - «old») - снять метку сумки волка рядом.</summary>
|
||||
[HarmonyPatch(typeof(EntityContainerAbs), nameof(EntityContainerAbs.removeBackpack))]
|
||||
public static class Patch_EntityContainerAbs_removeBackpack_WolfBag
|
||||
{
|
||||
public static void Postfix(EntityContainerAbs __instance)
|
||||
{
|
||||
if (__instance is EntityLootContainer)
|
||||
{
|
||||
WolfBag.RemoveWaypointAt(__instance.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>«Багажник» без рюкзака персонажа: окно мешка открывает его само (OnOpen), закрываем сразу же.</summary>
|
||||
[HarmonyPatch(typeof(XUiC_BagStorageWindowGroup), nameof(XUiC_BagStorageWindowGroup.OnOpen))]
|
||||
public static class Patch_XUiC_BagStorageWindowGroup_OnOpen_WolfBag
|
||||
{
|
||||
public static void Postfix(XUiC_BagStorageWindowGroup __instance)
|
||||
{
|
||||
if (WolfBag.WindowOpen && Possession.Active)
|
||||
{
|
||||
__instance.xui.playerUI.windowManager.Close("backpack");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Предмет, вытащенный за окно сумки волка, падает у волка, а не у тела (XUiM_PlayerInventory.DropItem
|
||||
/// берёт место у игрока - GetDropPosition).</summary>
|
||||
[HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.GetDropPosition))]
|
||||
public static class Patch_EntityPlayerLocal_GetDropPosition_WolfBag
|
||||
{
|
||||
public static void Postfix(EntityPlayerLocal __instance, ref Vector3 __result)
|
||||
{
|
||||
EntityAlive wolf = Possession.Zombie;
|
||||
if (WolfBag.WindowOpen && Possession.Active && __instance == Possession.Player && wolf != null)
|
||||
{
|
||||
__result = wolf.position + wolf.GetForwardVector() * 0.8f + Vector3.up * 0.5f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(EntityAlive), nameof(EntityAlive.OnEntityDeath))]
|
||||
public static class Patch_EntityAlive_OnEntityDeath_WolfBag
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user