ace/Source/ACE.Server/WorldObjects/WorldObject_Database.cs
Mag-nus 502e716e8e
Landblock item saving + Much more (#1071)
* Landblock EnqueueBroadcast cleanup

Avoid double cast by using OfType

Change excludeList from IEnumerable to ICollection because it was being enumerated more than once.

* SerializedShard handle OperationCancledException

* SerializedShard break on the exceptions

* Landblock save progress

* Landblock merge fix

* Decayable -> Dynamic

* Improved ShutdownServer to include landblock unload and db queue wait

* Sleep during shutdown instead of using up all the CPU

* Shard GetDynamic was comparing against the wrong id... woops

* Fix shard biota caching

* Improve shutdown logging and actions

* cosmetic code order

* Fix crash when trying to add a bad wo to a landblock

* This fixes reloading dynamic objects

* Don't save item on pickup

* Don't save item on pickup from chest

* Don't save items on drop

* Set Home Position only for creatures (Used for navigation)

* Improve the landblock SaveDB code

* Don't save player corpse on death. Landblock will do that for us in bulk.

* Add the housing objects to statics that persist to the shard

* WorldObjectFactory dont overwrite location if object came from database

* Improved IsStaticThatShouldPersistToShard

* This should fix player dropped missiles

* Move PositionType into ACE.Entity.Enum.Properties

* Ephemeral support for Positions

* Decay code/saving works much better

* Filter out contained and wielded items from shard GetDynamicsByLandblock

* TimeToRot provision for -1 (No rot)

* Corpse decay is now controlled by landblock and uses the same decay code

* ShardDatabase: Fix add/remove biota bug

* WorldObject.Destroy now destroys contained and equipped items as well

* TryRemoveFromInventoryWithNetworking no longer removes the object from the db

* Player corpse items now remain when the corpse decays

* Cleaned up SetPropertiesForWorld
2018-10-23 15:52:09 -05:00

138 lines
6.5 KiB
C#

using System;
using System.Threading;
using ACE.Database;
using ACE.Database.Models.Shard;
using ACE.Entity.Enum;
namespace ACE.Server.WorldObjects
{
partial class WorldObject
{
private readonly bool biotaOriginatedFromDatabase;
public DateTime LastRequestedDatabaseSave { get; protected set; }
/// <summary>
/// This variable is set to true when a change is made, and set to false before a save is requested.<para />
/// The primary use for this is to trigger save on add/modify/remove of properties.
/// </summary>
public bool ChangesDetected { get; set; }
/// <summary>
/// Best practice says you should use this lock any time you read/write the Biota.<para />
/// However, it's only a requirement to do this for properties/collections that will be modified after the initial biota has been created.<para />
/// There are several properties/collections of the biota that are simply duplicates of the original weenie and are never changed. You wouldn't need to use this lock to read those collections.<para />
/// <para />
/// For absolute maximum performance, if you're willing to assume (and risk) the following:<para />
/// - that the biota in the database will not be modified (in a way that adds or removes properties) outside of ACE while ACE is running with a reference to that biota<para />
/// - that the biota will only be read/modified by a single thread in ACE<para />
/// You can remove the lock usage for any Get/GetAll Property functions. You would simply use it for Set/Remove Property functions because each of these could end up adding/removing to the collections.<para />
/// The critical thing is that the collections are not added to or removed from while Entity Framework is iterating over them.<para />
/// Mag-nus 2018-08-19
/// </summary>
public readonly ReaderWriterLockSlim BiotaDatabaseLock = new ReaderWriterLockSlim();
/// <summary>
/// This will set the LastRequestedDatabaseSave to UtcNow and ChangesDetected to false.<para />
/// If enqueueSave is set to true, DatabaseManager.Shard.SaveBiota() will be called for the biota.<para />
/// Set enqueueSave to false if you want to perform all the normal routines for a save but not the actual save. This is useful if you're going to collect biotas in bulk for bulk saving.
/// </summary>
public virtual void SaveBiotaToDatabase(bool enqueueSave = true)
{
// Make sure all of our positions in the biota are up to date with our current cached values.
foreach (var kvp in positionCache)
Biota.SetPosition(kvp.Key, kvp.Value, BiotaDatabaseLock, out _);
LastRequestedDatabaseSave = DateTime.UtcNow;
ChangesDetected = false;
if (enqueueSave)
DatabaseManager.Shard.SaveBiota(Biota, BiotaDatabaseLock, null);
}
/// <summary>
/// This will set the LastRequestedDatabaseSave to MinValue and ChangesDetected to true.<para />
/// If enqueueRemove is set to true, DatabaseManager.Shard.RemoveBiota() will be called for the biota.<para />
/// Set enqueueRemove to false if you want to perform all the normal routines for a remove but not the actual removal. This is useful if you're going to collect biotas in bulk for bulk removing.
/// </summary>
public void RemoveBiotaFromDatabase(bool enqueueRemove = true)
{
// If this entity doesn't exist in the database, let's not queue up work unnecessary database work.
if (!biotaOriginatedFromDatabase && LastRequestedDatabaseSave == DateTime.MinValue)
{
ChangesDetected = true;
return;
}
LastRequestedDatabaseSave = DateTime.MinValue;
ChangesDetected = true;
if (enqueueRemove)
DatabaseManager.Shard.RemoveBiota(Biota, BiotaDatabaseLock, null);
}
/// <summary>
/// A static that should persist to the shard may be a hook with an item, or a house that's been purchased, or a housing chest that isn't empty, etc...<para />
/// If the world object originated from the database or has been saved to the database, this will also return true.
/// </summary>
public bool IsStaticThatShouldPersistToShard()
{
if (!Guid.IsStatic())
return false;
if (biotaOriginatedFromDatabase || LastRequestedDatabaseSave != DateTime.MinValue)
return true;
if (WeenieType == WeenieType.SlumLord && this is SlumLord slumlord)
{
if (slumlord.House != null && slumlord.House.HouseOwner.HasValue && slumlord.House.HouseOwner != 0)
return true;
}
if (WeenieType == WeenieType.House && this is House house)
{
if (house.HouseOwner.HasValue && house.HouseOwner != 0)
return true;
}
if ((WeenieType == WeenieType.Hook || WeenieType == WeenieType.Storage) && this is Container container)
{
if (container.Inventory.Count > 0)
return true;
}
return false;
}
/// <summary>
/// This will filter out the following:<para />
/// Ammunition and Spell projectiles.<para />
/// Monster corpses.<para />
/// Missiles that haven't been saved to the shard yet.<para />
/// If the world object originated from the database or has been saved to the database, this will also return true.
/// </summary>
/// <returns></returns>
public bool IsDecayableThatShouldPersistToShard()
{
if (!IsDecayable())
return false;
if (biotaOriginatedFromDatabase || LastRequestedDatabaseSave != DateTime.MinValue)
return true;
if (WeenieType == WeenieType.Ammunition || WeenieType == WeenieType.ProjectileSpell)
return false;
if (WeenieType == WeenieType.Corpse && this is Corpse corpse && corpse.IsMonster)
return false;
// Missiles are unique. The only missiles that are decayable are ones that already exist in the database.
var missile = Missile;
if (missile.HasValue && missile.Value)
return false;
return true;
}
}
}