/*
 * Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

using Framework.Collections;
using Framework.Configuration;
using Framework.Constants;
using Framework.Database;
using Game.BattlePets;
using Game.Chat;
using Game.Collision;
using Game.DataStorage;
using Game.Entities;
using Game.Maps;
using Game.Network;
using Game.Network.Packets;
using Game.Spells;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;

namespace Game
{
    public class WorldManager : Singleton<WorldManager>
    {
        WorldManager()
        {
            foreach (WorldTimers timer in Enum.GetValues(typeof(WorldTimers)))
                m_timers[timer] = new IntervalTimer();

            m_allowedSecurityLevel = AccountTypes.Player;
            m_gameTime = Time.UnixTime;
            m_startTime = m_gameTime;

            _realm = new Realm();
        }

        public Player FindPlayerInZone(uint zone)
        {
            foreach (var session in m_sessions)
            {
                Player player = session.Value.GetPlayer();
                if (player == null)
                    continue;

                if (player.IsInWorld && player.GetZoneId() == zone)
                {
                    // Used by the weather system. We return the player to broadcast the change weather message to him and all players in the zone.
                    return player;
                }
            }
            return null;
        }

        public bool IsClosed()
        {
            return m_isClosed;
        }

        public void SetClosed(bool val)
        {
            m_isClosed = val;
            Global.ScriptMgr.OnOpenStateChange(!val);
        }

        public void SetMotd(string motd)
        {
            Global.ScriptMgr.OnMotdChange(motd);

            m_motd.Clear();
            m_motd.AddRange(motd.Split('@'));
        }

        public List<string> GetMotd()
        {
            return m_motd;
        }

        public WorldSession FindSession(uint id)
        {
            return m_sessions.LookupByKey(id);
        }

        bool RemoveSession(uint id)
        {
            // Find the session, kick the user, but we can't delete session at this moment to prevent iterator invalidation
            var session = m_sessions.LookupByKey(id);

            if (session != null)
            {
                if (session.PlayerLoading())
                    return false;

                session.KickPlayer();
            }

            return true;
        }

        public void AddSession(WorldSession s)
        {
            addSessQueue.Enqueue(s);
        }

        public void AddInstanceSocket(WorldSocket sock, ulong connectToKey)
        {
            _linkSocketQueue.Enqueue(Tuple.Create(sock, connectToKey));
        }

        void AddSession_(WorldSession s)
        {
            Contract.Assert(s != null);

            //NOTE - Still there is race condition in WorldSession* being used in the Sockets

            // kick already loaded player with same account (if any) and remove session
            // if player is in loading and want to load again, return
            if (!RemoveSession(s.GetAccountId()))
            {
                s.KickPlayer();
                return;
            }

            // decrease session counts only at not reconnection case
            bool decrease_session = true;

            // if session already exist, prepare to it deleting at next world update
            // NOTE - KickPlayer() should be called on "old" in RemoveSession()
            {
                var old = m_sessions.LookupByKey(s.GetAccountId());
                if (old != null)
                {
                    // prevent decrease sessions count if session queued
                    if (RemoveQueuedPlayer(old))
                        decrease_session = false;
                }
            }

            m_sessions[s.GetAccountId()] = s;

            int Sessions = GetActiveAndQueuedSessionCount();
            uint pLimit = GetPlayerAmountLimit();
            int QueueSize = GetQueuedSessionCount(); //number of players in the queue

            //so we don't count the user trying to
            //login as a session and queue the socket that we are using
            if (decrease_session)
                --Sessions;

            if (pLimit > 0 && Sessions >= pLimit && !s.HasPermission(RBACPermissions.SkipQueue) && !HasRecentlyDisconnected(s))
            {
                AddQueuedPlayer(s);
                UpdateMaxSessionCounters();
                Log.outInfo(LogFilter.Server, "PlayerQueue: Account id {0} is in Queue Position ({1}).", s.GetAccountId(), ++QueueSize);
                return;
            }

            s.InitializeSession();

            UpdateMaxSessionCounters();

            // Updates the population
            if (pLimit > 0)
            {
                float popu = GetActiveSessionCount();              // updated number of users on the server
                popu /= pLimit;
                popu *= 2;
                Log.outInfo(LogFilter.Server, "Server Population ({0}).", popu);
            }
        }

        void ProcessLinkInstanceSocket(Tuple<WorldSocket, ulong> linkInfo)
        {
            if (!linkInfo.Item1.IsOpen())
                return;

            ConnectToKey key = new ConnectToKey();
            key.Raw = linkInfo.Item2;

            WorldSession session = FindSession(key.AccountId);
            if (!session || session.GetConnectToInstanceKey() != linkInfo.Item2)
            {
                linkInfo.Item1.SendAuthResponseError(BattlenetRpcErrorCode.TimedOut);
                linkInfo.Item1.CloseSocket();
                return;
            }

            linkInfo.Item1.SetWorldSession(session);
            session.AddInstanceConnection(linkInfo.Item1);
            session.HandleContinuePlayerLogin();
        }

        bool HasRecentlyDisconnected(WorldSession session)
        {
            if (session == null)
                return false;
            uint tolerance = 0;
            if (tolerance != 0)
            {
                foreach (var disconnect in m_disconnects)
                {
                    if ((disconnect.Value - Time.UnixTime) < tolerance)
                    {
                        if (disconnect.Key == session.GetAccountId())
                            return true;
                    }
                    else
                        m_disconnects.Remove(disconnect.Key);
                }
            }
            return false;
        }

        uint GetQueuePos(WorldSession sess)
        {
            uint position = 1;

            foreach (var iter in m_QueuedPlayer)
            {
                if (iter != sess)
                    ++position;
                else
                    return position;
            }
            return 0;
        }

        void AddQueuedPlayer(WorldSession sess)
        {
            sess.SetInQueue(true);
            m_QueuedPlayer.Add(sess);

            // The 1st SMSG_AUTH_RESPONSE needs to contain other info too.
            sess.SendAuthResponse(BattlenetRpcErrorCode.Ok, true, GetQueuePos(sess));
        }

        bool RemoveQueuedPlayer(WorldSession sess)
        {
            // sessions count including queued to remove (if removed_session set)
            int sessions = GetActiveSessionCount();

            uint position = 1;

            // search to remove and count skipped positions
            bool found = false;

            foreach (var iter in m_QueuedPlayer)
            {
                if (iter != sess)
                    ++position;
                else
                {
                    sess.SetInQueue(false);
                    sess.ResetTimeOutTime();
                    m_QueuedPlayer.Remove(iter);
                    found = true;                                   // removing queued session
                    break;
                }
            }

            // iter point to next socked after removed or end()
            // position store position of removed socket and then new position next socket after removed

            // if session not queued then we need decrease sessions count
            if (!found && sessions != 0)
                --sessions;

            // accept first in queue
            if ((m_playerLimit == 0 || sessions < m_playerLimit) && !m_QueuedPlayer.Empty())
            {
                WorldSession pop_sess = m_QueuedPlayer.First();
                pop_sess.InitializeSession();

                m_QueuedPlayer.RemoveAt(0);

                // update iter to point first queued socket or end() if queue is empty now
                position = 1;
            }

            // update position from iter to end()
            // iter point to first not updated socket, position store new position
            foreach (var iter in m_QueuedPlayer)
            {
                iter.SendAuthWaitQue(++position);
            }

            return found;
        }

        public void SetInitialWorldSettings()
        {
            // Server startup begin
            uint startupBegin = Time.GetMSTime();

            LoadRealmInfo();

            LoadConfigSettings();

            // Initialize Allowed Security Level
            LoadDBAllowedSecurityLevel();

            Global.ObjectMgr.SetHighestGuids();

            /*if (!Global.MapMgr.ExistMapAndVMap(0, -6240.32f, 331.033f) || !Global.MapMgr.ExistMapAndVMap(0, -8949.95f, -132.493f)
                || !Global.MapMgr.ExistMapAndVMap(1, -618.518f, -4251.67f) || !Global.MapMgr.ExistMapAndVMap(0, 1676.35f, 1677.45f)
                || !Global.MapMgr.ExistMapAndVMap(1, 10311.3f, 832.463f) || !Global.MapMgr.ExistMapAndVMap(1, -2917.58f, -257.98f)
                || (WorldConfig.GetIntValue(WorldCfg.Expansion) != 0 && (!Global.MapMgr.ExistMapAndVMap(530, 10349.6f, -6357.29f) || !Global.MapMgr.ExistMapAndVMap(530, -3961.64f, -13931.2f))))
            {
                Log.outError(LogFilter.ServerLoading, "Unable to load critical files - server shutting down !!!");
                ShutdownServ(0, 0, ShutdownExitCode.Error);
                return;
            }*/

            // Initialize pool manager
            Global.PoolMgr.Initialize();

            // Initialize game event manager
            Global.GameEventMgr.Initialize();

            Log.outInfo(LogFilter.ServerLoading, "Loading Cypher Strings...");
            Global.ObjectMgr.LoadCypherStrings();

            // not send custom type REALM_FFA_PVP to realm list
            RealmType server_type = IsFFAPvPRealm() ? RealmType.PVP : (RealmType)WorldConfig.GetIntValue(WorldCfg.GameType);
            uint realm_zone = WorldConfig.GetUIntValue(WorldCfg.RealmZone);

            DB.Login.Execute("UPDATE realmlist SET icon = {0}, timezone = {1} WHERE id = '{2}'", (byte)server_type, realm_zone, _realm.Id.Realm);      // One-time query

            Log.outInfo(LogFilter.ServerLoading, "Initialize DataStorage...");
            // Load DB2s
            CliDB.LoadStores(_dataPath, m_defaultDbcLocale);

            Log.outInfo(LogFilter.ServerLoading, "Loading hotfix info...");
            Global.DB2Mgr.LoadHotfixData();

            //- Load M2 fly by cameras
            M2Storage.LoadM2Cameras(_dataPath);

            //- Load GameTables
            CliDB.LoadGameTables(_dataPath);

            //Load weighted graph on taxi nodes path
            Global.TaxiPathGraph.Initialize();

            Global.MMapMgr.Initialize();

            Log.outInfo(LogFilter.ServerLoading, "Loading SpellInfo Storage...");
            Global.SpellMgr.LoadSpellInfoStore();

            Log.outInfo(LogFilter.ServerLoading, "Loading SpellInfo corrections...");
            Global.SpellMgr.LoadSpellInfoCorrections();

            Log.outInfo(LogFilter.ServerLoading, "Loading SkillLineAbility Data...");
            Global.SpellMgr.LoadSkillLineAbilityMap();

            Log.outInfo(LogFilter.ServerLoading, "Loading SpellInfo custom attributes...");
            Global.SpellMgr.LoadSpellInfoCustomAttributes();

            Log.outInfo(LogFilter.ServerLoading, "Loading PetFamilySpellsStore Data...");
            Global.SpellMgr.LoadPetFamilySpellsStore();

            Log.outInfo(LogFilter.ServerLoading, "Loading GameObject models...");
            GameObjectModel.LoadGameObjectModelList();

            Log.outInfo(LogFilter.ServerLoading, "Loading Script Names...");
            Global.ObjectMgr.LoadScriptNames();

            Log.outInfo(LogFilter.ServerLoading, "Loading Instance Template...");
            Global.ObjectMgr.LoadInstanceTemplate();

            // Must be called before `creature_respawn`/`gameobject_respawn` tables
            Log.outInfo(LogFilter.ServerLoading, "Loading instances...");
            Global.InstanceSaveMgr.LoadInstances();

            Log.outInfo(LogFilter.ServerLoading, "Loading Localization strings...");
            uint oldMSTime = Time.GetMSTime();
            Global.ObjectMgr.LoadCreatureLocales();
            Global.ObjectMgr.LoadGameObjectLocales();
            Global.ObjectMgr.LoadQuestTemplateLocale();
            Global.ObjectMgr.LoadQuestOfferRewardLocale();
            Global.ObjectMgr.LoadQuestRequestItemsLocale();
            Global.ObjectMgr.LoadQuestObjectivesLocale();
            Global.ObjectMgr.LoadPageTextLocales();
            Global.ObjectMgr.LoadGossipMenuItemsLocales();
            Global.ObjectMgr.LoadPointOfInterestLocales();
            Log.outInfo(LogFilter.ServerLoading, "Localization strings loaded in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime));

            Log.outInfo(LogFilter.ServerLoading, "Loading Account Roles and Permissions...");
            Global.AccountMgr.LoadRBAC();

            Log.outInfo(LogFilter.ServerLoading, "Loading Page Texts...");
            Global.ObjectMgr.LoadPageTexts();

            Log.outInfo(LogFilter.ServerLoading, "Loading GameObject Template...");
            Global.ObjectMgr.LoadGameObjectTemplate();

            Log.outInfo(LogFilter.ServerLoading, "Loading Game Object template addons...");
            Global.ObjectMgr.LoadGameObjectTemplateAddons();

            Log.outInfo(LogFilter.ServerLoading, "Loading Transport Templates...");
            Global.TransportMgr.LoadTransportTemplates();

            Log.outInfo(LogFilter.ServerLoading, "Loading Transport animations and rotations...");
            Global.TransportMgr.LoadTransportAnimationAndRotation();

            Log.outInfo(LogFilter.ServerLoading, "Loading Spell Rank Data...");
            Global.SpellMgr.LoadSpellRanks();

            Log.outInfo(LogFilter.ServerLoading, "Loading Spell Required Data...");
            Global.SpellMgr.LoadSpellRequired();

            Log.outInfo(LogFilter.ServerLoading, "Loading Spell Group types...");
            Global.SpellMgr.LoadSpellGroups();

            Log.outInfo(LogFilter.ServerLoading, "Loading Spell Learn Skills...");
            Global.SpellMgr.LoadSpellLearnSkills();

            Log.outInfo(LogFilter.ServerLoading, "Loading Spell Learn Spells...");
            Global.SpellMgr.LoadSpellLearnSpells();

            Log.outInfo(LogFilter.ServerLoading, "Loading Spell Proc Event conditions...");
            Global.SpellMgr.LoadSpellProcEvents();

            Log.outInfo(LogFilter.ServerLoading, "Loading Spell Proc conditions and data...");
            Global.SpellMgr.LoadSpellProcs();

            Log.outInfo(LogFilter.ServerLoading, "Loading Aggro Spells Definitions...");
            Global.SpellMgr.LoadSpellThreats();

            Log.outInfo(LogFilter.ServerLoading, "Loading Spell Group Stack Rules...");
            Global.SpellMgr.LoadSpellGroupStackRules();

            Log.outInfo(LogFilter.ServerLoading, "Loading NPC Texts...");
            Global.ObjectMgr.LoadNPCText();

            Log.outInfo(LogFilter.ServerLoading, "Loading Enchant Spells Proc datas...");
            Global.SpellMgr.LoadSpellEnchantProcData();

            Log.outInfo(LogFilter.ServerLoading, "Loading Item Random Enchantments Table...");
            ItemEnchantment.LoadRandomEnchantmentsTable();

            Log.outInfo(LogFilter.ServerLoading, "Loading Disables");                         // must be before loading quests and items
            Global.DisableMgr.LoadDisables();

            Log.outInfo(LogFilter.ServerLoading, "Loading Items...");                         // must be after LoadRandomEnchantmentsTable and LoadPageTexts
            Global.ObjectMgr.LoadItemTemplates();

            Log.outInfo(LogFilter.ServerLoading, "Loading Item set names...");                // must be after LoadItemPrototypes
            Global.ObjectMgr.LoadItemTemplateAddon();

            Log.outInfo(LogFilter.ServerLoading, "Loading Item Scripts...");                 // must be after LoadItemPrototypes
            Global.ObjectMgr.LoadItemScriptNames();

            Log.outInfo(LogFilter.ServerLoading, "Loading Creature Model Based Info Data...");
            Global.ObjectMgr.LoadCreatureModelInfo();

            Log.outInfo(LogFilter.ServerLoading, "Loading Creature templates...");
            Global.ObjectMgr.LoadCreatureTemplates();

            Log.outInfo(LogFilter.ServerLoading, "Loading Equipment templates...");
            Global.ObjectMgr.LoadEquipmentTemplates();

            Log.outInfo(LogFilter.ServerLoading, "Loading Creature template addons...");
            Global.ObjectMgr.LoadCreatureTemplateAddons();

            Log.outInfo(LogFilter.ServerLoading, "Loading Reputation Reward Rates...");
            Global.ObjectMgr.LoadReputationRewardRate();

            Log.outInfo(LogFilter.ServerLoading, "Loading Creature Reputation OnKill Data...");
            Global.ObjectMgr.LoadReputationOnKill();

            Log.outInfo(LogFilter.ServerLoading, "Loading Reputation Spillover Data...");
            Global.ObjectMgr.LoadReputationSpilloverTemplate();

            Log.outInfo(LogFilter.ServerLoading, "Loading Points Of Interest Data...");
            Global.ObjectMgr.LoadPointsOfInterest();

            Log.outInfo(LogFilter.ServerLoading, "Loading Creature Base Stats...");
            Global.ObjectMgr.LoadCreatureClassLevelStats();

            Log.outInfo(LogFilter.ServerLoading, "Loading Creature Data...");
            Global.ObjectMgr.LoadCreatures();

            Log.outInfo(LogFilter.ServerLoading, "Loading Temporary Summon Data...");
            Global.ObjectMgr.LoadTempSummons();                               // must be after LoadCreatureTemplates() and LoadGameObjectTemplates()

            Log.outInfo(LogFilter.ServerLoading, "Loading pet levelup spells...");
            Global.SpellMgr.LoadPetLevelupSpellMap();

            Log.outInfo(LogFilter.ServerLoading, "Loading pet default spells additional to levelup spells...");
            Global.SpellMgr.LoadPetDefaultSpells();

            Log.outInfo(LogFilter.ServerLoading, "Loading Creature Addon Data...");
            Global.ObjectMgr.LoadCreatureAddons();

            Log.outInfo(LogFilter.ServerLoading, "Loading GameObjects...");
            Global.ObjectMgr.LoadGameobjects();

            Log.outInfo(LogFilter.ServerLoading, "Loading GameObject Addon Data...");
            Global.ObjectMgr.LoadGameObjectAddons();                          // must be after LoadGameObjectTemplate() and LoadGameobjects()

            Log.outInfo(LogFilter.ServerLoading, "Loading GameObject Quest Items...");
            Global.ObjectMgr.LoadGameObjectQuestItems();

            Log.outInfo(LogFilter.ServerLoading, "Loading Creature Quest Items...");
            Global.ObjectMgr.LoadCreatureQuestItems();

            Log.outInfo(LogFilter.ServerLoading, "Loading Creature Linked Respawn...");
            Global.ObjectMgr.LoadLinkedRespawn();                             // must be after LoadCreatures(), LoadGameObjects()

            Log.outInfo(LogFilter.ServerLoading, "Loading Weather Data...");
            Global.WeatherMgr.LoadWeatherData();

            Log.outInfo(LogFilter.ServerLoading, "Loading Quests...");
            Global.ObjectMgr.LoadQuests();

            Log.outInfo(LogFilter.ServerLoading, "Checking Quest Disables");
            Global.DisableMgr.CheckQuestDisables();                           // must be after loading quests

            Log.outInfo(LogFilter.ServerLoading, "Loading Quest POI");
            Global.ObjectMgr.LoadQuestPOI();

            Log.outInfo(LogFilter.ServerLoading, "Loading Quests Starters and Enders...");
            Global.ObjectMgr.LoadQuestStartersAndEnders();                    // must be after quest load

            Log.outInfo(LogFilter.ServerLoading, "Loading Quest Greetings...");
            Global.ObjectMgr.LoadQuestGreetings();

            Log.outInfo(LogFilter.ServerLoading, "Loading Objects Pooling Data...");
            Global.PoolMgr.LoadFromDB();

            Log.outInfo(LogFilter.ServerLoading, "Loading Game Event Data...");               // must be after loading pools fully
            Global.GameEventMgr.LoadFromDB();

            Log.outInfo(LogFilter.ServerLoading, "Loading NPCSpellClick Data..."); // must be after LoadQuests
            Global.ObjectMgr.LoadNPCSpellClickSpells();

            Log.outInfo(LogFilter.ServerLoading, "Loading Vehicle Template Accessories...");
            Global.ObjectMgr.LoadVehicleTemplateAccessories();                // must be after LoadCreatureTemplates() and LoadNPCSpellClickSpells()

            Log.outInfo(LogFilter.ServerLoading, "Loading Vehicle Accessories...");
            Global.ObjectMgr.LoadVehicleAccessories();                       // must be after LoadCreatureTemplates() and LoadNPCSpellClickSpells()

            Log.outInfo(LogFilter.ServerLoading, "Loading SpellArea Data...");                // must be after quest load
            Global.SpellMgr.LoadSpellAreas();

            Log.outInfo(LogFilter.ServerLoading, "Loading AreaTrigger definitions...");
            Global.ObjectMgr.LoadAreaTriggerTeleports();

            Log.outInfo(LogFilter.ServerLoading, "Loading Access Requirements...");
            Global.ObjectMgr.LoadAccessRequirements();                        // must be after item template load

            Log.outInfo(LogFilter.ServerLoading, "Loading Quest Area Triggers...");
            Global.ObjectMgr.LoadQuestAreaTriggers();                         // must be after LoadQuests

            Log.outInfo(LogFilter.ServerLoading, "Loading Tavern Area Triggers...");
            Global.ObjectMgr.LoadTavernAreaTriggers();

            Log.outInfo(LogFilter.ServerLoading, "Loading AreaTrigger script names...");
            Global.ObjectMgr.LoadAreaTriggerScripts();

            Log.outInfo(LogFilter.ServerLoading, "Loading LFG entrance positions..."); // Must be after areatriggers
            Global.LFGMgr.LoadLFGDungeons();

            Log.outInfo(LogFilter.ServerLoading, "Loading Dungeon boss data...");
            Global.ObjectMgr.LoadInstanceEncounters();

            Log.outInfo(LogFilter.ServerLoading, "Loading LFG rewards...");
            Global.LFGMgr.LoadRewards();

            Log.outInfo(LogFilter.ServerLoading, "Loading Graveyard-zone links...");
            Global.ObjectMgr.LoadGraveyardZones();

            Log.outInfo(LogFilter.ServerLoading, "Loading spell pet auras...");
            Global.SpellMgr.LoadSpellPetAuras();

            Log.outInfo(LogFilter.ServerLoading, "Loading Spell target coordinates...");
            Global.SpellMgr.LoadSpellTargetPositions();

            Log.outInfo(LogFilter.ServerLoading, "Loading enchant custom attributes...");
            Global.SpellMgr.LoadEnchantCustomAttr();

            Log.outInfo(LogFilter.ServerLoading, "Loading linked spells...");
            Global.SpellMgr.LoadSpellLinked();

            Log.outInfo(LogFilter.ServerLoading, "Loading Player Create Data...");
            Global.ObjectMgr.LoadPlayerInfo();

            Log.outInfo(LogFilter.ServerLoading, "Loading Exploration BaseXP Data...");
            Global.ObjectMgr.LoadExplorationBaseXP();

            Log.outInfo(LogFilter.ServerLoading, "Loading Pet Name Parts...");
            Global.ObjectMgr.LoadPetNames();

            Log.outInfo(LogFilter.ServerLoading, "Loading AreaTrigger Templates...");
            Global.AreaTriggerDataStorage.LoadAreaTriggerTemplates();

            Log.outInfo(LogFilter.ServerLoading, "Loading Conversation Templates...");
            Global.ConversationDataStorage.LoadConversationTemplates();

            Log.outInfo(LogFilter.ServerLoading, "Loading Scenes Templates...");
            Global.ObjectMgr.LoadSceneTemplates();

            CharacterDatabaseCleaner.CleanDatabase();

            Log.outInfo(LogFilter.ServerLoading, "Loading the max pet number...");
            Global.ObjectMgr.LoadPetNumber();

            Log.outInfo(LogFilter.ServerLoading, "Loading pet level stats...");
            Global.ObjectMgr.LoadPetLevelInfo();

            Log.outInfo(LogFilter.ServerLoading, "Loading Player level dependent mail rewards...");
            Global.ObjectMgr.LoadMailLevelRewards();

            // Loot tables
            Loots.LootManager.LoadLootTables();

            Log.outInfo(LogFilter.ServerLoading, "Loading Skill Discovery Table...");
            SkillDiscovery.LoadSkillDiscoveryTable();

            Log.outInfo(LogFilter.ServerLoading, "Loading Skill Extra Item Table...");
            SkillExtraItems.LoadSkillExtraItemTable();

            Log.outInfo(LogFilter.ServerLoading, "Loading Skill Perfection Data Table...");
            SkillPerfectItems.LoadSkillPerfectItemTable();

            Log.outInfo(LogFilter.ServerLoading, "Loading Skill Fishing base level requirements...");
            Global.ObjectMgr.LoadFishingBaseSkillLevel();

            Log.outInfo(LogFilter.ServerLoading, "Loading skill tier info...");
            Global.ObjectMgr.LoadSkillTiers();

            Log.outInfo(LogFilter.ServerLoading, "Loading Criteria Modifier trees...");
            Global.CriteriaMgr.LoadCriteriaModifiersTree();
            Log.outInfo(LogFilter.ServerLoading, "Loading Criteria Lists...");
            Global.CriteriaMgr.LoadCriteriaList();
            Log.outInfo(LogFilter.ServerLoading, "Loading Criteria Data...");
            Global.CriteriaMgr.LoadCriteriaData();
            Log.outInfo(LogFilter.ServerLoading, "Loading Achievements...");
            Global.AchievementMgr.LoadAchievementReferenceList();
            Log.outInfo(LogFilter.ServerLoading, "Loading Achievement Rewards...");
            Global.AchievementMgr.LoadRewards();
            Log.outInfo(LogFilter.ServerLoading, "Loading Achievement Reward Locales...");
            Global.AchievementMgr.LoadRewardLocales();
            Log.outInfo(LogFilter.ServerLoading, "Loading Completed Achievements...");
            Global.AchievementMgr.LoadCompletedAchievements();

            // Load dynamic data tables from the database
            Log.outInfo(LogFilter.ServerLoading, "Loading Item Auctions...");
            Global.AuctionMgr.LoadAuctionItems();

            Log.outInfo(LogFilter.ServerLoading, "Loading Auctions...");
            Global.AuctionMgr.LoadAuctions();

            if (WorldConfig.GetBoolValue(WorldCfg.BlackmarketEnabled))
            {
                Log.outInfo(LogFilter.ServerLoading, "Loading Black Market Templates...");
                Global.BlackMarketMgr.LoadTemplates();

                Log.outInfo(LogFilter.ServerLoading, "Loading Black Market Auctions...");
                Global.BlackMarketMgr.LoadAuctions();
            }

            Log.outInfo(LogFilter.ServerLoading, "Loading Guild rewards...");
            Global.GuildMgr.LoadGuildRewards();

            Log.outInfo(LogFilter.ServerLoading, "Loading Guilds...");
            Global.GuildMgr.LoadGuilds();

            Global.GuildFinderMgr.LoadFromDB();

            Log.outInfo(LogFilter.ServerLoading, "Loading ArenaTeams...");
            Global.ArenaTeamMgr.LoadArenaTeams();

            Log.outInfo(LogFilter.ServerLoading, "Loading Groups...");
            Global.GroupMgr.LoadGroups();

            Log.outInfo(LogFilter.ServerLoading, "Loading ReservedNames...");
            Global.ObjectMgr.LoadReservedPlayersNames();

            Log.outInfo(LogFilter.ServerLoading, "Loading GameObjects for quests...");
            Global.ObjectMgr.LoadGameObjectForQuests();

            Log.outInfo(LogFilter.ServerLoading, "Loading BattleMasters...");
            Global.BattlegroundMgr.LoadBattleMastersEntry();

            Log.outInfo(LogFilter.ServerLoading, "Loading GameTeleports...");
            Global.ObjectMgr.LoadGameTele();

            Log.outInfo(LogFilter.ServerLoading, "Loading Gossip menu...");
            Global.ObjectMgr.LoadGossipMenu();

            Log.outInfo(LogFilter.ServerLoading, "Loading Gossip menu options...");
            Global.ObjectMgr.LoadGossipMenuItems();

            Log.outInfo(LogFilter.ServerLoading, "Loading Vendors...");
            Global.ObjectMgr.LoadVendors();                                   // must be after load CreatureTemplate and ItemTemplate

            Log.outInfo(LogFilter.ServerLoading, "Loading Trainers...");
            Global.ObjectMgr.LoadTrainerSpell();

            Log.outInfo(LogFilter.ServerLoading, "Loading Waypoints...");
            Global.WaypointMgr.Load();

            Log.outInfo(LogFilter.ServerLoading, "Loading SmartAI Waypoints...");
            Global.SmartAIMgr.LoadWaypointFromDB();

            Log.outInfo(LogFilter.ServerLoading, "Loading Creature Formations...");
            FormationMgr.LoadCreatureFormations();

            Log.outInfo(LogFilter.ServerLoading, "Loading World States...");              // must be loaded before Battleground, outdoor PvP and conditions
            LoadWorldStates();

            Log.outInfo(LogFilter.ServerLoading, "Loading Terrain Phase definitions...");
            Global.ObjectMgr.LoadTerrainPhaseInfo();

            Log.outInfo(LogFilter.ServerLoading, "Loading Terrain Swap Default definitions...");
            Global.ObjectMgr.LoadTerrainSwapDefaults();

            Log.outInfo(LogFilter.ServerLoading, "Loading Terrain World Map definitions...");
            Global.ObjectMgr.LoadTerrainWorldMaps();

            Log.outInfo(LogFilter.ServerLoading, "Loading Phase Area definitions...");
            Global.ObjectMgr.LoadAreaPhases();

            Log.outInfo(LogFilter.ServerLoading, "Loading Conditions...");
            Global.ConditionMgr.LoadConditions();

            Log.outInfo(LogFilter.ServerLoading, "Loading faction change achievement pairs...");
            Global.ObjectMgr.LoadFactionChangeAchievements();

            Log.outInfo(LogFilter.ServerLoading, "Loading faction change spell pairs...");
            Global.ObjectMgr.LoadFactionChangeSpells();

            Log.outInfo(LogFilter.ServerLoading, "Loading faction change item pairs...");
            Global.ObjectMgr.LoadFactionChangeItems();

            Log.outInfo(LogFilter.ServerLoading, "Loading faction change quest pairs...");
            Global.ObjectMgr.LoadFactionChangeQuests();

            Log.outInfo(LogFilter.ServerLoading, "Loading faction change reputation pairs...");
            Global.ObjectMgr.LoadFactionChangeReputations();

            Log.outInfo(LogFilter.ServerLoading, "Loading faction change title pairs...");
            Global.ObjectMgr.LoadFactionChangeTitles();

            Log.outInfo(LogFilter.ServerLoading, "Loading mount definitions...");
            CollectionMgr.LoadMountDefinitions();

            Log.outInfo(LogFilter.ServerLoading, "Loading GM bugs...");
            Global.SupportMgr.LoadBugTickets();

            Log.outInfo(LogFilter.ServerLoading, "Loading GM complaints...");
            Global.SupportMgr.LoadComplaintTickets();

            Log.outInfo(LogFilter.ServerLoading, "Loading GM suggestions...");
            Global.SupportMgr.LoadSuggestionTickets();

            //Log.outInfo(LogFilter.ServerLoading, "Loading GM surveys...");
            //Global.SupportMgr.LoadSurveys();

            Log.outInfo(LogFilter.ServerLoading, "Loading garrison info...");
            Global.GarrisonMgr.Initialize();

            // Handle outdated emails (delete/return)
            Log.outInfo(LogFilter.ServerLoading, "Returning old mails...");
            Global.ObjectMgr.ReturnOrDeleteOldMails(false);

            Log.outInfo(LogFilter.ServerLoading, "Loading Autobroadcasts...");
            LoadAutobroadcasts();

            // Load and initialize scripts
            Global.ObjectMgr.LoadSpellScripts();                              // must be after load Creature/Gameobject(Template/Data)
            Global.ObjectMgr.LoadEventScripts();                              // must be after load Creature/Gameobject(Template/Data)
            Global.ObjectMgr.LoadWaypointScripts();

            Log.outInfo(LogFilter.ServerLoading, "Loading spell script names...");
            Global.ObjectMgr.LoadSpellScriptNames();

            Log.outInfo(LogFilter.ServerLoading, "Loading Creature Texts...");
            Global.CreatureTextMgr.LoadCreatureTexts();

            Log.outInfo(LogFilter.ServerLoading, "Loading Creature Text Locales...");
            Global.CreatureTextMgr.LoadCreatureTextLocales();

            Log.outInfo(LogFilter.ServerLoading, "Initializing Scripts...");
            Global.ScriptMgr.Initialize();
            Global.ScriptMgr.OnConfigLoad(false);                                // must be done after the ScriptMgr has been properly initialized

            Log.outInfo(LogFilter.ServerLoading, "Validating spell scripts...");
            Global.ObjectMgr.ValidateSpellScripts();

            Log.outInfo(LogFilter.ServerLoading, "Loading SmartAI scripts...");
            Global.SmartAIMgr.LoadFromDB();

            Log.outInfo(LogFilter.ServerLoading, "Loading Calendar data...");
            Global.CalendarMgr.LoadFromDB();

            // Initialize game time and timers
            Log.outInfo(LogFilter.ServerLoading, "Initialize game time and timers");
            m_gameTime = Time.UnixTime;
            m_startTime = m_gameTime;

            DB.Login.Execute("INSERT INTO uptime (realmid, starttime, uptime, revision) VALUES({0}, {1}, 0, '{2}')", _realm.Id.Realm, m_startTime, "");       // One-time query

            m_timers[WorldTimers.Weathers].SetInterval(1 * Time.InMilliseconds);
            m_timers[WorldTimers.Auctions].SetInterval(Time.Minute * Time.InMilliseconds);
            m_timers[WorldTimers.AuctionsPending].SetInterval(250);

            //Update "uptime" table based on configuration entry in minutes.
            m_timers[WorldTimers.UpTime].SetInterval(10 * Time.Minute * Time.InMilliseconds);
            //erase corpses every 20 minutes
            m_timers[WorldTimers.Corpses].SetInterval(20 * Time.Minute * Time.InMilliseconds);
            m_timers[WorldTimers.CleanDB].SetInterval(WorldConfig.GetIntValue(WorldCfg.LogdbClearinterval) * Time.Minute * Time.InMilliseconds);
            m_timers[WorldTimers.AutoBroadcast].SetInterval(WorldConfig.GetIntValue(WorldCfg.AutoBroadcastInterval));
            // check for chars to delete every day
            m_timers[WorldTimers.DeleteChars].SetInterval(Time.Day * Time.InMilliseconds);
            // for AhBot
            m_timers[WorldTimers.AhBot].SetInterval(WorldConfig.GetIntValue(WorldCfg.AhbotUpdateInterval) * Time.InMilliseconds); // every 20 sec
            m_timers[WorldTimers.GuildSave].SetInterval(WorldConfig.GetIntValue(WorldCfg.GuildSaveInterval) * Time.Minute * Time.InMilliseconds);

            m_timers[WorldTimers.Blackmarket].SetInterval(10 * Time.InMilliseconds);

            blackmarket_timer = 0;

            //to set mailtimer to return mails every day between 4 and 5 am
            //mailtimer is increased when updating auctions
            //one second is 1000 -(tested on win system)
            /// @todo Get rid of magic numbers
            var localTime = Time.UnixTimeToDateTime(m_gameTime).ToLocalTime();
            mail_timer = ((((localTime.Hour + 20) % 24) * Time.Hour * Time.InMilliseconds) / m_timers[WorldTimers.Auctions].GetInterval());
            //1440
            mail_timer_expires = ((Time.Day * Time.InMilliseconds) / (m_timers[(int)WorldTimers.Auctions].GetInterval()));
            Log.outInfo(LogFilter.ServerLoading, "Mail timer set to: {0}, mail return is called every {1} minutes", mail_timer, mail_timer_expires);

            //- Initialize MapManager
            Log.outInfo(LogFilter.ServerLoading, "Starting Map System");
            Global.MapMgr.Initialize();

            Log.outInfo(LogFilter.ServerLoading, "Starting Game Event system...");
            uint nextGameEvent = Global.GameEventMgr.StartSystem();
            m_timers[WorldTimers.Events].SetInterval(nextGameEvent);    //depend on next event

            // Delete all characters which have been deleted X days before
            Player.DeleteOldCharacters();

            // Delete all custom channels which haven't been used for PreserveCustomChannelDuration days.
            Channel.CleanOldChannelsInDB();

            Log.outInfo(LogFilter.ServerLoading, "Initializing Opcodes...");
            PacketManager.Initialize();

            Log.outInfo(LogFilter.ServerLoading, "Starting Arena Season...");
            Global.GameEventMgr.StartArenaSeason();

            Global.SupportMgr.Initialize();

            // Initialize Battlegrounds
            Log.outInfo(LogFilter.ServerLoading, "Starting BattlegroundSystem");
            Global.BattlegroundMgr.LoadBattlegroundTemplates();

            // Initialize outdoor pvp
            Log.outInfo(LogFilter.ServerLoading, "Starting Outdoor PvP System");
            Global.OutdoorPvPMgr.InitOutdoorPvP();

            // Initialize Battlefield
            Log.outInfo(LogFilter.ServerLoading, "Starting Battlefield System");
            Global.BattleFieldMgr.InitBattlefield();

            Log.outInfo(LogFilter.ServerLoading, "Loading Transports...");
            Global.TransportMgr.SpawnContinentTransports();

            // Initialize Warden
            Log.outInfo(LogFilter.ServerLoading, "Loading Warden Checks...");
            Global.WardenCheckMgr.LoadWardenChecks();

            Log.outInfo(LogFilter.ServerLoading, "Loading Warden Action Overrides...");
            Global.WardenCheckMgr.LoadWardenOverrides();

            Log.outInfo(LogFilter.ServerLoading, "Deleting expired bans...");
            DB.Login.Execute("DELETE FROM ip_banned WHERE unbandate <= UNIX_TIMESTAMP() AND unbandate<>bandate");      // One-time query

            Log.outInfo(LogFilter.ServerLoading, "Calculate next daily quest reset time...");
            InitDailyQuestResetTime();

            Log.outInfo(LogFilter.ServerLoading, "Calculate next weekly quest reset time...");
            InitWeeklyQuestResetTime();

            Log.outInfo(LogFilter.ServerLoading, "Calculate next monthly quest reset time...");
            InitMonthlyQuestResetTime();

            Log.outInfo(LogFilter.ServerLoading, "Calculate random Battlegroundreset time...");
            InitRandomBGResetTime();

            Log.outInfo(LogFilter.ServerLoading, "Calculate Guild cap reset time...");
            InitGuildResetTime();

            Log.outInfo(LogFilter.ServerLoading, "Calculate next currency reset time...");
            InitCurrencyResetTime();

            LoadCharacterInfoStorage();

            Log.outInfo(LogFilter.ServerLoading, "Loading race and class expansion requirements...");
            Global.ObjectMgr.LoadRaceAndClassExpansionRequirements();

            Log.outInfo(LogFilter.ServerLoading, "Loading character templates...");
            Global.CharacterTemplateDataStorage.LoadCharacterTemplates();

            Log.outInfo(LogFilter.ServerLoading, "Loading realm names...");
            Global.ObjectMgr.LoadRealmNames();

            Log.outInfo(LogFilter.ServerLoading, "Loading battle pets info...");
            BattlePetMgr.Initialize();

            Log.outInfo(LogFilter.ServerLoading, "Loading scenarios");
            Global.ScenarioMgr.LoadDB2Data();
            Global.ScenarioMgr.LoadDBData();

            Log.outInfo(LogFilter.ServerLoading, "Loading scenario poi data");
            Global.ScenarioMgr.LoadScenarioPOI();

            // Preload all cells, if required for the base maps
            if (WorldConfig.GetBoolValue(WorldCfg.BasemapLoadGrids))
            {
                Global.MapMgr.DoForAllMaps(map =>
                {
                    if (!map.Instanceable())
                    {
                        Log.outInfo(LogFilter.ServerLoading, "Pre-loading base map data for map {0}", map.GetId());
                        map.LoadAllCells();
                    }
                });
            }

            uint startupDuration = Time.GetMSTimeDiffToNow(startupBegin);

            Log.outInfo(LogFilter.Server, "World initialized in {0} minutes {1} seconds", (startupDuration / 60000), ((startupDuration % 60000) / 1000));

            Log.SetRealmId(_realm.Id.Realm);
        }

        public void LoadConfigSettings(bool reload = false)
        {
            WorldConfig.Load(reload);

            m_defaultDbcLocale = (LocaleConstant)ConfigMgr.GetDefaultValue("DBC.Locale", 0);

            if (m_defaultDbcLocale >= LocaleConstant.Total || m_defaultDbcLocale == LocaleConstant.None)
            {
                Log.outError(LogFilter.ServerLoading, "Incorrect DBC.Locale! Must be >= 0 and < {0} and not {1} (set to 0)", LocaleConstant.Total, LocaleConstant.None);
                m_defaultDbcLocale = LocaleConstant.enUS;
            }

            Log.outInfo(LogFilter.ServerLoading, "Using {0} DBC Locale", m_defaultDbcLocale);

            Global.WorldMgr.SetPlayerAmountLimit((uint)ConfigMgr.GetDefaultValue("PlayerLimit", 100));
            Global.WorldMgr.SetMotd(ConfigMgr.GetDefaultValue("Motd", "Welcome to a Cypher Core Server."));

            if (reload)
            {
                Global.SupportMgr.SetSupportSystemStatus(WorldConfig.GetBoolValue(WorldCfg.SupportEnabled));
                Global.SupportMgr.SetTicketSystemStatus(WorldConfig.GetBoolValue(WorldCfg.SupportTicketsEnabled));
                Global.SupportMgr.SetBugSystemStatus(WorldConfig.GetBoolValue(WorldCfg.SupportBugsEnabled));
                Global.SupportMgr.SetComplaintSystemStatus(WorldConfig.GetBoolValue(WorldCfg.SupportComplaintsEnabled));
                Global.SupportMgr.SetSuggestionSystemStatus(WorldConfig.GetBoolValue(WorldCfg.SupportSuggestionsEnabled));

                Global.MapMgr.SetMapUpdateInterval(WorldConfig.GetIntValue(WorldCfg.IntervalMapupdate));
                Global.MapMgr.SetGridCleanUpDelay(WorldConfig.GetUIntValue(WorldCfg.IntervalGridclean));

                m_timers[WorldTimers.UpTime].SetInterval(WorldConfig.GetIntValue(WorldCfg.UptimeUpdate) * Time.Minute * Time.InMilliseconds);
                m_timers[WorldTimers.UpTime].Reset();

                m_timers[WorldTimers.CleanDB].SetInterval(WorldConfig.GetIntValue(WorldCfg.LogdbClearinterval) * Time.Minute * Time.InMilliseconds);
                m_timers[WorldTimers.CleanDB].Reset();


                m_timers[WorldTimers.AutoBroadcast].SetInterval(WorldConfig.GetIntValue(WorldCfg.AutoBroadcastInterval));
                m_timers[WorldTimers.AutoBroadcast].Reset();
            }

            for (byte i = 0; i < (int)UnitMoveType.Max; ++i)
                SharedConst.playerBaseMoveSpeed[i] = SharedConst.baseMoveSpeed[i] * WorldConfig.GetFloatValue(WorldCfg.RateMovespeed);

            var rateCreatureAggro = WorldConfig.GetFloatValue(WorldCfg.RateCreatureAggro);
            //visibility on continents
            m_MaxVisibleDistanceOnContinents = ConfigMgr.GetDefaultValue("Visibility.Distance.Continents", SharedConst.DefaultVisibilityDistance);
            if (m_MaxVisibleDistanceOnContinents < 45 * rateCreatureAggro)
            {
                Log.outError(LogFilter.ServerLoading, "Visibility.Distance.Continents can't be less max aggro radius {0}", 45 * rateCreatureAggro);
                m_MaxVisibleDistanceOnContinents = 45 * rateCreatureAggro;
            }
            else if (m_MaxVisibleDistanceOnContinents > SharedConst.MaxVisibilityDistance)
            {
                Log.outError(LogFilter.ServerLoading, "Visibility.Distance.Continents can't be greater {0}", SharedConst.MaxVisibilityDistance);
                m_MaxVisibleDistanceOnContinents = SharedConst.MaxVisibilityDistance;
            }

            //visibility in instances
            m_MaxVisibleDistanceInInstances = ConfigMgr.GetDefaultValue("Visibility.Distance.Instances", SharedConst.DefaultVisibilityInstance);
            if (m_MaxVisibleDistanceInInstances < 45 * rateCreatureAggro)
            {
                Log.outError(LogFilter.ServerLoading, "Visibility.Distance.Instances can't be less max aggro radius {0}", 45 * rateCreatureAggro);
                m_MaxVisibleDistanceInInstances = 45 * rateCreatureAggro;
            }
            else if (m_MaxVisibleDistanceInInstances > SharedConst.MaxVisibilityDistance)
            {
                Log.outError(LogFilter.ServerLoading, "Visibility.Distance.Instances can't be greater {0}", SharedConst.MaxVisibilityDistance);
                m_MaxVisibleDistanceInInstances = SharedConst.MaxVisibilityDistance;
            }

            //visibility in BG/Arenas
            m_MaxVisibleDistanceInBGArenas = ConfigMgr.GetDefaultValue("Visibility.Distance.BGArenas", SharedConst.DefaultVisibilityBGAreans);
            if (m_MaxVisibleDistanceInBGArenas < 45 * rateCreatureAggro)
            {
                Log.outError(LogFilter.ServerLoading, "Visibility.Distance.BGArenas can't be less max aggro radius {0}", 45 * rateCreatureAggro);
                m_MaxVisibleDistanceInBGArenas = 45 * rateCreatureAggro;
            }
            else if (m_MaxVisibleDistanceInBGArenas > SharedConst.MaxVisibilityDistance)
            {
                Log.outError(LogFilter.ServerLoading, "Visibility.Distance.BGArenas can't be greater {0}", SharedConst.MaxVisibilityDistance);
                m_MaxVisibleDistanceInBGArenas = SharedConst.MaxVisibilityDistance;
            }

            m_visibility_notify_periodOnContinents = ConfigMgr.GetDefaultValue("Visibility.Notify.Period.OnContinents", SharedConst.DefaultVisibilityNotifyPeriod);
            m_visibility_notify_periodInInstances = ConfigMgr.GetDefaultValue("Visibility.Notify.Period.InInstances", SharedConst.DefaultVisibilityNotifyPeriod);
            m_visibility_notify_periodInBGArenas = ConfigMgr.GetDefaultValue("Visibility.Notify.Period.InBGArenas", SharedConst.DefaultVisibilityNotifyPeriod);

            string dataPath = ConfigMgr.GetDefaultValue("DataDir", "./");
            if (reload)
            {
                if (dataPath != _dataPath)
                    Log.outError(LogFilter.ServerLoading, "DataDir option can't be changed at worldserver.conf reload, using current value ({0}).", _dataPath);
            }
            else
            {
                _dataPath = dataPath;
                Log.outInfo(LogFilter.ServerLoading, "Using DataDir {0}", _dataPath);
            }

            Log.outInfo(LogFilter.ServerLoading, @"WORLD: MMap data directory is: {0}\mmaps", _dataPath);

            bool EnableIndoor = ConfigMgr.GetDefaultValue("vmap.EnableIndoorCheck", true);
            bool EnableLOS = ConfigMgr.GetDefaultValue("vmap.EnableLOS", true);
            bool EnableHeight = ConfigMgr.GetDefaultValue("vmap.EnableHeight", true);

            if (!EnableHeight)
                Log.outError(LogFilter.ServerLoading, "VMap height checking Disabled! Creatures movements and other various things WILL be broken! Expect no support.");

            Global.VMapMgr.setEnableLineOfSightCalc(EnableLOS);
            Global.VMapMgr.setEnableHeightCalc(EnableHeight);

            Log.outInfo(LogFilter.ServerLoading, "VMap support included. LineOfSight: {0}, getHeight: {1}, indoorCheck: {2}", EnableLOS, EnableHeight, EnableIndoor);
            Log.outInfo(LogFilter.ServerLoading, @"VMap data directory is: {0}\vmaps", GetDataPath());
        }

        void ResetTimeDiffRecord()
        {
            if (m_updateTimeCount != 1)
                return;

            m_currentTime = Time.GetMSTime();
        }

        void RecordTimeDiff(string text)
        {
            if (m_updateTimeCount != 1)
                return;

            uint thisTime = Time.GetMSTime();
            uint diff = Time.GetMSTimeDiff(m_currentTime, thisTime);

            if (diff > WorldConfig.GetIntValue(WorldCfg.MinLogUpdate))
                Log.outInfo(LogFilter.Server, "Difftime {0}: {1}.", text, diff);

            m_currentTime = thisTime;
        }

        public void LoadAutobroadcasts()
        {
            uint oldMSTime = Time.GetMSTime();

            m_Autobroadcasts.Clear();

            PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_AUTOBROADCAST);
            stmt.AddValue(0, _realm.Id.Realm);

            SQLResult result = DB.Login.Query(stmt);
            if (result.IsEmpty())
            {
                Log.outInfo(LogFilter.ServerLoading, "Loaded 0 autobroadcasts definitions. DB table `autobroadcast` is empty for this realm!");
                return;
            }

            do
            {
                byte id = result.Read<byte>(0);

                m_Autobroadcasts[id] = new Autobroadcast(result.Read<string>(2), result.Read<byte>(1));

            } while (result.NextRow());

            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} autobroadcast definitions in {1} ms", m_Autobroadcasts.Count, Time.GetMSTimeDiffToNow(oldMSTime));
        }

        public void Update(uint diff)
        {
            m_updateTime = diff;

            if (diff > 100)
            {
                if (m_updateTimeSum > 60000)
                {
                    Log.outDebug(LogFilter.Server, "Update time diff: {0}. Players online: {1}.", m_updateTimeSum / m_updateTimeCount, GetActiveSessionCount());
                    m_updateTimeSum = m_updateTime;
                    m_updateTimeCount = 1;
                }
                else
                {
                    m_updateTimeSum += m_updateTime;
                    ++m_updateTimeCount;
                }
            }

            // Update the different timers
            for (WorldTimers i = 0; i < WorldTimers.Max; ++i)
            {
                if (m_timers[i].GetCurrent() >= 0)
                    m_timers[i].Update(diff);
                else
                    m_timers[i].SetCurrent(0);
            }

            _UpdateGameTime();

            // Handle daily quests reset time
            if (m_gameTime > m_NextDailyQuestReset)
            {
                DailyReset();
                InitDailyQuestResetTime(false);
            }

            // Handle weekly quests reset time
            if (m_gameTime > m_NextWeeklyQuestReset)
                ResetWeeklyQuests();

            // Handle monthly quests reset time
            if (m_gameTime > m_NextMonthlyQuestReset)
                ResetMonthlyQuests();

            if (m_gameTime > m_NextRandomBGReset)
                ResetRandomBG();

            if (m_gameTime > m_NextGuildReset)
                ResetGuildCap();

            if (m_gameTime > m_NextCurrencyReset)
                ResetCurrencyWeekCap();

            //Handle auctions when the timer has passed
            if (m_timers[WorldTimers.Auctions].Passed())
            {
                m_timers[WorldTimers.Auctions].Reset();

                // Update mails (return old mails with item, or delete them)
                if (++mail_timer > mail_timer_expires)
                {
                    mail_timer = 0;
                    Global.ObjectMgr.ReturnOrDeleteOldMails(true);
                }

                // Handle expired auctions
                Global.AuctionMgr.Update();
            }

            if (m_timers[WorldTimers.AuctionsPending].Passed())
            {
                m_timers[WorldTimers.AuctionsPending].Reset();

                //Global.AuctionMgr.UpdatePendingAuctions();
            }

            if (m_timers[WorldTimers.Blackmarket].Passed())
            {
                m_timers[WorldTimers.Blackmarket].Reset();

                ///- Update blackmarket, refresh auctions if necessary
                if ((blackmarket_timer * m_timers[WorldTimers.Blackmarket].GetInterval() >= WorldConfig.GetIntValue(WorldCfg.BlackmarketUpdatePeriod) * Time.Hour * Time.InMilliseconds) || blackmarket_timer == 0)
                {
                    Global.BlackMarketMgr.RefreshAuctions();
                    blackmarket_timer = 1; // timer is 0 on startup
                }
                else
                {
                    ++blackmarket_timer;
                    Global.BlackMarketMgr.Update();
                }
            }

            //Handle session updates when the timer has passed
            ResetTimeDiffRecord();
            UpdateSessions(diff);
            RecordTimeDiff("UpdateSessions");

            //Handle weather updates when the timer has passed
            if (m_timers[WorldTimers.Weathers].Passed())
            {
                m_timers[WorldTimers.Weathers].Reset();
                Global.WeatherMgr.Update((uint)m_timers[WorldTimers.Weathers].GetInterval());
            }

            /// <li> Update uptime table
            if (m_timers[WorldTimers.UpTime].Passed())
            {
                uint tmpDiff = (uint)(m_gameTime - m_startTime);
                uint maxOnlinePlayers = GetMaxPlayerCount();

                m_timers[WorldTimers.UpTime].Reset();

                PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_UPTIME_PLAYERS);

                stmt.AddValue(0, tmpDiff);
                stmt.AddValue(1, maxOnlinePlayers);
                stmt.AddValue(2, _realm.Id.Realm);
                stmt.AddValue(3, m_startTime);

                DB.Login.Execute(stmt);
            }

            /// <li> Clean logs table
            if (WorldConfig.GetIntValue(WorldCfg.LogdbCleartime) > 0) // if not enabled, ignore the timer
            {
                if (m_timers[WorldTimers.CleanDB].Passed())
                {
                    m_timers[WorldTimers.CleanDB].Reset();

                    PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_OLD_LOGS);
                    stmt.AddValue(0, WorldConfig.GetIntValue(WorldCfg.LogdbCleartime));
                    stmt.AddValue(1, 0);
                    stmt.AddValue(2, GetRealm().Id.Realm);

                    DB.Login.Execute(stmt);
                }
            }

            ResetTimeDiffRecord();
            Global.MapMgr.Update(diff);
            RecordTimeDiff("UpdateMapMgr");

            if (WorldConfig.GetBoolValue(WorldCfg.AutoBroadcast))
            {
                if (m_timers[WorldTimers.AutoBroadcast].Passed())
                {
                    m_timers[WorldTimers.AutoBroadcast].Reset();
                    SendAutoBroadcast();
                }
            }

            Global.BattlegroundMgr.Update(diff);
            RecordTimeDiff("UpdateBattlegroundMgr");

            Global.OutdoorPvPMgr.Update(diff);
            RecordTimeDiff("UpdateOutdoorPvPMgr");

            Global.BattleFieldMgr.Update(diff);
            RecordTimeDiff("BattlefieldMgr");

            //- Delete all characters which have been deleted X days before
            if (m_timers[WorldTimers.DeleteChars].Passed())
            {
                m_timers[WorldTimers.DeleteChars].Reset();
                Player.DeleteOldCharacters();
            }

            Global.LFGMgr.Update(diff);
            RecordTimeDiff("UpdateLFGMgr");

            Global.GroupMgr.Update(diff);
            RecordTimeDiff("GroupMgr");

            // execute callbacks from sql queries that were queued recently
            ProcessQueryCallbacks();
            RecordTimeDiff("ProcessQueryCallbacks");

            // Erase corpses once every 20 minutes
            if (m_timers[WorldTimers.Corpses].Passed())
            {
                m_timers[WorldTimers.Corpses].Reset();
                Global.MapMgr.DoForAllMaps(map => map.RemoveOldCorpses());
            }

            // Process Game events when necessary
            if (m_timers[WorldTimers.Events].Passed())
            {
                m_timers[WorldTimers.Events].Reset();                   // to give time for Update() to be processed
                uint nextGameEvent = Global.GameEventMgr.Update();
                m_timers[WorldTimers.Events].SetInterval(nextGameEvent);
                m_timers[WorldTimers.Events].Reset();
            }

            if (m_timers[WorldTimers.GuildSave].Passed())
            {
                m_timers[WorldTimers.GuildSave].Reset();
                Global.GuildMgr.SaveGuilds();
            }

            // update the instance reset times
            Global.InstanceSaveMgr.Update();

            Global.ScriptMgr.OnWorldUpdate(diff);
        }

        public void ForceGameEventUpdate()
        {
            m_timers[WorldTimers.Events].Reset();                   // to give time for Update() to be processed
            uint nextGameEvent = Global.GameEventMgr.Update();
            m_timers[WorldTimers.Events].SetInterval(nextGameEvent);
            m_timers[WorldTimers.Events].Reset();
        }

        public void SendGlobalMessage(ServerPacket packet, WorldSession self = null, Team team = 0)
        {
            foreach (var session in m_sessions.Values)
            {
                if (session.GetPlayer() != null && session.GetPlayer().IsInWorld && session != self &&
                    (team == 0 || session.GetPlayer().GetTeam() == team))
                {
                    session.SendPacket(packet);
                }
            }
        }

        public void SendGlobalGMMessage(ServerPacket packet, WorldSession self = null, Team team = 0)
        {
            foreach (var session in m_sessions.Values)
            {
                // check if session and can receive global GM Messages and its not self
                if (session == null || session == self || !session.HasPermission(RBACPermissions.ReceiveGlobalGmTextmessage))
                    continue;

                // Player should be in world
                Player player = session.GetPlayer();
                if (player == null || !player.IsInWorld)
                    continue;

                // Send only to same team, if team is given
                if (team == 0 || player.GetTeam() == team)
                    session.SendPacket(packet);
            }
        }

        // Send a System Message to all players (except self if mentioned)
        public void SendWorldText(CypherStrings string_id, params object[] args)
        {
            WorldWorldTextBuilder wt_builder = new WorldWorldTextBuilder((uint)string_id, args);
            var wt_do = new LocalizedPacketListDo(wt_builder);
            foreach (var session in m_sessions.Values)
            {
                if (session == null || !session.GetPlayer() || !session.GetPlayer().IsInWorld)
                    continue;

                wt_do.Invoke(session.GetPlayer());
            }
        }

        // Send a System Message to all GMs (except self if mentioned)
        public void SendGMText(CypherStrings string_id, params object[] args)
        {
            var wt_builder = new WorldWorldTextBuilder((uint)string_id, args);
            var wt_do = new LocalizedPacketListDo(wt_builder);
            foreach (var session in m_sessions.Values)
            {
                // Session should have permissions to receive global gm messages
                if (session == null || !session.HasPermission(RBACPermissions.ReceiveGlobalGmTextmessage))
                    continue;

                // Player should be in world
                Player player = session.GetPlayer();
                if (!player || !player.IsInWorld)
                    continue;

                wt_do.Invoke(player);
            }
        }

        // Send a packet to all players (or players selected team) in the zone (except self if mentioned)
        public bool SendZoneMessage(uint zone, ServerPacket packet, WorldSession self = null, uint team = 0)
        {
            bool foundPlayerToSend = false;
            packet.Write();
            foreach (var session in m_sessions.Values)
            {
                if (session != null && session.GetPlayer() && session.GetPlayer().IsInWorld &&
                    session.GetPlayer().GetZoneId() == zone && session != self && (team == 0 || (uint)session.GetPlayer().GetTeam() == team))
                {
                    session.SendPacket(packet, false);
                    foundPlayerToSend = true;
                }
            }

            return foundPlayerToSend;
        }

        // Send a System Message to all players in the zone (except self if mentioned)
        public void SendZoneText(uint zone, string text, WorldSession self = null, uint team = 0)
        {
            ChatPkt data = new ChatPkt();
            data.Initialize(ChatMsg.System, Language.Universal, null, null, text);
            SendZoneMessage(zone, data, self, team);
        }

        public void KickAll()
        {
            m_QueuedPlayer.Clear();                                 // prevent send queue update packet and login queued sessions

            // session not removed at kick and will removed in next update tick
            foreach (var session in m_sessions.Values)
                session.KickPlayer();
        }

        void KickAllLess(AccountTypes sec)
        {
            // session not removed at kick and will removed in next update tick
            foreach (var session in m_sessions.Values)
                if (session.GetSecurity() < sec)
                    session.KickPlayer();
        }

        /// Ban an account or ban an IP address, duration will be parsed using TimeStringToSecs if it is positive, otherwise permban
        public BanReturn BanAccount(BanMode mode, string nameOrIP, string duration, string reason, string author)
        {
            uint duration_secs = Time.TimeStringToSecs(duration);
            return BanAccount(mode, nameOrIP, duration_secs, reason, author);
        }

        /// Ban an account or ban an IP address, duration is in seconds if positive, otherwise permban
        public BanReturn BanAccount(BanMode mode, string nameOrIP, uint duration_secs, string reason, string author)
        {
            SQLResult resultAccounts;
            PreparedStatement stmt = null;

            // Update the database with ban information
            switch (mode)
            {
                case BanMode.IP:
                    // No SQL injection with prepared statements
                    stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_BY_IP);
                    stmt.AddValue(0, nameOrIP);
                    resultAccounts = DB.Login.Query(stmt);
                    stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_IP_BANNED);
                    stmt.AddValue(0, nameOrIP);
                    stmt.AddValue(1, duration_secs);
                    stmt.AddValue(2, author);
                    stmt.AddValue(3, reason);
                    DB.Login.Execute(stmt);
                    break;
                case BanMode.Account:
                    // No SQL injection with prepared statements
                    stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_ID_BY_NAME);
                    stmt.AddValue(0, nameOrIP);
                    resultAccounts = DB.Login.Query(stmt);
                    break;
                case BanMode.Character:
                    // No SQL injection with prepared statements
                    stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ACCOUNT_BY_NAME);
                    stmt.AddValue(0, nameOrIP);
                    resultAccounts = DB.Characters.Query(stmt);
                    break;
                default:
                    return BanReturn.SyntaxError;
            }

            if (resultAccounts == null)
            {
                if (mode == BanMode.IP)
                    return BanReturn.Success;                             // ip correctly banned but nobody affected (yet)
                else
                    return BanReturn.Notfound;                            // Nobody to ban
            }

            // Disconnect all affected players (for IP it can be several)
            SQLTransaction trans = new SQLTransaction();
            do
            {
                uint account = resultAccounts.Read<uint>(0);

                if (mode != BanMode.IP)
                {
                    // make sure there is only one active ban
                    stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_ACCOUNT_NOT_BANNED);
                    stmt.AddValue(0, account);
                    trans.Append(stmt);
                    // No SQL injection with prepared statements
                    stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_ACCOUNT_BANNED);
                    stmt.AddValue(0, account);
                    stmt.AddValue(1, duration_secs);
                    stmt.AddValue(2, author);
                    stmt.AddValue(3, reason);
                    trans.Append(stmt);
                }

                WorldSession sess = FindSession(account);
                if (sess)
                {
                    if (sess.GetPlayerName() != author)
                        sess.KickPlayer();
                }
            } while (resultAccounts.NextRow());

            DB.Login.CommitTransaction(trans);

            return BanReturn.Success;
        }

        /// Remove a ban from an account or IP address
        public bool RemoveBanAccount(BanMode mode, string nameOrIP)
        {
            PreparedStatement stmt = null;
            if (mode == BanMode.IP)
            {
                stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_IP_NOT_BANNED);
                stmt.AddValue(0, nameOrIP);
                DB.Login.Execute(stmt);
            }
            else
            {
                uint account = 0;
                if (mode == BanMode.Account)
                    account = Global.AccountMgr.GetId(nameOrIP);
                else if (mode == BanMode.Character)
                {
                    stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ACCOUNT_BY_NAME);
                    stmt.AddValue(0, nameOrIP);

                    SQLResult result = DB.Characters.Query(stmt);
                    if (!result.IsEmpty())
                        account = result.Read<uint>(0);
                }

                if (account == 0)
                    return false;

                //NO SQL injection as account is uint32
                stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_ACCOUNT_NOT_BANNED);
                stmt.AddValue(0, account);
                DB.Login.Execute(stmt);
            }
            return true;
        }

        /// Ban an account or ban an IP address, duration will be parsed using TimeStringToSecs if it is positive, otherwise permban
        public BanReturn BanCharacter(string name, string duration, string reason, string author)
        {
            Player pBanned = Global.ObjAccessor.FindConnectedPlayerByName(name);
            ObjectGuid guid;

            uint duration_secs = Time.TimeStringToSecs(duration);

            /// Pick a player to ban if not online
            if (!pBanned)
            {
                guid = ObjectManager.GetPlayerGUIDByName(name);
                if (guid.IsEmpty())
                    return BanReturn.Notfound;                                    // Nobody to ban
            }
            else
                guid = pBanned.GetGUID();

            // make sure there is only one active ban
            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHARACTER_BAN);
            stmt.AddValue(0, guid.GetCounter());
            DB.Characters.Execute(stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHARACTER_BAN);
            stmt.AddValue(0, guid.GetCounter());
            stmt.AddValue(1, duration_secs);
            stmt.AddValue(2, author);
            stmt.AddValue(3, reason);
            DB.Characters.Execute(stmt);

            if (pBanned)
                pBanned.GetSession().KickPlayer();

            return BanReturn.Success;
        }

        // Remove a ban from a character
        public bool RemoveBanCharacter(string name)
        {
            Player pBanned = Global.ObjAccessor.FindConnectedPlayerByName(name);
            ObjectGuid guid;

            // Pick a player to ban if not online
            if (!pBanned)
            {
                guid = ObjectManager.GetPlayerGUIDByName(name);
                if (guid.IsEmpty())
                    return false;                                    // Nobody to ban
            }
            else
                guid = pBanned.GetGUID();

            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHARACTER_BAN);
            stmt.AddValue(0, guid.GetCounter());
            DB.Characters.Execute(stmt);
            return true;
        }

        void _UpdateGameTime()
        {
            // update the time
            long thisTime = Time.UnixTime;
            uint elapsed = (uint)(thisTime - m_gameTime);
            m_gameTime = thisTime;

            //- if there is a shutdown timer
            if (!IsStopped && m_ShutdownTimer > 0 && elapsed > 0)
            {
                //- ... and it is overdue, stop the world
                if (m_ShutdownTimer <= elapsed)
                {
                    if (!m_ShutdownMask.HasAnyFlag(ShutdownMask.Idle) || GetActiveAndQueuedSessionCount() == 0)
                        IsStopped = true;                         // exist code already set
                    else
                        m_ShutdownTimer = 1;                        // minimum timer value to wait idle state
                }
                //- ... else decrease it and if necessary display a shutdown countdown to the users
                else
                {
                    m_ShutdownTimer -= elapsed;

                    ShutdownMsg();
                }
            }
        }

        public void ShutdownServ(uint time, ShutdownMask options, ShutdownExitCode exitcode, string reason = "")
        {
            // ignore if server shutdown at next tick
            if (IsStopped)
                return;

            m_ShutdownMask = options;
            m_ExitCode = exitcode;

            // If the shutdown time is 0, set m_stopEvent (except if shutdown is 'idle' with remaining sessions)
            if (time == 0)
            {
                if (!options.HasAnyFlag(ShutdownMask.Idle) || GetActiveAndQueuedSessionCount() == 0)
                    IsStopped = true;                             // exist code already set
                else
                    m_ShutdownTimer = 1;                            //So that the session count is re-evaluated at next world tick
            }
            // Else set the shutdown timer and warn users
            else
            {
                m_ShutdownTimer = time;
                ShutdownMsg(true, null, reason);
            }

            Global.ScriptMgr.OnShutdownInitiate(exitcode, options);
        }

        public void ShutdownMsg(bool show = false, Player player = null, string reason = "")
        {
            // not show messages for idle shutdown mode
            if (m_ShutdownMask.HasAnyFlag(ShutdownMask.Idle))
                return;

            // Display a message every 12 hours, hours, 5 minutes, minute, 5 seconds and finally seconds
            if (show ||
                (m_ShutdownTimer < 5 * Time.Minute && (m_ShutdownTimer % 15) == 0) || // < 5 min; every 15 sec
                (m_ShutdownTimer < 15 * Time.Minute && (m_ShutdownTimer % Time.Minute) == 0) || // < 15 min ; every 1 min
                (m_ShutdownTimer < 30 * Time.Minute && (m_ShutdownTimer % (5 * Time.Minute)) == 0) || // < 30 min ; every 5 min
                (m_ShutdownTimer < 12 * Time.Hour && (m_ShutdownTimer % Time.Hour) == 0) || // < 12 h ; every 1 h
                (m_ShutdownTimer > 12 * Time.Hour && (m_ShutdownTimer % (12 * Time.Hour)) == 0)) // > 12 h ; every 12 h
            {
                var str = Time.secsToTimeString(m_ShutdownTimer);
                if (!reason.IsEmpty())
                    str += " - " + reason;

                ServerMessageType msgid = m_ShutdownMask.HasAnyFlag(ShutdownMask.Restart) ? ServerMessageType.RestartTime : ServerMessageType.ShutdownTime;

                SendServerMessage(msgid, str, player);
                Log.outDebug(LogFilter.Server, "Server is {0} in {1}", (m_ShutdownMask.HasAnyFlag(ShutdownMask.Restart) ? "restart" : "shuttingdown"), str);
            }
        }

        public uint ShutdownCancel()
        {
            // nothing cancel or too later
            if (m_ShutdownTimer == 0 || IsStopped)
                return 0;

            ServerMessageType msgid = m_ShutdownMask.HasAnyFlag(ShutdownMask.Restart) ? ServerMessageType.RestartCancelled : ServerMessageType.ShutdownCancelled;

            uint oldTimer = m_ShutdownTimer;
            m_ShutdownMask = 0;
            m_ShutdownTimer = 0;
            m_ExitCode = (byte)ShutdownExitCode.Shutdown;                       // to default value
            SendServerMessage(msgid);

            Log.outDebug(LogFilter.Server, "Server {0} cancelled.", (m_ShutdownMask.HasAnyFlag(ShutdownMask.Restart) ? "restart" : "shutdown"));

            Global.ScriptMgr.OnShutdownCancel();
            return oldTimer;
        }

        public void SendServerMessage(ServerMessageType messageID, string stringParam = "", Player player = null)
        {
            ChatServerMessage packet = new ChatServerMessage();
            packet.MessageID = (int)messageID;
            if (messageID <= ServerMessageType.String)
                packet.StringParam = stringParam;

            if (player)
                player.SendPacket(packet);
            else
                SendGlobalMessage(packet);
        }

        public void UpdateSessions(uint diff)
        {
            Tuple<WorldSocket, ulong> linkInfo;
            while (_linkSocketQueue.TryDequeue(out linkInfo))
                ProcessLinkInstanceSocket(linkInfo);

            // Add new sessions
            WorldSession sess;
            while (addSessQueue.TryDequeue(out sess))
                AddSession_(sess);

            // Then send an update signal to remaining ones
            foreach (var pair in m_sessions.ToList())
            {
                WorldSession session = pair.Value;
                WorldSessionFilter updater = new WorldSessionFilter(session);
                if (!session.Update(diff, updater))    // As interval = 0
                {
                    if (!RemoveQueuedPlayer(session) && session != null && WorldConfig.GetIntValue(WorldCfg.IntervalDisconnectTolerance) != 0)
                        m_disconnects[session.GetAccountId()] = Time.UnixTime;

                    RemoveQueuedPlayer(session);
                    m_sessions.Remove(pair.Key);
                    session.Dispose();
                }
            }
        }

        void SendAutoBroadcast()
        {
            if (m_Autobroadcasts.Empty())
                return;

            var pair = m_Autobroadcasts.SelectRandomElementByWeight(autoPair =>
            {
                return autoPair.Value.Weight;
            });

            uint abcenter = WorldConfig.GetUIntValue(WorldCfg.AutoBroadcastCenter);

            if (abcenter == 0)
                SendWorldText(CypherStrings.AutoBroadcast, pair.Value.Message);
            else if (abcenter == 1)
                SendGlobalMessage(new PrintNotification(pair.Value.Message));
            else if (abcenter == 2)
            {
                SendWorldText(CypherStrings.AutoBroadcast, pair.Value.Message);
                SendGlobalMessage(new PrintNotification(pair.Value.Message));
            }

            Log.outDebug(LogFilter.Misc, "AutoBroadcast: '{0}'", pair.Value.Message);
        }

        public void UpdateRealmCharCount(uint accountId)
        {
            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_COUNT);
            stmt.AddValue(0, accountId);
            _queryProcessor.AddQuery(DB.Characters.AsyncQuery(stmt).WithCallback(_UpdateRealmCharCount));
        }

        void _UpdateRealmCharCount(SQLResult result)
        { 
            if (!result.IsEmpty())
            {
                uint Id = result.Read<uint>(0);
                uint charCount = result.Read<uint>(1);

                SQLTransaction trans = new SQLTransaction();

                PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_REALM_CHARACTERS_BY_REALM);
                stmt.AddValue(0, Id);
                stmt.AddValue(1, _realm.Id.Realm);
                trans.Append(stmt);

                stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_REALM_CHARACTERS);
                stmt.AddValue(0, charCount);
                stmt.AddValue(1, Id);
                stmt.AddValue(2, _realm.Id.Realm);
                trans.Append(stmt);

                DB.Login.CommitTransaction(trans);
            }
        }

        void InitWeeklyQuestResetTime()
        {
            long wstime = getWorldState(WorldStates.WeeklyQuestResetTime);
            long curtime = Time.UnixTime;
            m_NextWeeklyQuestReset = wstime < curtime ? curtime : wstime;
        }

        void InitDailyQuestResetTime(bool loading = true)
        {
            long mostRecentQuestTime = 0;

            if (loading)
            {
                SQLResult result = DB.Characters.Query("SELECT MAX(time) FROM character_queststatus_daily");
                if (!result.IsEmpty())
                {
                    mostRecentQuestTime = result.Read<uint>(0);
                }
            }


            // FIX ME: client not show day start time
            long curTime = Time.UnixTime;

            // current day reset time
            long curDayResetTime = Time.GetNextResetUnixTime(WorldConfig.GetIntValue(WorldCfg.DailyQuestResetTimeHour));

            // last reset time before current moment
            long resetTime = (curTime < curDayResetTime) ? curDayResetTime - Time.Day : curDayResetTime;

            // need reset (if we have quest time before last reset time (not processed by some reason)
            if (mostRecentQuestTime != 0 && mostRecentQuestTime <= resetTime)
                m_NextDailyQuestReset = mostRecentQuestTime;
            else // plan next reset time
                m_NextDailyQuestReset = (curTime >= curDayResetTime) ? curDayResetTime + Time.Day : curDayResetTime;
        }

        void InitMonthlyQuestResetTime()
        {
            long wstime = getWorldState(WorldStates.MonthlyQuestResetTime);
            long curtime = Time.UnixTime;
            m_NextMonthlyQuestReset = wstime < curtime ? curtime : wstime;
        }

        void InitRandomBGResetTime()
        {
            long bgtime = getWorldState(WorldStates.BGDailyResetTime);
            if (bgtime == 0)
                m_NextRandomBGReset = Time.UnixTime;         // game time not yet init

            // generate time by config
            long curTime = Time.UnixTime;

            // current day reset time
            long nextDayResetTime = Time.GetNextResetUnixTime(WorldConfig.GetIntValue(WorldCfg.RandomBgResetHour));

            // next reset time before current moment
            if (curTime >= nextDayResetTime)
                nextDayResetTime += Time.Day;

            // normalize reset time
            m_NextRandomBGReset = bgtime < curTime ? nextDayResetTime - Time.Day : nextDayResetTime;

            if (bgtime == 0)
                setWorldState(WorldStates.BGDailyResetTime, (ulong)m_NextRandomBGReset);
        }

        void InitGuildResetTime()
        {
            long gtime = getWorldState(WorldStates.GuildDailyResetTime);
            if (gtime == 0)
                m_NextGuildReset = Time.UnixTime;         // game time not yet init

            long curTime = Time.UnixTime;
            var nextDayResetTime = Time.GetNextResetUnixTime(WorldConfig.GetIntValue(WorldCfg.GuildResetHour));

            if (curTime >= nextDayResetTime)
                nextDayResetTime += Time.Day;

            // normalize reset time
            m_NextGuildReset = gtime < curTime ? nextDayResetTime - Time.Day : nextDayResetTime;

            if (gtime == 0)
                setWorldState(WorldStates.GuildDailyResetTime, (ulong)m_NextGuildReset);
        }

        void InitCurrencyResetTime()
        {
            long currencytime = getWorldState(WorldStates.CurrencyResetTime);
            if (currencytime == 0)
                m_NextCurrencyReset = Time.UnixTime;         // game time not yet init

            // generate time by config
            long curTime = Time.UnixTime;

            var nextWeekResetTime = Time.GetNextResetUnixTime(WorldConfig.GetIntValue(WorldCfg.CurrencyResetDay), WorldConfig.GetIntValue(WorldCfg.CurrencyResetHour));

            // next reset time before current moment
            if (curTime >= nextWeekResetTime)
                nextWeekResetTime += WorldConfig.GetIntValue(WorldCfg.CurrencyResetInterval) * Time.Day;

            // normalize reset time
            m_NextCurrencyReset = currencytime < curTime ? nextWeekResetTime - WorldConfig.GetIntValue(WorldCfg.CurrencyResetInterval) * Time.Day : nextWeekResetTime;

            if (currencytime == 0)
                setWorldState(WorldStates.CurrencyResetTime, (ulong)m_NextCurrencyReset);
        }

        void DailyReset()
        {
            Log.outInfo(LogFilter.Server, "Daily quests reset for all characters.");

            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_RESET_CHARACTER_QUESTSTATUS_DAILY);
            DB.Characters.Execute(stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHARACTER_GARRISON_FOLLOWER_ACTIVATIONS);
            stmt.AddValue(0, 1);
            DB.Characters.Execute(stmt);

            foreach (var session in m_sessions.Values)
                if (session.GetPlayer() != null)
                    session.GetPlayer().DailyReset();

            // change available dailies
            Global.PoolMgr.ChangeDailyQuests();
        }

        void ResetCurrencyWeekCap()
        {
            DB.Characters.Execute("UPDATE `character_currency` SET `WeeklyQuantity` = 0");

            foreach (var session in m_sessions.Values)
                if (session.GetPlayer() != null)
                    session.GetPlayer().ResetCurrencyWeekCap();

            m_NextCurrencyReset = m_NextCurrencyReset + Time.Day * WorldConfig.GetIntValue(WorldCfg.CurrencyResetInterval);
            setWorldState(WorldStates.CurrencyResetTime, (ulong)m_NextCurrencyReset);
        }

        public void LoadDBAllowedSecurityLevel()
        {
            PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_REALMLIST_SECURITY_LEVEL);
            stmt.AddValue(0, _realm.Id.Realm);
            SQLResult result = DB.Login.Query(stmt);

            if (!result.IsEmpty())
                SetPlayerSecurityLimit((AccountTypes)result.Read<byte>(0));
        }

        public void SetPlayerSecurityLimit(AccountTypes accountType)
        {
            AccountTypes security = accountType < AccountTypes.Console ? accountType : AccountTypes.Player;
            bool update = security > m_allowedSecurityLevel;
            m_allowedSecurityLevel = security;
            if (update)
                KickAllLess(m_allowedSecurityLevel);
        }

        void ResetWeeklyQuests()
        {
            Log.outInfo(LogFilter.Server, "Weekly quests reset for all characters.");

            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_RESET_CHARACTER_QUESTSTATUS_WEEKLY);
            DB.Characters.Execute(stmt);

            foreach (var session in m_sessions.Values)
                if (session.GetPlayer() != null)
                    session.GetPlayer().ResetWeeklyQuestStatus();

            m_NextWeeklyQuestReset = (m_NextWeeklyQuestReset + Time.Week);
            setWorldState(WorldStates.WeeklyQuestResetTime, (ulong)m_NextWeeklyQuestReset);

            // change available weeklies
            Global.PoolMgr.ChangeWeeklyQuests();
        }

        void ResetMonthlyQuests()
        {
            Log.outInfo(LogFilter.Server, "Monthly quests reset for all characters.");

            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_RESET_CHARACTER_QUESTSTATUS_MONTHLY);
            DB.Characters.Execute(stmt);

            foreach (var session in m_sessions.Values)
                if (session.GetPlayer() != null)
                    session.GetPlayer().ResetMonthlyQuestStatus();

            long curTime = Time.UnixTime;

            // current day reset time
            long curDayResetTime = Time.GetNextResetUnixTime(30, 1, 0);

            // last reset time before current moment
            long nextMonthResetTime = (curTime < curDayResetTime) ? curDayResetTime - Time.Day : curDayResetTime;

            // plan next reset time
            m_NextMonthlyQuestReset = (curTime >= nextMonthResetTime) ? nextMonthResetTime + Time.Month : nextMonthResetTime;

            setWorldState(WorldStates.MonthlyQuestResetTime, (ulong)m_NextMonthlyQuestReset);
        }

        public void ResetEventSeasonalQuests(ushort event_id)
        {
            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_RESET_CHARACTER_QUESTSTATUS_SEASONAL_BY_EVENT);
            stmt.AddValue(0, event_id);
            DB.Characters.Execute(stmt);

            foreach (var session in m_sessions.Values)
                if (session.GetPlayer())
                    session.GetPlayer().ResetSeasonalQuestStatus(event_id);
        }

        void ResetRandomBG()
        {
            Log.outInfo(LogFilter.Server, "Random BG status reset for all characters.");

            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_BATTLEGROUND_RANDOM_ALL);
            DB.Characters.Execute(stmt);

            foreach (var session in m_sessions.Values)
                if (session.GetPlayer())
                    session.GetPlayer().SetRandomWinner(false);

            m_NextRandomBGReset = m_NextRandomBGReset + Time.Day;
            setWorldState(WorldStates.BGDailyResetTime, (ulong)m_NextRandomBGReset);
        }

        void ResetGuildCap()
        {
            m_NextGuildReset = (m_NextGuildReset + Time.Day);
            setWorldState(WorldStates.GuildDailyResetTime, (ulong)m_NextGuildReset);
            ulong week = getWorldState(WorldStates.GuildWeeklyResetTime);
            week = week < 7 ? week + 1 : 1;

            Log.outInfo(LogFilter.Server, "Guild Daily Cap reset. Week: {0}", week == 1);
            setWorldState(WorldStates.GuildWeeklyResetTime, week);
            Global.GuildMgr.ResetTimes(week == 1);
        }

        void UpdateMaxSessionCounters()
        {
            m_maxActiveSessionCount = Math.Max(m_maxActiveSessionCount, (uint)(m_sessions.Count - m_QueuedPlayer.Count));
            m_maxQueuedSessionCount = Math.Max(m_maxQueuedSessionCount, (uint)m_QueuedPlayer.Count);
        }

        public string LoadDBVersion()
        {
            var DBVersion = "Unknown world database.";

            SQLResult result = DB.World.Query("SELECT db_version, cache_id, hotfix_cache_id FROM version LIMIT 1");
            if (!result.IsEmpty())
            {
                DBVersion = result.Read<string>(0);
                // will be overwrite by config values if different and non-0
                WorldConfig.SetValue(WorldCfg.ClientCacheVersion, result.Read<uint>(1));
                WorldConfig.SetValue(WorldCfg.HotfixCacheVersion, result.Read<uint>(2));
            }

            return DBVersion;
        }

        void UpdateAreaDependentAuras()
        {
            foreach (var session in m_sessions.Values)
            {
                if (session.GetPlayer() != null && session.GetPlayer().IsInWorld)
                {
                    session.GetPlayer().UpdateAreaDependentAuras(session.GetPlayer().GetAreaId());
                    session.GetPlayer().UpdateZoneDependentAuras(session.GetPlayer().GetZoneId());
                }
            }
        }

        void LoadWorldStates()
        {
            uint oldMSTime = Time.GetMSTime();

            SQLResult result = DB.Characters.Query("SELECT entry, value FROM worldstates");

            if (result.IsEmpty())
            {
                Log.outInfo(LogFilter.ServerLoading, "Loaded 0 world states. DB table `worldstates` is empty!");
                return;
            }

            uint count = 0;
            do
            {
                m_worldstates[result.Read<uint>(0)] = result.Read<uint>(1);
                ++count;
            }
            while (result.NextRow());

            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} world states in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
        }

        public void setWorldState(WorldStates index, ulong value)
        {
            setWorldState((uint)index, value);
        }

        public void setWorldState(uint index, object value)
        {
            PreparedStatement stmt;

            if (m_worldstates.ContainsKey(index))
            {
                if (m_worldstates[index] == Convert.ToUInt32(value))
                    return;

                stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_WORLDSTATE);
                stmt.AddValue(0, Convert.ToUInt32(value));
                stmt.AddValue(1, index);
            }
            else
            {
                stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_WORLDSTATE);

                stmt.AddValue(0, index);
                stmt.AddValue(1, Convert.ToUInt32(value));
            }
            DB.Characters.Execute(stmt);
            m_worldstates[index] = Convert.ToUInt32(value);
        }

        public uint getWorldState(WorldStates index)
        {
            return getWorldState((uint)index);
        }

        public uint getWorldState(uint index)
        {
            return m_worldstates.LookupByKey(index);
        }

        void ProcessQueryCallbacks()
        {
            _queryProcessor.ProcessReadyQueries();
        }

        public CharacterInfo GetCharacterInfo(ObjectGuid guid) { return _characterInfoStorage.LookupByKey(guid); }

        public void LoadCharacterInfoStorage()
        {
            Log.outInfo(LogFilter.ServerLoading, "Loading character name data");

            _characterInfoStorage.Clear();

            SQLResult result = DB.Characters.Query("SELECT guid, name, account, race, gender, class, level, deleteDate FROM characters");
            if (result.IsEmpty())
            {
                Log.outInfo(LogFilter.ServerLoading, "No character name data loaded, empty query");
                return;
            }

            uint count = 0;
            do
            {
                AddCharacterInfo(ObjectGuid.Create(HighGuid.Player, result.Read<ulong>(0)), result.Read<uint>(2), result.Read<string>(1),
                    result.Read<byte>(4), result.Read<byte>(3), result.Read<byte>(5), result.Read<byte>(6), result.Read<uint>(7) != 0);
                ++count;
            } while (result.NextRow());

            Log.outInfo(LogFilter.ServerLoading, "Loaded name data for {0} characters", count);
        }

        public void AddCharacterInfo(ObjectGuid guid, uint accountId, string name, byte gender, byte race, byte playerClass, byte level, bool isDeleted)
        {
            CharacterInfo data = new CharacterInfo();
            data.Name = name;
            data.AccountId = accountId;
            data.RaceID = (Race)race;
            data.Sex = (Gender)gender;
            data.ClassID = (Class)playerClass;
            data.Level = level;
            data.IsDeleted = isDeleted;
            _characterInfoStorage[guid] = data;
        }

        public void DeleteCharacterInfo(ObjectGuid guid) { _characterInfoStorage.Remove(guid); }

        public void UpdateCharacterInfo(ObjectGuid guid, string name, Gender gender = Gender.None, Race race = Race.None)
        {
            if (!_characterInfoStorage.ContainsKey(guid))
                return;

            var charData = _characterInfoStorage[guid];
            charData.Name = name;

            if (gender != Gender.None)
                charData.Sex = gender;

            if (race != Race.None)
                charData.RaceID = race;

            InvalidatePlayer data = new InvalidatePlayer();
            data.Guid = guid;
            SendGlobalMessage(data);
        }

        public void UpdateCharacterInfoLevel(ObjectGuid guid, byte level)
        {
            if (!_characterInfoStorage.ContainsKey(guid))
                return;

            _characterInfoStorage[guid].Level = level;
        }

        public void UpdateCharacterInfoDeleted(ObjectGuid guid, bool deleted, string name = null)
        {
            if (!_characterInfoStorage.ContainsKey(guid))
                return;

            _characterInfoStorage[guid].IsDeleted = deleted;

            if (!string.IsNullOrEmpty(name))
                _characterInfoStorage[guid].Name = name;
        }

        public void ReloadRBAC()
        {
            // Passive reload, we mark the data as invalidated and next time a permission is checked it will be reloaded
            Log.outInfo(LogFilter.Rbac, "World.ReloadRBAC()");
            foreach (var session in m_sessions.Values)
                session.InvalidateRBACData();
        }

        public bool HasCharacterInfo(ObjectGuid guid) { return _characterInfoStorage.ContainsKey(guid); }

        public List<WorldSession> GetAllSessions()
        {
            return m_sessions.Values.ToList();
        }

        public int GetActiveAndQueuedSessionCount() { return m_sessions.Count; }
        public int GetActiveSessionCount() { return m_sessions.Count - m_QueuedPlayer.Count; }
        public int GetQueuedSessionCount() { return m_QueuedPlayer.Count; }
        // Get the maximum number of parallel sessions on the server since last reboot
        public uint GetMaxQueuedSessionCount() { return m_maxQueuedSessionCount; }
        public uint GetMaxActiveSessionCount() { return m_maxActiveSessionCount; }

        public uint GetPlayerCount() { return m_PlayerCount; }
        public uint GetMaxPlayerCount() { return m_MaxPlayerCount; }

        public void IncreasePlayerCount()
        {
            m_PlayerCount++;
            m_MaxPlayerCount = Math.Max(m_MaxPlayerCount, m_PlayerCount);
        }
        public void DecreasePlayerCount() { m_PlayerCount--; }

        public AccountTypes GetPlayerSecurityLimit() { return m_allowedSecurityLevel; }

        public void SetPlayerAmountLimit(uint limit) { m_playerLimit = limit; }
        public uint GetPlayerAmountLimit() { return m_playerLimit; }

        /// Get the path where data (dbc, maps) are stored on disk
        public string GetDataPath() { return _dataPath; }

        public void SetDataPath(string path) { _dataPath = path; }

        // When server started?
        long GetStartTime() { return m_startTime; }
        // What time is it?
        public long GetGameTime() { return m_gameTime; }
        // Uptime (in secs)
        public uint GetUptime() { return (uint)(m_gameTime - m_startTime); }
        // Update time
        public uint GetUpdateTime() { return m_updateTime; }

        public long GetNextDailyQuestsResetTime() { return m_NextDailyQuestReset; }
        public long GetNextWeeklyQuestsResetTime() { return m_NextWeeklyQuestReset; }
        long GetNextRandomBGResetTime() { return m_NextRandomBGReset; }

        public uint GetConfigMaxSkillValue()
        {
            int lvl = WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel);
            return (uint)(lvl > 60 ? 300 + ((lvl - 60) * 75) / 10 : lvl * 5);
        }

        public bool IsShuttingDown() { return m_ShutdownTimer > 0; }
        public uint GetShutDownTimeLeft() { return m_ShutdownTimer; }

        public void StopNow(ShutdownExitCode exitcode = ShutdownExitCode.Error) { IsStopped = true; m_ExitCode = exitcode; }

        public bool IsPvPRealm()
        {
            RealmType realmtype = (RealmType)WorldConfig.GetIntValue(WorldCfg.GameType);
            return (realmtype == RealmType.PVP
                || realmtype == RealmType.RPPVP
                || realmtype == RealmType.FFAPVP);
        }
        public bool IsFFAPvPRealm()
        {
            return WorldConfig.GetIntValue(WorldCfg.GameType) == (int)RealmType.FFAPVP;
        }

        public LocaleConstant GetDefaultDbcLocale() { return m_defaultDbcLocale; }

        public bool LoadRealmInfo()
        {
            SQLResult result = DB.Login.Query("SELECT id, name, address, localAddress, localSubnetMask, port, icon, flag, timezone, allowedSecurityLevel, population, gamebuild, Region, Battlegroup FROM realmlist WHERE id = {0}", _realm.Id.Realm);
            if (result.IsEmpty())
                return false;

            _realm.SetName(result.Read<string>(1));
            _realm.ExternalAddress = System.Net.IPAddress.Parse(result.Read<string>(2));
            _realm.LocalAddress = System.Net.IPAddress.Parse(result.Read<string>(3));
            _realm.LocalSubnetMask = System.Net.IPAddress.Parse(result.Read<string>(4));
            _realm.Port = result.Read<ushort>(5);
            _realm.Type = result.Read<byte>(6);
            _realm.Flags = (RealmFlags)result.Read<byte>(7);
            _realm.Timezone = result.Read<byte>(8);
            _realm.AllowedSecurityLevel = (AccountTypes)result.Read<byte>(9);
            _realm.PopulationLevel = result.Read<float>(10);
            _realm.Id.Region = result.Read<byte>(12);
            _realm.Id.Site = result.Read<byte>(13);
            _realm.Build = result.Read<uint>(11);
            return true;
        }

        public Realm GetRealm() { return _realm; }
        public RealmHandle GetRealmId() { return _realm.Id; }

        public void RemoveOldCorpses()
        {
            m_timers[WorldTimers.Corpses].SetCurrent(m_timers[WorldTimers.Corpses].GetInterval());
        }

        public uint GetVirtualRealmAddress()
        {
            return _realm.Id.GetAddress();
        }

        public float GetMaxVisibleDistanceOnContinents() { return m_MaxVisibleDistanceOnContinents; }
        public float GetMaxVisibleDistanceInInstances() { return m_MaxVisibleDistanceInInstances; }
        public float GetMaxVisibleDistanceInBGArenas() { return m_MaxVisibleDistanceInBGArenas; }

        public int GetVisibilityNotifyPeriodOnContinents() { return m_visibility_notify_periodOnContinents; }
        public int GetVisibilityNotifyPeriodInInstances() { return m_visibility_notify_periodInInstances; }
        public int GetVisibilityNotifyPeriodInBGArenas() { return m_visibility_notify_periodInBGArenas; }

        public LocaleConstant GetAvailableDbcLocale(LocaleConstant locale)
        {
            //if (m_availableDbcLocaleMask & (1 << locale))
                //return locale;
            //else
                return m_defaultDbcLocale;
        }

        public CleaningFlags GetCleaningFlags() { return m_CleaningFlags; }
        public void SetCleaningFlags(CleaningFlags flags) { m_CleaningFlags = flags; }

        #region Fields
        uint m_ShutdownTimer;
        ShutdownMask m_ShutdownMask;
        ShutdownExitCode m_ExitCode;
        public bool IsStopped;

        Dictionary<byte, Autobroadcast> m_Autobroadcasts = new Dictionary<byte, Autobroadcast>();

        Dictionary<ObjectGuid, CharacterInfo> _characterInfoStorage = new Dictionary<ObjectGuid, CharacterInfo>();

        CleaningFlags m_CleaningFlags;

        float m_MaxVisibleDistanceOnContinents = SharedConst.DefaultVisibilityDistance;
        float m_MaxVisibleDistanceInInstances = SharedConst.DefaultVisibilityInstance;
        float m_MaxVisibleDistanceInBGArenas = SharedConst.DefaultVisibilityBGAreans;

        int m_visibility_notify_periodOnContinents = SharedConst.DefaultVisibilityNotifyPeriod;
        int m_visibility_notify_periodInInstances = SharedConst.DefaultVisibilityNotifyPeriod;
        int m_visibility_notify_periodInBGArenas = SharedConst.DefaultVisibilityNotifyPeriod;

        bool m_isClosed;

        long m_startTime;
        long m_gameTime;
        Dictionary<WorldTimers, IntervalTimer> m_timers = new Dictionary<WorldTimers, IntervalTimer>();
        long mail_timer;
        long mail_timer_expires;
        long blackmarket_timer;
        uint m_updateTime, m_updateTimeSum;
        uint m_updateTimeCount;
        uint m_currentTime;

        Dictionary<uint, WorldSession> m_sessions = new Dictionary<uint, WorldSession>();
        Dictionary<uint, long> m_disconnects = new Dictionary<uint, long>();
        uint m_maxActiveSessionCount;
        uint m_maxQueuedSessionCount;
        uint m_PlayerCount;
        uint m_MaxPlayerCount;

        Dictionary<uint, uint> m_worldstates = new Dictionary<uint, uint>();
        uint m_playerLimit;
        AccountTypes m_allowedSecurityLevel;
        LocaleConstant m_defaultDbcLocale;                     // from config for one from loaded DBC locales
        List<string> m_motd = new List<string>();

        // scheduled reset times
        long m_NextDailyQuestReset;
        long m_NextWeeklyQuestReset;
        long m_NextMonthlyQuestReset;
        long m_NextRandomBGReset;
        long m_NextGuildReset;
        long m_NextCurrencyReset;

        List<WorldSession> m_QueuedPlayer = new List<WorldSession>();
        ConcurrentQueue<WorldSession> addSessQueue = new ConcurrentQueue<WorldSession>();

        ConcurrentQueue<Tuple<WorldSocket, ulong>> _linkSocketQueue = new ConcurrentQueue<Tuple<WorldSocket, ulong>>();

        QueryCallbackProcessor _queryProcessor = new QueryCallbackProcessor();

        Realm _realm;

        string _dataPath;
        #endregion
    }

    /// Timers for different object refresh rates
    public enum WorldTimers
    {
        Auctions,
        AuctionsPending,
        Weathers,
        UpTime,
        Corpses,
        Events,
        CleanDB,
        AutoBroadcast,
        MailBox,
        DeleteChars,
        AhBot,
        PingDB,
        GuildSave,
        Blackmarket,
        Max
    }

    public enum ServerMessageType
    {
        ShutdownTime = 1,
        RestartTime = 2,
        String = 3,
        ShutdownCancelled = 4,
        RestartCancelled = 5,
        BgShutdownTime = 6,
        BgRestartTime = 7,
        InstanceShutdownTime = 8,
        InstanceRestartTime = 9,
        ContentReady = 10,
        TicketServicedSoon = 11,
        WaitTimeUnavailable = 12,
        TicketWaitTime = 13,
    }

    [Flags]
    public enum ShutdownMask
    {
        Restart = 1,
        Idle = 2,
        Force = 4
    }

    public enum ShutdownExitCode
    {
        Shutdown = 0,
        Error = 1,
        Restart = 2,
    }

    public class CharacterInfo
    {
        public string Name;
        public uint AccountId;
        public Class ClassID;
        public Race RaceID;
        public Gender Sex;
        public byte Level;
        public bool IsDeleted;
    }

    public class WorldWorldTextBuilder : MessageBuilder
    {        
        public WorldWorldTextBuilder(uint textId, params object[] args)
        {
            i_textId = textId;
            i_args = args;
        }

        public override void Invoke(List<ServerPacket> data_list, LocaleConstant loc_idx)
        {
            string text = Global.ObjectMgr.GetCypherString(i_textId, loc_idx);

            if (i_args != null)
                text = string.Format(text, i_args);

            ChatPkt messageChat = new ChatPkt();

            var lines = new StringArray(text, "\n");
            for (var i = 0; i < lines.Length; ++i)
            {
                messageChat.Initialize(ChatMsg.System, Language.Universal, null, null, lines[i]);
                data_list.Add(messageChat);
            }
        }

        uint i_textId;
        object[] i_args;
    }

    struct Autobroadcast
    {
        public Autobroadcast(string message, byte weight)
        {
            Message = message;
            Weight = weight;
        }

        public string Message;
        public byte Weight;
    }
}
