예제 #1
0
        public override void Close(Player player)
        {
            base.Close(player);

            if (VictimId == null)
            {
                return;
            }

            var victimGuid = new ObjectGuid(VictimId.Value);

            if (!victimGuid.IsPlayer())
            {
                // monster corpses -- after anyone with access to the locked corpse loots,
                // becomes open to anyone? or only after the killer loots?
                IsLooted = true;
            }
            else
            {
                var killerGuid = new ObjectGuid(KillerId ?? 0);

                // player corpses -- after corpse owner or killer loots, becomes open to anyone?
                if (player != null && (player.Guid == killerGuid || player.Guid == victimGuid))
                {
                    IsLooted = true;
                }
            }
        }
예제 #2
0
        /// <summary>
        /// Make sure this is called from within a lock(biotaCacheMutex)
        /// </summary>
        private void TryPerformMaintenance()
        {
            if (lastMaintenanceInterval + MaintenanceInterval > DateTime.UtcNow)
            {
                return;
            }

            var removals = new Collection <uint>();

            foreach (var kvp in biotaCache)
            {
                if (ObjectGuid.IsPlayer(kvp.Key))
                {
                    if (kvp.Value.LastSeen + PlayerBiotaRetentionTime < DateTime.UtcNow)
                    {
                        removals.Add(kvp.Key);
                    }
                }
                else
                {
                    if (kvp.Value.LastSeen + NonPlayerBiotaRetentionTime < DateTime.UtcNow)
                    {
                        removals.Add(kvp.Key);
                    }
                }
            }

            foreach (var removal in removals)
            {
                biotaCache.Remove(removal);
            }

            lastMaintenanceInterval = DateTime.UtcNow;
        }
예제 #3
0
 public override void HandleActionOnCollideEnd(ObjectGuid playerId)
 {
     if (!playerId.IsPlayer())
     {
         return;
     }
     if (Players.Any(k => k == playerId))
     {
         Players.Remove(playerId);
     }
 }
예제 #4
0
        /// <summary>
        /// Restore a WorldObject from the database.
        /// </summary>
        public Container(Biota biota) : base(biota)
        {
            SetEphemeralValues();

            // A player has their possessions passed via the ctor. All other world objects must load their own inventory
            if (!(this is Player) && !ObjectGuid.IsPlayer(ContainerId ?? 0))
            {
                DatabaseManager.Shard.GetInventoryInParallel(biota.Id, false, biotas =>
                {
                    EnqueueAction(new ActionEventDelegate(() => SortBiotasIntoInventory(biotas)));
                });
            }
        }
예제 #5
0
    public Unit GetUnit(WorldObject u, ObjectGuid guid)
    {
        if (guid.IsPlayer())
        {
            return(GetPlayer(u, guid));
        }

        if (guid.IsPet())
        {
            return(GetPet(u, guid));
        }

        return(GetCreature(u, guid));
    }
예제 #6
0
        static bool HandleGameObjectDeleteCommand(StringArguments args, CommandHandler handler)
        {
            // number or [name] Shift-click form |color|Hgameobject:go_guid|h[name]|h|r
            string id = handler.ExtractKeyFromLink(args, "Hgameobject");

            if (string.IsNullOrEmpty(id))
            {
                return(false);
            }

            if (!ulong.TryParse(id, out ulong guidLow) || guidLow == 0)
            {
                return(false);
            }

            Player player = handler.GetSession().GetPlayer();

            // force respawn to make sure we find something
            player.GetMap().RemoveRespawnTime(SpawnObjectType.GameObject, guidLow, true);
            GameObject obj = handler.GetObjectFromPlayerMapByDbGuid(guidLow);

            if (!obj)
            {
                handler.SendSysMessage(CypherStrings.CommandObjnotfound, guidLow);
                return(false);
            }

            ObjectGuid ownerGuid = obj.GetOwnerGUID();

            if (ownerGuid.IsEmpty())
            {
                Unit owner = Global.ObjAccessor.GetUnit(player, ownerGuid);
                if (!owner || !ownerGuid.IsPlayer())
                {
                    handler.SendSysMessage(CypherStrings.CommandDelobjrefercreature, ownerGuid.ToString(), obj.GetGUID().ToString());
                    return(false);
                }

                owner.RemoveGameObject(obj, false);
            }

            obj.SetRespawnTime(0);                                 // not save respawn time
            obj.Delete();
            obj.DeleteFromDB();

            handler.SendSysMessage(CypherStrings.CommandDelobjmessage, obj.GetGUID().ToString());

            return(true);
        }
예제 #7
0
 public override void HandleActionOnCollide(ObjectGuid playerId)
 {
     if (!playerId.IsPlayer())
     {
         return;
     }
     if (!Players.Any(k => k == playerId))
     {
         Players.Add(playerId);
     }
     if (ActionLoop == null)
     {
         ActionLoop = NextActionLoop;
         NextActionLoop.EnqueueChain();
     }
 }
예제 #8
0
파일: Container.cs 프로젝트: riptidedom/ACE
        /// <summary>
        /// Restore a WorldObject from the database.
        /// </summary>
        public Container(Biota biota) : base(biota)
        {
            if (Biota.TryRemoveProperty(PropertyBool.Open, out _, BiotaDatabaseLock))
            {
                ChangesDetected = true;
            }

            SetEphemeralValues();

            // A player has their possessions passed via the ctor. All other world objects must load their own inventory
            if (!(this is Player) && !ObjectGuid.IsPlayer(ContainerId ?? 0))
            {
                DatabaseManager.Shard.GetInventoryInParallel(biota.Id, false, biotas =>
                {
                    EnqueueAction(new ActionEventDelegate(() => SortBiotasIntoInventory(biotas)));
                });
            }
        }
예제 #9
0
        public void HandleActionPutItemInContainer(ObjectGuid itemGuid, ObjectGuid containerGuid, int placement = 0)
        {
            Container container;

            if (containerGuid.IsPlayer())
            {
                container = this;
            }
            else
            {
                // Ok I am going into player pack - not the main pack.

                // TODO pick up here - I have a generic object for a container, need to find out why.
                container = (Container)GetInventoryItem(containerGuid);
            }

            // is this something I already have? If not, it has to be a pickup - do the pickup and out.
            if (!HasInventoryItem(itemGuid) && !HasWieldedItem(itemGuid))
            {
                // This is a pickup into our main pack.
                PickupItem(container, itemGuid, placement, PropertyInstanceId.Container);
                return;
            }

            // Ok, I know my container and I know I must have the item so let's get it.
            WorldObject item = GetInventoryItem(itemGuid);

            // check wilded.
            if (item == null)
            {
                item = GetWieldedItem(itemGuid);
            }

            // Was I equiped?   If so, lets take care of that and unequip
            if (item.WielderId != null)
            {
                UnwieldItem(container, item, placement);
                return;
            }

            // if were are still here, this needs to do a pack pack or main pack move.
            MoveItem(ref item, container, placement);
        }
예제 #10
0
        static bool HandleGameObjectDeleteCommand(StringArguments args, CommandHandler handler)
        {
            // number or [name] Shift-click form |color|Hgameobject:go_guid|h[name]|h|r
            string id = handler.ExtractKeyFromLink(args, "Hgameobject");

            if (string.IsNullOrEmpty(id))
            {
                return(false);
            }

            if (!ulong.TryParse(id, out ulong spawnId) || spawnId == 0)
            {
                return(false);
            }

            GameObject obj = handler.GetObjectFromPlayerMapByDbGuid(spawnId);

            if (obj != null)
            {
                Player     player    = handler.GetSession().GetPlayer();
                ObjectGuid ownerGuid = obj.GetOwnerGUID();
                if (!ownerGuid.IsEmpty())
                {
                    Unit owner = Global.ObjAccessor.GetUnit(player, ownerGuid);
                    if (!owner || !ownerGuid.IsPlayer())
                    {
                        handler.SendSysMessage(CypherStrings.CommandDelobjrefercreature, ownerGuid.ToString(), obj.GetGUID().ToString());
                        return(false);
                    }

                    owner.RemoveGameObject(obj, false);
                }
            }

            if (GameObject.DeleteFromDB(spawnId))
            {
                handler.SendSysMessage(CypherStrings.CommandDelobjmessage, spawnId);
                return(true);
            }

            handler.SendSysMessage(CypherStrings.CommandObjnotfound, obj.GetGUID().ToString());
            return(false);
        }
        public override Biota GetBiota(uint id)
        {
            if (ObjectGuid.IsPlayer(id))
            {
                var context = new ShardDbContext();

                var biota = GetBiota(context, id);

                if (biota != null)
                {
                    BiotaContexts.Add(biota, context);
                }

                return(biota);
            }

            using (var context = new ShardDbContext())
            {
                context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

                return(GetBiota(context, id));
            }
        }
예제 #12
0
 private void TryAddToCache(ShardDbContext context, Biota biota)
 {
     lock (biotaCacheMutex)
     {
         if (ObjectGuid.IsPlayer(biota.Id))
         {
             if (PlayerBiotaRetentionTime > TimeSpan.Zero)
             {
                 biotaCache[biota.Id] = new CacheObject <Biota> {
                     LastSeen = DateTime.UtcNow, Context = context, CachedObject = biota
                 }
             }
             ;
         }
         else if (NonPlayerBiotaRetentionTime > TimeSpan.Zero)
         {
             biotaCache[biota.Id] = new CacheObject <Biota> {
                 LastSeen = DateTime.UtcNow, Context = context, CachedObject = biota
             }
         }
         ;
     }
 }
예제 #13
0
        public override Biota GetBiota(uint id, bool doNotAddToCache = false)
        {
            if (ObjectGuid.IsPlayer(id))
            {
                if (PlayerBiotaRetentionTime > TimeSpan.Zero)
                {
                    var context = new ShardDbContext();

                    var biota = GetBiota(context, id, doNotAddToCache); // This will add the result into the caches

                    return(biota);
                }
            }
            else if (NonPlayerBiotaRetentionTime > TimeSpan.Zero)
            {
                var context = new ShardDbContext();

                var biota = GetBiota(context, id, doNotAddToCache); // This will add the result into the caches

                return(biota);
            }

            return(base.GetBiota(id, doNotAddToCache));
        }
예제 #14
0
 public bool IsAi()
 {
     return(!PlayerGuid.IsPlayer());
 }
예제 #15
0
        /// <summary>
        /// Restore a WorldObject from the database.
        /// </summary>
        public Container(Biota biota) : base(biota)
        {
            if (Biota.TryRemoveProperty(PropertyBool.Open, BiotaDatabaseLock))
            {
                ChangesDetected = true;
            }

            // This is a temporary fix for objects that were loaded with this PR when EncumbranceVal was not treated as ephemeral. 2020-03-28
            // This can be removed later.
            if (Biota.PropertiesInt.ContainsKey(PropertyInt.EncumbranceVal))
            {
                var weenie = DatabaseManager.World.GetCachedWeenie(biota.WeenieClassId);

                if (weenie != null && weenie.PropertiesInt.TryGetValue(PropertyInt.EncumbranceVal, out var value))
                {
                    if (biota.PropertiesInt[PropertyInt.EncumbranceVal] != value)
                    {
                        biota.PropertiesInt[PropertyInt.EncumbranceVal] = value;
                        ChangesDetected = true;
                    }
                }
                else
                {
                    biota.PropertiesInt.Remove(PropertyInt.EncumbranceVal);
                    ChangesDetected = true;
                }
            }

            // This is a temporary fix for objects that were loaded with this PR when Value was not treated as ephemeral. 2020-03-28
            // This can be removed later.
            if (!(this is Creature) && Biota.PropertiesInt.ContainsKey(PropertyInt.Value))
            {
                var weenie = DatabaseManager.World.GetCachedWeenie(biota.WeenieClassId);

                if (weenie != null && weenie.PropertiesInt.TryGetValue(PropertyInt.Value, out var value))
                {
                    if (biota.PropertiesInt[PropertyInt.Value] != value)
                    {
                        biota.PropertiesInt[PropertyInt.Value] = value;
                        ChangesDetected = true;
                    }
                }
                else
                {
                    biota.PropertiesInt.Remove(PropertyInt.Value);
                    ChangesDetected = true;
                }
            }

            InitializePropertyDictionaries();
            SetEphemeralValues(true);

            // A player has their possessions passed via the ctor. All other world objects must load their own inventory
            if (!(this is Player) && !ObjectGuid.IsPlayer(ContainerId ?? 0))
            {
                DatabaseManager.Shard.GetInventoryInParallel(biota.Id, false, biotas =>
                {
                    //EnqueueAction(new ActionEventDelegate(() => SortBiotasIntoInventory(biotas)));
                    SortBiotasIntoInventory(biotas);
                });
            }
        }
예제 #16
0
        public static void HandleServerStatus(Session session, params string[] parameters)
        {
            // This is formatted very similarly to GDL.

            var sb = new StringBuilder();

            var proc = Process.GetCurrentProcess();

            sb.Append($"Server Status:{'\n'}");

            sb.Append($"Host Info: {Environment.OSVersion}, vCPU: {Environment.ProcessorCount}{'\n'}");

            var runTime = DateTime.Now - proc.StartTime;

            sb.Append($"Server Runtime: {(int)runTime.TotalHours}h {runTime.Minutes}m {runTime.Seconds}s{'\n'}");

            sb.Append($"Total CPU Time: {(int)proc.TotalProcessorTime.TotalHours}h {proc.TotalProcessorTime.Minutes}m {proc.TotalProcessorTime.Seconds}s, Threads: {proc.Threads.Count}{'\n'}");

            // todo, add actual system memory used/avail
            sb.Append($"{(proc.PrivateMemorySize64 >> 20):N0} MB used{'\n'}");  // sb.Append($"{(proc.PrivateMemorySize64 >> 20)} MB used, xxxx / yyyy MB physical mem free.{'\n'}");

            sb.Append($"{NetworkManager.GetSessionCount():N0} connections, {NetworkManager.GetUniqueSessionEndpointCount():N0} unique connections, {PlayerManager.GetOnlineCount():N0} players online{'\n'}");
            sb.Append($"Total Accounts Created: {DatabaseManager.Authentication.GetAccountCount():N0}, Total Characters Created: {(PlayerManager.GetOfflineCount() + PlayerManager.GetOnlineCount()):N0}{'\n'}");

            // 330 active objects, 1931 total objects(16777216 buckets.)

            // todo, expand this
            var loadedLandblocks = LandblockManager.GetLoadedLandblocks();
            int dormantLandblocks = 0, activeDungeonLandblocks = 0, dormantDungeonLandblocks = 0;
            int players = 0, creatures = 0, missiles = 0, other = 0, total = 0;

            foreach (var landblock in loadedLandblocks)
            {
                if (landblock.IsDormant)
                {
                    dormantLandblocks++;
                }

                if (landblock.IsDungeon)
                {
                    if (landblock.IsDormant)
                    {
                        dormantDungeonLandblocks++;
                    }
                    else
                    {
                        activeDungeonLandblocks++;
                    }
                }

                foreach (var worldObject in landblock.GetAllWorldObjectsForDiagnostics())
                {
                    if (worldObject is Player)
                    {
                        players++;
                    }
                    else if (worldObject is Creature)
                    {
                        creatures++;
                    }
                    else if (worldObject.Missile ?? false)
                    {
                        missiles++;
                    }
                    else
                    {
                        other++;
                    }

                    total++;
                }
            }
            sb.Append($"Landblocks: {(loadedLandblocks.Count - dormantLandblocks):N0} active ({activeDungeonLandblocks:N0} dungeons), {dormantLandblocks:N0} dormant ({dormantDungeonLandblocks:N0} dungeons), Landblock Groups: {LandblockManager.LandblockGroupsCount:N0} - Players: {players:N0}, Creatures: {creatures:N0}, Missiles: {missiles:N0}, Other: {other:N0}, Total: {total:N0}.{'\n'}"); // 11 total blocks loaded. 11 active. 0 pending dormancy. 0 dormant. 314 unloaded.
            // 11 total blocks loaded. 11 active. 0 pending dormancy. 0 dormant. 314 unloaded.

            if (ServerPerformanceMonitor.IsRunning)
            {
                sb.Append($"Server Performance Monitor - UpdateGameWorld ~5m {ServerPerformanceMonitor.GetEventHistory5m(ServerPerformanceMonitor.MonitorType.UpdateGameWorld_Entire).AverageEventDuration:N3}, ~1h {ServerPerformanceMonitor.GetEventHistory1h(ServerPerformanceMonitor.MonitorType.UpdateGameWorld_Entire).AverageEventDuration:N3} s{'\n'}");
            }
            else
            {
                sb.Append($"Server Performance Monitor - Not running. To start use /serverperformance start{'\n'}");
            }

            sb.Append($"Threading - WorldThreadCount: {ConfigManager.Config.Server.Threading.LandblockManagerParallelOptions.MaxDegreeOfParallelism}, Multithread Physics: {ConfigManager.Config.Server.Threading.MultiThreadedLandblockGroupPhysicsTicking}, Multithread Non-Physics: {ConfigManager.Config.Server.Threading.MultiThreadedLandblockGroupTicking}, DatabaseThreadCount: {ConfigManager.Config.Server.Threading.DatabaseParallelOptions.MaxDegreeOfParallelism}{'\n'}");

            sb.Append($"Physics Cache Counts - BSPCache: {BSPCache.Count:N0}, GfxObjCache: {GfxObjCache.Count:N0}, PolygonCache: {PolygonCache.Count:N0}, VertexCache: {VertexCache.Count:N0}{'\n'}");

            sb.Append($"Total Server Objects: {ServerObjectManager.ServerObjects.Count:N0}{'\n'}");

            sb.Append($"World DB Cache Counts - Weenies: {DatabaseManager.World.GetWeenieCacheCount():N0}, LandblockInstances: {DatabaseManager.World.GetLandblockInstancesCacheCount():N0}, PointsOfInterest: {DatabaseManager.World.GetPointsOfInterestCacheCount():N0}, Cookbooks: {DatabaseManager.World.GetCookbookCacheCount():N0}, Spells: {DatabaseManager.World.GetSpellCacheCount():N0}, Encounters: {DatabaseManager.World.GetEncounterCacheCount():N0}, Events: {DatabaseManager.World.GetEventsCacheCount():N0}{'\n'}");
            sb.Append($"Shard DB Counts - Biotas: {DatabaseManager.Shard.BaseDatabase.GetBiotaCount():N0}{'\n'}");
            if (DatabaseManager.Shard.BaseDatabase is ShardDatabaseWithCaching shardDatabaseWithCaching)
            {
                var biotaIds          = shardDatabaseWithCaching.GetBiotaCacheKeys();
                var playerBiotaIds    = biotaIds.Count(id => ObjectGuid.IsPlayer(id));
                var nonPlayerBiotaIds = biotaIds.Count - playerBiotaIds;
                sb.Append($"Shard DB Cache Counts - Player Biotas: {playerBiotaIds} ~ {shardDatabaseWithCaching.PlayerBiotaRetentionTime.TotalMinutes:N0} m, Non Players {nonPlayerBiotaIds} ~ {shardDatabaseWithCaching.NonPlayerBiotaRetentionTime.TotalMinutes:N0} m{'\n'}");
            }

            sb.Append(GuidManager.GetDynamicGuidDebugInfo() + '\n');

            sb.Append($"Portal.dat has {DatManager.PortalDat.FileCache.Count:N0} files cached of {DatManager.PortalDat.AllFiles.Count:N0} total{'\n'}");
            sb.Append($"Cell.dat has {DatManager.CellDat.FileCache.Count:N0} files cached of {DatManager.CellDat.AllFiles.Count:N0} total{'\n'}");

            CommandHandlerHelper.WriteOutputInfo(session, $"{sb}");
        }
        public override bool RemoveBiota(Biota biota, ReaderWriterLockSlim rwLock)
        {
            if (BiotaContexts.TryGetValue(biota, out var cachedContext))
            {
                BiotaContexts.Remove(biota);

                rwLock.EnterReadLock();
                try
                {
                    cachedContext.Biota.Remove(biota);

                    Exception firstException = null;
retry:

                    try
                    {
                        cachedContext.SaveChanges();

                        if (firstException != null)
                        {
                            log.Debug($"[DATABASE] RemoveBiota 0x{biota.Id:X8}:{biota.GetProperty(PropertyString.Name)} retry succeeded after initial exception of: {firstException.GetFullMessage()}");
                        }

                        return(true);
                    }
                    catch (Exception ex)
                    {
                        if (firstException == null)
                        {
                            firstException = ex;
                            goto retry;
                        }

                        // Character name might be in use or some other fault
                        log.Error($"[DATABASE] RemoveBiota 0x{biota.Id:X8}:{biota.GetProperty(PropertyString.Name)} failed first attempt with exception: {firstException}");
                        log.Error($"[DATABASE] RemoveBiota 0x{biota.Id:X8}:{biota.GetProperty(PropertyString.Name)} failed second attempt with exception: {ex}");
                        return(false);
                    }
                }
                finally
                {
                    rwLock.ExitReadLock();

                    cachedContext.Dispose();
                }
            }

            if (!ObjectGuid.IsPlayer(biota.Id))
            {
                using (var context = new ShardDbContext())
                {
                    var existingBiota = context.Biota
                                        .AsNoTracking()
                                        .FirstOrDefault(r => r.Id == biota.Id);

                    if (existingBiota == null)
                    {
                        return(true);
                    }

                    rwLock.EnterWriteLock();
                    try
                    {
                        context.Biota.Remove(biota);

                        Exception firstException = null;
retry:

                        try
                        {
                            context.SaveChanges();

                            if (firstException != null)
                            {
                                log.Debug($"[DATABASE] RemoveBiota 0x{biota.Id:X8}:{biota.GetProperty(PropertyString.Name)} retry succeeded after initial exception of: {firstException.GetFullMessage()}");
                            }

                            return(true);
                        }
                        catch (Exception ex)
                        {
                            if (firstException == null)
                            {
                                firstException = ex;
                                goto retry;
                            }

                            // Character name might be in use or some other fault
                            log.Error($"[DATABASE] RemoveBiota 0x{biota.Id:X8}:{biota.GetProperty(PropertyString.Name)} failed first attempt with exception: {firstException}");
                            log.Error($"[DATABASE] RemoveBiota 0x{biota.Id:X8}:{biota.GetProperty(PropertyString.Name)} failed second attempt with exception: {ex}");
                            return(false);
                        }
                    }
                    finally
                    {
                        rwLock.ExitWriteLock();
                    }
                }
            }

            // If we got here, the biota didn't come from the database through this class.
            // Most likely, it doesn't exist in the database, so, no need to remove.
            return(true);
        }
        public override bool SaveBiota(Biota biota, ReaderWriterLockSlim rwLock)
        {
            if (BiotaContexts.TryGetValue(biota, out var cachedContext))
            {
                rwLock.EnterReadLock();
                try
                {
                    SetBiotaPopulatedCollections(biota);

                    Exception firstException = null;
retry:

                    try
                    {
                        cachedContext.SaveChanges();

                        if (firstException != null)
                        {
                            log.Debug($"[DATABASE] SaveBiota 0x{biota.Id:X8}:{biota.GetProperty(PropertyString.Name)} retry succeeded after initial exception of: {firstException.GetFullMessage()}");
                        }

                        return(true);
                    }
                    catch (Exception ex)
                    {
                        if (firstException == null)
                        {
                            firstException = ex;
                            goto retry;
                        }

                        // Character name might be in use or some other fault
                        log.Error($"[DATABASE] SaveBiota 0x{biota.Id:X8}:{biota.GetProperty(PropertyString.Name)} failed first attempt with exception: {firstException}");
                        log.Error($"[DATABASE] SaveBiota 0x{biota.Id:X8}:{biota.GetProperty(PropertyString.Name)} failed second attempt with exception: {ex}");
                        return(false);
                    }
                }
                finally
                {
                    rwLock.ExitReadLock();
                }
            }

            if (ObjectGuid.IsPlayer(biota.Id))
            {
                var context = new ShardDbContext();

                BiotaContexts.Add(biota, context);

                rwLock.EnterReadLock();
                try
                {
                    SetBiotaPopulatedCollections(biota);

                    context.Biota.Add(biota);

                    Exception firstException = null;
retry:

                    try
                    {
                        context.SaveChanges();

                        if (firstException != null)
                        {
                            log.Debug($"[DATABASE] SaveBiota 0x{biota.Id:X8}:{biota.GetProperty(PropertyString.Name)} retry succeeded after initial exception of: {firstException.GetFullMessage()}");
                        }

                        return(true);
                    }
                    catch (Exception ex)
                    {
                        if (firstException == null)
                        {
                            firstException = ex;
                            goto retry;
                        }

                        // Character name might be in use or some other fault
                        log.Error($"[DATABASE] SaveBiota 0x{biota.Id:X8}:{biota.GetProperty(PropertyString.Name)} failed first attempt with exception: {firstException}");
                        log.Error($"[DATABASE] SaveBiota 0x{biota.Id:X8}:{biota.GetProperty(PropertyString.Name)} failed second attempt with exception: {ex}");
                        return(false);
                    }
                }
                finally
                {
                    rwLock.ExitReadLock();
                }
            }

            using (var context = new ShardDbContext())
            {
                var existingBiota = GetBiota(context, biota.Id);

                rwLock.EnterReadLock();
                try
                {
                    SetBiotaPopulatedCollections(biota);

                    if (existingBiota == null)
                    {
                        context.Biota.Add(biota);
                    }
                    else
                    {
                        UpdateBiota(context, existingBiota, biota);
                    }

                    Exception firstException = null;
retry:

                    try
                    {
                        context.SaveChanges();

                        if (firstException != null)
                        {
                            log.Debug($"[DATABASE] SaveBiota 0x{biota.Id:X8}:{biota.GetProperty(PropertyString.Name)} retry succeeded after initial exception of: {firstException.GetFullMessage()}");
                        }

                        return(true);
                    }
                    catch (Exception ex)
                    {
                        if (firstException == null)
                        {
                            firstException = ex;
                            goto retry;
                        }

                        // Character name might be in use or some other fault
                        log.Error($"[DATABASE] SaveBiota 0x{biota.Id:X8}:{biota.GetProperty(PropertyString.Name)} failed first attempt with exception: {firstException}");
                        log.Error($"[DATABASE] SaveBiota 0x{biota.Id:X8}:{biota.GetProperty(PropertyString.Name)} failed second attempt with exception: {ex}");
                        return(false);
                    }
                }
                finally
                {
                    rwLock.ExitReadLock();
                }
            }
        }