Ничейные питомцы чистятся при призыве и отзыве, не по таймеру

Уборка раз в 5 с снята. RemoveOrphans зовут призыв (после записи нового во владение) и отзыв; E с книгой без своего питомца убирает ничейных. Поиск ближайшего ничейного для отзыва убран.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlexCube
2026-10-13 14:15:29 +03:00
co-authored by Claude Opus 5.5
parent 74bd9eb2a1
commit 5e1d30d7d0
4 changed files with 16 additions and 59 deletions
-1
View File
@@ -460,7 +460,6 @@ namespace NecromancerTome
// ВЫШЕ проверки на пустой список питомцев намеренно: трупы помечает Нож некроманта,
// и к тому, есть ли у игрока сейчас призванный питомец, они отношения не имеют.
CorpseLoot.RetryPendingTints(world);
Patch_ItemActionSpawnEntity_Spawn_PetLimit.RemoveOrphans(world);
if (TrackedPets.Count == 0)
{
+16 -58
View File
@@ -467,18 +467,14 @@ namespace NecromancerTome
return;
}
List<OwnedEntityData> owned = holdingEntity.GetOwnedEntities(petClassId);
EntityAlive orphan;
if (owned.Count > 0)
{
RecallPet(holdingEntity, owned[0].Id, tooltips.RecalledKey, tooltips.ConsumesBook ? tooltips.SummonItemName : null);
}
else if ((orphan = NearestOrphan(holdingEntity, petClassId)) != null)
else if (holdingEntity.world != null && RemoveOrphans(holdingEntity.world) > 0)
{
// Питомец без хозяина (13.10: «летает могильный стервятник, которого я не могу отозвать» - 14620 из
// старого сейва, за игроком не записан): отзываем ближайшего ничейного того же вида.
Debug.Log("[NecromancerTome] SummonPatch: отзыв ничейного " + orphan.EntityName + " (" + orphan.entityId + "), " +
(orphan.position - holdingEntity.position).magnitude.ToString("F0") + " м");
RecallPet(holdingEntity, orphan.entityId, tooltips.RecalledKey, null);
// Своего нет, но ничейные были и убраны (13.10: стервятник 14620 из старого сейва) - для игрока это отзыв.
GameManager.ShowTooltip(GameManager.Instance.World.GetPrimaryPlayer(), tooltips.RecalledKey);
}
else if (holdingEntity.world != null)
{
@@ -486,58 +482,17 @@ namespace NecromancerTome
}
}
public const float OrphanRecallRange = 60f;
/// <summary>Ближайший живой питомец этого вида не дальше OrphanRecallRange, которого нет в списке владения
/// ни у одного игрока.</summary>
static EntityAlive NearestOrphan(EntityAlive _holder, int _classId)
/// <summary>Ничейных питомцев - убрать (указание 13.10: «при призыве нового питомца и при отзыве старого пусть
/// проверяются все остальные питомцы и вычищаются»; стервятник 14620 из старого сейва летал у игрока без
/// хозяина). Ничейный - питомец нашего вида, которого нет в списке владения ни у одного игрока. Зовут только
/// призыв (CreateEntity, после записи владения) и отзыв - не по таймеру. Только на сервере (в одиночной
/// игре он же). Возвращает, сколько убрано.</summary>
public static int RemoveOrphans(World _world)
{
World world = _holder.world;
if (world == null)
if (_world == null || !ConnectionManager.Instance.IsServer)
{
return null;
return 0;
}
EntityAlive best = null;
float bestSq = OrphanRecallRange * OrphanRecallRange;
for (int i = 0; i < world.EntityAlives.Count; i++)
{
EntityAlive e = world.EntityAlives[i];
if (e == null || e.entityClass != _classId || e.IsDead())
{
continue;
}
float sq = (e.position - _holder.position).sqrMagnitude;
if (sq > bestSq || OwnedByAnyPlayer(world, e.entityId, _classId))
{
continue;
}
best = e;
bestSq = sq;
}
return best;
}
/// <summary>Ничейных питомцев - убрать (указание 13.10: «лучше чистить таких непривязанных»; стервятник
/// 14620 из старого сейва летал у игрока без хозяина). Раз в OrphanSweepInterval, только на сервере
/// (в одиночной игре он же), и не раньше OrphanGrace после появления игрока: до того список владения
/// игрока может быть ещё не прочитан из сейва, и под удаление попали бы все свои.</summary>
public static void RemoveOrphans(World _world)
{
if (!ConnectionManager.Instance.IsServer || _world.GetPrimaryPlayer() == null && _world.Players.list.Count == 0)
{
_orphanSweepAt = 0f;
return;
}
if (_orphanSweepAt == 0f)
{
_orphanSweepAt = Time.time + OrphanGrace;
return;
}
if (Time.time < _orphanSweepAt)
{
return;
}
_orphanSweepAt = Time.time + OrphanSweepInterval;
if (_orphanClassIds == null)
{
_orphanClassIds = new HashSet<int>();
@@ -550,6 +505,7 @@ namespace NecromancerTome
}
}
}
int removed = 0;
for (int i = _world.EntityAlives.Count - 1; i >= 0; i--)
{
EntityAlive e = _world.EntityAlives[i];
@@ -561,11 +517,11 @@ namespace NecromancerTome
Debug.Log("[NecromancerTome] SummonPatch: ничейный питомец " + e.EntityName + " (" + e.entityId + ") убран");
// WolfBag.DropFrom здесь нельзя: сумка одна на игрока, ничейный волк вывалил бы сумку своего.
_world.RemoveEntity(e.entityId, EnumRemoveEntityReason.Killed);
removed++;
}
return removed;
}
public const float OrphanGrace = 20f, OrphanSweepInterval = 5f;
static float _orphanSweepAt;
static HashSet<int> _orphanClassIds;
static bool OwnedByAnyPlayer(World _world, int _entityId, int _classId)
@@ -601,6 +557,7 @@ namespace NecromancerTome
}
owner.RemoveOwnedEntity(petEntityId);
PetFollowPatch.Unregister(petEntityId);
RemoveOrphans(world);
if (summonItemName != null)
{
GiveBackSummonItem(owner, summonItemName);
@@ -853,6 +810,7 @@ namespace NecromancerTome
}
owner.AddOwnedEntity(__result);
Patch_ItemActionSpawnEntity_Spawn_PetLimit.RemoveOrphans(world); // призыв тоже чистит ничейных (13.10)
// Рой из плевка одержимого ранчера - не свиток: без !Possession.Active плевок списывал предмет, который
// держит тело игрока (тест 13.10).
if (petInfo.ConsumesBook && !Possession.Active && owner.inventory != null)
Binary file not shown.
Binary file not shown.