diff --git a/HarmonySrc/CinerariumLogist.cs b/HarmonySrc/CinerariumLogist.cs
index 495e12a..ffdf499 100644
--- a/HarmonySrc/CinerariumLogist.cs
+++ b/HarmonySrc/CinerariumLogist.cs
@@ -84,6 +84,28 @@ namespace NecromancerTome
}
__instance.SetCommonData(UIUtils.GetButtonIconForAction(__instance.xui.playerUI.playerInput.Activate), CinerariumLogistForm.OnBlockMenu,
new XUiC_Radial.RadialContextHoldingSlotIndex(pl.inventory.holdingItemIdx), -1, true, XUiC_Radial.RadialValidSameHoldingSlotIndex);
+ Debug.Log("[NecromancerTome] LogistForm: меню блока - " + held.Name + ", пунктов " + __instance.currentEnabledEntriesCount());
+ }
+ }
+
+ /// Диагностика #77 (23.10.2026: «зажимаю R - меню не открывается, только поворот»): на нажатие R с урной-логистом в
+ /// руке - что видит игра (блок ли в руке, не считает ли предмет оружием). Убрать, когда причина найдена.
+ [HarmonyPatch(typeof(PlayerMoveController), nameof(PlayerMoveController.Update))]
+ public static class Patch_PlayerMoveController_Update_LogistFormDiag
+ {
+ public static void Postfix(PlayerMoveController __instance)
+ {
+ EntityPlayerLocal pl = __instance.entityPlayerLocal;
+ PlayerActionsLocal inp = __instance.playerInput;
+ if (pl?.inventory == null || inp == null || !CinerariumLogistForm.IsLogistItem(pl.inventory.holdingItem) ||
+ !(inp.Reload.WasPressed || inp.PermanentActions.Reload.WasPressed))
+ {
+ return;
+ }
+ XUiC_Radial r = __instance.playerUI?.xui?.RadialWindow;
+ Debug.Log("[NecromancerTome] LogistForm: R - " + pl.inventory.holdingItem.Name + ", блок " + (pl.inventory.GetHoldingBlock() != null) +
+ ", оружие " + pl.inventory.IsHoldingGun() + "/" + pl.inventory.IsHoldingDynamicMelee() + ", смена руки " + pl.inventory.IsHandSwitching() +
+ ", меню " + (r != null && r.IsOpen) + ", модальное " + (__instance.playerUI?.windowManager?.IsModalWindowOpen() ?? false));
}
}
diff --git a/HarmonySrc/PetFarRecall.cs b/HarmonySrc/PetFarRecall.cs
new file mode 100644
index 0000000..0513214
--- /dev/null
+++ b/HarmonySrc/PetFarRecall.cs
@@ -0,0 +1,78 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace NecromancerTome
+{
+ ///
+ /// Дальний питомец после одержимости (указание 23.10.2026, #35: «Если питомец далеко от хозяина и хозяин вышел из режима
+ /// одержимости, то питомец должен сразу быть отозван. Скажем, если больше 100 блоков, то отзовём, если меньше, то
+ /// возвращается своим ходом»). Отзыв - тот же, что книгой (RecallPet: подсказка «… отозван», книга назад, если призыв её
+ /// тратит). Далеко ушедший питомец иначе остаётся в выгруженном участке, и PetFollowPatch снимает с него владение.
+ ///
+ public static class PetFarRecall
+ {
+ /// Порог в блоках; 0 - выключено (питомец всегда идёт своим ходом, как раньше).
+ public static float Distance = 100f;
+
+ /// true - питомец отозван (дальше ничего с ним не делать).
+ public static bool TryRecall(EntityPlayer _owner, EntityAlive _pet)
+ {
+ if (Distance <= 0f || _owner == null || _pet == null || _pet.IsDead())
+ {
+ return false;
+ }
+ float d = Vector3.Distance(_owner.position, _pet.position);
+ string cls = EntityClass.list.TryGetValue(_pet.entityClass, out EntityClass ec) ? ec.entityClassName : null;
+ if (d <= Distance || cls == null ||
+ !Patch_ItemActionSpawnEntity_Spawn_PetLimit.LimitedPets.TryGetValue(cls, out Patch_ItemActionSpawnEntity_Spawn_PetLimit.PetInfo info) ||
+ info.RecalledKey == null)
+ {
+ return false;
+ }
+ Debug.Log("[NecromancerTome] PetFarRecall: " + cls + " " + _pet.entityId + " в " + Mathf.RoundToInt(d) + " блоках от хозяина - отозван");
+ Patch_ItemActionSpawnEntity_Spawn_PetLimit.RecallPet(_owner, _pet.entityId, info.RecalledKey, info.ConsumesBook ? info.SummonItemName : null);
+ return true;
+ }
+ }
+
+ public class ConsoleCmdNecroPetFar : ConsoleCmdAbstract
+ {
+ public override bool IsExecuteOnClient => true;
+
+ public override bool AllowedInMainMenu => false;
+
+ public override string[] getCommands()
+ {
+ return new string[] { "necropetfar" };
+ }
+
+ public override string getDescription()
+ {
+ return "Necromancer's Tome: recall a far pet when leaving possession - distance.";
+ }
+
+ public override string getHelp()
+ {
+ return "necropetfar - status\n" +
+ "necropetfar - recall the pet if it is farther than this from you\n" +
+ "necropetfar off - never recall, the pet always walks back";
+ }
+
+ public override void Execute(List _params, CommandSenderInfo _senderInfo)
+ {
+ SdtdConsole con = SingletonMonoBehaviour.Instance;
+ if (_params.Count >= 1)
+ {
+ if (_params[0] == "off")
+ {
+ PetFarRecall.Distance = 0f;
+ }
+ else if (float.TryParse(_params[0], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float v) && v >= 0f)
+ {
+ PetFarRecall.Distance = v;
+ }
+ }
+ con.Output("necropetfar: " + (PetFarRecall.Distance > 0f ? "recall farther than " + PetFarRecall.Distance + " blocks" : "off - the pet always walks back"));
+ }
+ }
+}
diff --git a/HarmonySrc/Possession.cs b/HarmonySrc/Possession.cs
index 865f14b..c17bb53 100644
--- a/HarmonySrc/Possession.cs
+++ b/HarmonySrc/Possession.cs
@@ -449,10 +449,12 @@ namespace NecromancerTome
}
}
// Питомец просто отпущен: его ИИ (следование, охрана, поводок) сам вернёт его к хозяину.
+ // Далеко ушедший - сразу отозван (23.10, #35: PetFarRecall).
if (pet)
{
z.SetMoveForwardWithModifiers(0f, 0f, 0f, false);
z.SetAttackTarget(null, 0);
+ PetFarRecall.TryRecall(player, z);
return;
}
// Сам зомби в ужасе бежит от тела игрока (решение 03.10: «пугается и убегает как при отрицании»).
diff --git a/NecromancerHarmony.dll b/NecromancerHarmony.dll
index 28e596a..26ce755 100644
Binary files a/NecromancerHarmony.dll and b/NecromancerHarmony.dll differ
diff --git a/NecromancerHarmony.pdb b/NecromancerHarmony.pdb
index f7507a6..584c1de 100644
Binary files a/NecromancerHarmony.pdb and b/NecromancerHarmony.pdb differ