using System; using System.Linq; using ACE.Entity; using ACE.Entity.Enum; using ACE.Server.Entity.Actions; using ACE.Server.Managers; using ACE.Server.Network.GameMessages.Messages; namespace ACE.Server.WorldObjects { partial class WorldObject { /// /// The default number of seconds for a object on a landblock to disappear /// Current default is 5 minutes /// protected TimeSpan DefaultTimeToRot { get; set; } = TimeSpan.FromMinutes(5); /// /// A decayable object is one that, when it exists on a landblock, would decay (rot) over time. /// When it rots, it would be destroyed, and removed from the landblock. /// In most cases, these should be player dropped items or corpses. It can also be a missile or spell projectile. /// Items that have a TimeToRot value of -1 will return false. /// Generators and items still linked to a generator will return false. /// public bool IsDecayable() { if (!Guid.IsDynamic()) return false; // Don't rot generators, and items that were generated by a generator // If the item was generated by a generator and then picked up by a player, the wo.Generator property would be set to null. if (IsGenerator || Generator != null) return false; // Don't rot items with lifespan, they'll handle their own version of decay. if (Lifespan.HasValue) return false; if (TimeToRot.HasValue) return TimeToRot != -1; // -1 = Never Rot return true; } private bool decayCompleted; public void Decay(TimeSpan elapsed) { // http://asheron.wikia.com/wiki/Item_Decay if (decayCompleted) return; var previousTTR = TimeToRot; if (!TimeToRot.HasValue) { TimeToRot = DefaultTimeToRot.TotalSeconds; if (this is Corpse && Level.HasValue) log.InfoFormat("[CORPSE] {0} (0x{1}).Decay: TimeToRot had no value, set to {2}", Name, Guid, TimeToRot); return; } var corpse = this as Corpse; if (corpse != null) { if (!corpse.InventoryLoaded) return; if (corpse.Inventory.Count == 0 && TimeToRot.Value > Corpse.EmptyDecayTime) { TimeToRot = Corpse.EmptyDecayTime; if (Level.HasValue && PropertyManager.GetBool("corpse_decay_tick_logging").Item) log.InfoFormat("[CORPSE] {0} (0x{1}).Decay({2}): InventoryLoaded = {3} | Inventory.Count = {4} | previous TimeToRot: {5} | current TimeToRot: {6}", corpse.Name, corpse.Guid, elapsed, corpse.InventoryLoaded, corpse.Inventory.Count, previousTTR, TimeToRot); return; } } if (TimeToRot > 0) { TimeToRot -= elapsed.TotalSeconds; if (this is Corpse && Level.HasValue && PropertyManager.GetBool("corpse_decay_tick_logging").Item) log.InfoFormat("[CORPSE] {0} (0x{1}).Decay({2}): previous TimeToRot: {3} | current TimeToRot: {4}", corpse.Name, corpse.Guid, elapsed, previousTTR, TimeToRot); // Is there still time left? if (TimeToRot > 0) return; TimeToRot = -2; // We force it to -2 to make sure it doesn't end up at 0 or -1. 0 indicates instant rot. -1 indicates no rot. 0 and -1 can be found in weenie defaults if (this is Corpse && Level.HasValue && PropertyManager.GetBool("corpse_decay_tick_logging").Item) log.InfoFormat("[CORPSE] {0} (0x{1}).Decay({2}): previous TimeToRot: {3} | current TimeToRot: {4}", corpse.Name, corpse.Guid, elapsed, previousTTR, TimeToRot); } if (this is Container container && container.IsOpen) { // If you wanted to add a grace period to the container to give Player B more time to open it after Player A closes it, it would go here. return; } // Time to rot has elapsed, time to disappear... decayCompleted = true; // If this is a player corpse, puke out the corpses contents onto the landblock if (corpse != null && !corpse.IsMonster) { var inventoryGUIDs = corpse.Inventory.Keys.ToList(); var pukedItems = ""; foreach (var guid in inventoryGUIDs) { if (corpse.TryRemoveFromInventory(guid, out var item)) { item.Location = new Position(corpse.Location); item.Location.PositionZ += 0.05f * (item.ObjScale ?? 1.0f); item.Placement = ACE.Entity.Enum.Placement.Resting; // This is needed to make items lay flat on the ground. CurrentLandblock.AddWorldObject(item); item.SaveBiotaToDatabase(); pukedItems += $"{item.Name} (0x{item.Guid.Full.ToString("X8")}), "; } } if (pukedItems.EndsWith(", ")) pukedItems = pukedItems.Substring(0, pukedItems.Length - 2); log.Info($"[CORPSE] {corpse.Name} (0x{corpse.Guid}) at {corpse.Location.ToLOCString()} has decayed{((pukedItems == "") ? "" : $" and placed the following items on the landblock: {pukedItems}")}."); } if (corpse != null) { EnqueueBroadcast(new GameMessageScript(Guid, PlayScript.Destroy)); var actionChain = new ActionChain(); actionChain.AddDelaySeconds(1.0f); actionChain.AddAction(this, () => Destroy()); actionChain.EnqueueChain(); } else Destroy(); } public void DeleteObject(Container rootOwner = null) { if (Wielder != null) { if (Wielder is Player player) player.TryDequipObjectWithNetworking(Guid, out _, Player.DequipObjectAction.ConsumeItem); else if (Wielder is Creature creature) creature.TryUnwieldObjectWithBroadcasting(Guid, out _, out _); } else if (rootOwner != null) { if (rootOwner is Player player) player.TryRemoveFromInventoryWithNetworking(Guid, out _, Player.RemoveFromInventoryAction.ConsumeItem); else rootOwner.TryRemoveFromInventory(Guid); } Destroy(); } } }