コード例 #1
0
 internal static void Open(Player player)
 {
     WorldStatus = WorldStatusState.Open;
     PlayerManager.BroadcastToAuditChannel(player, "World is now open");
 }
コード例 #2
0
        /// <summary>
        /// Threaded task created when performing a server shutdown
        /// </summary>
        private static void ShutdownServer()
        {
            var shutdownTime = DateTime.UtcNow.AddSeconds(ShutdownInterval);

            ShutdownTime = shutdownTime;

            var lastNoticeTime = DateTime.UtcNow;

            // wait for shutdown interval to expire
            while (shutdownTime != DateTime.MinValue && shutdownTime >= DateTime.UtcNow)
            {
                // this allows the server shutdown to be canceled
                if (!ShutdownInitiated)
                {
                    // reset shutdown details
                    string shutdownText = $"The server has canceled the shutdown procedure @ {DateTime.UtcNow} UTC";
                    log.Info(shutdownText);

                    // special text
                    foreach (var player in PlayerManager.GetAllOnline())
                    {
                        player.Session.WorldBroadcast(shutdownText);
                    }

                    // break function
                    return;
                }

                lastNoticeTime = NotifyPlayersOfPendingShutdown(lastNoticeTime, shutdownTime.AddSeconds(1));

                Thread.Sleep(10);
            }

            ShutdownInProgress = true;

            PropertyManager.ResyncVariables();
            PropertyManager.StopUpdating();

            WorldManager.EnqueueAction(new ActionEventDelegate(() =>
            {
                log.Debug("Logging off all players...");

                // logout each player
                foreach (var player in PlayerManager.GetAllOnline())
                {
                    player.Session.LogOffPlayer(true);
                }
            }));

            // Wait for all players to log out
            var logUpdateTS = DateTime.MinValue;
            int playerCount;

            while ((playerCount = PlayerManager.GetOnlineCount()) > 0)
            {
                logUpdateTS = LogStatusUpdate(logUpdateTS, $"Waiting for {playerCount} player{(playerCount > 1 ? "s" : "")} to log off...");
                Thread.Sleep(10);
            }

            WorldManager.EnqueueAction(new ActionEventDelegate(() =>
            {
                log.Debug("Disconnecting all sessions...");

                // disconnect each session
                NetworkManager.DisconnectAllSessionsForShutdown();
            }));

            // Wait for all sessions to drop out
            logUpdateTS = DateTime.MinValue;
            int sessionCount;

            while ((sessionCount = NetworkManager.GetSessionCount()) > 0)
            {
                logUpdateTS = LogStatusUpdate(logUpdateTS, $"Waiting for {sessionCount} session{(sessionCount > 1 ? "s" : "")} to disconnect...");
                Thread.Sleep(10);
            }

            log.Debug("Adding all landblocks to destruction queue...");

            // Queue unloading of all the landblocks
            // The actual unloading will happen in WorldManager.UpdateGameWorld
            LandblockManager.AddAllActiveLandblocksToDestructionQueue();

            // Wait for all landblocks to unload
            logUpdateTS = DateTime.MinValue;
            int landblockCount;

            while ((landblockCount = LandblockManager.GetLoadedLandblocks().Count) > 0)
            {
                logUpdateTS = LogStatusUpdate(logUpdateTS, $"Waiting for {landblockCount} loaded landblock{(landblockCount > 1 ? "s" : "")} to unload...");
                Thread.Sleep(10);
            }

            log.Debug("Stopping world...");

            // Disabled thread update loop
            WorldManager.StopWorld();

            // Wait for world to end
            logUpdateTS = DateTime.MinValue;
            while (WorldManager.WorldActive)
            {
                logUpdateTS = LogStatusUpdate(logUpdateTS, "Waiting for world to stop...");
                Thread.Sleep(10);
            }

            log.Info("Saving OfflinePlayers that have unsaved changes...");
            PlayerManager.SaveOfflinePlayersWithChanges();

            // Wait for the database queue to empty
            logUpdateTS = DateTime.MinValue;
            int shardQueueCount;

            while ((shardQueueCount = DatabaseManager.Shard.QueueCount) > 0)
            {
                logUpdateTS = LogStatusUpdate(logUpdateTS, $"Waiting for database queue ({shardQueueCount}) to empty...");
                Thread.Sleep(10);
            }

            // Write exit to console/log
            log.Info($"Exiting at {DateTime.UtcNow}");

            // System exit
            Environment.Exit(Environment.ExitCode);
        }
コード例 #3
0
        public static void HandlePlayerDelete(uint playerGuid)
        {
            var player = PlayerManager.FindByGuid(playerGuid);

            if (player == null)
            {
                Console.WriteLine($"AllegianceManager.HandlePlayerDelete({playerGuid:X8}): couldn't find player guid");
                return;
            }
            var allegiance = GetAllegiance(player);

            if (allegiance == null)
            {
                return;
            }

            allegiance.Members.TryGetValue(player.Guid, out var allegianceNode);

            var players = new List <IPlayer>()
            {
                player
            };

            if (player.PatronId != null)
            {
                var patron = PlayerManager.FindByGuid(player.PatronId.Value);

                if (patron != null)
                {
                    players.Add(patron);
                }
            }

            player.PatronId  = null;
            player.MonarchId = null;

            // vassals now become monarchs...
            foreach (var vassal in allegianceNode.Vassals.Values)
            {
                var vassalPlayer = PlayerManager.FindByGuid(vassal.PlayerGuid, out bool isOnline);

                if (vassalPlayer == null)
                {
                    continue;
                }

                vassalPlayer.PatronId  = null;
                vassalPlayer.MonarchId = null;

                players.Add(vassal.Player);
            }

            RemoveCache(allegiance);

            // rebuild for those directly involved
            foreach (var p in players)
            {
                Rebuild(GetAllegiance(p));
            }

            foreach (var p in players)
            {
                LoadPlayer(p);
            }

            foreach (var p in players)
            {
                HandleNoAllegiance(p);
            }

            // save immediately?
            foreach (var p in players)
            {
                var offline = PlayerManager.GetOfflinePlayer(p.Guid);
                if (offline != null)
                {
                    offline.SaveBiotaToDatabase();
                }
                else
                {
                    var online = PlayerManager.GetOnlinePlayer(p.Guid);
                    if (online != null)
                    {
                        online.SaveBiotaToDatabase();
                    }
                }
            }
        }
コード例 #4
0
        /// <summary>
        /// Manages updating all entities on the world.
        ///  - Server-side command-line commands are handled in their own thread.
        ///  - Database I/O is handled in its own thread.
        ///  - Network commands come from their own listener threads, and are queued for each sessions which are then processed here.
        ///  - This thread does the rest of the work!
        /// </summary>
        private static void UpdateWorld()
        {
            log.DebugFormat("Starting UpdateWorld thread");

            WorldActive = true;
            var worldTickTimer = new Stopwatch();

            while (!pendingWorldStop)
            {
                /*
                 * When it comes to thread safety for Landblocks and WorldObjects, ACE makes the following assumptions:
                 *
                 * Inbound ClientMessages and GameActions are handled on the main UpdateWorld thread.
                 * - These actions may load Landblocks and modify other WorldObjects safely.
                 *
                 * PlayerEnterWorld queue is run on the main UpdateWorld thread.
                 * - These actions may load Landblocks and modify other WorldObjects safely.
                 *
                 * Landblock Groups (calculated by LandblockManager) can be processed in parallel.
                 *
                 * Adjacent Landblocks will always be run on the same thread.
                 *
                 * Non-adjacent landblocks might be run on different threads.
                 * - If two non-adjacent landblocks both touch the same landblock, and that landblock is active, they will be run on the same thread.
                 *
                 * Database results are returned from a task spawned in SerializedShardDatabase (via callback).
                 * - Minimal processing should be done from the callback. Return as quickly as possible to let the database thread do database work.
                 * - The processing of these results should be queued to an ActionQueue
                 *
                 * The only cases where it's acceptable for to create a new Task, Thread or Parallel loop are the following:
                 * - Every scenario must be one where you don't care about breaking ACE
                 * - DeveloperCommand Handlers
                 */

                worldTickTimer.Restart();

                ServerPerformanceMonitor.RestartEvent(ServerPerformanceMonitor.MonitorType.PlayerManager_Tick);
                PlayerManager.Tick();
                ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.PlayerManager_Tick);

                ServerPerformanceMonitor.RestartEvent(ServerPerformanceMonitor.MonitorType.NetworkManager_InboundClientMessageQueueRun);
                NetworkManager.InboundMessageQueue.RunActions();
                ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.NetworkManager_InboundClientMessageQueueRun);

                // This will consist of PlayerEnterWorld actions, as well as other game world actions that require thread safety
                ServerPerformanceMonitor.RestartEvent(ServerPerformanceMonitor.MonitorType.actionQueue_RunActions);
                actionQueue.RunActions();
                ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.actionQueue_RunActions);

                ServerPerformanceMonitor.RestartEvent(ServerPerformanceMonitor.MonitorType.DelayManager_RunActions);
                DelayManager.RunActions();
                ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.DelayManager_RunActions);

                ServerPerformanceMonitor.RestartEvent(ServerPerformanceMonitor.MonitorType.UpdateGameWorld);
                var gameWorldUpdated = UpdateGameWorld();
                ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.UpdateGameWorld);

                ServerPerformanceMonitor.RestartEvent(ServerPerformanceMonitor.MonitorType.NetworkManager_DoSessionWork);
                int sessionCount = NetworkManager.DoSessionWork();
                ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.NetworkManager_DoSessionWork);

                ServerPerformanceMonitor.Tick();

                // We only relax the CPU if our game world is able to update at the target rate.
                // We do not sleep if our game world just updated. This is to prevent the scenario where our game world can't keep up. We don't want to add further delays.
                // If our game world is able to keep up, it will not be updated on most ticks. It's on those ticks (between updates) that we will relax the CPU.
                if (!gameWorldUpdated)
                {
                    Thread.Sleep(sessionCount == 0 ? 10 : 1); // Relax the CPU more if no sessions are connected
                }
                Timers.PortalYearTicks += worldTickTimer.Elapsed.TotalSeconds;
            }

            // World has finished operations and concedes the thread to garbage collection
            WorldActive = false;
        }
コード例 #5
0
        public static void PassXP(AllegianceNode vassalNode, ulong amount, bool direct)
        {
            // http://asheron.wikia.com/wiki/Allegiance_Experience

            // Pre-patch:
            // Vassal-to-patron pass-up has no effective cap, but patron-to-grandpatron pass-up caps with an effective Loyalty of 175.
            // If you're sworn for 10 days to the same patron, the base Loyalty required to maximize the pass-up from your vassal through you to your patron is only 88.
            // Take into account level 7 enchantments, and you can see how there's practically no need to spend XP on the skill, due to the ease in reaching the cap.
            // Leadership is arguably worse off. In theory, you need to train Leadership and spend XP on it in order to get maximum results.
            // However, effective Leadership is multiplied by two factors: how many vassals you have, and how long they have been sworn to you, with the emphasis on the number of vassals you have.
            // The effect of Leadership on pass-up caps at around 165 effective Leadership, or 83 base Leadership before the modifier.
            // The end result of this is that if you have two active vassals and you can get 10 mules sworn underneath you for an average of 5 in-game days,
            // you never need to raise your Leadership beyond 83. Again, take into account level 7 enchantments and you can see why few people even bother training the skill. It's just too easy to reach the cap.

            // Post-patch:
            // - Leadership and Loyalty are not based on Self attribute
            // - Effective Loyalty is still modified by the time you have spent sworn to your patron
            // - Effective Leadership is still modified by number of vassals and average time that they have been sworn to you,
            //   but the emphasis is on the "time sworn" side and not on the "number of vassals" side. In fact, the vassals
            //   required to achieve the maximum benefit has been decreased from 12 to 4. This is to reduce the incentive of having non-playing vassals.
            // - For both Loyalty and Leadership, the time sworn modifier will now be a factor of both in-game time and real time.
            // - Most importantly, Leadership and Loyalty no longer "cap"

            // XP pass-up:
            // - New minimums and maximums
            // - Vassal-to-patron pass-up will have a minimum of 25% of earned XP, and a maximum of 90% of earned XP.
            //   Under the old system, the minimum was about 9% of earned XP, and the effective maximum was somewhere near 44% of earned XP.
            // - Patron-to-grandpatron pass-up will have a minimum of 0% of XP passed-up by the patron's vassal, and a maximum of 10% of passed-up XP.
            //   Under the old system, the minimum was about 30% and the maximum was about 94%.

            // Original system: up to January 12, 2004
            // Follow-up: all XP instead of just kill XP: October 2009

            // Formulas:
            // http://asheron.wikia.com/wiki/XP_Passup

            // Thanks for Xerxes of Thistledown, who verified accuracy over four months of testing and research!

            // Generated % - Percentage of XP passed to the patron through the vassal's earned XP (hunting and most quests).
            // Received % - Percentage of XP that patron will receive from his vassal's Generated XP.
            // Passup % -  Percentage of XP actually received by patron from vassal's earned XP (hunting and most quests).

            // Generated % = 50.0 + 22.5 * (loyalty / 291) * (1.0 + (RT/730) * (IG/720))
            // Received % = 50.0 + 22.5 * (leadership / 291) * (1.0 + V * (RT2/730) * (IG2/720))
            // Passup % = Generated% * Received% / 100.0

            // Where:
            // Loyalty = Buffed Loyalty (291 max)
            // Leadership = Buffed Leadership (291 max)
            // RT = actual real time sworn to patron in days (730 max)
            // IG = actual in-game time sworn to patron in hours (720 max)
            // RT2 = average real time sworn to patron for all vassals in days (730 max)
            // IG2 = average in-game time sworn to patron for all vassals in hours (720 max)
            // V = vassal factor(1 = 0.25, 2 = 0.50, 3 = 0.75, 4 + = 1.00) (1.0 max)

            var patronNode = vassalNode.Patron;

            if (patronNode == null)
            {
                return;
            }

            var vassal = vassalNode.Player;
            var patron = patronNode.Player;

            var loyalty    = Math.Min(vassal.GetCurrentLoyalty(), SkillCap);
            var leadership = Math.Min(patron.GetCurrentLeadership(), SkillCap);

            var timeReal = Math.Min(RealCap, RealCap);
            var timeGame = Math.Min(GameCap, GameCap);

            var timeRealAvg = Math.Min(RealCap, RealCap);
            var timeGameAvg = Math.Min(GameCap, GameCap);

            var vassalFactor = Math.Min(0.25f * patronNode.TotalVassals, 1.0f);

            var factor1 = direct ? 50.0f : 16.0f;
            var factor2 = direct ? 22.5f : 8.0f;

            var generated = (factor1 + factor2 * (loyalty / SkillCap) * (1.0f + (timeReal / RealCap) * (timeGame / GameCap))) * 0.01f;
            var received  = (factor1 + factor2 * (leadership / SkillCap) * (1.0f + vassalFactor * (timeRealAvg / RealCap) * (timeGameAvg / GameCap))) * 0.01f;
            var passup    = generated * received;

            var generatedAmount = (uint)(amount * generated);
            var passupAmount    = (uint)(amount * passup);

            /*Console.WriteLine("---");
             * Console.WriteLine("AllegianceManager.PassXP(" + amount + ")");
             * Console.WriteLine("Vassal: " + vassal.Name);
             * Console.WriteLine("Patron: " + patron.Name);
             *
             * Console.WriteLine("Generated: " + Math.Round(generated * 100, 2) + "%");
             * Console.WriteLine("Received: " + Math.Round(received * 100, 2) + "%");
             * Console.WriteLine("Passup: " + Math.Round(passup * 100, 2) + "%");
             *
             * Console.WriteLine("Generated amount: " + generatedAmount);
             * Console.WriteLine("Passup amount: " + passupAmount);*/

            if (passupAmount > 0)
            {
                //vassal.CPTithed += generatedAmount;
                //patron.CPCached += passupAmount;
                //patron.CPPoolToUnload += passupAmount;

                vassal.AllegianceXPGenerated += generatedAmount;
                patron.AllegianceXPCached    += passupAmount;

                var onlinePatron = PlayerManager.GetOnlinePlayer(patron.Guid);
                if (onlinePatron != null)
                {
                    onlinePatron.AddAllegianceXP();
                }

                // call recursively
                PassXP(patronNode, passupAmount, false);
            }
        }
コード例 #6
0
 /// <summary>
 /// Returns a list of all players under a monarch
 /// </summary>
 public static List <IPlayer> FindAllPlayers(ObjectGuid monarchGuid)
 {
     return(PlayerManager.FindAllByMonarch(monarchGuid));
 }
コード例 #7
0
        /// <summary>
        /// Handles the eviction process for a player house
        /// </summary>
        public static void HandleEviction(House house, uint playerGuid, bool multihouse = false)
        {
            // clear out slumlord inventory
            var slumlord = house.SlumLord;

            slumlord.ClearInventory(true);

            var player = PlayerManager.FindByGuid(playerGuid, out bool isOnline);

            if (!PropertyManager.GetBool("house_rent_enabled", true).Item&& !multihouse)
            {
                // rent disabled, push forward
                var purchaseTime = (uint)(player.HousePurchaseTimestamp ?? 0);
                var nextRentTime = house.GetRentDue(purchaseTime);
                player.HouseRentTimestamp = (int)nextRentTime;

                log.Info($"HouseManager.HandleRentPaid({player.Name}): house rent disabled via config");

                // re-add item to queue
                AddRentQueue(player, house);
                return;
            }

            // handle eviction
            house.HouseOwner     = null;
            house.MonarchId      = null;
            house.HouseOwnerName = null;

            house.ClearPermissions();

            house.SaveBiotaToDatabase();

            // relink
            house.UpdateLinks();

            // player slumlord 'off' animation
            var off = new Motion(MotionStance.Invalid, MotionCommand.Off);

            slumlord.CurrentMotionState = off;
            slumlord.EnqueueBroadcastMotion(off);

            // reset slumlord name
            var weenie = DatabaseManager.World.GetCachedWeenie(slumlord.WeenieClassId);
            var wo     = WorldObjectFactory.CreateWorldObject(weenie, ObjectGuid.Invalid);

            slumlord.Name = wo.Name;

            slumlord.EnqueueBroadcast(new GameMessagePublicUpdatePropertyString(slumlord, PropertyString.Name, wo.Name));
            slumlord.SaveBiotaToDatabase();

            // if evicting a multihouse owner's previous house,
            // no update for player properties
            if (player.HouseInstance == house.Guid.Full)
            {
                player.HouseId       = null;
                player.HouseInstance = null;
                //player.HousePurchaseTimestamp = null;
                player.HouseRentTimestamp = null;
            }
            else
            {
                log.Warn($"HouseManager.HandleRentEviction({house.Guid}, {player.Name}, {multihouse}): house guids don't match {player.HouseInstance}");
            }

            house.ClearRestrictions();

            log.Info($"HouseManager.HandleRentEviction({player.Name})");

            if (multihouse)
            {
                RemoveRentQueue(house.Guid.Full);

                player.SaveBiotaToDatabase();

                return;
            }

            if (!isOnline)
            {
                // inform player of eviction when they log in
                var offlinePlayer = PlayerManager.GetOfflinePlayer(playerGuid);
                if (offlinePlayer == null)
                {
                    log.Warn($"{player.Name}.HandleEviction(): couldn't find offline player");
                    return;
                }
                offlinePlayer.SetProperty(PropertyBool.HouseEvicted, true);
                offlinePlayer.SaveBiotaToDatabase();
                return;
            }

            var onlinePlayer = PlayerManager.GetOnlinePlayer(playerGuid);

            onlinePlayer.House = null;

            // send text message
            onlinePlayer.Session.Network.EnqueueSend(new GameMessageSystemChat("You abandon your house!", ChatMessageType.Broadcast));
            onlinePlayer.RemoveDeed();

            onlinePlayer.SaveBiotaToDatabase();

            // clear house panel for online player
            var actionChain = new ActionChain();

            actionChain.AddDelaySeconds(3.0f);  // wait for slumlord inventory biotas above to save
            actionChain.AddAction(onlinePlayer, onlinePlayer.HandleActionQueryHouse);
            actionChain.EnqueueChain();
        }
コード例 #8
0
        /// <summary>
        /// Queries the status of multi-house owners on the server
        /// </summary>
        private static void QueryMultiHouse()
        {
            var slumlordBiotas = DatabaseManager.Shard.GetBiotasByType(WeenieType.SlumLord);

            var playerHouses  = new Dictionary <IPlayer, List <Biota> >();
            var accountHouses = new Dictionary <string, List <Biota> >();

            foreach (var slumlord in slumlordBiotas)
            {
                var biotaOwner = slumlord.BiotaPropertiesIID.FirstOrDefault(i => i.Type == (ushort)PropertyInstanceId.HouseOwner);
                if (biotaOwner == null)
                {
                    // this is fine. this is just a house that was purchased, and then later abandoned
                    //Console.WriteLine($"HouseManager.QueryMultiHouse(): couldn't find owner for house {slumlord.Id:X8}");
                    continue;
                }
                var owner = PlayerManager.FindByGuid(biotaOwner.Value);
                if (owner == null)
                {
                    Console.WriteLine($"HouseManager.QueryMultiHouse(): couldn't find owner {biotaOwner.Value:X8}");
                    continue;
                }

                if (!playerHouses.TryGetValue(owner, out var houses))
                {
                    houses = new List <Biota>();
                    playerHouses.Add(owner, houses);
                }
                houses.Add(slumlord);

                var accountName = owner.Account != null ? owner.Account.AccountName : "NULL";

                if (!accountHouses.TryGetValue(accountName, out var aHouses))
                {
                    aHouses = new List <Biota>();
                    accountHouses.Add(accountName, aHouses);
                }
                aHouses.Add(slumlord);
            }


            if (PropertyManager.GetBool("house_per_char").Item)
            {
                var results = playerHouses.Where(i => i.Value.Count() > 1).OrderByDescending(i => i.Value.Count());

                if (results.Count() > 0)
                {
                    Console.WriteLine("Multi-house owners:");
                }

                foreach (var playerHouse in results)
                {
                    Console.WriteLine($"{playerHouse.Key.Name}: {playerHouse.Value.Count}");

                    for (var i = 0; i < playerHouse.Value.Count; i++)
                    {
                        Console.WriteLine($"{i + 1}. {GetCoords(playerHouse.Value[i])}");
                    }
                }
            }
            else
            {
                var results = accountHouses.Where(i => i.Value.Count() > 1).OrderByDescending(i => i.Value.Count());

                if (results.Count() > 0)
                {
                    Console.WriteLine("Multi-house owners:");
                }

                foreach (var accountHouse in results)
                {
                    Console.WriteLine($"{accountHouse.Key}: {accountHouse.Value.Count}");

                    for (var i = 0; i < accountHouse.Value.Count; i++)
                    {
                        Console.WriteLine($"{i + 1}. {GetCoords(accountHouse.Value[i])}");
                    }
                }
            }
        }
コード例 #9
0
        public static async void HandleEviction(PlayerHouse playerHouse)
        {
            // todo: copied from Player_House.HandleActionAbandonHouse, move to House.Abandon()
            // todo: get online copy of house
            var house = playerHouse.House;

            // clear out slumlord inventory
            // todo: get online copy of house
            var slumlord = house.SlumLord;

            slumlord.ClearInventory(true);

            var player = PlayerManager.FindByGuid(playerHouse.PlayerGuid, out bool isOnline);

            if (!PropertyManager.GetBool("house_rent_enabled", true).Item)
            {
                // rent disabled, push forward
                var purchaseTime = (uint)(player.HousePurchaseTimestamp ?? 0);
                var nextRentTime = house.GetRentDue(purchaseTime);
                player.HouseRentTimestamp = (int)nextRentTime;

                log.Info($"HouseManager.HandleRentPaid({playerHouse.PlayerName}): house rent disabled via config");

                BuildRentQueue();
                return;
            }

            house.HouseOwner     = null;
            house.MonarchId      = null;
            house.HouseOwnerName = null;

            house.ClearPermissions();

            house.SaveBiotaToDatabase();

            // relink
            house.UpdateLinks();

            // player slumlord 'off' animation
            var off = new Motion(MotionStance.Invalid, MotionCommand.Off);

            slumlord.CurrentMotionState = off;
            slumlord.EnqueueBroadcastMotion(off);

            // reset slumlord name
            var weenie = DatabaseManager.World.GetCachedWeenie(slumlord.WeenieClassId);
            var wo     = WorldObjectFactory.CreateWorldObject(weenie, ObjectGuid.Invalid);

            slumlord.Name = wo.Name;

            slumlord.EnqueueBroadcast(new GameMessagePublicUpdatePropertyString(slumlord, PropertyString.Name, wo.Name));
            slumlord.SaveBiotaToDatabase();

            player.HouseId       = null;
            player.HouseInstance = null;
            //player.HousePurchaseTimestamp = null;
            player.HouseRentTimestamp = null;

            house.ClearRestrictions();

            log.Info($"HouseManager.HandleRentEviction({playerHouse.PlayerName})");

            BuildRentQueue();

            if (!isOnline)
            {
                // inform player of eviction when they log in
                var offlinePlayer = PlayerManager.GetOfflinePlayer(playerHouse.PlayerGuid);
                if (offlinePlayer == null)
                {
                    log.Warn($"{playerHouse.PlayerName}.HandleEviction(): couldn't find offline player");
                    return;
                }
                offlinePlayer.SetProperty(PropertyBool.HouseEvicted, true);
                offlinePlayer.SaveBiotaToDatabase();
                return;
            }

            var onlinePlayer = PlayerManager.GetOnlinePlayer(playerHouse.PlayerGuid);

            onlinePlayer.House = null;

            // send text message
            onlinePlayer.Session.Network.EnqueueSend(new GameMessageSystemChat("You abandon your house!", ChatMessageType.Broadcast));
            onlinePlayer.RemoveDeed();

            await Task.Delay(3000);     // wait for slumlord inventory biotas above to save

            onlinePlayer.HandleActionQueryHouse();
        }