From 18b75761edb614aee0ff3935644e5a7d27615592 Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Wed, 10 Aug 2022 17:10:19 -0400 Subject: [PATCH 01/26] Add audit trail to Destroy, TryRemoveFromInventory, TryDequipObject --- .../Enum/Properties/PositionType.cs | 5 +- .../Enum/Properties/PropertyString.cs | 10 +++ Source/ACE.Server/WorldObjects/Container.cs | 27 ++++++++ .../WorldObjects/Creature_Equipment.cs | 22 ++++++ Source/ACE.Server/WorldObjects/WorldObject.cs | 68 ++++++++++++++++++- 5 files changed, 128 insertions(+), 4 deletions(-) diff --git a/Source/ACE.Entity/Enum/Properties/PositionType.cs b/Source/ACE.Entity/Enum/Properties/PositionType.cs index d08731fdd8..5f3bc25a57 100644 --- a/Source/ACE.Entity/Enum/Properties/PositionType.cs +++ b/Source/ACE.Entity/Enum/Properties/PositionType.cs @@ -183,6 +183,9 @@ public enum PositionType : ushort TeleportedCharacter = 27, [ServerOnly] - PCAPRecordedLocation = 8040 + PCAPRecordedLocation = 8040, + + [ServerOnly] + PreDestroyLocation = 9000 } } diff --git a/Source/ACE.Entity/Enum/Properties/PropertyString.cs b/Source/ACE.Entity/Enum/Properties/PropertyString.cs index 864b7fcc40..9f88ebf867 100644 --- a/Source/ACE.Entity/Enum/Properties/PropertyString.cs +++ b/Source/ACE.Entity/Enum/Properties/PropertyString.cs @@ -96,6 +96,16 @@ public enum PropertyString : ushort GodState = 9006, [ServerOnly] TinkerLog = 9007, + [ServerOnly] + DestroyStackLog = 9008, + [ServerOnly] + PreviousOwners = 9009, + [ServerOnly] + PreviousWielders = 9010, + [ServerOnly] + PreviousOwnerStackLog = 9011, + [ServerOnly] + PreviousWielderStackLog = 9012, } public static class PropertyStringExtensions diff --git a/Source/ACE.Server/WorldObjects/Container.cs b/Source/ACE.Server/WorldObjects/Container.cs index 3eafb1b238..f7f4f5ff5a 100644 --- a/Source/ACE.Server/WorldObjects/Container.cs +++ b/Source/ACE.Server/WorldObjects/Container.cs @@ -648,6 +648,33 @@ public bool TryRemoveFromInventory(ObjectGuid objectGuid, out WorldObject item, { int removedItemsPlacementPosition = item.PlacementPosition ?? 0; + var currentPrevOwners = item.GetProperty(PropertyString.PreviousOwners) ?? ""; + var currentOwner = item.OwnerId ?? 0; + if (!ObjectGuid.IsPlayer(currentOwner)) + { + if (item.Container is Container && item.Container.Container is Player) + currentOwner = item.Container.Container.Guid.Full; + } + + //if (!ObjectGuid.IsPlayer(currentOwner) && !ObjectGuid.IsStatic(currentOwner)) + if (!ObjectGuid.IsPlayer(currentOwner)) + currentOwner = 0; + + if (currentOwner > 0) + { + var owners = currentPrevOwners.Split(";", StringSplitOptions.RemoveEmptyEntries); + if (owners.Length > 0) + { + var lastOwner = owners[owners.Length - 1]; + if (Convert.ToUInt32(lastOwner[2..10], 16) != currentOwner) + item.SetProperty(PropertyString.PreviousOwners, currentPrevOwners + $"0x{currentOwner:X8}:{Common.Time.GetUnixTime()};"); + } + else + item.SetProperty(PropertyString.PreviousOwners, currentPrevOwners + $"0x{currentOwner:X8}:{Common.Time.GetUnixTime()};"); + } + + item.SetProperty(PropertyString.PreviousOwnerStackLog, Environment.StackTrace); + item.OwnerId = null; item.ContainerId = null; item.Container = null; diff --git a/Source/ACE.Server/WorldObjects/Creature_Equipment.cs b/Source/ACE.Server/WorldObjects/Creature_Equipment.cs index 5ebefcb71b..3f87ea6fdc 100644 --- a/Source/ACE.Server/WorldObjects/Creature_Equipment.cs +++ b/Source/ACE.Server/WorldObjects/Creature_Equipment.cs @@ -379,6 +379,28 @@ public bool TryDequipObject(ObjectGuid objectGuid, out WorldObject worldObject, RemoveItemFromEquippedItemsRatingCache(worldObject); + var currentPrevOwners = worldObject.GetProperty(PropertyString.PreviousWielders) ?? ""; + var currentOwner = worldObject.WielderId ?? 0; + + //if (!ObjectGuid.IsPlayer(currentOwner) && !ObjectGuid.IsStatic(currentOwner)) + if (!ObjectGuid.IsPlayer(currentOwner)) + currentOwner = 0; + + if (currentOwner > 0) + { + var owners = currentPrevOwners.Split(";", StringSplitOptions.RemoveEmptyEntries); + if (owners.Length > 0) + { + var lastOwner = owners[owners.Length - 1]; + if (Convert.ToUInt32(lastOwner[2..10], 16) != currentOwner) + worldObject.SetProperty(PropertyString.PreviousWielders, currentPrevOwners + $"0x{currentOwner:X8}:{Time.GetUnixTime()};"); + } + else + worldObject.SetProperty(PropertyString.PreviousWielders, currentPrevOwners + $"0x{currentOwner:X8}:{Time.GetUnixTime()};"); + } + + worldObject.SetProperty(PropertyString.PreviousWielderStackLog, Environment.StackTrace); + wieldedLocation = worldObject.CurrentWieldedLocation ?? EquipMask.None; worldObject.RemoveProperty(PropertyInt.CurrentWieldedLocation); diff --git a/Source/ACE.Server/WorldObjects/WorldObject.cs b/Source/ACE.Server/WorldObjects/WorldObject.cs index 720b870953..3547f25fc4 100644 --- a/Source/ACE.Server/WorldObjects/WorldObject.cs +++ b/Source/ACE.Server/WorldObjects/WorldObject.cs @@ -883,10 +883,72 @@ public void Destroy(bool raiseNotifyOfDestructionEvent = true, bool fromLandbloc CurrentLandblock?.RemoveWorldObject(Guid); - RemoveBiotaFromDatabase(); + if (Stuck || ValidLocations < EquipMask.HeadWear || (Container?.Guid.IsStatic() ?? false) || (!Wielder?.Guid.IsPlayer() ?? false)) + { + RemoveBiotaFromDatabase(); - if (Guid.IsDynamic()) - GuidManager.RecycleDynamicGuid(Guid); + if (Guid.IsDynamic()) + GuidManager.RecycleDynamicGuid(Guid); + } + else + { + var logline = "[DESTROY] "; + if (StackSize > 1) + logline += $"{StackSize:N0}x "; + logline += $"{GetNameWithMaterial(StackSize)} "; + logline += $"({Name} | {WeenieClassId} | 0x{Guid}) "; + logline += "has been destroyed but not deleted. "; + logline += $"OwnerId: 0x{OwnerId ?? 0:X8} | WielderId: 0x{WielderId ?? 0:X8} | ContainerId: 0x{ContainerId ?? 0:X8}\n"; + if (Location != null && Location.LandblockId.Raw > 0) + { + logline += $"LOC: {Location.ToLOCString()}\n"; + SetPosition(PositionType.PreDestroyLocation, new Position(Location)); + Location = null; + } + else + logline += $"No Previous Location\n"; + var previousOwners = GetProperty(PropertyString.PreviousOwners) ?? ""; + var prevOwners = previousOwners.Split(";", StringSplitOptions.RemoveEmptyEntries); + if (prevOwners.Length > 0) + { + logline += "Previous Owners: "; + foreach (var p in prevOwners) + { + var po = PlayerManager.FindByGuid(new ObjectGuid(Convert.ToUInt32(p[0..10], 16))); + if (po != null) + logline += $"{po.Name} (0x{po.Guid}) ({Time.GetDateTimeFromTimestamp(Convert.ToDouble(p[11..])):G}), "; + else + logline += $"{p[0..10]} ({Time.GetDateTimeFromTimestamp(Convert.ToDouble(p[11..])):G}), "; + } + if (logline.EndsWith(", ")) + logline = logline[..^2] + "\n"; + } + else + logline += $"No Previous Owners\n"; + var previousWielders = GetProperty(PropertyString.PreviousWielders) ?? ""; + var prevWielders = previousWielders.Split(";", StringSplitOptions.RemoveEmptyEntries); + if (prevWielders.Length > 0) + { + logline += "Previous Wielders: "; + foreach (var p in prevWielders) + { + var po = PlayerManager.FindByGuid(new ObjectGuid(Convert.ToUInt32(p[0..10], 16))); + if (po != null) + logline += $"{po.Name} (0x{po.Guid}) ({Time.GetDateTimeFromTimestamp(Convert.ToDouble(p[11..])):G}), "; + else + logline += $"{p[0..10]} ({Time.GetDateTimeFromTimestamp(Convert.ToDouble(p[11..])):G}), "; + } + if (logline.EndsWith(", ")) + logline = logline[..^2] + "\n"; + } + else + logline += $"No Previous Wielders\n"; + var loglineStackTrace = System.Environment.StackTrace; + //logline += $"StackTrace: {loglineStackTrace}"; + log.Debug(logline); + SetProperty(PropertyString.DestroyStackLog, loglineStackTrace); + SaveBiotaToDatabase(); + } } public void FadeOutAndDestroy(bool raiseNotifyOfDestructionEvent = true) From 9aba635321e364f8372f7648f11708cd36461456 Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Wed, 10 Aug 2022 17:29:54 -0400 Subject: [PATCH 02/26] Update Container.cs --- Source/ACE.Server/WorldObjects/Container.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ACE.Server/WorldObjects/Container.cs b/Source/ACE.Server/WorldObjects/Container.cs index f7f4f5ff5a..9281d87651 100644 --- a/Source/ACE.Server/WorldObjects/Container.cs +++ b/Source/ACE.Server/WorldObjects/Container.cs @@ -666,7 +666,7 @@ public bool TryRemoveFromInventory(ObjectGuid objectGuid, out WorldObject item, if (owners.Length > 0) { var lastOwner = owners[owners.Length - 1]; - if (Convert.ToUInt32(lastOwner[2..10], 16) != currentOwner) + if (Convert.ToUInt32(lastOwner[0..10], 16) != currentOwner) item.SetProperty(PropertyString.PreviousOwners, currentPrevOwners + $"0x{currentOwner:X8}:{Common.Time.GetUnixTime()};"); } else From d36f56dc226dd3fde805fa0713a525ed74055312 Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Wed, 10 Aug 2022 17:29:56 -0400 Subject: [PATCH 03/26] Update Creature_Equipment.cs --- Source/ACE.Server/WorldObjects/Creature_Equipment.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ACE.Server/WorldObjects/Creature_Equipment.cs b/Source/ACE.Server/WorldObjects/Creature_Equipment.cs index 3f87ea6fdc..7cf26e3665 100644 --- a/Source/ACE.Server/WorldObjects/Creature_Equipment.cs +++ b/Source/ACE.Server/WorldObjects/Creature_Equipment.cs @@ -392,7 +392,7 @@ public bool TryDequipObject(ObjectGuid objectGuid, out WorldObject worldObject, if (owners.Length > 0) { var lastOwner = owners[owners.Length - 1]; - if (Convert.ToUInt32(lastOwner[2..10], 16) != currentOwner) + if (Convert.ToUInt32(lastOwner[0..10], 16) != currentOwner) worldObject.SetProperty(PropertyString.PreviousWielders, currentPrevOwners + $"0x{currentOwner:X8}:{Time.GetUnixTime()};"); } else From dc4ae6a35d9ed727dbb6489c6988bf88a97ec341 Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Wed, 10 Aug 2022 19:50:46 -0400 Subject: [PATCH 04/26] Update WorldObject.cs --- Source/ACE.Server/WorldObjects/WorldObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ACE.Server/WorldObjects/WorldObject.cs b/Source/ACE.Server/WorldObjects/WorldObject.cs index 3547f25fc4..bfc1cb6cd0 100644 --- a/Source/ACE.Server/WorldObjects/WorldObject.cs +++ b/Source/ACE.Server/WorldObjects/WorldObject.cs @@ -883,7 +883,7 @@ public void Destroy(bool raiseNotifyOfDestructionEvent = true, bool fromLandbloc CurrentLandblock?.RemoveWorldObject(Guid); - if (Stuck || ValidLocations < EquipMask.HeadWear || (Container?.Guid.IsStatic() ?? false) || (!Wielder?.Guid.IsPlayer() ?? false)) + if (Stuck || ValidLocations < EquipMask.HeadWear || (Container?.Guid.IsStatic() ?? false) || (!Wielder?.Guid.IsPlayer() ?? false) || Container is Corpse && !Container.Level.HasValue) { RemoveBiotaFromDatabase(); From b6b876a2ae6a9b04bffac8bb9fc064276073eb0a Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Wed, 10 Aug 2022 19:54:59 -0400 Subject: [PATCH 05/26] Update WorldObject.cs --- Source/ACE.Server/WorldObjects/WorldObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ACE.Server/WorldObjects/WorldObject.cs b/Source/ACE.Server/WorldObjects/WorldObject.cs index bfc1cb6cd0..ca7011f6af 100644 --- a/Source/ACE.Server/WorldObjects/WorldObject.cs +++ b/Source/ACE.Server/WorldObjects/WorldObject.cs @@ -883,7 +883,7 @@ public void Destroy(bool raiseNotifyOfDestructionEvent = true, bool fromLandbloc CurrentLandblock?.RemoveWorldObject(Guid); - if (Stuck || ValidLocations < EquipMask.HeadWear || (Container?.Guid.IsStatic() ?? false) || (!Wielder?.Guid.IsPlayer() ?? false) || Container is Corpse && !Container.Level.HasValue) + if (Stuck || (ValidLocations < EquipMask.HeadWear) || (Container?.Guid.IsStatic() ?? false) || (!Wielder?.Guid.IsPlayer() ?? false) || (Container is Corpse && !Container.Level.HasValue)) { RemoveBiotaFromDatabase(); From 60554fbdf97cbae09888b6765cb26eeec2645b67 Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Wed, 10 Aug 2022 20:27:42 -0400 Subject: [PATCH 06/26] Update WorldObject.cs --- Source/ACE.Server/WorldObjects/WorldObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ACE.Server/WorldObjects/WorldObject.cs b/Source/ACE.Server/WorldObjects/WorldObject.cs index ca7011f6af..692bf86c7a 100644 --- a/Source/ACE.Server/WorldObjects/WorldObject.cs +++ b/Source/ACE.Server/WorldObjects/WorldObject.cs @@ -883,7 +883,7 @@ public void Destroy(bool raiseNotifyOfDestructionEvent = true, bool fromLandbloc CurrentLandblock?.RemoveWorldObject(Guid); - if (Stuck || (ValidLocations < EquipMask.HeadWear) || (Container?.Guid.IsStatic() ?? false) || (!Wielder?.Guid.IsPlayer() ?? false) || (Container is Corpse && !Container.Level.HasValue)) + if (Stuck || (ValidLocations < EquipMask.HeadWear) || (Container?.Guid.IsStatic() ?? false) || (!Wielder?.Guid.IsPlayer() ?? false) || (Container is Corpse && !Container.Level.HasValue) || Generator is not null) { RemoveBiotaFromDatabase(); From a43092e02a8999c40280c8b2226ff389686ea10c Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Wed, 10 Aug 2022 20:32:40 -0400 Subject: [PATCH 07/26] Update WorldObject.cs --- Source/ACE.Server/WorldObjects/WorldObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ACE.Server/WorldObjects/WorldObject.cs b/Source/ACE.Server/WorldObjects/WorldObject.cs index 692bf86c7a..141d7173b7 100644 --- a/Source/ACE.Server/WorldObjects/WorldObject.cs +++ b/Source/ACE.Server/WorldObjects/WorldObject.cs @@ -883,7 +883,7 @@ public void Destroy(bool raiseNotifyOfDestructionEvent = true, bool fromLandbloc CurrentLandblock?.RemoveWorldObject(Guid); - if (Stuck || (ValidLocations < EquipMask.HeadWear) || (Container?.Guid.IsStatic() ?? false) || (!Wielder?.Guid.IsPlayer() ?? false) || (Container is Corpse && !Container.Level.HasValue) || Generator is not null) + if (Stuck || (ValidLocations < EquipMask.HeadWear) || (Container?.Guid.IsStatic() ?? false) || (!Wielder?.Guid.IsPlayer() ?? false) || (Container is Corpse && !Container.Level.HasValue) || (Generator is not null) || (this is Missile)) { RemoveBiotaFromDatabase(); From 6428b7f0f3c14204081b88d8887f5ecbbc5ebe3a Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Wed, 10 Aug 2022 20:41:14 -0400 Subject: [PATCH 08/26] Update WorldObject.cs --- Source/ACE.Server/WorldObjects/WorldObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ACE.Server/WorldObjects/WorldObject.cs b/Source/ACE.Server/WorldObjects/WorldObject.cs index 141d7173b7..49d53e93f1 100644 --- a/Source/ACE.Server/WorldObjects/WorldObject.cs +++ b/Source/ACE.Server/WorldObjects/WorldObject.cs @@ -883,7 +883,7 @@ public void Destroy(bool raiseNotifyOfDestructionEvent = true, bool fromLandbloc CurrentLandblock?.RemoveWorldObject(Guid); - if (Stuck || (ValidLocations < EquipMask.HeadWear) || (Container?.Guid.IsStatic() ?? false) || (!Wielder?.Guid.IsPlayer() ?? false) || (Container is Corpse && !Container.Level.HasValue) || (Generator is not null) || (this is Missile)) + if (Stuck || (ValidLocations < EquipMask.HeadWear) || (Container?.Guid.IsStatic() ?? false) || (!Wielder?.Guid.IsPlayer() ?? false) || (Container is Corpse && !Container.Level.HasValue) || (Generator is not null) || (this is Missile) || (this is Ammunition)) { RemoveBiotaFromDatabase(); From 55316c20b092c2436ba070138d29aa4eba7e7cbe Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Thu, 11 Aug 2022 03:43:33 -0400 Subject: [PATCH 09/26] Update WorldObject.cs --- Source/ACE.Server/WorldObjects/WorldObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ACE.Server/WorldObjects/WorldObject.cs b/Source/ACE.Server/WorldObjects/WorldObject.cs index 49d53e93f1..2fc84e8828 100644 --- a/Source/ACE.Server/WorldObjects/WorldObject.cs +++ b/Source/ACE.Server/WorldObjects/WorldObject.cs @@ -883,7 +883,7 @@ public void Destroy(bool raiseNotifyOfDestructionEvent = true, bool fromLandbloc CurrentLandblock?.RemoveWorldObject(Guid); - if (Stuck || (ValidLocations < EquipMask.HeadWear) || (Container?.Guid.IsStatic() ?? false) || (!Wielder?.Guid.IsPlayer() ?? false) || (Container is Corpse && !Container.Level.HasValue) || (Generator is not null) || (this is Missile) || (this is Ammunition)) + if (Stuck || ((ValidLocations ?? 0) < EquipMask.HeadWear) || (Container?.Guid.IsStatic() ?? false) || (!Wielder?.Guid.IsPlayer() ?? false) || (Container is Corpse && !Container.Level.HasValue) || (Generator is not null) || (this is Missile) || (this is Ammunition)) { RemoveBiotaFromDatabase(); From c99769760d16363311eecacfdff4b43c26efc7c5 Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Thu, 11 Aug 2022 04:28:22 -0400 Subject: [PATCH 10/26] Update WorldObject.cs --- Source/ACE.Server/WorldObjects/WorldObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ACE.Server/WorldObjects/WorldObject.cs b/Source/ACE.Server/WorldObjects/WorldObject.cs index 2fc84e8828..17a2134c09 100644 --- a/Source/ACE.Server/WorldObjects/WorldObject.cs +++ b/Source/ACE.Server/WorldObjects/WorldObject.cs @@ -883,7 +883,7 @@ public void Destroy(bool raiseNotifyOfDestructionEvent = true, bool fromLandbloc CurrentLandblock?.RemoveWorldObject(Guid); - if (Stuck || ((ValidLocations ?? 0) < EquipMask.HeadWear) || (Container?.Guid.IsStatic() ?? false) || (!Wielder?.Guid.IsPlayer() ?? false) || (Container is Corpse && !Container.Level.HasValue) || (Generator is not null) || (this is Missile) || (this is Ammunition)) + if (Stuck || ((ValidLocations ?? 0) < EquipMask.HeadWear) || (Container?.Guid.IsStatic() ?? false) || (!Wielder?.Guid.IsPlayer() ?? false) || (Container is Corpse && !Container.Level.HasValue) || (Container is Creature and not Player) || (Container is Chest and not Storage) || (this is Missile) || (this is Ammunition)) { RemoveBiotaFromDatabase(); From 3a74be716000d96ae20c9de60dd054b2f9b28ed9 Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Thu, 11 Aug 2022 10:59:47 -0400 Subject: [PATCH 11/26] Update WorldObject.cs --- Source/ACE.Server/WorldObjects/WorldObject.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/ACE.Server/WorldObjects/WorldObject.cs b/Source/ACE.Server/WorldObjects/WorldObject.cs index 17a2134c09..0fc96522b6 100644 --- a/Source/ACE.Server/WorldObjects/WorldObject.cs +++ b/Source/ACE.Server/WorldObjects/WorldObject.cs @@ -916,9 +916,9 @@ public void Destroy(bool raiseNotifyOfDestructionEvent = true, bool fromLandbloc { var po = PlayerManager.FindByGuid(new ObjectGuid(Convert.ToUInt32(p[0..10], 16))); if (po != null) - logline += $"{po.Name} (0x{po.Guid}) ({Time.GetDateTimeFromTimestamp(Convert.ToDouble(p[11..])):G}), "; + logline += $"{po.Name} (0x{po.Guid}) ({Time.GetDateTimeFromTimestamp(Convert.ToDouble(p[11..])).ToLocalTime:G}), "; else - logline += $"{p[0..10]} ({Time.GetDateTimeFromTimestamp(Convert.ToDouble(p[11..])):G}), "; + logline += $"{p[0..10]} ({Time.GetDateTimeFromTimestamp(Convert.ToDouble(p[11..])).ToLocalTime:G}), "; } if (logline.EndsWith(", ")) logline = logline[..^2] + "\n"; @@ -934,9 +934,9 @@ public void Destroy(bool raiseNotifyOfDestructionEvent = true, bool fromLandbloc { var po = PlayerManager.FindByGuid(new ObjectGuid(Convert.ToUInt32(p[0..10], 16))); if (po != null) - logline += $"{po.Name} (0x{po.Guid}) ({Time.GetDateTimeFromTimestamp(Convert.ToDouble(p[11..])):G}), "; + logline += $"{po.Name} (0x{po.Guid}) ({Time.GetDateTimeFromTimestamp(Convert.ToDouble(p[11..])).ToLocalTime:G}), "; else - logline += $"{p[0..10]} ({Time.GetDateTimeFromTimestamp(Convert.ToDouble(p[11..])):G}), "; + logline += $"{p[0..10]} ({Time.GetDateTimeFromTimestamp(Convert.ToDouble(p[11..])).ToLocalTime:G}), "; } if (logline.EndsWith(", ")) logline = logline[..^2] + "\n"; From a1fe96a966cbf9586cdf8df7dfe8f82d3b552518 Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Fri, 12 Aug 2022 12:08:57 -0400 Subject: [PATCH 12/26] Update WorldObject.cs --- Source/ACE.Server/WorldObjects/WorldObject.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Source/ACE.Server/WorldObjects/WorldObject.cs b/Source/ACE.Server/WorldObjects/WorldObject.cs index 0fc96522b6..5dd1812b30 100644 --- a/Source/ACE.Server/WorldObjects/WorldObject.cs +++ b/Source/ACE.Server/WorldObjects/WorldObject.cs @@ -883,7 +883,7 @@ public void Destroy(bool raiseNotifyOfDestructionEvent = true, bool fromLandbloc CurrentLandblock?.RemoveWorldObject(Guid); - if (Stuck || ((ValidLocations ?? 0) < EquipMask.HeadWear) || (Container?.Guid.IsStatic() ?? false) || (!Wielder?.Guid.IsPlayer() ?? false) || (Container is Corpse && !Container.Level.HasValue) || (Container is Creature and not Player) || (Container is Chest and not Storage) || (this is Missile) || (this is Ammunition)) + if (Stuck || ((ValidLocations ?? 0) < EquipMask.HeadWear) || (Container?.Guid.IsStatic() ?? false) || (!Wielder?.Guid.IsPlayer() ?? false) || (Container is Corpse && !Container.Level.HasValue) || (Container is Creature and not Player) || (Container is Chest and not Storage) || (this is Missile) || (this is Ammunition) || fromLandblockUnload) { RemoveBiotaFromDatabase(); @@ -899,6 +899,12 @@ public void Destroy(bool raiseNotifyOfDestructionEvent = true, bool fromLandbloc logline += $"({Name} | {WeenieClassId} | 0x{Guid}) "; logline += "has been destroyed but not deleted. "; logline += $"OwnerId: 0x{OwnerId ?? 0:X8} | WielderId: 0x{WielderId ?? 0:X8} | ContainerId: 0x{ContainerId ?? 0:X8}\n"; + if (OwnerId > 0) + OwnerId = null; + if (WielderId > 0) + WielderId = null; + if (ContainerId > 0) + ContainerId = null; if (Location != null && Location.LandblockId.Raw > 0) { logline += $"LOC: {Location.ToLOCString()}\n"; From 9363eadb052f1adf1ea07e0b74d2f5eae99d6ec5 Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Fri, 12 Aug 2022 12:48:51 -0400 Subject: [PATCH 13/26] Update WorldObject.cs --- Source/ACE.Server/WorldObjects/WorldObject.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/ACE.Server/WorldObjects/WorldObject.cs b/Source/ACE.Server/WorldObjects/WorldObject.cs index 5dd1812b30..ae8bab9be5 100644 --- a/Source/ACE.Server/WorldObjects/WorldObject.cs +++ b/Source/ACE.Server/WorldObjects/WorldObject.cs @@ -922,9 +922,9 @@ public void Destroy(bool raiseNotifyOfDestructionEvent = true, bool fromLandbloc { var po = PlayerManager.FindByGuid(new ObjectGuid(Convert.ToUInt32(p[0..10], 16))); if (po != null) - logline += $"{po.Name} (0x{po.Guid}) ({Time.GetDateTimeFromTimestamp(Convert.ToDouble(p[11..])).ToLocalTime:G}), "; + logline += $"{po.Name} (0x{po.Guid}) ({Time.GetDateTimeFromTimestamp(Convert.ToDouble(p[11..])).ToLocalTime():G}), "; else - logline += $"{p[0..10]} ({Time.GetDateTimeFromTimestamp(Convert.ToDouble(p[11..])).ToLocalTime:G}), "; + logline += $"{p[0..10]} ({Time.GetDateTimeFromTimestamp(Convert.ToDouble(p[11..])).ToLocalTime():G}), "; } if (logline.EndsWith(", ")) logline = logline[..^2] + "\n"; @@ -940,9 +940,9 @@ public void Destroy(bool raiseNotifyOfDestructionEvent = true, bool fromLandbloc { var po = PlayerManager.FindByGuid(new ObjectGuid(Convert.ToUInt32(p[0..10], 16))); if (po != null) - logline += $"{po.Name} (0x{po.Guid}) ({Time.GetDateTimeFromTimestamp(Convert.ToDouble(p[11..])).ToLocalTime:G}), "; + logline += $"{po.Name} (0x{po.Guid}) ({Time.GetDateTimeFromTimestamp(Convert.ToDouble(p[11..])).ToLocalTime():G}), "; else - logline += $"{p[0..10]} ({Time.GetDateTimeFromTimestamp(Convert.ToDouble(p[11..])).ToLocalTime:G}), "; + logline += $"{p[0..10]} ({Time.GetDateTimeFromTimestamp(Convert.ToDouble(p[11..])).ToLocalTime():G}), "; } if (logline.EndsWith(", ")) logline = logline[..^2] + "\n"; From dec623712902318686ec27111bdb5277c43c9f33 Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Fri, 19 Aug 2022 13:35:04 -0400 Subject: [PATCH 14/26] lots o' changes --- Source/ACE.Common/OfflineConfiguration.cs | 8 +++++ Source/ACE.Database/ShardDatabase.cs | 7 ++-- .../ACE.Database/ShardDatabaseOfflineTools.cs | 34 +++++++++++++++++-- Source/ACE.Server/Config.js.docker | 11 ++++-- Source/ACE.Server/Config.js.example | 11 ++++-- Source/ACE.Server/Managers/GuidManager.cs | 6 ++-- Source/ACE.Server/Managers/PropertyManager.cs | 6 ++++ Source/ACE.Server/Program.cs | 7 ++++ Source/ACE.Server/WorldObjects/Container.cs | 3 +- .../WorldObjects/Creature_Equipment.cs | 3 +- Source/ACE.Server/WorldObjects/WorldObject.cs | 17 +++++++--- 11 files changed, 95 insertions(+), 18 deletions(-) diff --git a/Source/ACE.Common/OfflineConfiguration.cs b/Source/ACE.Common/OfflineConfiguration.cs index b4e7bdecd9..127dc3ea68 100644 --- a/Source/ACE.Common/OfflineConfiguration.cs +++ b/Source/ACE.Common/OfflineConfiguration.cs @@ -35,6 +35,14 @@ public class OfflineConfiguration [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] public bool PurgeOrphanedBiotas { get; set; } + [System.ComponentModel.DefaultValue(false)] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public bool PurgeReleasedBiotas { get; set; } + + [System.ComponentModel.DefaultValue(30)] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public int PurgeReleasedBiotasDays { get; set; } = 30; + /// /// Prune deleted characters from all friend lists /// diff --git a/Source/ACE.Database/ShardDatabase.cs b/Source/ACE.Database/ShardDatabase.cs index 6599836e07..b507f00602 100644 --- a/Source/ACE.Database/ShardDatabase.cs +++ b/Source/ACE.Database/ShardDatabase.cs @@ -94,8 +94,11 @@ public uint GetMaxGuidFoundInRange(uint min, uint max) " JOIN biota" + Environment.NewLine + " WHERE id > " + min + Environment.NewLine + " ORDER BY id" + Environment.NewLine + - " ) AS z" + Environment.NewLine + - "WHERE z.gap_ends_at_not_inclusive!=0 AND @available_ids<" + limitAvailableIDsReturned + "; "; + " ) AS z" + Environment.NewLine; + if (limitAvailableIDsReturned != uint.MaxValue) + sql += "WHERE z.gap_ends_at_not_inclusive!=0 AND @available_ids<" + limitAvailableIDsReturned + "; "; + else + sql += "WHERE z.gap_ends_at_not_inclusive!=0;"; using (var context = new ShardDbContext()) { diff --git a/Source/ACE.Database/ShardDatabaseOfflineTools.cs b/Source/ACE.Database/ShardDatabaseOfflineTools.cs index 386508513c..7f1576212f 100644 --- a/Source/ACE.Database/ShardDatabaseOfflineTools.cs +++ b/Source/ACE.Database/ShardDatabaseOfflineTools.cs @@ -333,6 +333,7 @@ public static void PurgeOrphanedBiotasInParallel(ShardDbContext context, out int HashSet playerBiotaIds = null; HashSet characterIds = null; + HashSet releasedIds = null; Dictionary biotas = null; Dictionary containerPointers = null; @@ -428,6 +429,8 @@ from c in combined.DefaultIfEmpty() }); } + releasedIds = context.BiotaPropertiesFloat.AsNoTracking().Where(r => r.Type == (ushort)PropertyFloat.ReleasedTimestamp).Select(r => r.ObjectId).ToHashSet(); + // Purge contained items that belong to a parent container that no longer exists { // select * from biota_properties_i_i_d iid left join biota on biota.id=iid.`value` where iid.`type`=2 and biota.id is null; @@ -460,7 +463,7 @@ from b in combined.DefaultIfEmpty() Parallel.ForEach(results, ConfigManager.Config.Server.Threading.DatabaseParallelOptions, result => { - if (PurgeBiota(result.ObjectId, "Parent container not found")) + if (!releasedIds.Contains(result.ObjectId) && PurgeBiota(result.ObjectId, "Parent container not found")) Interlocked.Increment(ref totalNumberOfBiotasPurged); }); @@ -498,7 +501,7 @@ from b in combined.DefaultIfEmpty() Parallel.ForEach(results, ConfigManager.Config.Server.Threading.DatabaseParallelOptions, result => { - if (PurgeBiota(result.ObjectId, "Parent wielder not found")) + if (!releasedIds.Contains(result.ObjectId) && PurgeBiota(result.ObjectId, "Parent wielder not found")) Interlocked.Increment(ref totalNumberOfBiotasPurged); }); @@ -512,6 +515,10 @@ from b in combined.DefaultIfEmpty() foreach (var kvp in biotas) { + // exclude released objects + if (releasedIds.Contains(kvp.Key)) + continue; + // exclude allegiances if (kvp.Value == WeenieType.Allegiance) continue; @@ -919,6 +926,29 @@ public static void PurgeOrphanedBiotasInParallel(out int numberOfBiotasPurged) PurgeOrphanedBiotasInParallel(context, out numberOfBiotasPurged); } + public static void PurgeReleasedBiotasInParallel(ShardDbContext context, int daysLimiter, out int numberOfBiotasPurged) + { + var deleteLimit = Time.GetUnixTime(DateTime.UtcNow.AddDays(-daysLimiter)); + + var releasedIds = context.BiotaPropertiesFloat.AsNoTracking().Where(r => r.Type == (ushort)PropertyFloat.ReleasedTimestamp && r.Value <= deleteLimit).Select(r => new { r.ObjectId, r.Value }).AsEnumerable().Select(r => (Id: r.ObjectId, ReleasedTimestamp: r.Value)).ToHashSet(); + + int biotaPurgedTotal = 0; + + Parallel.ForEach(releasedIds, ConfigManager.Config.Server.Threading.DatabaseParallelOptions, result => + { + if (PurgeBiota(result.Id, $"Released on {Time.GetDateTimeFromTimestamp(result.ReleasedTimestamp).ToLocalTime()} which is older than {daysLimiter} days")) + Interlocked.Increment(ref biotaPurgedTotal); + }); + + numberOfBiotasPurged = biotaPurgedTotal; + } + + public static void PurgeReleasedBiotasInParallel(int daysLimiter, out int numberOfBiotasPurged) + { + using (var context = new ShardDbContext()) + PurgeReleasedBiotasInParallel(context, daysLimiter, out numberOfBiotasPurged); + } + /// /// This is temporary and can be removed in the near future, 2020-04-05 Mag-nus /// diff --git a/Source/ACE.Server/Config.js.docker b/Source/ACE.Server/Config.js.docker index 50003a7ed5..901bd4afb9 100644 --- a/Source/ACE.Server/Config.js.docker +++ b/Source/ACE.Server/Config.js.docker @@ -174,13 +174,20 @@ // This section can trigger events that may happen before the world starts up, or after it shuts down // The shard should be in a disconnected state from any running ACE world "Offline": { - // Purge characters that have been deleted longer than PruneDeletedCharactersDays - // These characters, and their associated biotas, will be deleted permanantly! + // Purge characters that have been deleted longer than PurgeDeletedCharactersDays + // These characters, and their associated biotas, will be deleted permanently! "PurgeDeletedCharacters": false, // Number of days a character must have been deleted for before eligible for purging "PurgeDeletedCharactersDays": 30, + // Purge biotas that have been released longer than PurgeReleasedBiotasDays + // These biotas (World Objects) will be deleted permanently! + "PurgeReleasedBiotas": false, + + // Number of days a biota must have been released for before eligible for purging + "PurgeReleasedBiotasDays": 30, + // This will purge biotas that are completely disconnected from the world // These may have been items that were never deleted properly, items that were given to the town crier before delete was implemented, etc... // This can be time consuming so it's not something you would have set to true for every server startup. You might run this once every few months diff --git a/Source/ACE.Server/Config.js.example b/Source/ACE.Server/Config.js.example index 6e39ffc133..140ce2c444 100644 --- a/Source/ACE.Server/Config.js.example +++ b/Source/ACE.Server/Config.js.example @@ -174,13 +174,20 @@ // This section can trigger events that may happen before the world starts up, or after it shuts down // The shard should be in a disconnected state from any running ACE world "Offline": { - // Purge characters that have been deleted longer than PruneDeletedCharactersDays - // These characters, and their associated biotas, will be deleted permanantly! + // Purge characters that have been deleted longer than PurgeDeletedCharactersDays + // These characters, and their associated biotas, will be deleted permanently! "PurgeDeletedCharacters": false, // Number of days a character must have been deleted for before eligible for purging "PurgeDeletedCharactersDays": 30, + // Purge biotas that have been released longer than PurgeReleasedBiotasDays + // These biotas (World Objects) will be deleted permanently! + "PurgeReleasedBiotas": false, + + // Number of days a biota must have been released for before eligible for purging + "PurgeReleasedBiotasDays": 30, + // This will purge biotas that are completely disconnected from the world // These may have been items that were never deleted properly, items that were given to the town crier before delete was implemented, etc... // This can be time consuming so it's not something you would have set to true for every server startup. You might run this once every few months diff --git a/Source/ACE.Server/Managers/GuidManager.cs b/Source/ACE.Server/Managers/GuidManager.cs index acc3dfd680..c3ba81bc7c 100644 --- a/Source/ACE.Server/Managers/GuidManager.cs +++ b/Source/ACE.Server/Managers/GuidManager.cs @@ -127,7 +127,7 @@ private class DynamicGuidAllocator private bool useSequenceGapExhaustedMessageDisplayed; private LinkedList<(uint start, uint end)> availableIDs = new LinkedList<(uint start, uint end)>(); - public DynamicGuidAllocator(uint min, uint max, string name) + public DynamicGuidAllocator(uint min, uint max, string name, bool unlimitedGaps) { this.max = max; @@ -164,7 +164,7 @@ public DynamicGuidAllocator(uint min, uint max, string name) lock (this) { bool done = false; - Database.DatabaseManager.Shard.GetSequenceGaps(ObjectGuid.DynamicMin, limitAvailableIDsReturnedInGetSequenceGaps, gaps => + Database.DatabaseManager.Shard.GetSequenceGaps(ObjectGuid.DynamicMin, unlimitedGaps ? uint.MaxValue : limitAvailableIDsReturnedInGetSequenceGaps, gaps => { lock (this) { @@ -294,7 +294,7 @@ public override string ToString() public static void Initialize() { playerAlloc = new PlayerGuidAllocator(ObjectGuid.PlayerMin, ObjectGuid.PlayerMax, "player"); - dynamicAlloc = new DynamicGuidAllocator(ObjectGuid.DynamicMin, ObjectGuid.DynamicMax, "dynamic"); + dynamicAlloc = new DynamicGuidAllocator(ObjectGuid.DynamicMin, ObjectGuid.DynamicMax, "dynamic", PropertyManager.GetBool("unlimited_sequence_gaps").Item); } /// diff --git a/Source/ACE.Server/Managers/PropertyManager.cs b/Source/ACE.Server/Managers/PropertyManager.cs index eb7d44ef2e..aca519a182 100644 --- a/Source/ACE.Server/Managers/PropertyManager.cs +++ b/Source/ACE.Server/Managers/PropertyManager.cs @@ -545,6 +545,7 @@ public static void LoadDefaultProperties() ("craft_exact_msg", new Property(false, "If TRUE, and player has crafting chance of success dialog enabled, shows them an additional message in their chat window with exact %")), ("creature_name_check", new Property(true, "if enabled, creature names in world database restricts player names during character creation")), ("creatures_drop_createlist_wield", new Property(false, "If FALSE then Wielded items in CreateList will not drop. Retail defaulted to TRUE but there are currently data errors")), + ("destroy_deletes_from_database", new Property(true, "when an item is destroyed, if it was stored in database it will be immediately deleted")), ("equipmentsetid_enabled", new Property(true, "enable this to allow adding EquipmentSetIDs to loot armor")), ("equipmentsetid_name_decoration", new Property(false, "enable this to add the EquipmentSet name to loot armor name")), ("fastbuff", new Property(true, "If TRUE, enables the fast buffing trick from retail.")), @@ -581,6 +582,10 @@ public static void LoadDefaultProperties() ("rares_real_time", new Property(true, "allow for second chance roll based on an rng seeded timestamp for a rare on rare eligible kills that do not generate a rare, rares_max_seconds_between defines maximum seconds before second chance kicks in")), ("rares_real_time_v2", new Property(false, "chances for a rare to be generated on rare eligible kills are modified by the last time one was found per each player, rares_max_days_between defines maximum days before guaranteed rare generation")), ("runrate_add_hooks", new Property(false, "if TRUE, adds some runrate hooks that were missing from retail (exhaustion done, raise skill/attribute")), + ("record_destroy_stacktrace", new Property(false, "logs stack trace when object is destroyed")), + ("record_dequip_stacktrace", new Property(false, "logs stack trace when object is dequiped")), + ("record_remove_stacktrace", new Property(false, "logs stack trace when object is removed")), + ("recycle_guids", new Property(true, "allows dynamic objects guids to be recycled")), ("reportbug_enabled", new Property(false, "toggles the /reportbug player command")), ("require_spell_comps", new Property(true, "if FALSE, spell components are no longer required to be in inventory to cast spells. defaults to enabled, as in retail")), ("safe_spell_comps", new Property(false, "if TRUE, disables spell component burning for everyone")), @@ -600,6 +605,7 @@ public static void LoadDefaultProperties() ("trajectory_alt_solver", new Property(false, "use the alternate trajectory solver for missiles and spell projectiles")), ("universal_masteries", new Property(true, "if TRUE, matches end of retail masteries - players wielding almost any weapon get +5 DR, except if the weapon \"seems tough to master\". " + "if FALSE, players start with mastery of 1 melee and 1 ranged weapon type based on heritage, and can later re-select these 2 masteries")), + ("unlimited_sequence_gaps", new Property(false, "upon startup, allows server to find all unused guids in a range instead of a set hard limit")), ("use_generator_rotation_offset", new Property(true, "enables or disables using the generator's current rotation when offseting relative positions")), ("use_turbine_chat", new Property(true, "enables or disables global chat channels (General, LFG, Roleplay, Trade, Olthoi, Society, Allegience)")), ("use_wield_requirements", new Property(true, "disable this to bypass wield requirements. mostly for dev debugging")), diff --git a/Source/ACE.Server/Program.cs b/Source/ACE.Server/Program.cs index 9665869a01..a330904c03 100644 --- a/Source/ACE.Server/Program.cs +++ b/Source/ACE.Server/Program.cs @@ -164,6 +164,13 @@ public static void Main(string[] args) log.Info($"Purged {charactersPurged:N0} characters, {playerBiotasPurged:N0} player biotas and {possessionsPurged:N0} possessions."); } + if (ConfigManager.Config.Offline.PurgeReleasedBiotas) + { + log.Info($"Purging released biotas older than {ConfigManager.Config.Offline.PurgeReleasedBiotasDays} days ({DateTime.Now.AddDays(-ConfigManager.Config.Offline.PurgeReleasedBiotasDays)})..."); + ShardDatabaseOfflineTools.PurgeReleasedBiotasInParallel(ConfigManager.Config.Offline.PurgeReleasedBiotasDays, out var numberOfBiotasPurged); + log.Info($"Purged {numberOfBiotasPurged:N0} biotas."); + } + if (ConfigManager.Config.Offline.PurgeOrphanedBiotas) { log.Info($"Purging orphaned biotas..."); diff --git a/Source/ACE.Server/WorldObjects/Container.cs b/Source/ACE.Server/WorldObjects/Container.cs index 9281d87651..3f8eb4d7c7 100644 --- a/Source/ACE.Server/WorldObjects/Container.cs +++ b/Source/ACE.Server/WorldObjects/Container.cs @@ -673,7 +673,8 @@ public bool TryRemoveFromInventory(ObjectGuid objectGuid, out WorldObject item, item.SetProperty(PropertyString.PreviousOwners, currentPrevOwners + $"0x{currentOwner:X8}:{Common.Time.GetUnixTime()};"); } - item.SetProperty(PropertyString.PreviousOwnerStackLog, Environment.StackTrace); + if (PropertyManager.GetBool("record_remove_stacktrace").Item) + item.SetProperty(PropertyString.PreviousOwnerStackLog, Environment.StackTrace); item.OwnerId = null; item.ContainerId = null; diff --git a/Source/ACE.Server/WorldObjects/Creature_Equipment.cs b/Source/ACE.Server/WorldObjects/Creature_Equipment.cs index 7cf26e3665..eb079dd27c 100644 --- a/Source/ACE.Server/WorldObjects/Creature_Equipment.cs +++ b/Source/ACE.Server/WorldObjects/Creature_Equipment.cs @@ -399,7 +399,8 @@ public bool TryDequipObject(ObjectGuid objectGuid, out WorldObject worldObject, worldObject.SetProperty(PropertyString.PreviousWielders, currentPrevOwners + $"0x{currentOwner:X8}:{Time.GetUnixTime()};"); } - worldObject.SetProperty(PropertyString.PreviousWielderStackLog, Environment.StackTrace); + if (PropertyManager.GetBool("record_dequip_stacktrace").Item) + worldObject.SetProperty(PropertyString.PreviousWielderStackLog, Environment.StackTrace); wieldedLocation = worldObject.CurrentWieldedLocation ?? EquipMask.None; diff --git a/Source/ACE.Server/WorldObjects/WorldObject.cs b/Source/ACE.Server/WorldObjects/WorldObject.cs index ae8bab9be5..6f0e3c70cb 100644 --- a/Source/ACE.Server/WorldObjects/WorldObject.cs +++ b/Source/ACE.Server/WorldObjects/WorldObject.cs @@ -883,11 +883,14 @@ public void Destroy(bool raiseNotifyOfDestructionEvent = true, bool fromLandbloc CurrentLandblock?.RemoveWorldObject(Guid); - if (Stuck || ((ValidLocations ?? 0) < EquipMask.HeadWear) || (Container?.Guid.IsStatic() ?? false) || (!Wielder?.Guid.IsPlayer() ?? false) || (Container is Corpse && !Container.Level.HasValue) || (Container is Creature and not Player) || (Container is Chest and not Storage) || (this is Missile) || (this is Ammunition) || fromLandblockUnload) + var recycleGuids = PropertyManager.GetBool("recycle_guids").Item; + var destroyItem = PropertyManager.GetBool("destroy_deletes_from_database").Item; + + if (destroyItem || Stuck || ((ValidLocations ?? 0) < EquipMask.HeadWear) || (Container?.Guid.IsStatic() ?? false) || (!Wielder?.Guid.IsPlayer() ?? false) || (Container is Corpse && !Container.Level.HasValue) || (Container is Creature and not Player) || (Container is Chest and not Storage) || (this is Missile) || (this is Ammunition) || fromLandblockUnload) { RemoveBiotaFromDatabase(); - if (Guid.IsDynamic()) + if (Guid.IsDynamic() && recycleGuids) GuidManager.RecycleDynamicGuid(Guid); } else @@ -949,10 +952,14 @@ public void Destroy(bool raiseNotifyOfDestructionEvent = true, bool fromLandbloc } else logline += $"No Previous Wielders\n"; - var loglineStackTrace = System.Environment.StackTrace; - //logline += $"StackTrace: {loglineStackTrace}"; + if (PropertyManager.GetBool("record_destroy_stacktrace").Item) + { + var loglineStackTrace = System.Environment.StackTrace; + //logline += $"StackTrace: {loglineStackTrace}"; + + SetProperty(PropertyString.DestroyStackLog, loglineStackTrace); + } log.Debug(logline); - SetProperty(PropertyString.DestroyStackLog, loglineStackTrace); SaveBiotaToDatabase(); } } From a1aae16e7279b19275d4387d8e6504ac67de2d15 Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Fri, 19 Aug 2022 13:50:19 -0400 Subject: [PATCH 15/26] . --- Source/ACE.Server/Config.js.docker | 2 +- Source/ACE.Server/Config.js.example | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/ACE.Server/Config.js.docker b/Source/ACE.Server/Config.js.docker index 901bd4afb9..dd60d1214e 100644 --- a/Source/ACE.Server/Config.js.docker +++ b/Source/ACE.Server/Config.js.docker @@ -188,7 +188,7 @@ // Number of days a biota must have been released for before eligible for purging "PurgeReleasedBiotasDays": 30, - // This will purge biotas that are completely disconnected from the world + // This will purge biotas that are completely disconnected from the world, excluding biotas marked as released // These may have been items that were never deleted properly, items that were given to the town crier before delete was implemented, etc... // This can be time consuming so it's not something you would have set to true for every server startup. You might run this once every few months "PurgeOrphanedBiotas": false, diff --git a/Source/ACE.Server/Config.js.example b/Source/ACE.Server/Config.js.example index 140ce2c444..c4392448b9 100644 --- a/Source/ACE.Server/Config.js.example +++ b/Source/ACE.Server/Config.js.example @@ -188,7 +188,7 @@ // Number of days a biota must have been released for before eligible for purging "PurgeReleasedBiotasDays": 30, - // This will purge biotas that are completely disconnected from the world + // This will purge biotas that are completely disconnected from the world, excluding biotas marked as released // These may have been items that were never deleted properly, items that were given to the town crier before delete was implemented, etc... // This can be time consuming so it's not something you would have set to true for every server startup. You might run this once every few months "PurgeOrphanedBiotas": false, From 07a814393bca6c3761f5ba1badc0784717bc084b Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Sun, 21 Aug 2022 14:00:20 -0400 Subject: [PATCH 16/26] add option to destroy failed spawns landblock_destroys_failed_unstuck_spawns for unmanaged, unstuck items. --- Source/ACE.Server/Entity/Landblock.cs | 5 +++++ Source/ACE.Server/Managers/PropertyManager.cs | 1 + 2 files changed, 6 insertions(+) diff --git a/Source/ACE.Server/Entity/Landblock.cs b/Source/ACE.Server/Entity/Landblock.cs index fa7b917840..1f67e96548 100644 --- a/Source/ACE.Server/Entity/Landblock.cs +++ b/Source/ACE.Server/Entity/Landblock.cs @@ -874,6 +874,11 @@ private bool AddWorldObjectInternal(WorldObject wo) else if (wo.ProjectileTarget == null && !(wo is SpellProjectile)) log.Warn($"AddWorldObjectInternal: couldn't spawn 0x{wo.Guid}:{wo.Name} [{wo.WeenieClassId} - {wo.WeenieType}] at {wo.Location.ToLOCString()}"); + if (PropertyManager.GetBool("landblock_destroys_failed_unstuck_spawns").Item && wo is not Creature && !wo.Stuck && wo.Generator is null) + { + wo.Destroy(); + } + return false; } } diff --git a/Source/ACE.Server/Managers/PropertyManager.cs b/Source/ACE.Server/Managers/PropertyManager.cs index aca519a182..9c89883435 100644 --- a/Source/ACE.Server/Managers/PropertyManager.cs +++ b/Source/ACE.Server/Managers/PropertyManager.cs @@ -563,6 +563,7 @@ public static void LoadDefaultProperties() ("house_rent_enabled", new Property(true, "If FALSE then rent is not required")), ("iou_trades", new Property(false, "(non-retail function) If enabled, IOUs can be traded for objects that are missing in DB but added/restored later on")), ("item_dispel", new Property(false, "if enabled, allows players to dispel items. defaults to end of retail, where item dispels could only target creatures")), + ("landblock_destroys_failed_unstuck_spawns", new Property(false, "if enabled, any unmanaged/uncontrolled unstuck object that fails to spawn on a landblock will be destroyed")), ("legacy_loot_system", new Property(false, "use the previous iteration of the ace lootgen system")), ("lifestone_broadcast_death", new Property(true, "if true, player deaths are additionally broadcast to other players standing near the destination lifestone")), ("loot_quality_mod", new Property(true, "if FALSE then the loot quality modifier of a Death Treasure profile does not affect loot generation")), From 94fa841d50ef6a3bfc453c23207c566efa5f316a Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Mon, 29 Aug 2022 13:13:05 -0400 Subject: [PATCH 17/26] Update ShardDatabaseOfflineTools.cs --- .../ACE.Database/ShardDatabaseOfflineTools.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Source/ACE.Database/ShardDatabaseOfflineTools.cs b/Source/ACE.Database/ShardDatabaseOfflineTools.cs index 7f1576212f..1cd4a0d541 100644 --- a/Source/ACE.Database/ShardDatabaseOfflineTools.cs +++ b/Source/ACE.Database/ShardDatabaseOfflineTools.cs @@ -943,6 +943,30 @@ public static void PurgeReleasedBiotasInParallel(ShardDbContext context, int day numberOfBiotasPurged = biotaPurgedTotal; } + public static void PurgeDoDBiotasInParallel(out int numberOfBiotasPurged) + { + using (var context = new ShardDbContext()) + { + context.Database.SetCommandTimeout(900); + PurgeDoDBiotasInParallel(context, out numberOfBiotasPurged); + } + } + + public static void PurgeDoDBiotasInParallel(ShardDbContext context, out int numberOfBiotasPurged) + { + var destroyedIds = context.BiotaPropertiesInt.AsNoTracking().Where(r => r.Type == (ushort)PropertyInt.Bonded && r.Value == (int)BondedStatus.Destroy).Select(r => r.ObjectId).ToHashSet(); + + int biotaPurgedTotal = 0; + + Parallel.ForEach(destroyedIds, ConfigManager.Config.Server.Threading.DatabaseParallelOptions, result => + { + if (PurgeBiota(result, $"Biota has BondedStatus.Destroy")) + Interlocked.Increment(ref biotaPurgedTotal); + }); + + numberOfBiotasPurged = biotaPurgedTotal; + } + public static void PurgeReleasedBiotasInParallel(int daysLimiter, out int numberOfBiotasPurged) { using (var context = new ShardDbContext()) From 2223f6f2a90adaf354ba29126dab38c38951fc5d Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Mon, 29 Aug 2022 13:13:07 -0400 Subject: [PATCH 18/26] Update DeveloperFixCommands.cs --- .../Command/Handlers/DeveloperFixCommands.cs | 230 ++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/Source/ACE.Server/Command/Handlers/DeveloperFixCommands.cs b/Source/ACE.Server/Command/Handlers/DeveloperFixCommands.cs index ebe019416c..5495c31231 100644 --- a/Source/ACE.Server/Command/Handlers/DeveloperFixCommands.cs +++ b/Source/ACE.Server/Command/Handlers/DeveloperFixCommands.cs @@ -1636,5 +1636,235 @@ join name in ctx.BiotaPropertiesString on biota.Id equals name.ObjectId Console.WriteLine($"Verified {results.Count:N0} shields"); } } + + //[CommandHandler("verify-destroy", AccessLevel.Admin, CommandHandlerFlag.ConsoleInvoke, "Verifies and optionally fixes any bugs with player attribute data")] + //public static void HandleVerifyDestroy(Session session, params string[] parameters) + //{ + // var players = PlayerManager.GetAllOffline(); + + // var fix = parameters.Length > 0 && parameters[0].Equals("fix"); + // var fixStr = fix ? " -- fixed" : ""; + // var foundIssues = false; + + // foreach (var player in players) + // { + // var updated = false; + + // //foreach (var attr in new Dictionary(player.Biota.PropertiesAttribute)) + // //{ + // // // ensure this is a valid attribute + // // if (attr.Key < PropertyAttribute.Strength || attr.Key > PropertyAttribute.Self) + // // { + // // Console.WriteLine($"{player.Name} has unknown attribute {attr.Key}{fixStr}"); + // // foundIssues = true; + + // // if (fix) + // // { + // // // i have found no instances of this situation being run into, + // // // but if it does happen, verify-xp will refund the player xp properly + + // // player.Biota.PropertiesAttribute.Remove(attr); + // // updated = true; + // // } + // // continue; + // // } + + // // var rank = attr.Value.LevelFromCP; + + // // // verify attribute rank + // // var correctRank = Player.CalcAttributeRank(attr.Value.CPSpent); + // // if (rank != correctRank) + // // { + // // Console.WriteLine($"{player.Name}'s {attr.Key} rank is {rank}, should be {correctRank}{fixStr}"); + // // foundIssues = true; + + // // if (fix) + // // { + // // attr.Value.LevelFromCP = (ushort)correctRank; + // // updated = true; + // // } + // // } + + // // // verify attribute xp is within bounds + // // var attributeXPTable = DatManager.PortalDat.XpTable.AttributeXpList; + // // var maxAttributeXp = attributeXPTable[attributeXPTable.Count - 1]; + + // // if (attr.Value.CPSpent > maxAttributeXp) + // // { + // // Console.WriteLine($"{player.Name}'s {attr.Key} attribute total xp is {attr.Value.CPSpent:N0}, should be capped at {maxAttributeXp:N0}{fixStr}"); + // // foundIssues = true; + + // // if (fix) + // // { + // // // again i have found no instances of this situation being run into, + // // // but if it does happen, verify-xp will refund the player xp properly + + // // attr.Value.CPSpent = maxAttributeXp; + // // updated = true; + // // } + // // } + // //} + // //if (fix && updated) + // // player.SaveBiotaToDatabase(); + + // var biotasToSave = new System.Collections.ObjectModel.Collection<(ACE.Entity.Models.Biota biota, System.Threading.ReaderWriterLockSlim rwLock)>(); + + // DatabaseManager.Shard.GetPossessedBiotasInParallel(player.Guid.Full, biotas => + // { + // //log.Debug($"GetPossessedBiotasInParallel for {character.Name} took {(DateTime.UtcNow - start).TotalMilliseconds:N0} ms"); + + // //ActionQueue.EnqueueAction(new ActionEventDelegate(() => DoPlayerEnterWorld(session, character, offlinePlayer.Biota, biotas))); + + // //foreach (var biota in biotas.Inventory) + // //{ + + // //} + + // //foreach (var biota in biotas.WieldedItems) + // //{ + + // //} + + // System.Threading.Tasks.Parallel.ForEach(biotas.Inventory.Union(biotas.WieldedItems), ConfigManager.Config.Server.Threading.DatabaseParallelOptions, result => + // { + // var biota = Database.Adapter.BiotaConverter.ConvertToEntityBiota(result); + + // var x = biota.GetProperty(PropertyFloat.ReleasedTimestamp, new()); + + // if (x is not null) + // { + // Console.WriteLine($"Player {player.Name} (0x{player.Guid}) has {biota.GetName()} (0x{biota.Id:X8}) in inventory which has ReleasedTimestamp set to {x.Value} ({Time.GetDateTimeFromTimestamp(x.Value).ToLocalTime()}){fixStr}"); + // if (fix) + // { + // biota.TryRemoveProperty(PropertyFloat.ReleasedTimestamp, new()); + // biotasToSave.Add((biota, new())); + // } + // } + + // }); + // }); + // } + + // if (!fix && foundIssues) + // Console.WriteLine($"Dry run completed. Type 'verify-destroy fix' to fix any issues."); + + // if (!foundIssues) + // Console.WriteLine($"Verified no possessed destroyed items for {players.Count:N0} players"); + //} + + //[CommandHandler("verify-bonded", AccessLevel.Admin, CommandHandlerFlag.ConsoleInvoke, "Verifies and optionally fixes any bugs with player attribute data")] + //public static void HandleVerifyBonded(Session session, params string[] parameters) + //{ + // var players = PlayerManager.GetAllOffline(); + + // var fix = parameters.Length > 0 && parameters[0].Equals("fix"); + // var fixStr = fix ? " -- fixed" : ""; + // var foundIssues = false; + + // foreach (var player in players) + // { + // var updated = false; + + // //foreach (var attr in new Dictionary(player.Biota.PropertiesAttribute)) + // //{ + // // // ensure this is a valid attribute + // // if (attr.Key < PropertyAttribute.Strength || attr.Key > PropertyAttribute.Self) + // // { + // // Console.WriteLine($"{player.Name} has unknown attribute {attr.Key}{fixStr}"); + // // foundIssues = true; + + // // if (fix) + // // { + // // // i have found no instances of this situation being run into, + // // // but if it does happen, verify-xp will refund the player xp properly + + // // player.Biota.PropertiesAttribute.Remove(attr); + // // updated = true; + // // } + // // continue; + // // } + + // // var rank = attr.Value.LevelFromCP; + + // // // verify attribute rank + // // var correctRank = Player.CalcAttributeRank(attr.Value.CPSpent); + // // if (rank != correctRank) + // // { + // // Console.WriteLine($"{player.Name}'s {attr.Key} rank is {rank}, should be {correctRank}{fixStr}"); + // // foundIssues = true; + + // // if (fix) + // // { + // // attr.Value.LevelFromCP = (ushort)correctRank; + // // updated = true; + // // } + // // } + + // // // verify attribute xp is within bounds + // // var attributeXPTable = DatManager.PortalDat.XpTable.AttributeXpList; + // // var maxAttributeXp = attributeXPTable[attributeXPTable.Count - 1]; + + // // if (attr.Value.CPSpent > maxAttributeXp) + // // { + // // Console.WriteLine($"{player.Name}'s {attr.Key} attribute total xp is {attr.Value.CPSpent:N0}, should be capped at {maxAttributeXp:N0}{fixStr}"); + // // foundIssues = true; + + // // if (fix) + // // { + // // // again i have found no instances of this situation being run into, + // // // but if it does happen, verify-xp will refund the player xp properly + + // // attr.Value.CPSpent = maxAttributeXp; + // // updated = true; + // // } + // // } + // //} + // //if (fix && updated) + // // player.SaveBiotaToDatabase(); + + // var biotasToSave = new System.Collections.ObjectModel.Collection<(ACE.Entity.Models.Biota biota, System.Threading.ReaderWriterLockSlim rwLock)>(); + + // DatabaseManager.Shard.GetPossessedBiotasInParallel(player.Guid.Full, biotas => + // { + // //log.Debug($"GetPossessedBiotasInParallel for {character.Name} took {(DateTime.UtcNow - start).TotalMilliseconds:N0} ms"); + + // //ActionQueue.EnqueueAction(new ActionEventDelegate(() => DoPlayerEnterWorld(session, character, offlinePlayer.Biota, biotas))); + + // //foreach (var biota in biotas.Inventory) + // //{ + + // //} + + // //foreach (var biota in biotas.WieldedItems) + // //{ + + // //} + + // System.Threading.Tasks.Parallel.ForEach(biotas.Inventory.Union(biotas.WieldedItems), ConfigManager.Config.Server.Threading.DatabaseParallelOptions, result => + // { + // var biota = Database.Adapter.BiotaConverter.ConvertToEntityBiota(result); + + // var x = biota.GetProperty(PropertyInt.Bonded, new()); + + // if (x is not null && x.Value == (int)BondedStatus.Destroy) + // { + // Console.WriteLine($"Player {player.Name} (0x{player.Guid}) has {biota.GetName()} (0x{biota.Id:X8} - {biota.WeenieClassId}) in possession which has Bonded set to Destroy{fixStr}"); + // if (fix) + // { + // //biota.TryRemoveProperty(PropertyFloat.ReleasedTimestamp, new()); + // //biotasToSave.Add((biota, new())); + // } + // } + + // }); + // }); + // } + + // if (!fix && foundIssues) + // Console.WriteLine($"Dry run completed. Type 'verify-destroy fix' to fix any issues."); + + // if (!foundIssues) + // Console.WriteLine($"Verified no possessed destroyed items for {players.Count:N0} players"); + //} } } From 509ce734cfd37fb47060429a433341b33ad80315 Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Mon, 29 Aug 2022 13:13:11 -0400 Subject: [PATCH 19/26] Update PropertyManager.cs --- Source/ACE.Server/Managers/PropertyManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/ACE.Server/Managers/PropertyManager.cs b/Source/ACE.Server/Managers/PropertyManager.cs index f94719bdee..04a5128d2e 100644 --- a/Source/ACE.Server/Managers/PropertyManager.cs +++ b/Source/ACE.Server/Managers/PropertyManager.cs @@ -624,6 +624,7 @@ public static void LoadDefaultProperties() ("chat_requires_player_level", new Property(0, "the level a player is required to have for global chat privileges")), ("corpse_spam_limit", new Property(15, "the number of corpses a player is allowed to leave on a landblock at one time")), ("default_subscription_level", new Property(1, "retail defaults to 1, 1 = standard subscription (same as 2 and 3), 4 grants ToD pre-order bonus item Asheron's Benediction")), + ("destroy_saves_older_than_seconds", new Property(86400, "the amount of time in seconds a destroyed object, which is not Stuck, must have existed for before it is worth saving from immediate destruction. Object can then later be purged via startup database maintanence")), ("fellowship_even_share_level", new Property(50, "level when fellowship XP sharing is no longer restricted")), ("mansion_min_rank", new Property(6, "overrides the default allegiance rank required to own a mansion")), ("max_chars_per_account", new Property(11, "retail defaults to 11, client supports up to 20")), From cfb236825b5b817523abe86350fc92efd64c0d7d Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Mon, 29 Aug 2022 13:13:16 -0400 Subject: [PATCH 20/26] Update Program.cs --- Source/ACE.Server/Program.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/ACE.Server/Program.cs b/Source/ACE.Server/Program.cs index a330904c03..ba43a3568d 100644 --- a/Source/ACE.Server/Program.cs +++ b/Source/ACE.Server/Program.cs @@ -171,6 +171,13 @@ public static void Main(string[] args) log.Info($"Purged {numberOfBiotasPurged:N0} biotas."); } + //if (ConfigManager.Config.Offline.PurgeBondedDestroyBiotas) + //{ + // log.Info($"Purging Bonded.Destroy biotas..."); + // ShardDatabaseOfflineTools.PurgeDoDBiotasInParallel(out var numberOfBiotasPurged); + // log.Info($"Purged {numberOfBiotasPurged:N0} biotas."); + //} + if (ConfigManager.Config.Offline.PurgeOrphanedBiotas) { log.Info($"Purging orphaned biotas..."); From f8ad27f5e01ce427983137aea0b30219f2998698 Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Mon, 29 Aug 2022 13:13:19 -0400 Subject: [PATCH 21/26] Update WorldObject.cs --- Source/ACE.Server/WorldObjects/WorldObject.cs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/Source/ACE.Server/WorldObjects/WorldObject.cs b/Source/ACE.Server/WorldObjects/WorldObject.cs index 224bb47570..22ef8f6063 100644 --- a/Source/ACE.Server/WorldObjects/WorldObject.cs +++ b/Source/ACE.Server/WorldObjects/WorldObject.cs @@ -844,7 +844,10 @@ public void Destroy(bool raiseNotifyOfDestructionEvent = true, bool fromLandbloc IsDestroyed = true; - ReleasedTimestamp = Time.GetUnixTime(); + var utcNow = DateTime.UtcNow; + var timestamp = Time.GetUnixTime(utcNow); + + ReleasedTimestamp = timestamp; if (this is Container container) { @@ -886,8 +889,24 @@ public void Destroy(bool raiseNotifyOfDestructionEvent = true, bool fromLandbloc var recycleGuids = PropertyManager.GetBool("recycle_guids").Item; var destroyItem = PropertyManager.GetBool("destroy_deletes_from_database").Item; - if (destroyItem || Stuck || ((ValidLocations ?? 0) < EquipMask.HeadWear) || (Container?.Guid.IsStatic() ?? false) || (!Wielder?.Guid.IsPlayer() ?? false) || (Container is Corpse && !Container.Level.HasValue) || (Container is Creature and not Player) || (Container is Chest and not Storage) || (this is Missile) || (this is Ammunition) || fromLandblockUnload) + var creationTimestamp = CreationTimestamp; + var createdOn = creationTimestamp.HasValue ? Time.GetDateTimeFromTimestamp(creationTimestamp.Value) : utcNow; + var destroyLimiter = PropertyManager.GetLong("destroy_saves_older_than_seconds").Item; + var destroyLimit = utcNow.AddSeconds(-destroyLimiter); + var isOlderThanLimit = createdOn < destroyLimit; + + //if (destroyItem || Stuck || ((ValidLocations ?? 0) < EquipMask.HeadWear) || (Container?.Guid.IsStatic() ?? false) || (!Wielder?.Guid.IsPlayer() ?? false) || (Container is Corpse && !Container.Level.HasValue) || (Container is Creature and not Player) || (Container is Chest and not Storage) || (this is Missile) || (this is Ammunition) || fromLandblockUnload) + if (destroyItem || Stuck || (Container is Creature and not Player) || (Container is Chest and not Storage) || (Container?.Guid.IsStatic() ?? false) || (Container is Corpse && !Container.Level.HasValue) || (!Wielder?.Guid.IsPlayer() ?? false) || !isOlderThanLimit) { + if (OwnerId > 0) + OwnerId = null; + if (WielderId > 0) + WielderId = null; + if (ContainerId > 0) + ContainerId = null; + if (Location != null && Location.LandblockId.Raw > 0) + Location = null; + RemoveBiotaFromDatabase(); if (Guid.IsDynamic() && recycleGuids) @@ -902,6 +921,7 @@ public void Destroy(bool raiseNotifyOfDestructionEvent = true, bool fromLandbloc logline += $"({Name} | {WeenieClassId} | 0x{Guid}) "; logline += "has been destroyed but not deleted. "; logline += $"OwnerId: 0x{OwnerId ?? 0:X8} | WielderId: 0x{WielderId ?? 0:X8} | ContainerId: 0x{ContainerId ?? 0:X8}\n"; + logline += $"CreationTimestamp: {createdOn.ToLocalTime():G} ({creationTimestamp})\n"; if (OwnerId > 0) OwnerId = null; if (WielderId > 0) From a957cc2835017c97b1bed0e59a37088608b07c3a Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Wed, 31 Aug 2022 13:23:09 -0400 Subject: [PATCH 22/26] Update ShardDatabaseOfflineTools.cs --- Source/ACE.Database/ShardDatabaseOfflineTools.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/ACE.Database/ShardDatabaseOfflineTools.cs b/Source/ACE.Database/ShardDatabaseOfflineTools.cs index 1cd4a0d541..6bf74063d9 100644 --- a/Source/ACE.Database/ShardDatabaseOfflineTools.cs +++ b/Source/ACE.Database/ShardDatabaseOfflineTools.cs @@ -429,6 +429,7 @@ from c in combined.DefaultIfEmpty() }); } + context.Database.SetCommandTimeout(900); releasedIds = context.BiotaPropertiesFloat.AsNoTracking().Where(r => r.Type == (ushort)PropertyFloat.ReleasedTimestamp).Select(r => r.ObjectId).ToHashSet(); // Purge contained items that belong to a parent container that no longer exists From cc3d8de735bb45049e2bc11e0f3a162129741126 Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Wed, 31 Aug 2022 18:15:48 -0400 Subject: [PATCH 23/26] Update WorldObject.cs --- Source/ACE.Server/WorldObjects/WorldObject.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Source/ACE.Server/WorldObjects/WorldObject.cs b/Source/ACE.Server/WorldObjects/WorldObject.cs index 22ef8f6063..122616fb2e 100644 --- a/Source/ACE.Server/WorldObjects/WorldObject.cs +++ b/Source/ACE.Server/WorldObjects/WorldObject.cs @@ -898,15 +898,6 @@ public void Destroy(bool raiseNotifyOfDestructionEvent = true, bool fromLandbloc //if (destroyItem || Stuck || ((ValidLocations ?? 0) < EquipMask.HeadWear) || (Container?.Guid.IsStatic() ?? false) || (!Wielder?.Guid.IsPlayer() ?? false) || (Container is Corpse && !Container.Level.HasValue) || (Container is Creature and not Player) || (Container is Chest and not Storage) || (this is Missile) || (this is Ammunition) || fromLandblockUnload) if (destroyItem || Stuck || (Container is Creature and not Player) || (Container is Chest and not Storage) || (Container?.Guid.IsStatic() ?? false) || (Container is Corpse && !Container.Level.HasValue) || (!Wielder?.Guid.IsPlayer() ?? false) || !isOlderThanLimit) { - if (OwnerId > 0) - OwnerId = null; - if (WielderId > 0) - WielderId = null; - if (ContainerId > 0) - ContainerId = null; - if (Location != null && Location.LandblockId.Raw > 0) - Location = null; - RemoveBiotaFromDatabase(); if (Guid.IsDynamic() && recycleGuids) From 33e6055663efe54828258a046ee67ab50f09b656 Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Sat, 24 Sep 2022 14:49:53 -0400 Subject: [PATCH 24/26] Update ShardDatabaseOfflineTools.cs --- Source/ACE.Database/ShardDatabaseOfflineTools.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/ACE.Database/ShardDatabaseOfflineTools.cs b/Source/ACE.Database/ShardDatabaseOfflineTools.cs index 6bf74063d9..6a07ad1528 100644 --- a/Source/ACE.Database/ShardDatabaseOfflineTools.cs +++ b/Source/ACE.Database/ShardDatabaseOfflineTools.cs @@ -931,6 +931,8 @@ public static void PurgeReleasedBiotasInParallel(ShardDbContext context, int day { var deleteLimit = Time.GetUnixTime(DateTime.UtcNow.AddDays(-daysLimiter)); + context.Database.SetCommandTimeout(900); + var releasedIds = context.BiotaPropertiesFloat.AsNoTracking().Where(r => r.Type == (ushort)PropertyFloat.ReleasedTimestamp && r.Value <= deleteLimit).Select(r => new { r.ObjectId, r.Value }).AsEnumerable().Select(r => (Id: r.ObjectId, ReleasedTimestamp: r.Value)).ToHashSet(); int biotaPurgedTotal = 0; From 3cf2357aa0e5de2de9d914effac838e84dc82364 Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Mon, 5 Feb 2024 01:53:27 -0500 Subject: [PATCH 25/26] Update OfflineConfiguration.cs --- Source/ACE.Common/OfflineConfiguration.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Source/ACE.Common/OfflineConfiguration.cs b/Source/ACE.Common/OfflineConfiguration.cs index 2210089e67..f6661a3ac5 100644 --- a/Source/ACE.Common/OfflineConfiguration.cs +++ b/Source/ACE.Common/OfflineConfiguration.cs @@ -8,7 +8,7 @@ namespace ACE.Common public class OfflineConfiguration { /// - /// Purge characters that have been deleted longer than PruneDeletedCharactersDays + /// Purge characters that have been deleted longer than PurgeDeletedCharactersDays /// These characters, and their associated biotas, will be deleted permanantly! /// public bool PurgeDeletedCharacters { get; set; } = false; @@ -25,12 +25,15 @@ public class OfflineConfiguration /// public bool PurgeOrphanedBiotas { get; set; } = false; - [System.ComponentModel.DefaultValue(false)] - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] - public bool PurgeReleasedBiotas { get; set; } + /// + /// Purge biota that have been deleted longer than PurgeReleasedBiotasDays + /// These biota will be deleted permanantly! + /// + public bool PurgeReleasedBiotas { get; set; } = false; - [System.ComponentModel.DefaultValue(30)] - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + /// + /// Number of days a biota must have been deleted for before eligible for purging + /// public int PurgeReleasedBiotasDays { get; set; } = 30; /// From 9fa3437477e185574ddac2818c977d6a1ef69047 Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Mon, 5 Feb 2024 01:54:19 -0500 Subject: [PATCH 26/26] Update OfflineConfiguration.cs --- Source/ACE.Common/OfflineConfiguration.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/ACE.Common/OfflineConfiguration.cs b/Source/ACE.Common/OfflineConfiguration.cs index f6661a3ac5..d6b800fef2 100644 --- a/Source/ACE.Common/OfflineConfiguration.cs +++ b/Source/ACE.Common/OfflineConfiguration.cs @@ -9,7 +9,7 @@ public class OfflineConfiguration { /// /// Purge characters that have been deleted longer than PurgeDeletedCharactersDays - /// These characters, and their associated biotas, will be deleted permanantly! + /// These characters, and their associated biotas, will be deleted permanently! /// public bool PurgeDeletedCharacters { get; set; } = false; @@ -27,7 +27,7 @@ public class OfflineConfiguration /// /// Purge biota that have been deleted longer than PurgeReleasedBiotasDays - /// These biota will be deleted permanantly! + /// These biota will be deleted permanently! /// public bool PurgeReleasedBiotas { get; set; } = false;