private static DateTime NotifyPlayersOfPendingShutdown(DateTime lastNoticeTime, DateTime shutdownTime) { var notify = false; var sdt = shutdownTime - DateTime.UtcNow; var timeHrs = $"{(sdt.Hours >= 1 ? $"{sdt.ToString("%h")}" : "")}{(sdt.Hours >= 2 ? $" hours" : sdt.Hours == 1 ? " hour" : "")}"; var timeMins = $"{(sdt.Minutes != 0 ? $"{sdt.ToString("%m")}" : "")}{(sdt.Minutes >= 2 ? $" minutes" : sdt.Minutes == 1 ? " minute" : "")}"; var timeSecs = $"{(sdt.Seconds != 0 ? $"{sdt.ToString("%s")}" : "")}{(sdt.Seconds >= 2 ? $" seconds" : sdt.Seconds == 1 ? " second" : "")}"; var time = $"{(timeHrs != "" ? timeHrs : "")}{(timeMins != "" ? $"{((timeHrs != "") ? ", " : "")}" + timeMins : "")}{(timeSecs != "" ? $"{((timeHrs != "" || timeMins != "") ? " and " : "")}" + timeSecs : "")}"; switch (time) { case "2 hours": case "1 hour": case "45 minutes": case "30 minutes": case "15 minutes": case "10 minutes": case "5 minutes": case "2 minutes": case "1 minute and 30 seconds": case "1 minute": case "30 seconds": case "15 seconds": case "10 seconds": case "5 seconds": notify = true; break; } // Console.WriteLine(time); if (notify && (DateTime.UtcNow - lastNoticeTime).TotalSeconds > 2) { foreach (var player in PlayerManager.GetAllOnline()) { if (sdt.TotalSeconds > 10) { player.Session.WorldBroadcast($"Broadcast from System> {(sdt.TotalMinutes > 1.5 ? "ATTENTION" : "WARNING")} - This Asheron's Call Server is shutting down in {time}.{(sdt.TotalMinutes <= 3 ? " Please log out." : "")}"); } else { player.Session.WorldBroadcast($"Broadcast from System> ATTENTION - This Asheron's Call Server is shutting down NOW!!!!"); } } return(DateTime.UtcNow); } else { return(lastNoticeTime); } }
/// <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); } PropertyManager.ResyncVariables(); PropertyManager.StopUpdating(); log.Debug("Logging off all players..."); // logout each player foreach (var player in PlayerManager.GetAllOnline()) { player.Session.LogOffPlayer(); } log.Info("Waiting for all players to log off..."); // wait 10 seconds for log-off while (PlayerManager.GetAllOnline().Count > 0) { 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(); log.Info("Waiting for all active landblocks to unload..."); while (LandblockManager.GetLoadedLandblocks().Count > 0) { Thread.Sleep(10); } log.Debug("Stopping world..."); // Disabled thread update loop WorldManager.StopWorld(); log.Info("Waiting for world to stop..."); // Wait for world to end while (WorldManager.WorldActive) { Thread.Sleep(10); } //log.Info("Saving OfflinePlayers that have unsaved changes..."); //PlayerManager.SaveOfflinePlayersWithChanges(); log.Info("Waiting for database queue to empty..."); // Wait for the database queue to empty while (DatabaseManager.Shard.QueueCount > 0) { Thread.Sleep(10); } // Write exit to console/log log.Info($"Exiting at {DateTime.UtcNow}"); // System exit Environment.Exit(Environment.ExitCode); }
public void Execute(Emote emote, WorldObject target) { var player = target as Player; var creature = target as Creature; switch (emote.Type) { case EmoteType.Act: if (player != null) { WorldObject.ActOnUse(player); } break; case EmoteType.Activate: target.Activate(WorldObject); break; case EmoteType.AddCharacterTitle: if (player != null) { player.AddTitle((CharacterTitle)emote.Stat); } break; case EmoteType.AddContract: if (player != null) { player.AddContract(emote.Stat); } break; case EmoteType.AdminSpam: var text = Replace(emote.Message, WorldObject, target); var players = PlayerManager.GetAllOnline(); foreach (var _player in players) { _player.Session.Network.EnqueueSend(new GameMessageSystemChat(text, ChatMessageType.AdminTell)); } break; case EmoteType.AwardLevelProportionalSkillXP: if (player != null) { player.GrantLevelProportionalSkillXP((Skill)emote.Stat, emote.Percent, emote.Max); } break; case EmoteType.AwardLevelProportionalXP: if (player != null) { player.GrantLevelProportionalXp(emote.Percent, emote.Max); } break; case EmoteType.AwardLuminance: if (player != null) { player.GrantLuminance((long)emote.Amount); } break; case EmoteType.AwardNoShareXP: if (player != null) { player.EarnXP((long)emote.Amount, false); } break; case EmoteType.AwardSkillPoints: if (player != null) { player.AwardSkillPoints((Skill)emote.Stat, (uint)emote.Amount); } break; case EmoteType.AwardSkillXP: if (player != null) { player.RaiseSkillGameAction((Skill)emote.Stat, (uint)emote.Amount, true); } break; case EmoteType.AwardTrainingCredits: if (player != null) { player.AddSkillCredits((int)emote.Amount, true); } break; case EmoteType.AwardXP: if (player != null) { player.EarnXP((long)emote.Amount); } break; case EmoteType.BLog: break; case EmoteType.CastSpell: if (WorldObject is Player) { (WorldObject as Player).CreatePlayerSpell(emote.SpellId); } else if (WorldObject is Creature) { (WorldObject as Creature).CreateCreatureSpell(target.Guid, emote.SpellId); } break; case EmoteType.CastSpellInstant: if (WorldObject is Player) { (WorldObject as Player).CreatePlayerSpell(emote.SpellId); } else if (WorldObject is Creature) { (WorldObject as Creature).CreateCreatureSpell(target.Guid, emote.SpellId); } break; case EmoteType.CloseMe: target.Close(WorldObject); break; case EmoteType.CreateTreasure: break; case EmoteType.DecrementIntStat: var id = (PropertyInt)emote.Stat; var prop = target.GetProperty(id); if (prop != null) { target.SetProperty(id, prop.Value - 1); } break; case EmoteType.DecrementMyQuest: break; case EmoteType.DecrementQuest: break; case EmoteType.DeleteSelf: WorldObject.CurrentLandblock?.RemoveWorldObject(WorldObject.Guid, false); break; case EmoteType.DirectBroadcast: text = Replace(emote.Message, WorldObject, target); if (player != null) { player.Session.Network.EnqueueSend(new GameMessageSystemChat(text, ChatMessageType.Broadcast)); } break; case EmoteType.EraseMyQuest: break; case EmoteType.EraseQuest: break; case EmoteType.FellowBroadcast: text = Replace(emote.Message, WorldObject, target); if (player != null) { var fellowship = player.Fellowship; if (fellowship != null) { player.Session.Network.EnqueueSend(new GameMessageSystemChat(text, ChatMessageType.Broadcast)); } else { foreach (var fellow in fellowship.FellowshipMembers) { player.Session.Network.EnqueueSend(new GameMessageSystemChat(text, ChatMessageType.Broadcast)); } } } break; case EmoteType.ForceMotion: WorldObject.PhysicsObj.DoMotion(emote.Motion, new MovementParameters()); break; case EmoteType.Generate: var wcid = emote.CreateProfile.ClassID; var item = WorldObjectFactory.CreateNewWorldObject(wcid); break; case EmoteType.Give: if (player != null) { wcid = emote.CreateProfile.ClassID; item = WorldObjectFactory.CreateNewWorldObject(wcid); if (item == null) { break; } var success = player.TryAddToInventory(item); } break; case EmoteType.Goto: //InqCategory(EmoteCategory.GotoSet, emote); break; case EmoteType.IncrementIntStat: id = (PropertyInt)emote.Stat; prop = target.GetProperty(id); if (prop != null) { target.SetProperty(id, prop.Value + 1); } break; case EmoteType.IncrementMyQuest: break; case EmoteType.IncrementQuest: break; case EmoteType.InflictVitaePenalty: if (player != null) { player.VitaeCpPool++; } break; case EmoteType.InqAttributeStat: if (creature != null) { var attr = creature.Attributes[(PropertyAttribute)emote.Stat]; var success = attr != null && attr.Ranks >= emote.Min && attr.Ranks <= emote.Max; InqCategory(success ? EmoteCategory.TestSuccess : EmoteCategory.TestFailure, emote); } break; case EmoteType.InqBoolStat: InqProperty(target.GetProperty((PropertyBool)emote.Stat), emote); break; case EmoteType.InqContractsFull: if (player != null) { var contracts = player.TrackedContracts; InqCategory(contracts.Count != 0 ? EmoteCategory.TestSuccess : EmoteCategory.TestFailure, emote); } break; case EmoteType.InqEvent: var started = EventManager.IsEventStarted(emote.Message); InqCategory(started ? EmoteCategory.TestSuccess : EmoteCategory.TestFailure, emote); break; case EmoteType.InqFellowNum: InqCategory(player != null && player.Fellowship != null ? EmoteCategory.TestSuccess : EmoteCategory.TestNoFellow, emote); break; case EmoteType.InqFellowQuest: break; case EmoteType.InqFloatStat: InqProperty(target.GetProperty((PropertyFloat)emote.Stat), emote); break; case EmoteType.InqInt64Stat: InqProperty(target.GetProperty((PropertyInt64)emote.Stat), emote); break; case EmoteType.InqIntStat: InqProperty(target.GetProperty((PropertyInt)emote.Stat), emote); break; case EmoteType.InqMyQuest: break; case EmoteType.InqMyQuestBitsOff: break; case EmoteType.InqMyQuestBitsOn: break; case EmoteType.InqMyQuestSolves: break; case EmoteType.InqNumCharacterTitles: if (player != null) { InqCategory(player.NumCharacterTitles != 0 ? EmoteCategory.TestSuccess : EmoteCategory.TestFailure, emote); } break; case EmoteType.InqOwnsItems: if (player != null) { InqCategory(player.Inventory.Count > 0 ? EmoteCategory.TestSuccess : EmoteCategory.TestFailure, emote); } break; case EmoteType.InqPackSpace: if (player != null) { var freeSpace = player.ContainerCapacity > player.ItemCapacity; InqCategory(freeSpace ? EmoteCategory.TestSuccess : EmoteCategory.TestFailure, emote); } break; case EmoteType.InqQuest: break; case EmoteType.InqQuestBitsOff: break; case EmoteType.InqQuestBitsOn: break; case EmoteType.InqQuestSolves: break; case EmoteType.InqRawAttributeStat: if (creature != null) { var attr = creature.Attributes[(PropertyAttribute)emote.Stat]; var success = attr != null && attr.Base >= emote.Min && attr.Base <= emote.Max; InqCategory(success ? EmoteCategory.TestSuccess : EmoteCategory.TestFailure, emote); } break; case EmoteType.InqRawSecondaryAttributeStat: if (creature != null) { var vital = creature.Vitals[(PropertyAttribute2nd)emote.Stat]; var success = vital != null && vital.Base >= emote.Min && vital.Base <= emote.Max; InqCategory(success ? EmoteCategory.TestSuccess : EmoteCategory.TestFailure, emote); } break; case EmoteType.InqRawSkillStat: if (creature != null) { var skill = creature.GetCreatureSkill((Skill)emote.Stat); var success = skill != null && skill.Base >= emote.Min && skill.Base <= emote.Max; InqCategory(success ? EmoteCategory.TestSuccess : EmoteCategory.TestFailure, emote); } break; case EmoteType.InqSecondaryAttributeStat: if (creature != null) { var vital = creature.Vitals[(PropertyAttribute2nd)emote.Stat]; var success = vital != null && vital.Ranks >= emote.Min && vital.Ranks <= emote.Max; InqCategory(success ? EmoteCategory.TestSuccess : EmoteCategory.TestFailure, emote); } break; case EmoteType.InqSkillSpecialized: if (creature != null) { var skill = creature.GetCreatureSkill((Skill)emote.Stat); InqProperty(skill.AdvancementClass == SkillAdvancementClass.Specialized, emote); } break; case EmoteType.InqSkillStat: if (creature != null) { var skill = creature.GetCreatureSkill((Skill)emote.Stat); var success = skill != null && skill.Ranks >= emote.Min && skill.Ranks <= emote.Max; InqCategory(success ? EmoteCategory.TestSuccess : EmoteCategory.TestFailure, emote); } break; case EmoteType.InqSkillTrained: if (creature != null) { var skill = creature.GetCreatureSkill((Skill)emote.Stat); InqProperty(skill.AdvancementClass == SkillAdvancementClass.Trained || skill.AdvancementClass == SkillAdvancementClass.Specialized, emote); } break; case EmoteType.InqStringStat: InqProperty(target.GetProperty((PropertyString)emote.Stat), emote); break; case EmoteType.InqYesNo: ConfirmationManager.ProcessConfirmation(emote.Stat, true); break; case EmoteType.Invalid: break; case EmoteType.KillSelf: if (player != null) { player.Smite(WorldObject); } break; case EmoteType.LocalBroadcast: text = Replace(emote.Message, WorldObject, target); WorldObject.EnqueueBroadcast(new GameMessageSystemChat(text, ChatMessageType.Broadcast)); break; case EmoteType.LocalSignal: break; case EmoteType.LockFellow: if (player != null && player.Fellowship != null) { player.HandleActionFellowshipChangeOpenness(false); } break; case EmoteType.Motion: WorldObject.PhysicsObj.DoMotion(emote.Motion, new MovementParameters()); break; case EmoteType.Move: if (creature != null) { var movement = creature.PhysicsObj.MovementManager.MoveToManager; movement.MoveToPosition(new Physics.Common.Position(emote.Position), new MovementParameters()); } break; case EmoteType.MoveHome: if (creature != null) { var movement = creature.PhysicsObj.MovementManager.MoveToManager; movement.MoveToPosition(new Physics.Common.Position(creature.Home), new MovementParameters()); } break; case EmoteType.MoveToPos: if (creature != null) { var movement = creature.PhysicsObj.MovementManager.MoveToManager; movement.MoveToPosition(new Physics.Common.Position(emote.Position), new MovementParameters()); } break; case EmoteType.OpenMe: target.Open(WorldObject); break; case EmoteType.PetCastSpellOnOwner: if (WorldObject is Creature) { (WorldObject as Creature).CreateCreatureSpell(target.Guid, emote.SpellId); } break; case EmoteType.PhysScript: target.PhysicsObj.play_script(emote.PScript, 1.0f); break; case EmoteType.PopUp: ConfirmationManager.AddConfirmation(new Confirmation((ConfirmationType)emote.Stat, emote.Message, WorldObject, target)); break; case EmoteType.RemoveContract: if (player != null) { player.HandleActionAbandonContract(emote.Stat); } break; case EmoteType.RemoveVitaePenalty: if (player != null) { player.VitaeCpPool = 0; } break; case EmoteType.ResetHomePosition: if (creature != null) { target.Home = emote.Position; } break; case EmoteType.Say: text = Replace(emote.Message, WorldObject, target); if (player != null) { player.EnqueueBroadcast(new GameMessageCreatureMessage(text, player.Name, player.Guid.Full, ChatMessageType.Speech)); } break; case EmoteType.SetAltRacialSkills: break; case EmoteType.SetBoolStat: target.SetProperty((PropertyBool)emote.Stat, emote.Amount == 0 ? false : true); break; case EmoteType.SetEyePalette: if (creature != null) { creature.EyesPaletteDID = (uint)emote.Display; } break; case EmoteType.SetEyeTexture: if (creature != null) { creature.EyesTextureDID = (uint)emote.Display; } break; case EmoteType.SetFloatStat: target.SetProperty((PropertyFloat)emote.Stat, (float)emote.Amount); break; case EmoteType.SetHeadObject: if (creature != null) { creature.HeadObjectDID = (uint)emote.Display; } break; case EmoteType.SetHeadPalette: break; case EmoteType.SetInt64Stat: target.SetProperty((PropertyInt)emote.Stat, (int)emote.Amount); break; case EmoteType.SetIntStat: target.SetProperty((PropertyInt)emote.Stat, (int)emote.Amount); break; case EmoteType.SetMouthPalette: break; case EmoteType.SetMouthTexture: if (creature != null) { creature.MouthTextureDID = (uint)emote.Display; } break; case EmoteType.SetMyQuestBitsOff: break; case EmoteType.SetMyQuestBitsOn: break; case EmoteType.SetMyQuestCompletions: break; case EmoteType.SetNosePalette: break; case EmoteType.SetNoseTexture: if (creature != null) { creature.NoseTextureDID = (uint)emote.Display; } break; case EmoteType.SetQuestBitsOff: break; case EmoteType.SetQuestBitsOn: break; case EmoteType.SetQuestCompletions: break; case EmoteType.SetSanctuaryPosition: if (player != null) { player.Sanctuary = emote.Position; } break; case EmoteType.Sound: target.EnqueueBroadcast(new GameMessageSound(target.Guid, (Sound)emote.Sound, 1.0f)); break; case EmoteType.SpendLuminance: if (player != null) { player.SpendLuminance((long)emote.Amount); } break; case EmoteType.StampFellowQuest: break; case EmoteType.StampMyQuest: break; case EmoteType.StampQuest: break; case EmoteType.StartBarber: break; case EmoteType.StartEvent: EventManager.StartEvent(emote.Message); break; case EmoteType.StopEvent: EventManager.StopEvent(emote.Message); break; case EmoteType.TakeItems: if (player != null) { wcid = emote.CreateProfile.ClassID; item = WorldObjectFactory.CreateNewWorldObject(wcid); if (item == null) { break; } var success = player.TryConsumeFromInventoryWithNetworking(item, (int)emote.Amount); } break; case EmoteType.TeachSpell: if (player != null) { player.LearnSpellWithNetworking(emote.SpellId); } break; case EmoteType.TeleportSelf: if (WorldObject is Player) { (WorldObject as Player).Teleport(emote.Position); } break; case EmoteType.TeleportTarget: if (player != null) { player.Teleport(emote.Position); } break; case EmoteType.Tell: text = Replace(emote.Message, WorldObject, target); if (player != null) { player.Session.Network.EnqueueSend(new GameMessageSystemChat(text, ChatMessageType.Tell)); } break; case EmoteType.TellFellow: text = Replace(emote.Message, WorldObject, target); if (player != null) { var fellowship = player.Fellowship; if (fellowship != null) { player.Session.Network.EnqueueSend(new GameMessageSystemChat(text, ChatMessageType.Tell)); } else { foreach (var fellow in fellowship.FellowshipMembers) { player.Session.Network.EnqueueSend(new GameMessageSystemChat(text, ChatMessageType.Tell)); } } } break; case EmoteType.TextDirect: text = Replace(emote.Message, WorldObject, target); if (player != null) { player.Session.Network.EnqueueSend(new GameMessageSystemChat(text, ChatMessageType.AdminTell)); } break; case EmoteType.Turn: var mvp = new MovementParameters(); mvp.DesiredHeading = new AFrame(emote.Frame).get_heading(); // increment animation sequence WorldObject.PhysicsObj.LastMoveWasAutonomous = false; WorldObject.PhysicsObj.cancel_moveto(); WorldObject.PhysicsObj.TurnToHeading(mvp); break; case EmoteType.TurnToTarget: WorldObject.PhysicsObj.TurnToObject(target.Guid.Full, new MovementParameters()); break; case EmoteType.UntrainSkill: if (player != null) { player.UntrainSkill((Skill)emote.Stat, 1); } break; case EmoteType.UpdateFellowQuest: break; case EmoteType.UpdateMyQuest: break; case EmoteType.UpdateQuest: break; case EmoteType.WorldBroadcast: text = Replace(emote.Message, WorldObject, target); players = PlayerManager.GetAllOnline(); foreach (var _player in players) { _player.Session.Network.EnqueueSend(new GameMessageSystemChat(text, ChatMessageType.WorldBroadcast)); } break; } }
/// <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(); 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); } 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); }
/// <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 shut down has been cancelled @ {DateTime.Now} ({DateTime.UtcNow} UTC)"; log.Info(shutdownText); // special text foreach (var player in PlayerManager.GetAllOnline()) { player.Session.WorldBroadcast($"Broadcast from System> ATTENTION - This Asheron's Call Server shut down has been cancelled."); } // 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; var playerLogoffStart = DateTime.UtcNow; while ((playerCount = PlayerManager.GetOnlineCount()) > 0) { logUpdateTS = LogStatusUpdate(logUpdateTS, $"Waiting for {playerCount} player{(playerCount > 1 ? "s" : "")} to log off..."); Thread.Sleep(10); if (playerCount > 0 && DateTime.UtcNow - playerLogoffStart > TimeSpan.FromMinutes(5)) { playerLogoffStart = DateTime.UtcNow; log.Warn($"5 minute log off failsafe reached and there are {playerCount} player{(playerCount > 1 ? "s" : "")} still online."); foreach (var player in PlayerManager.GetAllOnline()) { log.Warn($"Player {player.Name} (0x{player.Guid}) appears to be stuck in world and unable to log off normally. Requesting Forced Logoff..."); player.ForcedLogOffRequested = true; player.ForceLogoff(); } } } 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.GetAuthenticatedSessionCount()) > 0) { logUpdateTS = LogStatusUpdate(logUpdateTS, $"Waiting for {sessionCount} authenticated 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); }