<commit_before>/*
 * AscEmu Framework based on ArcEmu MMORPG Server
 * Copyright (c) 2014-2019 AscEmu Team <http://www.ascemu.org>
 * Copyright (C) 2008-2012 ArcEmu Team <http://www.ArcEmu.org/>
 * Copyright (C) 2005-2007 Ascent Team
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "StdAfx.h"
#include "Storage/MySQLDataStore.hpp"
#include "Server/MainServerDefines.h"
#include "MapMgr.h"
#include "WorldCreator.h"

SERVER_DECL InstanceMgr sInstanceMgr;

InstanceMgr::InstanceMgr()
{
    memset(m_maps, 0, sizeof(Map*)* MAX_NUM_MAPS);
    memset(m_instances, 0, sizeof(InstanceMap*) * MAX_NUM_MAPS);
    memset(m_singleMaps, 0, sizeof(MapMgr*) * MAX_NUM_MAPS);
    memset(&m_nextInstanceReset, 0, sizeof(time_t) * MAX_NUM_MAPS);
    m_InstanceHigh = 0;
}

void InstanceMgr::Load(TaskList* l)
{
    // Create all non-instance type maps.
    QueryResult* result = CharacterDatabase.Query("SELECT MAX(id) FROM instances");
    if (result)
    {
        m_InstanceHigh = result->Fetch()[0].GetUInt32() + 1;
        delete result;
    }
    else
        m_InstanceHigh = 1;

    // load each map we have in the database.
    result = WorldDatabase.Query("SELECT DISTINCT Map FROM creature_spawns WHERE min_build <= %u AND max_build >= %u AND event_entry = 0;", VERSION_STRING, VERSION_STRING);
    if (result)
    {
        do
        {
            if (sMySQLStore.getWorldMapInfo(result->Fetch()[0].GetUInt32()) == nullptr)
                continue;

            if (result->Fetch()[0].GetUInt32() >= MAX_NUM_MAPS)
            {
                LOG_ERROR("One or more of your creature_spawns rows specifies an invalid map: %u", result->Fetch()[0].GetUInt32());
                continue;
            }

            //_CreateMap(result->Fetch()[0].GetUInt32());
            l->AddTask(new Task(new CallbackP1<InstanceMgr, uint32>(this, &InstanceMgr::_CreateMap, result->Fetch()[0].GetUInt32())));
        }
        while (result->NextRow());
        delete result;
    }

    l->wait();

    // create maps for any we don't have yet.
    MySQLDataStore::WorldMapInfoContainer const* its = sMySQLStore.getWorldMapInfoStore();
    for (MySQLDataStore::WorldMapInfoContainer::const_iterator itr = its->begin(); itr != its->end(); ++itr)
    {
        if (itr->second.mapid >= MAX_NUM_MAPS)
        {
            LOG_ERROR("One or more of your worldmap_info rows specifies an invalid map: %u", itr->second.mapid);
            continue;
        }

        if (m_maps[itr->second.mapid] == NULL)
        {
            l->AddTask(new Task(new CallbackP1<InstanceMgr, uint32>(this, &InstanceMgr::_CreateMap, itr->second.mapid)));
        }
    }

    l->wait();

    // load reset times
    result = CharacterDatabase.Query("SELECT setting_id, setting_value FROM server_settings WHERE setting_id LIKE 'next_instance_reset_%%'");
    if (result)
    {
        do
        {
            const char* id = result->Fetch()[0].GetString();
            uint32 value = result->Fetch()[1].GetUInt32();
            if (strlen(id) <= 20)
                continue;
            uint32 mapId = atoi(id + 20);
            if (mapId >= MAX_NUM_MAPS)
                continue;
            m_nextInstanceReset[mapId] = value;
        }
        while (result->NextRow());
        delete result;
    }

    // load saved instances
    _LoadInstances();
}

InstanceMgr::~InstanceMgr()
{}

void InstanceMgr::Shutdown()
{
    uint32 i;
    InstanceMap::iterator itr;
    for (i = 0; i < MAX_NUM_MAPS; ++i)
    {
        if (m_instances[i] != NULL)
        {
            for (itr = m_instances[i]->begin(); itr != m_instances[i]->end(); ++itr)
            {
                if (itr->second->m_mapMgr)
                    itr->second->m_mapMgr->KillThread();

                delete itr->second;
            }

            delete m_instances[i];
            m_instances[i] = NULL;
        }

        if (m_singleMaps[i] != NULL)
        {
            MapMgr* ptr = m_singleMaps[i];
            ptr->KillThread();
            delete ptr;
            m_singleMaps[i] = NULL;
        }

        if (m_maps[i] != NULL)
        {
            delete m_maps[i];
            m_maps[i] = NULL;
        }
    }
}

uint32 InstanceMgr::PreTeleport(uint32 mapid, Player* plr, uint32 instanceid)
{
    // preteleport is where all the magic happens :P instance creation, etc.
    MySQLStructure::MapInfo const* inf = sMySQLStore.getWorldMapInfo(mapid);
    Group* pGroup;
    InstanceMap* instancemap;
    Instance* in;

    if (inf == NULL || mapid >= MAX_NUM_MAPS)
        return INSTANCE_ABORT_NOT_FOUND;

    // main continent check.
    if (inf->type == INSTANCE_NULL)
    {
        // this will be useful when clustering comes into play.
        // we can check if the destination world server is online or not and then cancel them before they load.
        return (m_singleMaps[mapid] != NULL) ? INSTANCE_OK : INSTANCE_ABORT_NOT_FOUND;
    }

    // shouldn't happen
    if (inf->type == INSTANCE_BATTLEGROUND)
        return INSTANCE_ABORT_NOT_FOUND;

    pGroup = plr->GetGroup();

    // players without groups cannot enter raids and heroic instances

    if (pGroup == NULL &&
        inf->type == INSTANCE_RAID &&
        !plr->m_cheats.TriggerpassCheat)
        return INSTANCE_ABORT_NOT_IN_RAID_GROUP;

    if (pGroup == NULL &&
        (inf->type == INSTANCE_NONRAID && plr->iInstanceType == MODE_HEROIC) &&
        !plr->m_cheats.TriggerpassCheat)
        return INSTANCE_ABORT_NOT_IN_RAID_GROUP;


    // players without raid groups cannot enter raid instances
    if (pGroup != NULL && pGroup->getGroupType() != GROUP_TYPE_RAID && inf->type == INSTANCE_RAID && !plr->m_cheats.TriggerpassCheat)
        return INSTANCE_ABORT_NOT_IN_RAID_GROUP;

    // We deny transfer if we requested a heroic instance of a map that has no heroic mode
    // We are trying to enter into a non-multi instance with a heroic group, downscaling
    if (inf->type == INSTANCE_NONRAID && plr->GetDungeonDifficulty() == MODE_HEROIC)
    {
        plr->SetDungeonDifficulty(MODE_NORMAL);
        plr->sendDungeonDifficultyPacket();

        Group* grp = plr->GetGroup();
        if (grp != NULL)
            grp->SetDungeonDifficulty(MODE_NORMAL);
    }

    // if it's not a normal / 10men normal then check if we even have this mode
    if (inf->type == INSTANCE_RAID && plr->GetRaidDifficulty() != MODE_NORMAL_10MEN)
    {
        uint8_t newtype = 0;

        if (!inf->hasDifficulty(plr->GetRaidDifficulty()))
        {
            // no it doesn't so we will downscale it

            ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
            // This part is totally speculative, if you know how this is done actually then do change it
            //
            switch (plr->GetRaidDifficulty())
            {
                case MODE_NORMAL_25MEN:
                case MODE_HEROIC_10MEN:
                {
                    newtype = MODE_NORMAL_10MEN;
                    break;
                }

                case MODE_HEROIC_25MEN:
                {
                    newtype = MODE_NORMAL_25MEN;
                    break;
                }
            }

            ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

            // check if we have this mode
            if (!inf->hasDifficulty(newtype))
            {

                //appearantly we don't so we set to 10men normal, which is the default for old raids too
                //regardless of their playerlimit
                newtype = MODE_NORMAL_10MEN;
            }

            // Setting the new mode on us and our group
            if (plr->GetRaidDifficulty() != newtype)
            {

                plr->SetRaidDifficulty(newtype);
                plr->sendRaidDifficultyPacket();

                Group* grp = plr->GetGroup();
                if (grp != nullptr)
                    grp->SetRaidDifficulty(newtype);
            }
        }
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // If we are here, it means:
    // 1.) We're a simple non-raid and non-heroic dungeon
    // 2.) We're a multi-dungeon set to heroic and we are in a group
    // 3.) We're a raid, and we are in a raid group
    // 4.) We're a raid, we are in a raid group, and we have the right mode set
    //
    // So, first we have to check if they have an instance on this map already, if so, allow them to teleport to that.
    // Otherwise, we will try to create them a new one.
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    m_mapLock.Acquire();
    instancemap = m_instances[mapid];

    // If there are no instances of this map yet, we need to create the map
    if (instancemap == NULL)
    {
        if (instanceid != 0)
        {
            m_mapLock.Release();
            return INSTANCE_ABORT_NOT_FOUND;
        }

        m_instances[mapid] = new InstanceMap;
        instancemap = m_instances[mapid];
    }
    else
    {
        InstanceMap::iterator itr;

        // this is the case when we enter an already existing instance (with summons for example)
        if (instanceid != 0)
        {
            itr = instancemap->find(instanceid);
            if (itr != instancemap->end())
            {
                in = itr->second;

                if (!CHECK_INSTANCE_GROUP(in, pGroup))
                {
                    // Another group is already playing in this instance of the dungeon...
                    m_mapLock.Release();
                    sChatHandler.SystemMessage(plr->GetSession(), "Another group is already inside this instance of the dungeon.");
                    return INSTANCE_ABORT_NOT_IN_RAID_GROUP;
                }

                // Try to add instance ID to player
                plr->SetPersistentInstanceId(in);

                // Set current group
                if (pGroup)
                    in->m_creatorGroup = pGroup->GetID();

                m_mapLock.Release();
                return INSTANCE_OK;
            }
            else
            {
                m_mapLock.Release();
                return INSTANCE_ABORT_NOT_FOUND;
            }
        }
        else  // this is the case when we enter the normal way (e.g. we enter thru the portal)
        {
            in = NULL;
            if (pGroup != NULL) // we are in a group
            {

                uint8 grpdiff;

                // We want to use the raid difficulty for raids, and dungeon difficulty for dungeons
                if (inf->type == INSTANCE_RAID)
                    grpdiff = pGroup->m_raiddifficulty;
                else
                    grpdiff = pGroup->m_difficulty;

                if ((inf->type == INSTANCE_MULTIMODE && grpdiff == MODE_HEROIC) || inf->type == INSTANCE_RAID)
                {
                    // This is the case when we don't have this map on this difficulty saved yet for the player entering
                    if (plr->GetPersistentInstanceId(mapid, grpdiff) == 0)
                    {
                        // The group has this instance saved already so we will use it
                        if (pGroup->m_instanceIds[mapid][grpdiff] != 0)
                        {
                            in = sInstanceMgr.GetInstanceByIds(mapid, pGroup->m_instanceIds[mapid][grpdiff]);
                        }
                        else if (worldConfig.instance.useGroupLeaderInstanceId)
                        {
                            PlayerInfo* pLeaderInfo = pGroup->GetLeader();
                            if (pLeaderInfo)
                            {
                                pLeaderInfo->savedInstanceIdsLock.Acquire();
                                PlayerInstanceMap::iterator itrLeader = pLeaderInfo->savedInstanceIds[grpdiff].find(mapid);
                                if (itrLeader != pLeaderInfo->savedInstanceIds[grpdiff].end())
                                {
                                    in = sInstanceMgr.GetInstanceByIds(mapid, (*itrLeader).second);
                                }
                                pLeaderInfo->savedInstanceIdsLock.Release();
                            }
                        }
                    }

                    // If we have it saved to the player then use that
                    if (in == NULL && plr->GetPersistentInstanceId(mapid, grpdiff) != 0)
                    {
                        in = sInstanceMgr.GetInstanceByIds(mapid, plr->GetPersistentInstanceId(mapid, grpdiff));
                    }
                }
                else
                {
                    if (pGroup->m_instanceIds[mapid][grpdiff] != 0)
                    {
                        in = sInstanceMgr.GetInstanceByIds(mapid, pGroup->m_instanceIds[mapid][grpdiff]);
                    }
                }
            }

            if (in == NULL)
            {
                // We are not in a group, so we will look for an instance that we own and has the right difficulty

                uint32 diff;

                if (inf->type == INSTANCE_RAID)
                    diff = plr->GetRaidDifficulty();
                else
                    diff = plr->GetDungeonDifficulty();

                for (itr = instancemap->begin(); itr != instancemap->end();)
                {
                    in = itr->second;
                    ++itr;

                    if (in->m_difficulty == diff && PlayerOwnsInstance(in, plr))
                        break;

                    in = NULL;
                }
            }

            // We've found an instance!
            if (in != NULL)
            {
                m_mapLock.Release();

                // check the player count and in combat status.
                if (in->m_mapMgr)
                {
                    if (in->m_mapMgr->IsCombatInProgress())
                        return INSTANCE_ABORT_ENCOUNTER;

                    if (in->m_mapMgr->GetPlayerCount() >= inf->playerlimit)
                        return INSTANCE_ABORT_FULL;
                }

                if (!CHECK_INSTANCE_GROUP(in, pGroup))
                {
                    // Another group is already playing in this instance of the dungeon...
                    sChatHandler.SystemMessage(plr->GetSession(), "Another group is already inside this instance of the dungeon.");
                    return INSTANCE_ABORT_NOT_IN_RAID_GROUP;
                }

                // Try to add instance ID to player
                plr->SetPersistentInstanceId(in);

                // Set current group
                if (pGroup)
                    in->m_creatorGroup = pGroup->GetID();

                plr->SetInstanceID(in->m_instanceId);

                // found our instance, allow him in.
                return INSTANCE_OK;
            }
        }
    }

    // if we're here, it means we need to create a new instance.
    in = new Instance;
    in->m_creation = UNIXTIME;

    switch (inf->type)
    {
        case INSTANCE_NONRAID:
        case INSTANCE_MULTIMODE:
            in->m_difficulty = plr->GetDungeonDifficulty();
            break;

        case INSTANCE_RAID:
            in->m_difficulty = plr->GetRaidDifficulty();
            break;
    }

    in->m_instanceId = GenerateInstanceID();
    in->m_mapId = mapid;
    in->m_mapInfo = inf;
    in->m_mapMgr = NULL;        // always start off without a map manager, it is created in GetInstance()
    in->m_isBattleground = false;
    in->m_persistent = IS_PERSISTENT_INSTANCE(in) && objmgr.m_InstanceBossInfoMap[mapid] == NULL;
    in->m_creatorGuid = pGroup ? 0 : plr->getGuidLow();        // creator guid is 0 if its owned by a group.
    in->m_creatorGroup = pGroup ? pGroup->GetID() : 0;

    if (worldConfig.instance.isRelativeExpirationEnabled)
    {
        if (inf->type == INSTANCE_MULTIMODE && in->m_difficulty == MODE_HEROIC)
            in->m_expiration = UNIXTIME + TIME_DAY;
        else
            in->m_expiration = (inf->type == INSTANCE_NONRAID || (inf->type == INSTANCE_MULTIMODE && in->m_difficulty == MODE_NORMAL)) ? 0 : UNIXTIME + inf->cooldown;
    }
    else
    {
        if (inf->type == INSTANCE_MULTIMODE && in->m_difficulty >= MODE_HEROIC)
        {
            in->m_expiration = UNIXTIME - (UNIXTIME % TIME_DAY) + ((UNIXTIME % TIME_DAY) > (worldConfig.instance.relativeDailyHeroicInstanceResetHour * TIME_HOUR) ? 82800 : -3600) + ((worldConfig.instance.relativeDailyHeroicInstanceResetHour - worldConfig.server.gmtTimeZone) * TIME_HOUR);
        }
        else if (IS_PERSISTENT_INSTANCE(in))
        {
            if (m_nextInstanceReset[in->m_mapId] == 0)
            {
                m_nextInstanceReset[in->m_mapId] = UNIXTIME - (UNIXTIME % TIME_DAY) - ((worldConfig.server.gmtTimeZone + 1) * TIME_HOUR) + (in->m_mapInfo->cooldown == 0 ? TIME_DAY : in->m_mapInfo->cooldown);
                CharacterDatabase.Execute("DELETE FROM server_settings WHERE setting_id LIKE 'next_instance_reset_%u';", in->m_mapId);
                CharacterDatabase.Execute("INSERT INTO server_settings VALUES ('next_instance_reset_%u', '%u')", in->m_mapId, m_nextInstanceReset[in->m_mapId]);
            }
            if (m_nextInstanceReset[in->m_mapId] + (TIME_MINUTE * 15) < UNIXTIME)
            {
                do
                {
                    time_t tmp = m_nextInstanceReset[in->m_mapId];
                    if (tmp + (TIME_MINUTE * 15) < UNIXTIME)
                        m_nextInstanceReset[in->m_mapId] = tmp + (in->m_mapInfo->cooldown == 0 ? TIME_DAY : in->m_mapInfo->cooldown);
                }
                while (m_nextInstanceReset[in->m_mapId] + (TIME_MINUTE * 15) < UNIXTIME);
                CharacterDatabase.Execute("DELETE FROM server_settings WHERE setting_id LIKE 'next_instance_reset_%u';", in->m_mapId);
                CharacterDatabase.Execute("INSERT INTO server_settings VALUES ('next_instance_reset_%u', '%u')", in->m_mapId, m_nextInstanceReset[in->m_mapId]);
            }
            in->m_expiration = m_nextInstanceReset[in->m_mapId];
        }
        else
        {
            in->m_expiration = (inf->type == INSTANCE_NONRAID || (inf->type == INSTANCE_MULTIMODE && in->m_difficulty == MODE_NORMAL)) ? 0 : UNIXTIME + inf->cooldown;
        }
    }
    plr->SetInstanceID(in->m_instanceId);
    LOG_DEBUG("Creating instance for player %u and group %u on map %u. (%u)", in->m_creatorGuid, in->m_creatorGroup, in->m_mapId, in->m_instanceId);

    // save our new instance to the database.
    in->SaveToDB();

    // apply it in the instance map
    instancemap->insert(InstanceMap::value_type(in->m_instanceId, in));

    // Try to add instance ID to player
    plr->SetPersistentInstanceId(in);

    // instance created ok, i guess? return the ok for him to transport.
    m_mapLock.Release();

    return INSTANCE_OK;
}

MapMgr* InstanceMgr::GetMapMgr(uint32 mapId)
{
    return m_singleMaps[mapId];
}

MapMgr* InstanceMgr::GetInstance(Object* obj)
{
    MySQLStructure::MapInfo const* inf = sMySQLStore.getWorldMapInfo(obj->GetMapId());
    if (inf == nullptr || obj->GetMapId() >= MAX_NUM_MAPS)
        return nullptr;

    if (obj->isPlayer())
    {
        // players can join instances based on their groups/solo status.
        Player* plr = static_cast< Player* >(obj);

        // single-instance maps never go into the instance set.
        if (inf->type == INSTANCE_NULL)
            return m_singleMaps[obj->GetMapId()];

        m_mapLock.Acquire();
        InstanceMap* instancemap = m_instances[obj->GetMapId()];
        if (instancemap != nullptr)
        {
            // check our saved instance id. see if its valid, and if we can join before trying to find one.
            InstanceMap::iterator itr = instancemap->find(obj->GetInstanceID());
            if (itr != instancemap->end())
            {
                if (itr->second->m_mapMgr == NULL)
                {
                    itr->second->m_mapMgr = _CreateInstance(itr->second);
                }
                if (itr->second->m_mapMgr)
                {
                    m_mapLock.Release();
                    return itr->second->m_mapMgr;
                }
            }

            // iterate over our instances, and see if any of them are owned/joinable by him.
            for (itr = instancemap->begin(); itr != instancemap->end();)
            {
                Instance* in = itr->second;
                ++itr;

                uint32 difficulty;

                if (in->m_mapInfo->type == INSTANCE_RAID)
                    difficulty = plr->GetRaidDifficulty();
                else
                    difficulty = plr->GetDungeonDifficulty();

                if (in->m_difficulty == difficulty && PlayerOwnsInstance(in, plr))
                {
                    // this is our instance.
                    if (in->m_mapMgr == NULL)
                    {
                        /*if (plr->m_TeleportState == 1)
                        {
                        // the player is loading. boot him out to the entry point, we don't want to spam useless instances on startup.
                        m_mapLock.Release();
                        return NULL;
                        }*/

                        // create the actual instance.
                        in->m_mapMgr = _CreateInstance(in);
                        m_mapLock.Release();
                        return in->m_mapMgr;
                    }
                    else
                    {
                        // instance is already created.
                        m_mapLock.Release();
                        return in->m_mapMgr;
                    }
                }
            }
        }

        // if we're here, it means there are no instances on that map, or none of the instances on that map are joinable
        // by this player.
        m_mapLock.Release();
        return nullptr;
    }
    else
    {
        // units are *always* limited to their set instance ids.
        if (inf->type == INSTANCE_NULL)
            return m_singleMaps[obj->GetMapId()];

        m_mapLock.Acquire();
        InstanceMap* instancemap = m_instances[obj->GetMapId()];
        if (instancemap)
        {
            InstanceMap::iterator itr = instancemap->find(obj->GetInstanceID());
            if (itr != instancemap->end())
            {
                // we never create instances just for units.
                m_mapLock.Release();
                return itr->second->m_mapMgr;
            }
        }

        // instance is non-existent (shouldn't really happen for units...)
        m_mapLock.Release();
        return nullptr;
    }
}

MapMgr* InstanceMgr::_CreateInstance(uint32 mapid, uint32 instanceid)
{
    MySQLStructure::MapInfo const* inf = sMySQLStore.getWorldMapInfo(mapid);

    ARCEMU_ASSERT(inf != nullptr && inf->type == INSTANCE_NULL);
    ARCEMU_ASSERT(mapid < MAX_NUM_MAPS && m_maps[mapid] != NULL);

    LogNotice("InstanceMgr : Creating continent %s.", m_maps[mapid]->GetMapName().c_str());

    MapMgr* newMap = new MapMgr(m_maps[mapid], mapid, instanceid);

    ARCEMU_ASSERT(newMap != NULL);

    // Scheduling the new map for running
    ThreadPool.ExecuteTask(newMap);
    m_singleMaps[mapid] = newMap;

    return newMap;
}

MapMgr* InstanceMgr::_CreateInstance(Instance* in)
{
    if (m_maps[in->m_mapId] == 0)
        return NULL;

    LogNotice("InstanceMgr : Creating saved instance %u (%s)", in->m_instanceId, m_maps[in->m_mapId]->GetMapName().c_str());
    ARCEMU_ASSERT(in->m_mapMgr == NULL);

    // we don't have to check for world map info here, since the instance wouldn't have been saved if it didn't have any.
    in->m_mapMgr = new MapMgr(m_maps[in->m_mapId], in->m_mapId, in->m_instanceId);
    in->m_mapMgr->pInstance = in;
    in->m_mapMgr->iInstanceMode = in->m_difficulty;
    in->m_mapMgr->InactiveMoveTime = 60 + UNIXTIME;

    ThreadPool.ExecuteTask(in->m_mapMgr);
    return in->m_mapMgr;
}

void InstanceMgr::_CreateMap(uint32 mapid)
{
    if (mapid >= MAX_NUM_MAPS)
        return;

    MySQLStructure::MapInfo const* inf = sMySQLStore.getWorldMapInfo(mapid);
    if (inf == nullptr)
        return;

    if (m_maps[mapid] != NULL)
        return;

    m_maps[mapid] = new Map(mapid, inf);
    if (inf->type == INSTANCE_NULL)
    {
        // we're a continent, create the instance.
        _CreateInstance(mapid, GenerateInstanceID());
    }
}

uint32 InstanceMgr::GenerateInstanceID()
{
    uint32 iid;
    m_mapLock.Acquire();
    iid = m_InstanceHigh++;
    m_mapLock.Release();
    return iid;
}

void InstanceMgr::_LoadInstances()
{
    MySQLStructure::MapInfo const* inf;
    Instance* in;
    QueryResult* result;

    // clear any instances that have expired.
    LogDetail("InstanceMgr : Deleting Expired Instances...");
    CharacterDatabase.WaitExecute("DELETE FROM instances WHERE expiration > 0 AND expiration <= %u", UNIXTIME);
    CharacterDatabase.Execute("DELETE FROM instanceids WHERE instanceid NOT IN (SELECT id FROM instances)");

    // load saved instances
    result = CharacterDatabase.Query("SELECT id, mapid, creation, expiration, killed_npc_guids, difficulty, creator_group, creator_guid, persistent FROM instances");
    LogDetail("InstanceMgr : Loading %u saved instances.", result ? result->GetRowCount() : 0);

    if (result)
    {
        do
        {
            inf = sMySQLStore.getWorldMapInfo(result->Fetch()[1].GetUInt32());
            if (inf == NULL || result->Fetch()[1].GetUInt32() >= MAX_NUM_MAPS)
            {
                CharacterDatabase.Execute("DELETE FROM instances WHERE mapid = %u", result->Fetch()[1].GetUInt32());
                continue;
            }

            in = new Instance();
            in->m_mapInfo = inf;
            in->LoadFromDB(result->Fetch());

            // this assumes that groups are already loaded at this point.
            if (!in->m_persistent && in->m_creatorGroup && objmgr.GetGroupById(in->m_creatorGroup) == NULL)
            {
                CharacterDatabase.Execute("DELETE FROM `instances` WHERE `id` = %u", in->m_instanceId);
                delete in;
                continue;
            }

            if (m_instances[in->m_mapId] == NULL)
                m_instances[in->m_mapId] = new InstanceMap;

            m_instances[in->m_mapId]->insert(InstanceMap::value_type(in->m_instanceId, in));

        }
        while (result->NextRow());
        delete result;
    }
}

void Instance::LoadFromDB(Field* fields)
{
    char* p, *q;
    char* m_npcstring = strdup(fields[4].GetString());

    m_instanceId = fields[0].GetUInt32();
    m_mapId = fields[1].GetUInt32();
    m_creation = fields[2].GetUInt32();
    m_expiration = fields[3].GetUInt32();
    m_difficulty = fields[5].GetUInt8();
    m_creatorGroup = fields[6].GetUInt32();
    m_creatorGuid = fields[7].GetUInt32();
    m_persistent = fields[8].GetBool();
    m_mapMgr = NULL;
    m_isBattleground = false;

    // Reset group binding if it's a persistent instance
    if (m_persistent)
        m_creatorGroup = 0;

    // process saved npc's
    q = m_npcstring;
    p = strchr(m_npcstring, ' ');
    while (p)
    {
        *p = 0;
        uint32 val = atol(q);
        if (val)
            m_killedNpcs.insert(val);
        q = p + 1;
        p = strchr(q, ' ');
    }

    free(m_npcstring);
}

void InstanceMgr::ResetSavedInstances(Player* plr)
{
    WorldPacket data(SMSG_INSTANCE_RESET, 4);
    Instance* in;
    InstanceMap::iterator itr;
    InstanceMap* instancemap;
    uint32 i;

    if (!plr->IsInWorld() || plr->GetMapMgr()->GetMapInfo()->type != INSTANCE_NULL)
        return;

    m_mapLock.Acquire();
    for (i = 0; i < MAX_NUM_MAPS; ++i)
    {
        if (m_instances[i] != NULL)
        {
            instancemap = m_instances[i];
            for (itr = instancemap->begin(); itr != instancemap->end();)
            {
                in = itr->second;
                ++itr;

                if (IS_RESETABLE_INSTANCE(in) && (CHECK_INSTANCE_GROUP(in, plr->GetGroup()) || plr->getGuidLow() == in->m_creatorGuid))
                {
                    if (in->m_mapMgr && in->m_mapMgr->HasPlayers())
                    {
                        plr->GetSession()->SystemMessage("Failed to reset instance %u (%s), due to players still inside.", in->m_instanceId, in->m_mapMgr->GetMapInfo()->name.c_str());
                        continue;
                    }

                    // <mapid> has been reset.
                    data << uint32(in->m_mapId);
                    plr->GetSession()->SendPacket(&data);

                    // destroy the instance
                    _DeleteInstance(in, true);
                }
            }
        }
    }
    m_mapLock.Release();
}

void InstanceMgr::OnGroupDestruction(Group* pGroup)
{
    // this means a group has been deleted, so lets clear out all instances that they owned.
    // (instances don't transfer to the group leader, or anything)
    Instance* in;
    InstanceMap::iterator itr;
    InstanceMap* instancemap;
    uint32 i;

    m_mapLock.Acquire();
    for (i = 0; i < MAX_NUM_MAPS; ++i)
    {
        instancemap = m_instances[i];
        if (instancemap)
        {
            for (itr = instancemap->begin(); itr != instancemap->end();)
            {
                in = itr->second;
                ++itr;

                if (in->m_mapMgr && in->m_creatorGroup && in->m_creatorGroup == pGroup->GetID())
                {
                    if (IS_RESETABLE_INSTANCE(in))
                    {
                        _DeleteInstance(in, false);
                    }
                    else if (in->m_mapMgr->HasPlayers())
                    {
                        WorldPacket data(SMSG_RAID_GROUP_ONLY, 8);
                        data << uint32(60000);
                        data << uint32(1);

                        for (PlayerStorageMap::iterator mitr = in->m_mapMgr->m_PlayerStorage.begin(); mitr != in->m_mapMgr->m_PlayerStorage.end(); ++mitr)
                        {
                            if ((*mitr).second->IsInWorld() && !(*mitr).second->raidgrouponlysent && (*mitr).second->GetInstanceID() == (int32)in->m_instanceId)
                            {
                                (*mitr).second->GetSession()->SendPacket(&data);
                                (*mitr).second->raidgrouponlysent = true;

                                sEventMgr.AddEvent((*mitr).second, &Player::EjectFromInstance, EVENT_PLAYER_EJECT_FROM_INSTANCE, 60000, 1, EVENT_FLAG_DO_NOT_EXECUTE_IN_WORLD_CONTEXT);
                            }
                        }
                    }
                }
            }
        }
    }
    m_mapLock.Release();
}

bool InstanceMgr::_DeleteInstance(Instance* in, bool ForcePlayersOut)
{
    m_mapLock.Acquire();
    InstanceMap* instancemap;
    InstanceMap::iterator itr;

    if (in->m_mapMgr)
    {
        // "ForcePlayersOut" will teleport the players in this instance to their entry point/hearthstone.
        // otherwise, they will get a 60 second timeout telling them they are not in this instance's group.
        if (in->m_mapMgr->HasPlayers())
        {
            if (ForcePlayersOut)
                in->m_mapMgr->InstanceShutdown();
            else
            {
                in->m_mapMgr->BeginInstanceExpireCountdown();
                in->m_mapMgr->pInstance = NULL;
            }
        }
        else
            in->m_mapMgr->InstanceShutdown();
    }

    // remove the instance from the large map.
    instancemap = m_instances[in->m_mapId];
    if (instancemap)
    {
        itr = instancemap->find(in->m_instanceId);
        if (itr != instancemap->end())
            instancemap->erase(itr);
    }

    // cleanup corpses, database references
    in->DeleteFromDB();

    // delete the instance pointer.
    delete in;

    m_mapLock.Release();

    return true;
}

void Instance::DeleteFromDB()
{
    // cleanup all the corpses on this instance
    CharacterDatabase.Execute("DELETE FROM corpses WHERE instanceid = %u", m_instanceId);

    // delete from the database
    CharacterDatabase.Execute("DELETE FROM instances WHERE id = %u", m_instanceId);

    // Delete all instance assignments
    CharacterDatabase.Execute("DELETE FROM instanceids WHERE mapId = %u AND instanceId = %u AND mode = %u", m_mapId, m_instanceId, m_difficulty);
}

void InstanceMgr::CheckForExpiredInstances()
{
    // checking for any expired instances.
    Instance* in;
    InstanceMap::iterator itr;
    InstanceMap* instancemap;
    uint32 i;

    m_mapLock.Acquire();
    for (i = 0; i < MAX_NUM_MAPS; ++i)
    {
        instancemap = m_instances[i];
        if (instancemap)
        {
            for (itr = instancemap->begin(); itr != instancemap->end();)
            {
                in = itr->second;
                ++itr;

                // use a "soft" delete here.
                if (in->m_mapInfo->type != INSTANCE_NONRAID && !(in->m_mapInfo->type == INSTANCE_MULTIMODE && in->m_difficulty == MODE_NORMAL) && HasInstanceExpired(in))
                    _DeleteInstance(in, false);
            }

        }
    }
    m_mapLock.Release();
}

void InstanceMgr::BuildSavedInstancesForPlayer(Player* plr)
{
#if VERSION_STRING < Cata
    WorldPacket data(4);
    Instance* in;
    InstanceMap::iterator itr;
    InstanceMap* instancemap;
    uint32 i;

    if (!plr->IsInWorld() || plr->GetMapMgr()->GetMapInfo()->type != INSTANCE_NULL)
    {
        m_mapLock.Acquire();
        for (i = 0; i < MAX_NUM_MAPS; ++i)
        {
            if (m_instances[i] != NULL)
            {
                instancemap = m_instances[i];
                for (itr = instancemap->begin(); itr != instancemap->end();)
                {
                    in = itr->second;
                    ++itr;

                    if (PlayerOwnsInstance(in, plr) && in->m_mapInfo->type == INSTANCE_NONRAID)
                    {
                        m_mapLock.Release();

                        data.SetOpcode(SMSG_UPDATE_LAST_INSTANCE);
                        data << uint32(in->m_mapId);
                        plr->GetSession()->SendPacket(&data);

                        data.Initialize(SMSG_UPDATE_INSTANCE_OWNERSHIP);
                        data << uint32(0x01);
                        plr->GetSession()->SendPacket(&data);

                        return;
                    }
                }
            }
        }
        m_mapLock.Release();
    }

    data.SetOpcode(SMSG_UPDATE_INSTANCE_OWNERSHIP);
    data << uint32(0x00);
    plr->GetSession()->SendPacket(&data);
#else
    if (plr == nullptr) { return; }
#endif
}

void InstanceMgr::BuildRaidSavedInstancesForPlayer(Player* plr)
{
#if VERSION_STRING < Cata
    WorldPacket data(SMSG_RAID_INSTANCE_INFO, 200);
#else
    WorldPacket data(SMSG_RAID_INSTANCE_INFO, 4);
#endif
    uint32 counter = 0;

    data << counter;
    m_mapLock.Acquire();
  
    for (i = 0; i < MAX_NUM_MAPS; ++i)
    {
        if (m_instances[i] != NULL)
        {
            InstanceMap* instancemap = m_instances[i];
            for (InstanceMap::iterator itr = instancemap->begin(); itr != instancemap->end();)
            {
                Instance* in = itr->second;
                ++itr;

                if (in->m_persistent && PlayerOwnsInstance(in, plr))
                {
                    data << uint32(in->m_mapId);                         // obviously the mapid
                    data << uint32(in->m_difficulty);                    // instance difficulty
                    data << uint64(in->m_instanceId);                    // self-explanatory
                    data << uint8(1);                                    // expired = 0
                    data << uint8(0);                                    // extended = 1

                    if (in->m_expiration > UNIXTIME)
                        data << uint32(in->m_expiration - UNIXTIME);
                    else
                        data << uint32(0);

#if VERSION_STRING >= Cata
                    data << uint32(0);
#endif

                    ++counter;
                }
            }
        }
    }<commit_msg>Update WorldCreator.cpp<commit_after>/*
 * AscEmu Framework based on ArcEmu MMORPG Server
 * Copyright (c) 2014-2019 AscEmu Team <http://www.ascemu.org>
 * Copyright (C) 2008-2012 ArcEmu Team <http://www.ArcEmu.org/>
 * Copyright (C) 2005-2007 Ascent Team
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "StdAfx.h"
#include "Storage/MySQLDataStore.hpp"
#include "Server/MainServerDefines.h"
#include "MapMgr.h"
#include "WorldCreator.h"

SERVER_DECL InstanceMgr sInstanceMgr;

InstanceMgr::InstanceMgr()
{
    memset(m_maps, 0, sizeof(Map*)* MAX_NUM_MAPS);
    memset(m_instances, 0, sizeof(InstanceMap*) * MAX_NUM_MAPS);
    memset(m_singleMaps, 0, sizeof(MapMgr*) * MAX_NUM_MAPS);
    memset(&m_nextInstanceReset, 0, sizeof(time_t) * MAX_NUM_MAPS);
    m_InstanceHigh = 0;
}

void InstanceMgr::Load(TaskList* l)
{
    // Create all non-instance type maps.
    QueryResult* result = CharacterDatabase.Query("SELECT MAX(id) FROM instances");
    if (result)
    {
        m_InstanceHigh = result->Fetch()[0].GetUInt32() + 1;
        delete result;
    }
    else
        m_InstanceHigh = 1;

    // load each map we have in the database.
    result = WorldDatabase.Query("SELECT DISTINCT Map FROM creature_spawns WHERE min_build <= %u AND max_build >= %u AND event_entry = 0;", VERSION_STRING, VERSION_STRING);
    if (result)
    {
        do
        {
            if (sMySQLStore.getWorldMapInfo(result->Fetch()[0].GetUInt32()) == nullptr)
                continue;

            if (result->Fetch()[0].GetUInt32() >= MAX_NUM_MAPS)
            {
                LOG_ERROR("One or more of your creature_spawns rows specifies an invalid map: %u", result->Fetch()[0].GetUInt32());
                continue;
            }

            //_CreateMap(result->Fetch()[0].GetUInt32());
            l->AddTask(new Task(new CallbackP1<InstanceMgr, uint32>(this, &InstanceMgr::_CreateMap, result->Fetch()[0].GetUInt32())));
        }
        while (result->NextRow());
        delete result;
    }

    l->wait();

    // create maps for any we don't have yet.
    MySQLDataStore::WorldMapInfoContainer const* its = sMySQLStore.getWorldMapInfoStore();
    for (MySQLDataStore::WorldMapInfoContainer::const_iterator itr = its->begin(); itr != its->end(); ++itr)
    {
        if (itr->second.mapid >= MAX_NUM_MAPS)
        {
            LOG_ERROR("One or more of your worldmap_info rows specifies an invalid map: %u", itr->second.mapid);
            continue;
        }

        if (m_maps[itr->second.mapid] == NULL)
        {
            l->AddTask(new Task(new CallbackP1<InstanceMgr, uint32>(this, &InstanceMgr::_CreateMap, itr->second.mapid)));
        }
    }

    l->wait();

    // load reset times
    result = CharacterDatabase.Query("SELECT setting_id, setting_value FROM server_settings WHERE setting_id LIKE 'next_instance_reset_%%'");
    if (result)
    {
        do
        {
            const char* id = result->Fetch()[0].GetString();
            uint32 value = result->Fetch()[1].GetUInt32();
            if (strlen(id) <= 20)
                continue;
            uint32 mapId = atoi(id + 20);
            if (mapId >= MAX_NUM_MAPS)
                continue;
            m_nextInstanceReset[mapId] = value;
        }
        while (result->NextRow());
        delete result;
    }

    // load saved instances
    _LoadInstances();
}

InstanceMgr::~InstanceMgr()
{}

void InstanceMgr::Shutdown()
{
    uint32 i;
    InstanceMap::iterator itr;
    for (i = 0; i < MAX_NUM_MAPS; ++i)
    {
        if (m_instances[i] != NULL)
        {
            for (itr = m_instances[i]->begin(); itr != m_instances[i]->end(); ++itr)
            {
                if (itr->second->m_mapMgr)
                    itr->second->m_mapMgr->KillThread();

                delete itr->second;
            }

            delete m_instances[i];
            m_instances[i] = NULL;
        }

        if (m_singleMaps[i] != NULL)
        {
            MapMgr* ptr = m_singleMaps[i];
            ptr->KillThread();
            delete ptr;
            m_singleMaps[i] = NULL;
        }

        if (m_maps[i] != NULL)
        {
            delete m_maps[i];
            m_maps[i] = NULL;
        }
    }
}

uint32 InstanceMgr::PreTeleport(uint32 mapid, Player* plr, uint32 instanceid)
{
    // preteleport is where all the magic happens :P instance creation, etc.
    MySQLStructure::MapInfo const* inf = sMySQLStore.getWorldMapInfo(mapid);
    Group* pGroup;
    InstanceMap* instancemap;
    Instance* in;

    if (inf == NULL || mapid >= MAX_NUM_MAPS)
        return INSTANCE_ABORT_NOT_FOUND;

    // main continent check.
    if (inf->type == INSTANCE_NULL)
    {
        // this will be useful when clustering comes into play.
        // we can check if the destination world server is online or not and then cancel them before they load.
        return (m_singleMaps[mapid] != NULL) ? INSTANCE_OK : INSTANCE_ABORT_NOT_FOUND;
    }

    // shouldn't happen
    if (inf->type == INSTANCE_BATTLEGROUND)
        return INSTANCE_ABORT_NOT_FOUND;

    pGroup = plr->GetGroup();

    // players without groups cannot enter raids and heroic instances

    if (pGroup == NULL &&
        inf->type == INSTANCE_RAID &&
        !plr->m_cheats.TriggerpassCheat)
        return INSTANCE_ABORT_NOT_IN_RAID_GROUP;

    if (pGroup == NULL &&
        (inf->type == INSTANCE_NONRAID && plr->iInstanceType == MODE_HEROIC) &&
        !plr->m_cheats.TriggerpassCheat)
        return INSTANCE_ABORT_NOT_IN_RAID_GROUP;


    // players without raid groups cannot enter raid instances
    if (pGroup != NULL && pGroup->getGroupType() != GROUP_TYPE_RAID && inf->type == INSTANCE_RAID && !plr->m_cheats.TriggerpassCheat)
        return INSTANCE_ABORT_NOT_IN_RAID_GROUP;

    // We deny transfer if we requested a heroic instance of a map that has no heroic mode
    // We are trying to enter into a non-multi instance with a heroic group, downscaling
    if (inf->type == INSTANCE_NONRAID && plr->GetDungeonDifficulty() == MODE_HEROIC)
    {
        plr->SetDungeonDifficulty(MODE_NORMAL);
        plr->sendDungeonDifficultyPacket();

        Group* grp = plr->GetGroup();
        if (grp != NULL)
            grp->SetDungeonDifficulty(MODE_NORMAL);
    }

    // if it's not a normal / 10men normal then check if we even have this mode
    if (inf->type == INSTANCE_RAID && plr->GetRaidDifficulty() != MODE_NORMAL_10MEN)
    {
        uint8_t newtype = 0;

        if (!inf->hasDifficulty(plr->GetRaidDifficulty()))
        {
            // no it doesn't so we will downscale it

            ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
            // This part is totally speculative, if you know how this is done actually then do change it
            //
            switch (plr->GetRaidDifficulty())
            {
                case MODE_NORMAL_25MEN:
                case MODE_HEROIC_10MEN:
                {
                    newtype = MODE_NORMAL_10MEN;
                    break;
                }

                case MODE_HEROIC_25MEN:
                {
                    newtype = MODE_NORMAL_25MEN;
                    break;
                }
            }

            ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

            // check if we have this mode
            if (!inf->hasDifficulty(newtype))
            {

                //appearantly we don't so we set to 10men normal, which is the default for old raids too
                //regardless of their playerlimit
                newtype = MODE_NORMAL_10MEN;
            }

            // Setting the new mode on us and our group
            if (plr->GetRaidDifficulty() != newtype)
            {

                plr->SetRaidDifficulty(newtype);
                plr->sendRaidDifficultyPacket();

                Group* grp = plr->GetGroup();
                if (grp != nullptr)
                    grp->SetRaidDifficulty(newtype);
            }
        }
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // If we are here, it means:
    // 1.) We're a simple non-raid and non-heroic dungeon
    // 2.) We're a multi-dungeon set to heroic and we are in a group
    // 3.) We're a raid, and we are in a raid group
    // 4.) We're a raid, we are in a raid group, and we have the right mode set
    //
    // So, first we have to check if they have an instance on this map already, if so, allow them to teleport to that.
    // Otherwise, we will try to create them a new one.
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    m_mapLock.Acquire();
    instancemap = m_instances[mapid];

    // If there are no instances of this map yet, we need to create the map
    if (instancemap == NULL)
    {
        if (instanceid != 0)
        {
            m_mapLock.Release();
            return INSTANCE_ABORT_NOT_FOUND;
        }

        m_instances[mapid] = new InstanceMap;
        instancemap = m_instances[mapid];
    }
    else
    {
        InstanceMap::iterator itr;

        // this is the case when we enter an already existing instance (with summons for example)
        if (instanceid != 0)
        {
            itr = instancemap->find(instanceid);
            if (itr != instancemap->end())
            {
                in = itr->second;

                if (!CHECK_INSTANCE_GROUP(in, pGroup))
                {
                    // Another group is already playing in this instance of the dungeon...
                    m_mapLock.Release();
                    sChatHandler.SystemMessage(plr->GetSession(), "Another group is already inside this instance of the dungeon.");
                    return INSTANCE_ABORT_NOT_IN_RAID_GROUP;
                }

                // Try to add instance ID to player
                plr->SetPersistentInstanceId(in);

                // Set current group
                if (pGroup)
                    in->m_creatorGroup = pGroup->GetID();

                m_mapLock.Release();
                return INSTANCE_OK;
            }
            else
            {
                m_mapLock.Release();
                return INSTANCE_ABORT_NOT_FOUND;
            }
        }
        else  // this is the case when we enter the normal way (e.g. we enter thru the portal)
        {
            in = NULL;
            if (pGroup != NULL) // we are in a group
            {

                uint8 grpdiff;

                // We want to use the raid difficulty for raids, and dungeon difficulty for dungeons
                if (inf->type == INSTANCE_RAID)
                    grpdiff = pGroup->m_raiddifficulty;
                else
                    grpdiff = pGroup->m_difficulty;

                if ((inf->type == INSTANCE_MULTIMODE && grpdiff == MODE_HEROIC) || inf->type == INSTANCE_RAID)
                {
                    // This is the case when we don't have this map on this difficulty saved yet for the player entering
                    if (plr->GetPersistentInstanceId(mapid, grpdiff) == 0)
                    {
                        // The group has this instance saved already so we will use it
                        if (pGroup->m_instanceIds[mapid][grpdiff] != 0)
                        {
                            in = sInstanceMgr.GetInstanceByIds(mapid, pGroup->m_instanceIds[mapid][grpdiff]);
                        }
                        else if (worldConfig.instance.useGroupLeaderInstanceId)
                        {
                            PlayerInfo* pLeaderInfo = pGroup->GetLeader();
                            if (pLeaderInfo)
                            {
                                pLeaderInfo->savedInstanceIdsLock.Acquire();
                                PlayerInstanceMap::iterator itrLeader = pLeaderInfo->savedInstanceIds[grpdiff].find(mapid);
                                if (itrLeader != pLeaderInfo->savedInstanceIds[grpdiff].end())
                                {
                                    in = sInstanceMgr.GetInstanceByIds(mapid, (*itrLeader).second);
                                }
                                pLeaderInfo->savedInstanceIdsLock.Release();
                            }
                        }
                    }

                    // If we have it saved to the player then use that
                    if (in == NULL && plr->GetPersistentInstanceId(mapid, grpdiff) != 0)
                    {
                        in = sInstanceMgr.GetInstanceByIds(mapid, plr->GetPersistentInstanceId(mapid, grpdiff));
                    }
                }
                else
                {
                    if (pGroup->m_instanceIds[mapid][grpdiff] != 0)
                    {
                        in = sInstanceMgr.GetInstanceByIds(mapid, pGroup->m_instanceIds[mapid][grpdiff]);
                    }
                }
            }

            if (in == NULL)
            {
                // We are not in a group, so we will look for an instance that we own and has the right difficulty

                uint32 diff;

                if (inf->type == INSTANCE_RAID)
                    diff = plr->GetRaidDifficulty();
                else
                    diff = plr->GetDungeonDifficulty();

                for (itr = instancemap->begin(); itr != instancemap->end();)
                {
                    in = itr->second;
                    ++itr;

                    if (in->m_difficulty == diff && PlayerOwnsInstance(in, plr))
                        break;

                    in = NULL;
                }
            }

            // We've found an instance!
            if (in != NULL)
            {
                m_mapLock.Release();

                // check the player count and in combat status.
                if (in->m_mapMgr)
                {
                    if (in->m_mapMgr->IsCombatInProgress())
                        return INSTANCE_ABORT_ENCOUNTER;

                    if (in->m_mapMgr->GetPlayerCount() >= inf->playerlimit)
                        return INSTANCE_ABORT_FULL;
                }

                if (!CHECK_INSTANCE_GROUP(in, pGroup))
                {
                    // Another group is already playing in this instance of the dungeon...
                    sChatHandler.SystemMessage(plr->GetSession(), "Another group is already inside this instance of the dungeon.");
                    return INSTANCE_ABORT_NOT_IN_RAID_GROUP;
                }

                // Try to add instance ID to player
                plr->SetPersistentInstanceId(in);

                // Set current group
                if (pGroup)
                    in->m_creatorGroup = pGroup->GetID();

                plr->SetInstanceID(in->m_instanceId);

                // found our instance, allow him in.
                return INSTANCE_OK;
            }
        }
    }

    // if we're here, it means we need to create a new instance.
    in = new Instance;
    in->m_creation = UNIXTIME;

    switch (inf->type)
    {
        case INSTANCE_NONRAID:
        case INSTANCE_MULTIMODE:
            in->m_difficulty = plr->GetDungeonDifficulty();
            break;

        case INSTANCE_RAID:
            in->m_difficulty = plr->GetRaidDifficulty();
            break;
    }

    in->m_instanceId = GenerateInstanceID();
    in->m_mapId = mapid;
    in->m_mapInfo = inf;
    in->m_mapMgr = NULL;        // always start off without a map manager, it is created in GetInstance()
    in->m_isBattleground = false;
    in->m_persistent = IS_PERSISTENT_INSTANCE(in) && objmgr.m_InstanceBossInfoMap[mapid] == NULL;
    in->m_creatorGuid = pGroup ? 0 : plr->getGuidLow();        // creator guid is 0 if its owned by a group.
    in->m_creatorGroup = pGroup ? pGroup->GetID() : 0;

    if (worldConfig.instance.isRelativeExpirationEnabled)
    {
        if (inf->type == INSTANCE_MULTIMODE && in->m_difficulty == MODE_HEROIC)
            in->m_expiration = UNIXTIME + TIME_DAY;
        else
            in->m_expiration = (inf->type == INSTANCE_NONRAID || (inf->type == INSTANCE_MULTIMODE && in->m_difficulty == MODE_NORMAL)) ? 0 : UNIXTIME + inf->cooldown;
    }
    else
    {
        if (inf->type == INSTANCE_MULTIMODE && in->m_difficulty >= MODE_HEROIC)
        {
            in->m_expiration = UNIXTIME - (UNIXTIME % TIME_DAY) + ((UNIXTIME % TIME_DAY) > (worldConfig.instance.relativeDailyHeroicInstanceResetHour * TIME_HOUR) ? 82800 : -3600) + ((worldConfig.instance.relativeDailyHeroicInstanceResetHour - worldConfig.server.gmtTimeZone) * TIME_HOUR);
        }
        else if (IS_PERSISTENT_INSTANCE(in))
        {
            if (m_nextInstanceReset[in->m_mapId] == 0)
            {
                m_nextInstanceReset[in->m_mapId] = UNIXTIME - (UNIXTIME % TIME_DAY) - ((worldConfig.server.gmtTimeZone + 1) * TIME_HOUR) + (in->m_mapInfo->cooldown == 0 ? TIME_DAY : in->m_mapInfo->cooldown);
                CharacterDatabase.Execute("DELETE FROM server_settings WHERE setting_id LIKE 'next_instance_reset_%u';", in->m_mapId);
                CharacterDatabase.Execute("INSERT INTO server_settings VALUES ('next_instance_reset_%u', '%u')", in->m_mapId, m_nextInstanceReset[in->m_mapId]);
            }
            if (m_nextInstanceReset[in->m_mapId] + (TIME_MINUTE * 15) < UNIXTIME)
            {
                do
                {
                    time_t tmp = m_nextInstanceReset[in->m_mapId];
                    if (tmp + (TIME_MINUTE * 15) < UNIXTIME)
                        m_nextInstanceReset[in->m_mapId] = tmp + (in->m_mapInfo->cooldown == 0 ? TIME_DAY : in->m_mapInfo->cooldown);
                }
                while (m_nextInstanceReset[in->m_mapId] + (TIME_MINUTE * 15) < UNIXTIME);
                CharacterDatabase.Execute("DELETE FROM server_settings WHERE setting_id LIKE 'next_instance_reset_%u';", in->m_mapId);
                CharacterDatabase.Execute("INSERT INTO server_settings VALUES ('next_instance_reset_%u', '%u')", in->m_mapId, m_nextInstanceReset[in->m_mapId]);
            }
            in->m_expiration = m_nextInstanceReset[in->m_mapId];
        }
        else
        {
            in->m_expiration = (inf->type == INSTANCE_NONRAID || (inf->type == INSTANCE_MULTIMODE && in->m_difficulty == MODE_NORMAL)) ? 0 : UNIXTIME + inf->cooldown;
        }
    }
    plr->SetInstanceID(in->m_instanceId);
    LOG_DEBUG("Creating instance for player %u and group %u on map %u. (%u)", in->m_creatorGuid, in->m_creatorGroup, in->m_mapId, in->m_instanceId);

    // save our new instance to the database.
    in->SaveToDB();

    // apply it in the instance map
    instancemap->insert(InstanceMap::value_type(in->m_instanceId, in));

    // Try to add instance ID to player
    plr->SetPersistentInstanceId(in);

    // instance created ok, i guess? return the ok for him to transport.
    m_mapLock.Release();

    return INSTANCE_OK;
}

MapMgr* InstanceMgr::GetMapMgr(uint32 mapId)
{
    return m_singleMaps[mapId];
}

MapMgr* InstanceMgr::GetInstance(Object* obj)
{
    MySQLStructure::MapInfo const* inf = sMySQLStore.getWorldMapInfo(obj->GetMapId());
    if (inf == nullptr || obj->GetMapId() >= MAX_NUM_MAPS)
        return nullptr;

    if (obj->isPlayer())
    {
        // players can join instances based on their groups/solo status.
        Player* plr = static_cast< Player* >(obj);

        // single-instance maps never go into the instance set.
        if (inf->type == INSTANCE_NULL)
            return m_singleMaps[obj->GetMapId()];

        m_mapLock.Acquire();
        InstanceMap* instancemap = m_instances[obj->GetMapId()];
        if (instancemap != nullptr)
        {
            // check our saved instance id. see if its valid, and if we can join before trying to find one.
            InstanceMap::iterator itr = instancemap->find(obj->GetInstanceID());
            if (itr != instancemap->end())
            {
                if (itr->second->m_mapMgr == NULL)
                {
                    itr->second->m_mapMgr = _CreateInstance(itr->second);
                }
                if (itr->second->m_mapMgr)
                {
                    m_mapLock.Release();
                    return itr->second->m_mapMgr;
                }
            }

            // iterate over our instances, and see if any of them are owned/joinable by him.
            for (itr = instancemap->begin(); itr != instancemap->end();)
            {
                Instance* in = itr->second;
                ++itr;

                uint32 difficulty;

                if (in->m_mapInfo->type == INSTANCE_RAID)
                    difficulty = plr->GetRaidDifficulty();
                else
                    difficulty = plr->GetDungeonDifficulty();

                if (in->m_difficulty == difficulty && PlayerOwnsInstance(in, plr))
                {
                    // this is our instance.
                    if (in->m_mapMgr == NULL)
                    {
                        /*if (plr->m_TeleportState == 1)
                        {
                        // the player is loading. boot him out to the entry point, we don't want to spam useless instances on startup.
                        m_mapLock.Release();
                        return NULL;
                        }*/

                        // create the actual instance.
                        in->m_mapMgr = _CreateInstance(in);
                        m_mapLock.Release();
                        return in->m_mapMgr;
                    }
                    else
                    {
                        // instance is already created.
                        m_mapLock.Release();
                        return in->m_mapMgr;
                    }
                }
            }
        }

        // if we're here, it means there are no instances on that map, or none of the instances on that map are joinable
        // by this player.
        m_mapLock.Release();
        return nullptr;
    }
    else
    {
        // units are *always* limited to their set instance ids.
        if (inf->type == INSTANCE_NULL)
            return m_singleMaps[obj->GetMapId()];

        m_mapLock.Acquire();
        InstanceMap* instancemap = m_instances[obj->GetMapId()];
        if (instancemap)
        {
            InstanceMap::iterator itr = instancemap->find(obj->GetInstanceID());
            if (itr != instancemap->end())
            {
                // we never create instances just for units.
                m_mapLock.Release();
                return itr->second->m_mapMgr;
            }
        }

        // instance is non-existent (shouldn't really happen for units...)
        m_mapLock.Release();
        return nullptr;
    }
}

MapMgr* InstanceMgr::_CreateInstance(uint32 mapid, uint32 instanceid)
{
    MySQLStructure::MapInfo const* inf = sMySQLStore.getWorldMapInfo(mapid);

    ARCEMU_ASSERT(inf != nullptr && inf->type == INSTANCE_NULL);
    ARCEMU_ASSERT(mapid < MAX_NUM_MAPS && m_maps[mapid] != NULL);

    LogNotice("InstanceMgr : Creating continent %s.", m_maps[mapid]->GetMapName().c_str());

    MapMgr* newMap = new MapMgr(m_maps[mapid], mapid, instanceid);

    ARCEMU_ASSERT(newMap != NULL);

    // Scheduling the new map for running
    ThreadPool.ExecuteTask(newMap);
    m_singleMaps[mapid] = newMap;

    return newMap;
}

MapMgr* InstanceMgr::_CreateInstance(Instance* in)
{
    if (m_maps[in->m_mapId] == 0)
        return NULL;

    LogNotice("InstanceMgr : Creating saved instance %u (%s)", in->m_instanceId, m_maps[in->m_mapId]->GetMapName().c_str());
    ARCEMU_ASSERT(in->m_mapMgr == NULL);

    // we don't have to check for world map info here, since the instance wouldn't have been saved if it didn't have any.
    in->m_mapMgr = new MapMgr(m_maps[in->m_mapId], in->m_mapId, in->m_instanceId);
    in->m_mapMgr->pInstance = in;
    in->m_mapMgr->iInstanceMode = in->m_difficulty;
    in->m_mapMgr->InactiveMoveTime = 60 + UNIXTIME;

    ThreadPool.ExecuteTask(in->m_mapMgr);
    return in->m_mapMgr;
}

void InstanceMgr::_CreateMap(uint32 mapid)
{
    if (mapid >= MAX_NUM_MAPS)
        return;

    MySQLStructure::MapInfo const* inf = sMySQLStore.getWorldMapInfo(mapid);
    if (inf == nullptr)
        return;

    if (m_maps[mapid] != NULL)
        return;

    m_maps[mapid] = new Map(mapid, inf);
    if (inf->type == INSTANCE_NULL)
    {
        // we're a continent, create the instance.
        _CreateInstance(mapid, GenerateInstanceID());
    }
}

uint32 InstanceMgr::GenerateInstanceID()
{
    uint32 iid;
    m_mapLock.Acquire();
    iid = m_InstanceHigh++;
    m_mapLock.Release();
    return iid;
}

void InstanceMgr::_LoadInstances()
{
    MySQLStructure::MapInfo const* inf;
    Instance* in;
    QueryResult* result;

    // clear any instances that have expired.
    LogDetail("InstanceMgr : Deleting Expired Instances...");
    CharacterDatabase.WaitExecute("DELETE FROM instances WHERE expiration > 0 AND expiration <= %u", UNIXTIME);
    CharacterDatabase.Execute("DELETE FROM instanceids WHERE instanceid NOT IN (SELECT id FROM instances)");

    // load saved instances
    result = CharacterDatabase.Query("SELECT id, mapid, creation, expiration, killed_npc_guids, difficulty, creator_group, creator_guid, persistent FROM instances");
    LogDetail("InstanceMgr : Loading %u saved instances.", result ? result->GetRowCount() : 0);

    if (result)
    {
        do
        {
            inf = sMySQLStore.getWorldMapInfo(result->Fetch()[1].GetUInt32());
            if (inf == NULL || result->Fetch()[1].GetUInt32() >= MAX_NUM_MAPS)
            {
                CharacterDatabase.Execute("DELETE FROM instances WHERE mapid = %u", result->Fetch()[1].GetUInt32());
                continue;
            }

            in = new Instance();
            in->m_mapInfo = inf;
            in->LoadFromDB(result->Fetch());

            // this assumes that groups are already loaded at this point.
            if (!in->m_persistent && in->m_creatorGroup && objmgr.GetGroupById(in->m_creatorGroup) == NULL)
            {
                CharacterDatabase.Execute("DELETE FROM `instances` WHERE `id` = %u", in->m_instanceId);
                delete in;
                continue;
            }

            if (m_instances[in->m_mapId] == NULL)
                m_instances[in->m_mapId] = new InstanceMap;

            m_instances[in->m_mapId]->insert(InstanceMap::value_type(in->m_instanceId, in));

        }
        while (result->NextRow());
        delete result;
    }
}

void Instance::LoadFromDB(Field* fields)
{
    char* p, *q;
    char* m_npcstring = strdup(fields[4].GetString());

    m_instanceId = fields[0].GetUInt32();
    m_mapId = fields[1].GetUInt32();
    m_creation = fields[2].GetUInt32();
    m_expiration = fields[3].GetUInt32();
    m_difficulty = fields[5].GetUInt8();
    m_creatorGroup = fields[6].GetUInt32();
    m_creatorGuid = fields[7].GetUInt32();
    m_persistent = fields[8].GetBool();
    m_mapMgr = NULL;
    m_isBattleground = false;

    // Reset group binding if it's a persistent instance
    if (m_persistent)
        m_creatorGroup = 0;

    // process saved npc's
    q = m_npcstring;
    p = strchr(m_npcstring, ' ');
    while (p)
    {
        *p = 0;
        uint32 val = atol(q);
        if (val)
            m_killedNpcs.insert(val);
        q = p + 1;
        p = strchr(q, ' ');
    }

    free(m_npcstring);
}

void InstanceMgr::ResetSavedInstances(Player* plr)
{
    WorldPacket data(SMSG_INSTANCE_RESET, 4);
    Instance* in;
    InstanceMap::iterator itr;
    InstanceMap* instancemap;
    uint32 i;

    if (!plr->IsInWorld() || plr->GetMapMgr()->GetMapInfo()->type != INSTANCE_NULL)
        return;

    m_mapLock.Acquire();
    for (i = 0; i < MAX_NUM_MAPS; ++i)
    {
        if (m_instances[i] != NULL)
        {
            instancemap = m_instances[i];
            for (itr = instancemap->begin(); itr != instancemap->end();)
            {
                in = itr->second;
                ++itr;

                if (IS_RESETABLE_INSTANCE(in) && (CHECK_INSTANCE_GROUP(in, plr->GetGroup()) || plr->getGuidLow() == in->m_creatorGuid))
                {
                    if (in->m_mapMgr && in->m_mapMgr->HasPlayers())
                    {
                        plr->GetSession()->SystemMessage("Failed to reset instance %u (%s), due to players still inside.", in->m_instanceId, in->m_mapMgr->GetMapInfo()->name.c_str());
                        continue;
                    }

                    // <mapid> has been reset.
                    data << uint32(in->m_mapId);
                    plr->GetSession()->SendPacket(&data);

                    // destroy the instance
                    _DeleteInstance(in, true);
                }
            }
        }
    }
    m_mapLock.Release();
}

void InstanceMgr::OnGroupDestruction(Group* pGroup)
{
    // this means a group has been deleted, so lets clear out all instances that they owned.
    // (instances don't transfer to the group leader, or anything)
    Instance* in;
    InstanceMap::iterator itr;
    InstanceMap* instancemap;
    uint32 i;

    m_mapLock.Acquire();
    for (i = 0; i < MAX_NUM_MAPS; ++i)
    {
        instancemap = m_instances[i];
        if (instancemap)
        {
            for (itr = instancemap->begin(); itr != instancemap->end();)
            {
                in = itr->second;
                ++itr;

                if (in->m_mapMgr && in->m_creatorGroup && in->m_creatorGroup == pGroup->GetID())
                {
                    if (IS_RESETABLE_INSTANCE(in))
                    {
                        _DeleteInstance(in, false);
                    }
                    else if (in->m_mapMgr->HasPlayers())
                    {
                        WorldPacket data(SMSG_RAID_GROUP_ONLY, 8);
                        data << uint32(60000);
                        data << uint32(1);

                        for (PlayerStorageMap::iterator mitr = in->m_mapMgr->m_PlayerStorage.begin(); mitr != in->m_mapMgr->m_PlayerStorage.end(); ++mitr)
                        {
                            if ((*mitr).second->IsInWorld() && !(*mitr).second->raidgrouponlysent && (*mitr).second->GetInstanceID() == (int32)in->m_instanceId)
                            {
                                (*mitr).second->GetSession()->SendPacket(&data);
                                (*mitr).second->raidgrouponlysent = true;

                                sEventMgr.AddEvent((*mitr).second, &Player::EjectFromInstance, EVENT_PLAYER_EJECT_FROM_INSTANCE, 60000, 1, EVENT_FLAG_DO_NOT_EXECUTE_IN_WORLD_CONTEXT);
                            }
                        }
                    }
                }
            }
        }
    }
    m_mapLock.Release();
}

bool InstanceMgr::_DeleteInstance(Instance* in, bool ForcePlayersOut)
{
    m_mapLock.Acquire();
    InstanceMap* instancemap;
    InstanceMap::iterator itr;

    if (in->m_mapMgr)
    {
        // "ForcePlayersOut" will teleport the players in this instance to their entry point/hearthstone.
        // otherwise, they will get a 60 second timeout telling them they are not in this instance's group.
        if (in->m_mapMgr->HasPlayers())
        {
            if (ForcePlayersOut)
                in->m_mapMgr->InstanceShutdown();
            else
            {
                in->m_mapMgr->BeginInstanceExpireCountdown();
                in->m_mapMgr->pInstance = NULL;
            }
        }
        else
            in->m_mapMgr->InstanceShutdown();
    }

    // remove the instance from the large map.
    instancemap = m_instances[in->m_mapId];
    if (instancemap)
    {
        itr = instancemap->find(in->m_instanceId);
        if (itr != instancemap->end())
            instancemap->erase(itr);
    }

    // cleanup corpses, database references
    in->DeleteFromDB();

    // delete the instance pointer.
    delete in;

    m_mapLock.Release();

    return true;
}

void Instance::DeleteFromDB()
{
    // cleanup all the corpses on this instance
    CharacterDatabase.Execute("DELETE FROM corpses WHERE instanceid = %u", m_instanceId);

    // delete from the database
    CharacterDatabase.Execute("DELETE FROM instances WHERE id = %u", m_instanceId);

    // Delete all instance assignments
    CharacterDatabase.Execute("DELETE FROM instanceids WHERE mapId = %u AND instanceId = %u AND mode = %u", m_mapId, m_instanceId, m_difficulty);
}

void InstanceMgr::CheckForExpiredInstances()
{
    // checking for any expired instances.
    Instance* in;
    InstanceMap::iterator itr;
    InstanceMap* instancemap;
    uint32 i;

    m_mapLock.Acquire();
    for (i = 0; i < MAX_NUM_MAPS; ++i)
    {
        instancemap = m_instances[i];
        if (instancemap)
        {
            for (itr = instancemap->begin(); itr != instancemap->end();)
            {
                in = itr->second;
                ++itr;

                // use a "soft" delete here.
                if (in->m_mapInfo->type != INSTANCE_NONRAID && !(in->m_mapInfo->type == INSTANCE_MULTIMODE && in->m_difficulty == MODE_NORMAL) && HasInstanceExpired(in))
                    _DeleteInstance(in, false);
            }

        }
    }
    m_mapLock.Release();
}

void InstanceMgr::BuildSavedInstancesForPlayer(Player* plr)
{
#if VERSION_STRING < Cata
    WorldPacket data(4);
    Instance* in;
    InstanceMap::iterator itr;
    InstanceMap* instancemap;
    uint32 i;

    if (!plr->IsInWorld() || plr->GetMapMgr()->GetMapInfo()->type != INSTANCE_NULL)
    {
        m_mapLock.Acquire();
        for (i = 0; i < MAX_NUM_MAPS; ++i)
        {
            if (m_instances[i] != NULL)
            {
                instancemap = m_instances[i];
                for (itr = instancemap->begin(); itr != instancemap->end();)
                {
                    in = itr->second;
                    ++itr;

                    if (PlayerOwnsInstance(in, plr) && in->m_mapInfo->type == INSTANCE_NONRAID)
                    {
                        m_mapLock.Release();

                        data.SetOpcode(SMSG_UPDATE_LAST_INSTANCE);
                        data << uint32(in->m_mapId);
                        plr->GetSession()->SendPacket(&data);

                        data.Initialize(SMSG_UPDATE_INSTANCE_OWNERSHIP);
                        data << uint32(0x01);
                        plr->GetSession()->SendPacket(&data);

                        return;
                    }
                }
            }
        }
        m_mapLock.Release();
    }

    data.SetOpcode(SMSG_UPDATE_INSTANCE_OWNERSHIP);
    data << uint32(0x00);
    plr->GetSession()->SendPacket(&data);
#else
    if (plr == nullptr) { return; }
#endif
}

void InstanceMgr::BuildRaidSavedInstancesForPlayer(Player* plr)
{
#if VERSION_STRING < Cata
    WorldPacket data(SMSG_RAID_INSTANCE_INFO, 200);
#else
    WorldPacket data(SMSG_RAID_INSTANCE_INFO, 4);
#endif
    uint32 counter = 0;

    data << counter;
    m_mapLock.Acquire();
  
    for (uint32_t i = 0; i < MAX_NUM_MAPS; ++i)
    {
        if (m_instances[i] != NULL)
        {
            InstanceMap* instancemap = m_instances[i];
            for (InstanceMap::iterator itr = instancemap->begin(); itr != instancemap->end();)
            {
                Instance* in = itr->second;
                ++itr;

                if (in->m_persistent && PlayerOwnsInstance(in, plr))
                {
                    data << uint32(in->m_mapId);                         // obviously the mapid
                    data << uint32(in->m_difficulty);                    // instance difficulty
                    data << uint64(in->m_instanceId);                    // self-explanatory
                    data << uint8(1);                                    // expired = 0
                    data << uint8(0);                                    // extended = 1

                    if (in->m_expiration > UNIXTIME)
                        data << uint32(in->m_expiration - UNIXTIME);
                    else
                        data << uint32(0);

#if VERSION_STRING >= Cata
                    data << uint32(0);
#endif

                    ++counter;
                }
            }
        }
    }
{'fraction_non_alphanumeric': 0.12269880561078873, 'fraction_numerical': 0.007277252499019329, 'mean_word_length': 1.681343391846801, 'pattern_counts': {'":': 0, '<': 107, '<?xml version=': 0, '>': 711, 'https://': 0, 'lorem ipsum': 0, 'www.': 6, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6177081', 'n_tokens_mistral': 22137, 'n_tokens_neox': 21150, 'n_words': 4968}