2010-10-07 15:35:36 +02:00
/*
* Copyright (C) 2008-2010 TrinityCore <http://www.trinitycore.org/>
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*
* 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 2 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/>.
2008-10-02 16:23:55 -05:00
*/
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
#include "Common.h"
2010-06-08 16:13:55 -06:00
#include "SQLStorage.h"
2008-10-02 16:23:55 -05:00
#include "Player.h"
#include "GridNotifiers.h"
#include "Log.h"
#include "GridStates.h"
#include "CellImpl.h"
#include "Map.h"
#include "MapManager.h"
#include "MapInstanced.h"
#include "InstanceSaveMgr.h"
2010-06-07 23:40:17 +02:00
#include "Timer.h"
2008-10-02 16:23:55 -05:00
#include "GridNotifiersImpl.h"
2010-07-29 01:22:45 +02:00
#include "Config.h"
2010-06-07 23:40:17 +02:00
#include "Transport.h"
2008-10-02 16:23:55 -05:00
#include "ObjectMgr.h"
#include "World.h"
#include "Group.h"
2010-08-08 22:54:58 +06:00
#include "InstanceScript.h"
2008-10-02 16:23:55 -05:00
#include "ProgressBar.h"
2009-10-17 15:51:44 -07:00
2010-09-25 14:47:24 +02:00
uint16 InstanceSaveManager :: ResetTimeDelay [] = { 3600 , 900 , 300 , 60 };
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
InstanceSaveManager ::~ InstanceSaveManager ()
{
// it is undefined whether this or objectmgr will be unloaded first
// so we must be prepared for both cases
lock_instLists = true ;
for ( InstanceSaveHashMap :: iterator itr = m_instanceSaveById . begin (); itr != m_instanceSaveById . end (); ++ itr )
{
InstanceSave * save = itr -> second ;
2009-10-17 16:20:24 -07:00
for ( InstanceSave :: PlayerListType :: iterator itr2 = save -> m_playerList . begin (), next = itr2 ; itr2 != save -> m_playerList . end (); itr2 = next )
2008-10-02 16:23:55 -05:00
{
++ next ;
2009-04-27 18:42:35 -05:00
( * itr2 ) -> UnbindInstance ( save -> GetMapId (), save -> GetDifficulty (), true );
2008-10-02 16:23:55 -05:00
}
save -> m_playerList . clear ();
2009-10-17 16:20:24 -07:00
for ( InstanceSave :: GroupListType :: iterator itr2 = save -> m_groupList . begin (), next = itr2 ; itr2 != save -> m_groupList . end (); itr2 = next )
2008-10-02 16:23:55 -05:00
{
++ next ;
2009-04-27 18:42:35 -05:00
( * itr2 ) -> UnbindInstance ( save -> GetMapId (), save -> GetDifficulty (), true );
2008-10-02 16:23:55 -05:00
}
save -> m_groupList . clear ();
delete save ;
}
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
/*
- adding instance into manager
- called from InstanceMap::Add, _LoadBoundInstances, LoadGroups
*/
2009-12-17 11:18:07 +01:00
InstanceSave * InstanceSaveManager :: AddInstanceSave ( uint32 mapId , uint32 instanceId , Difficulty difficulty , time_t resetTime , bool canReset , bool load )
2008-10-02 16:23:55 -05:00
{
2010-04-07 19:14:10 +02:00
if ( InstanceSave * old_save = GetInstanceSave ( instanceId ))
2009-12-17 11:18:07 +01:00
return old_save ;
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
const MapEntry * entry = sMapStore . LookupEntry ( mapId );
2009-12-17 11:18:07 +01:00
if ( ! entry )
2008-10-02 16:23:55 -05:00
{
2009-12-19 14:02:26 +01:00
sLog . outError ( "InstanceSaveManager::AddInstanceSave: wrong mapid = %d, instanceid = %d!" , mapId , instanceId );
2009-12-17 11:18:07 +01:00
return NULL ;
}
if ( instanceId == 0 )
{
sLog . outError ( "InstanceSaveManager::AddInstanceSave: mapid = %d, wrong instanceid = %d!" , mapId , instanceId );
return NULL ;
}
if ( difficulty >= ( entry -> IsRaid () ? MAX_RAID_DIFFICULTY : MAX_DUNGEON_DIFFICULTY ))
{
sLog . outError ( "InstanceSaveManager::AddInstanceSave: mapid = %d, instanceid = %d, wrong dificalty %u!" , mapId , instanceId , difficulty );
2008-10-02 16:23:55 -05:00
return NULL ;
}
2009-10-17 15:51:44 -07:00
2010-04-07 19:14:10 +02:00
if ( ! resetTime )
2008-10-02 16:23:55 -05:00
{
// initialize reset time
// for normal instances if no creatures are killed the instance will reset in two hours
2010-04-07 19:14:10 +02:00
if ( entry -> map_type == MAP_RAID || difficulty > DUNGEON_DIFFICULTY_NORMAL )
2009-12-17 11:18:07 +01:00
resetTime = GetResetTimeFor ( mapId , difficulty );
2008-10-02 16:23:55 -05:00
else
{
2009-12-24 10:20:15 +01:00
resetTime = time ( NULL ) + 2 * HOUR ;
2008-10-02 16:23:55 -05:00
// normally this will be removed soon after in InstanceMap::Add, prevent error
2009-12-17 11:18:07 +01:00
ScheduleReset ( true , resetTime , InstResetEvent ( 0 , mapId , difficulty , instanceId ));
2008-10-02 16:23:55 -05:00
}
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
sLog . outDebug ( "InstanceSaveManager::AddInstanceSave: mapid = %d, instanceid = %d" , mapId , instanceId );
2009-10-17 15:51:44 -07:00
2009-12-17 11:18:07 +01:00
InstanceSave * save = new InstanceSave ( mapId , instanceId , difficulty , resetTime , canReset );
2010-04-07 19:14:10 +02:00
if ( ! load ) save -> SaveToDB ();
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
m_instanceSaveById [ instanceId ] = save ;
return save ;
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
InstanceSave * InstanceSaveManager :: GetInstanceSave ( uint32 InstanceId )
{
InstanceSaveHashMap :: iterator itr = m_instanceSaveById . find ( InstanceId );
return itr != m_instanceSaveById . end () ? itr -> second : NULL ;
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
void InstanceSaveManager :: DeleteInstanceFromDB ( uint32 instanceid )
{
2010-08-21 03:19:25 +02:00
SQLTransaction trans = CharacterDatabase . BeginTransaction ();
trans -> PAppend ( "DELETE FROM instance WHERE id = '%u'" , instanceid );
trans -> PAppend ( "DELETE FROM character_instance WHERE instance = '%u'" , instanceid );
trans -> PAppend ( "DELETE FROM group_instance WHERE instance = '%u'" , instanceid );
CharacterDatabase . CommitTransaction ( trans );
2008-10-02 16:23:55 -05:00
// respawn times should be deleted only when the map gets unloaded
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
void InstanceSaveManager :: RemoveInstanceSave ( uint32 InstanceId )
{
2010-04-07 22:59:46 +02:00
InstanceSaveHashMap :: iterator itr = m_instanceSaveById . find ( InstanceId );
2010-04-07 19:14:10 +02:00
if ( itr != m_instanceSaveById . end ())
2008-10-02 16:23:55 -05:00
{
// save the resettime for normal instances only when they get unloaded
2010-04-07 19:14:10 +02:00
if ( time_t resettime = itr -> second -> GetResetTimeForDB ())
2009-05-31 16:15:12 -05:00
CharacterDatabase . PExecute ( "UPDATE instance SET resettime = '" UI64FMTD "' WHERE id = '%u'" , ( uint64 ) resettime , InstanceId );
2008-10-02 16:23:55 -05:00
delete itr -> second ;
m_instanceSaveById . erase ( itr );
}
}
2009-10-17 15:51:44 -07:00
2009-12-17 11:18:07 +01:00
InstanceSave :: InstanceSave ( uint16 MapId , uint32 InstanceId , Difficulty difficulty , time_t resetTime , bool canReset )
2009-02-24 11:14:28 -06:00
: m_resetTime ( resetTime ), m_instanceid ( InstanceId ), m_mapid ( MapId ),
2008-10-02 16:23:55 -05:00
m_difficulty ( difficulty ), m_canReset ( canReset )
{
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
InstanceSave ::~ InstanceSave ()
{
// the players and groups must be unbound before deleting the save
2010-07-30 20:04:28 +02:00
ASSERT ( m_playerList . empty () && m_groupList . empty ());
2008-10-02 16:23:55 -05:00
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
/*
Called from AddInstanceSave
*/
void InstanceSave :: SaveToDB ()
{
// save instance data too
std :: string data ;
2009-10-17 15:51:44 -07:00
2010-06-25 00:18:01 +02:00
Map * map = sMapMgr . FindMap ( GetMapId (), m_instanceid );
2010-04-07 19:14:10 +02:00
if ( map )
2008-10-02 16:23:55 -05:00
{
2010-07-30 20:04:28 +02:00
ASSERT ( map -> IsDungeon ());
2010-08-08 22:54:58 +06:00
if ( InstanceScript * iData = (( InstanceMap * ) map ) -> GetInstanceScript ())
2008-10-02 16:23:55 -05:00
{
2009-05-11 03:17:13 -05:00
data = iData -> GetSaveData ();
2010-04-07 19:14:10 +02:00
if ( ! data . empty ())
2009-05-11 03:17:13 -05:00
CharacterDatabase . escape_string ( data );
2008-10-02 16:23:55 -05:00
}
}
2009-10-17 15:51:44 -07:00
2009-05-31 16:15:12 -05:00
CharacterDatabase . PExecute ( "INSERT INTO instance VALUES ('%u', '%u', '" UI64FMTD "', '%u', '%s')" , m_instanceid , GetMapId (), ( uint64 ) GetResetTimeForDB (), GetDifficulty (), data . c_str ());
2008-10-02 16:23:55 -05:00
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
time_t InstanceSave :: GetResetTimeForDB ()
{
// only save the reset time for normal instances
const MapEntry * entry = sMapStore . LookupEntry ( GetMapId ());
2010-04-07 19:14:10 +02:00
if ( ! entry || entry -> map_type == MAP_RAID || GetDifficulty () == DUNGEON_DIFFICULTY_HEROIC )
2008-10-02 16:23:55 -05:00
return 0 ;
else
return GetResetTime ();
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
// to cache or not to cache, that is the question
InstanceTemplate const * InstanceSave :: GetTemplate ()
{
2010-08-08 05:25:45 +02:00
return sObjectMgr . GetInstanceTemplate ( m_mapid );
2008-10-02 16:23:55 -05:00
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
MapEntry const * InstanceSave :: GetMapEntry ()
{
return sMapStore . LookupEntry ( m_mapid );
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
void InstanceSave :: DeleteFromDB ()
{
InstanceSaveManager :: DeleteInstanceFromDB ( GetInstanceId ());
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
/* true if the instance save is still valid */
bool InstanceSave :: UnloadIfEmpty ()
{
2010-04-07 19:14:10 +02:00
if ( m_playerList . empty () && m_groupList . empty ())
2008-10-02 16:23:55 -05:00
{
2010-08-08 05:25:45 +02:00
if ( ! sInstanceSaveMgr . lock_instLists )
sInstanceSaveMgr . RemoveInstanceSave ( GetInstanceId ());
2008-10-02 16:23:55 -05:00
return false ;
}
else
return true ;
}
2009-10-17 15:51:44 -07:00
2010-09-02 17:47:50 +02:00
void InstanceSaveManager :: _DelHelper ( const char * fields , const char * table , const char * queryTail ,...)
2008-10-02 16:23:55 -05:00
{
2010-10-21 16:56:51 +02:00
Tokens fieldTokens ( fields , ',' );
2010-07-30 20:04:28 +02:00
ASSERT ( fieldTokens . size () != 0 );
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
va_list ap ;
char szQueryTail [ MAX_QUERY_LEN ];
va_start ( ap , queryTail );
2010-04-19 09:26:37 +02:00
vsnprintf ( szQueryTail , MAX_QUERY_LEN , queryTail , ap );
2008-10-02 16:23:55 -05:00
va_end ( ap );
2009-10-17 15:51:44 -07:00
2010-09-11 21:10:54 +02:00
QueryResult result = CharacterDatabase . PQuery ( "SELECT %s FROM %s %s" , fields , table , szQueryTail );
2010-04-07 19:14:10 +02:00
if ( result )
2008-10-02 16:23:55 -05:00
{
do
{
Field * fields = result -> Fetch ();
std :: ostringstream ss ;
2009-10-17 16:20:24 -07:00
for ( size_t i = 0 ; i < fieldTokens . size (); i ++ )
2008-10-02 16:23:55 -05:00
{
2010-09-24 22:16:21 +02:00
std :: string fieldValue = fields [ i ]. GetString ();
2010-09-02 18:20:59 +02:00
CharacterDatabase . escape_string ( fieldValue );
2008-10-02 16:23:55 -05:00
ss << ( i != 0 ? " AND " : "" ) << fieldTokens [ i ] << " = '" << fieldValue << "'" ;
}
2010-10-22 21:38:22 +02:00
CharacterDatabase . DirectPExecute ( "DELETE FROM %s WHERE %s" , table , ss . str (). c_str ());
2008-10-02 16:23:55 -05:00
} while ( result -> NextRow ());
}
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
void InstanceSaveManager :: CleanupInstances ()
{
barGoLink bar ( 2 );
bar . step ();
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
// load reset times and clean expired instances
2010-08-08 05:25:45 +02:00
sInstanceSaveMgr . LoadResetTimes ();
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
// clean character/group - instance binds with invalid group/characters
2010-09-02 17:47:50 +02:00
_DelHelper ( "character_instance.guid, instance" , "character_instance" , "LEFT JOIN characters ON character_instance.guid = characters.guid WHERE characters.guid IS NULL" );
_DelHelper ( "group_instance.guid, instance" , "group_instance" , "LEFT JOIN groups ON group_instance.guid = groups.guid LEFT JOIN characters ON groups.leaderGuid = characters.guid WHERE characters.guid IS NULL OR groups.guid IS NULL" );
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
// clean instances that do not have any players or groups bound to them
2010-09-02 17:47:50 +02:00
_DelHelper ( "id, map, difficulty" , "instance" , "LEFT JOIN character_instance ON character_instance.instance = id LEFT JOIN group_instance ON group_instance.instance = id WHERE character_instance.instance IS NULL AND group_instance.instance IS NULL" );
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
// clean invalid instance references in other tables
2010-09-02 17:47:50 +02:00
_DelHelper ( "character_instance.guid, instance" , "character_instance" , "LEFT JOIN instance ON character_instance.instance = instance.id WHERE instance.id IS NULL" );
_DelHelper ( "guid, instance" , "group_instance" , "LEFT JOIN instance ON group_instance.instance = instance.id WHERE instance.id IS NULL" );
2009-10-17 15:51:44 -07:00
2010-11-19 15:53:14 +01:00
// Clean respawn data from non existing instances
CharacterDatabase . PExecute ( "DELETE FROM creature_respawn WHERE instance <> 0 AND instance NOT IN (SELECT id FROM instance)" );
CharacterDatabase . PExecute ( "DELETE FROM gameobject_respawn WHERE instance <> 0 AND instance NOT IN (SELECT id FROM instance)" );
2008-10-02 16:23:55 -05:00
// first, obtain total instance set
2009-02-09 20:09:03 -06:00
std :: set < uint32 > InstanceSet ;
2010-09-11 21:10:54 +02:00
QueryResult result = CharacterDatabase . Query ( "SELECT id FROM instance" );
2010-01-01 14:19:29 -06:00
if ( result )
2008-10-02 16:23:55 -05:00
{
do
{
Field * fields = result -> Fetch ();
InstanceSet . insert ( fields [ 0 ]. GetUInt32 ());
}
while ( result -> NextRow ());
}
2009-10-17 15:51:44 -07:00
2009-08-26 18:11:14 -05:00
// characters
2009-06-13 10:37:56 -05:00
result = CharacterDatabase . Query ( "SELECT DISTINCT(instance_id) FROM characters WHERE instance_id <> 0" );
2010-01-01 14:19:29 -06:00
if ( result )
2009-06-13 10:37:56 -05:00
{
do
{
Field * fields = result -> Fetch ();
2010-01-01 14:19:29 -06:00
if ( InstanceSet . find ( fields [ 0 ]. GetUInt32 ()) == InstanceSet . end ())
2009-06-13 10:37:56 -05:00
CharacterDatabase . PExecute ( "UPDATE characters SET instance_id = '0' WHERE instance_id = '%u'" , fields [ 0 ]. GetUInt32 ());
}
while ( result -> NextRow ());
}
2009-10-17 15:51:44 -07:00
2009-08-26 18:11:14 -05:00
// corpse
result = CharacterDatabase . Query ( "SELECT DISTINCT(instance) FROM corpse WHERE instance <> 0" );
2010-01-01 14:19:29 -06:00
if ( result )
2009-08-26 18:11:14 -05:00
{
do
{
Field * fields = result -> Fetch ();
2010-01-01 14:19:29 -06:00
if ( InstanceSet . find ( fields [ 0 ]. GetUInt32 ()) == InstanceSet . end ())
2009-08-26 18:11:14 -05:00
CharacterDatabase . PExecute ( "UPDATE corpse SET instance = '0' WHERE instance = '%u'" , fields [ 0 ]. GetUInt32 ());
}
while ( result -> NextRow ());
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
bar . step ();
2009-04-03 16:36:20 -06:00
sLog . outString ();
2010-04-07 22:59:46 +02:00
sLog . outString ( ">> Initialized %u instances" , ( uint32 ) InstanceSet . size ());
2008-10-02 16:23:55 -05:00
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
void InstanceSaveManager :: PackInstances ()
{
// this routine renumbers player instance associations in such a way so they start from 1 and go up
// TODO: this can be done a LOT more efficiently
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
// obtain set of all associations
2009-02-09 20:09:03 -06:00
std :: set < uint32 > InstanceSet ;
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
// all valid ids are in the instance table
// any associations to ids not in this table are assumed to be
// cleaned already in CleanupInstances
2010-09-11 21:10:54 +02:00
QueryResult result = CharacterDatabase . Query ( "SELECT id FROM instance" );
2010-01-01 14:19:29 -06:00
if ( result )
2008-10-02 16:23:55 -05:00
{
do
{
Field * fields = result -> Fetch ();
InstanceSet . insert ( fields [ 0 ]. GetUInt32 ());
}
while ( result -> NextRow ());
}
2009-10-17 15:51:44 -07:00
2010-01-01 14:19:29 -06:00
barGoLink bar ( InstanceSet . size () + 1 );
2008-10-02 16:23:55 -05:00
bar . step ();
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
uint32 InstanceNumber = 1 ;
// we do assume std::set is sorted properly on integer value
2009-02-09 20:09:03 -06:00
for ( std :: set < uint32 >:: iterator i = InstanceSet . begin (); i != InstanceSet . end (); ++ i )
2008-10-02 16:23:55 -05:00
{
if ( * i != InstanceNumber )
{
// remap instance id
2010-11-19 15:53:14 +01:00
CharacterDatabase . PExecute ( "UPDATE creature_respawn SET instance = '%u' WHERE instance = '%u'" , InstanceNumber , * i );
CharacterDatabase . PExecute ( "UPDATE gameobject_respawn SET instance = '%u' WHERE instance = '%u'" , InstanceNumber , * i );
2009-05-25 21:17:34 -05:00
CharacterDatabase . PExecute ( "UPDATE characters SET instance_id = '%u' WHERE instance_id = '%u'" , InstanceNumber , * i );
2008-10-02 16:23:55 -05:00
CharacterDatabase . PExecute ( "UPDATE corpse SET instance = '%u' WHERE instance = '%u'" , InstanceNumber , * i );
CharacterDatabase . PExecute ( "UPDATE character_instance SET instance = '%u' WHERE instance = '%u'" , InstanceNumber , * i );
CharacterDatabase . PExecute ( "UPDATE instance SET id = '%u' WHERE id = '%u'" , InstanceNumber , * i );
CharacterDatabase . PExecute ( "UPDATE group_instance SET instance = '%u' WHERE instance = '%u'" , InstanceNumber , * i );
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
++ InstanceNumber ;
bar . step ();
}
2009-10-17 15:51:44 -07:00
2009-04-03 16:36:20 -06:00
sLog . outString ();
2010-04-07 22:59:46 +02:00
sLog . outString ( ">> Instance numbers remapped, next instance id is %u" , InstanceNumber );
2008-10-02 16:23:55 -05:00
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
void InstanceSaveManager :: LoadResetTimes ()
{
2009-12-24 10:20:15 +01:00
time_t now = time ( NULL );
2008-10-02 16:23:55 -05:00
time_t today = ( now / DAY ) * DAY ;
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
// NOTE: Use DirectPExecute for tables that will be queried later
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
// get the current reset times for normal instances (these may need to be updated)
// these are only kept in memory for InstanceSaves that are loaded later
// resettime = 0 in the DB for raid/heroic instances so those are skipped
2009-12-17 11:18:07 +01:00
typedef std :: pair < uint32 /*PAIR32(map,difficulty)*/ , time_t > ResetTimeMapDiffType ;
typedef std :: map < uint32 , ResetTimeMapDiffType > InstResetTimeMapDiffType ;
InstResetTimeMapDiffType instResetTime ;
// index instance ids by map/difficulty pairs for fast reset warning send
typedef std :: multimap < uint32 /*PAIR32(map,difficulty)*/ , uint32 /*instanceid*/ > ResetTimeMapDiffInstances ;
ResetTimeMapDiffInstances mapDiffResetInstances ;
2010-09-11 21:10:54 +02:00
QueryResult result = CharacterDatabase . Query ( "SELECT id, map, difficulty, resettime FROM instance WHERE resettime > 0" );
2010-04-07 22:59:46 +02:00
if ( result )
2008-10-02 16:23:55 -05:00
{
do
{
2010-04-07 19:14:10 +02:00
if ( time_t resettime = time_t (( * result )[ 3 ]. GetUInt64 ()))
2008-10-02 16:23:55 -05:00
{
uint32 id = ( * result )[ 0 ]. GetUInt32 ();
uint32 mapid = ( * result )[ 1 ]. GetUInt32 ();
2009-12-17 11:18:07 +01:00
uint32 difficulty = ( * result )[ 2 ]. GetUInt32 ();
instResetTime [ id ] = ResetTimeMapDiffType ( MAKE_PAIR32 ( mapid , difficulty ), resettime );
mapDiffResetInstances . insert ( ResetTimeMapDiffInstances :: value_type ( MAKE_PAIR32 ( mapid , difficulty ), id ));
2008-10-02 16:23:55 -05:00
}
}
while ( result -> NextRow ());
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
// update reset time for normal instances with the max creature respawn time + X hours
2010-11-19 15:53:14 +01:00
result = CharacterDatabase . Query ( "SELECT MAX(respawntime), instance FROM creature_respawn WHERE instance > 0 GROUP BY instance" );
2010-04-07 22:59:46 +02:00
if ( result )
2008-10-02 16:23:55 -05:00
{
do
{
Field * fields = result -> Fetch ();
uint32 instance = fields [ 1 ]. GetUInt32 ();
2009-02-24 11:14:28 -06:00
time_t resettime = time_t ( fields [ 0 ]. GetUInt64 () + 2 * HOUR );
2009-12-17 11:18:07 +01:00
InstResetTimeMapDiffType :: iterator itr = instResetTime . find ( instance );
2010-04-07 19:14:10 +02:00
if ( itr != instResetTime . end () && itr -> second . second != resettime )
2008-10-02 16:23:55 -05:00
{
2010-10-22 21:38:22 +02:00
CharacterDatabase . DirectPExecute ( "UPDATE instance SET resettime = '" UI64FMTD "' WHERE id = '%u'" , uint64 ( resettime ), instance );
2008-10-02 16:23:55 -05:00
itr -> second . second = resettime ;
}
}
while ( result -> NextRow ());
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
// schedule the reset times
2010-04-07 19:12:44 +02:00
for ( InstResetTimeMapDiffType :: iterator itr = instResetTime . begin (); itr != instResetTime . end (); ++ itr )
2010-04-07 19:14:10 +02:00
if ( itr -> second . second > now )
2009-12-17 11:18:07 +01:00
ScheduleReset ( true , itr -> second . second , InstResetEvent ( 0 , PAIR32_LOPART ( itr -> second . first ), Difficulty ( PAIR32_HIPART ( itr -> second . first )), itr -> first ));
2008-10-02 16:23:55 -05:00
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
// load the global respawn times for raid/heroic instances
2010-08-23 19:56:47 -07:00
uint32 diff = sWorld . getIntConfig ( CONFIG_INSTANCE_RESET_TIME_HOUR ) * HOUR ;
2009-12-17 11:18:07 +01:00
result = CharacterDatabase . Query ( "SELECT mapid, difficulty, resettime FROM instance_reset" );
2010-04-07 19:14:10 +02:00
if ( result )
2008-10-02 16:23:55 -05:00
{
do
{
Field * fields = result -> Fetch ();
uint32 mapid = fields [ 0 ]. GetUInt32 ();
2009-12-17 11:18:07 +01:00
Difficulty difficulty = Difficulty ( fields [ 1 ]. GetUInt32 ());
uint64 oldresettime = fields [ 2 ]. GetUInt64 ();
MapDifficulty const * mapDiff = GetMapDifficultyData ( mapid , difficulty );
2010-04-07 19:14:10 +02:00
if ( ! mapDiff )
2008-10-02 16:23:55 -05:00
{
2009-12-17 11:18:07 +01:00
sLog . outError ( "InstanceSaveManager::LoadResetTimes: invalid mapid(%u)/difficulty(%u) pair in instance_reset!" , mapid , difficulty );
2010-10-22 21:38:22 +02:00
CharacterDatabase . DirectPExecute ( "DELETE FROM instance_reset WHERE mapid = '%u' AND difficulty = '%u'" , mapid , difficulty );
2008-10-02 16:23:55 -05:00
continue ;
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
// update the reset time if the hour in the configs changes
uint64 newresettime = ( oldresettime / DAY ) * DAY + diff ;
2010-04-07 19:14:10 +02:00
if ( oldresettime != newresettime )
2010-10-22 21:51:19 +02:00
CharacterDatabase . DirectPExecute ( "UPDATE instance_reset SET resettime = '" UI64FMTD "' WHERE mapid = '%u' AND difficulty = '%u'" , newresettime , mapid , difficulty );
2009-10-17 15:51:44 -07:00
2009-12-17 11:18:07 +01:00
SetResetTimeFor ( mapid , difficulty , newresettime );
2010-04-07 19:13:19 +02:00
} while ( result -> NextRow ());
2008-10-02 16:23:55 -05:00
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
// clean expired instances, references to them will be deleted in CleanupInstances
// must be done before calculating new reset times
2010-09-02 17:47:50 +02:00
_DelHelper ( "id, map, instance.difficulty" , "instance" , "LEFT JOIN instance_reset ON mapid = map AND instance.difficulty = instance_reset.difficulty WHERE (instance.resettime < '" UI64FMTD "' AND instance.resettime > '0') OR (NOT instance_reset.resettime IS NULL AND instance_reset.resettime < '" UI64FMTD "')" , ( uint64 ) now , ( uint64 ) now );
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
// calculate new global reset times for expired instances and those that have never been reset yet
// add the global reset times to the priority queue
2010-04-07 19:12:44 +02:00
for ( MapDifficultyMap :: const_iterator itr = sMapDifficultyMap . begin (); itr != sMapDifficultyMap . end (); ++ itr )
2008-10-02 16:23:55 -05:00
{
2009-12-17 11:18:07 +01:00
uint32 map_diff_pair = itr -> first ;
uint32 mapid = PAIR32_LOPART ( map_diff_pair );
Difficulty difficulty = Difficulty ( PAIR32_HIPART ( map_diff_pair ));
MapDifficulty const * mapDiff = & itr -> second ;
if ( ! mapDiff -> resetTime )
2008-10-02 16:23:55 -05:00
continue ;
2009-10-17 15:51:44 -07:00
2009-12-17 11:18:07 +01:00
// the reset_delay must be at least one day
2010-08-22 12:39:39 -07:00
uint32 period = uint32 ((( mapDiff -> resetTime * sWorld . getRate ( RATE_INSTANCE_RESET_TIME )) / DAY ) * DAY );
2010-02-04 22:03:09 +01:00
if ( period < DAY )
period = DAY ;
2009-12-17 11:18:07 +01:00
time_t t = GetResetTimeFor ( mapid , difficulty );
2010-04-07 19:14:10 +02:00
if ( ! t )
2008-10-02 16:23:55 -05:00
{
// initialize the reset time
t = today + period + diff ;
2010-10-22 21:38:22 +02:00
CharacterDatabase . DirectPExecute ( "INSERT INTO instance_reset VALUES ('%u','%u','" UI64FMTD "')" , mapid , difficulty , ( uint64 ) t );
2008-10-02 16:23:55 -05:00
}
2009-10-17 15:51:44 -07:00
2010-04-07 19:14:10 +02:00
if ( t < now )
2008-10-02 16:23:55 -05:00
{
// assume that expired instances have already been cleaned
// calculate the next reset time
t = ( t / DAY ) * DAY ;
t += (( today - t ) / period + 1 ) * period + diff ;
2010-10-22 21:38:22 +02:00
CharacterDatabase . DirectPExecute ( "UPDATE instance_reset SET resettime = '" UI64FMTD "' WHERE mapid = '%u' AND difficulty= '%u'" , ( uint64 ) t , mapid , difficulty );
2008-10-02 16:23:55 -05:00
}
2009-10-17 15:51:44 -07:00
2009-12-17 11:18:07 +01:00
SetResetTimeFor ( mapid , difficulty , t );
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
// schedule the global reset/warning
uint8 type = 1 ;
2010-04-07 19:12:44 +02:00
for (; type < 4 ; type ++ )
2010-09-25 14:47:24 +02:00
if ( t - ResetTimeDelay [ type - 1 ] > now )
2009-12-17 11:18:07 +01:00
break ;
2010-09-25 14:47:24 +02:00
ScheduleReset ( true , t - ResetTimeDelay [ type - 1 ], InstResetEvent ( type , mapid , difficulty , 0 ));
2010-02-04 22:03:09 +01:00
2010-04-07 19:12:44 +02:00
for ( ResetTimeMapDiffInstances :: const_iterator in_itr = mapDiffResetInstances . lower_bound ( map_diff_pair );
2009-12-17 11:18:07 +01:00
in_itr != mapDiffResetInstances . upper_bound ( map_diff_pair ); ++ in_itr )
{
2010-09-25 14:47:24 +02:00
ScheduleReset ( true , t - ResetTimeDelay [ type - 1 ], InstResetEvent ( type , mapid , difficulty , in_itr -> second ));
2009-12-17 11:18:07 +01:00
}
2008-10-02 16:23:55 -05:00
}
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
void InstanceSaveManager :: ScheduleReset ( bool add , time_t time , InstResetEvent event )
{
2010-04-07 19:14:10 +02:00
if ( add ) m_resetTimeQueue . insert ( std :: pair < time_t , InstResetEvent > ( time , event ));
2008-10-02 16:23:55 -05:00
else
{
// find the event in the queue and remove it
ResetTimeQueue :: iterator itr ;
std :: pair < ResetTimeQueue :: iterator , ResetTimeQueue :: iterator > range ;
range = m_resetTimeQueue . equal_range ( time );
2009-10-17 16:20:24 -07:00
for ( itr = range . first ; itr != range . second ; ++ itr )
2010-04-07 19:14:10 +02:00
if ( itr -> second == event ) { m_resetTimeQueue . erase ( itr ); return ; }
2008-10-02 16:23:55 -05:00
// in case the reset time changed (should happen very rarely), we search the whole queue
2010-04-07 19:14:10 +02:00
if ( itr == range . second )
2008-10-02 16:23:55 -05:00
{
2009-10-17 16:20:24 -07:00
for ( itr = m_resetTimeQueue . begin (); itr != m_resetTimeQueue . end (); ++ itr )
2010-04-07 19:14:10 +02:00
if ( itr -> second == event ) { m_resetTimeQueue . erase ( itr ); return ; }
if ( itr == m_resetTimeQueue . end ())
2008-10-02 16:23:55 -05:00
sLog . outError ( "InstanceSaveManager::ScheduleReset: cannot cancel the reset, the event(%d,%d,%d) was not found!" , event . type , event . mapid , event . instanceId );
}
}
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
void InstanceSaveManager :: Update ()
{
2009-12-24 10:20:15 +01:00
time_t now = time ( NULL ), t ;
2010-04-07 19:13:19 +02:00
while ( ! m_resetTimeQueue . empty () && ( t = m_resetTimeQueue . begin () -> first ) < now )
2008-10-02 16:23:55 -05:00
{
InstResetEvent & event = m_resetTimeQueue . begin () -> second ;
2010-04-07 19:14:10 +02:00
if ( event . type == 0 )
2008-10-02 16:23:55 -05:00
{
// for individual normal instances, max creature respawn + X hours
_ResetInstance ( event . mapid , event . instanceId );
m_resetTimeQueue . erase ( m_resetTimeQueue . begin ());
}
else
{
// global reset/warning for a certain map
2009-12-17 11:18:07 +01:00
time_t resetTime = GetResetTimeFor ( event . mapid , event . difficulty );
2010-09-03 21:55:57 -07:00
_ResetOrWarnAll ( event . mapid , event . difficulty , event . type != 4 , uint32 ( resetTime - now ));
2010-04-07 19:14:10 +02:00
if ( event . type != 4 )
2008-10-02 16:23:55 -05:00
{
// schedule the next warning/reset
event . type ++ ;
2010-09-25 14:47:24 +02:00
ScheduleReset ( true , resetTime - ResetTimeDelay [ event . type - 1 ], event );
2008-10-02 16:23:55 -05:00
}
m_resetTimeQueue . erase ( m_resetTimeQueue . begin ());
}
}
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
void InstanceSaveManager :: _ResetSave ( InstanceSaveHashMap :: iterator & itr )
{
// unbind all players bound to the instance
// do not allow UnbindInstance to automatically unload the InstanceSaves
lock_instLists = true ;
InstanceSave :: PlayerListType & pList = itr -> second -> m_playerList ;
2010-04-07 19:13:19 +02:00
while ( ! pList . empty ())
2008-10-02 16:23:55 -05:00
{
Player * player = * ( pList . begin ());
player -> UnbindInstance ( itr -> second -> GetMapId (), itr -> second -> GetDifficulty (), true );
}
InstanceSave :: GroupListType & gList = itr -> second -> m_groupList ;
2010-04-07 19:13:19 +02:00
while ( ! gList . empty ())
2008-10-02 16:23:55 -05:00
{
Group * group = * ( gList . begin ());
group -> UnbindInstance ( itr -> second -> GetMapId (), itr -> second -> GetDifficulty (), true );
}
2009-04-27 21:26:28 -05:00
delete itr -> second ;
2009-04-27 21:26:59 -05:00
m_instanceSaveById . erase ( itr ++ );
2008-10-02 16:23:55 -05:00
lock_instLists = false ;
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
void InstanceSaveManager :: _ResetInstance ( uint32 mapid , uint32 instanceId )
{
sLog . outDebug ( "InstanceSaveMgr::_ResetInstance %u, %u" , mapid , instanceId );
2010-06-25 00:18:01 +02:00
Map * map = ( MapInstanced * ) sMapMgr . CreateBaseMap ( mapid );
2010-04-07 19:14:10 +02:00
if ( ! map -> Instanceable ())
2008-10-02 16:23:55 -05:00
return ;
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
InstanceSaveHashMap :: iterator itr = m_instanceSaveById . find ( instanceId );
2010-04-07 19:14:10 +02:00
if ( itr != m_instanceSaveById . end ()) _ResetSave ( itr );
2008-10-02 16:23:55 -05:00
DeleteInstanceFromDB ( instanceId ); // even if save not loaded
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
Map * iMap = (( MapInstanced * ) map ) -> FindMap ( instanceId );
2010-04-07 19:14:10 +02:00
if ( iMap && iMap -> IsDungeon ()) (( InstanceMap * ) iMap ) -> Reset ( INSTANCE_RESET_RESPAWN_DELAY );
2010-08-08 05:25:45 +02:00
else sObjectMgr . DeleteRespawnTimeForInstance ( instanceId ); // even if map is not loaded
2008-10-02 16:23:55 -05:00
}
2009-10-17 15:51:44 -07:00
2009-12-17 11:18:07 +01:00
void InstanceSaveManager :: _ResetOrWarnAll ( uint32 mapid , Difficulty difficulty , bool warn , uint32 timeLeft )
2008-10-02 16:23:55 -05:00
{
// global reset for all instances of the given map
2009-12-17 11:18:07 +01:00
MapEntry const * mapEntry = sMapStore . LookupEntry ( mapid );
if ( ! mapEntry -> Instanceable ())
2008-10-02 16:23:55 -05:00
return ;
2009-12-17 11:18:07 +01:00
2010-01-06 20:43:41 +03:00
uint64 now = ( uint64 ) time ( NULL );
2009-10-17 15:51:44 -07:00
2010-04-07 19:14:10 +02:00
if ( ! warn )
2008-10-02 16:23:55 -05:00
{
2009-12-17 11:18:07 +01:00
MapDifficulty const * mapDiff = GetMapDifficultyData ( mapid , difficulty );
if ( ! mapDiff || ! mapDiff -> resetTime )
2008-10-02 16:23:55 -05:00
{
2009-12-17 11:18:07 +01:00
sLog . outError ( "InstanceSaveManager::ResetOrWarnAll: not valid difficulty or no reset delay for map %d" , mapid );
2008-10-02 16:23:55 -05:00
return ;
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
// remove all binds to instances of the given map
2009-11-20 19:11:28 -08:00
for ( InstanceSaveHashMap :: iterator itr = m_instanceSaveById . begin (); itr != m_instanceSaveById . end ();)
2008-10-02 16:23:55 -05:00
{
2009-12-17 11:18:07 +01:00
if ( itr -> second -> GetMapId () == mapid && itr -> second -> GetDifficulty () == difficulty )
2008-10-02 16:23:55 -05:00
_ResetSave ( itr );
else
++ itr ;
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
// delete them from the DB, even if not loaded
2010-08-21 03:19:25 +02:00
SQLTransaction trans = CharacterDatabase . BeginTransaction ();
trans -> PAppend ( "DELETE FROM character_instance USING character_instance LEFT JOIN instance ON character_instance.instance = id WHERE map = '%u' and difficulty='%u'" , mapid , difficulty );
trans -> PAppend ( "DELETE FROM group_instance USING group_instance LEFT JOIN instance ON group_instance.instance = id WHERE map = '%u' and difficulty='%u'" , mapid , difficulty );
trans -> PAppend ( "DELETE FROM instance WHERE map = '%u' and difficulty='%u'" , mapid , difficulty );
CharacterDatabase . CommitTransaction ( trans );
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
// calculate the next reset time
2010-08-23 19:56:47 -07:00
uint32 diff = sWorld . getIntConfig ( CONFIG_INSTANCE_RESET_TIME_HOUR ) * HOUR ;
2010-02-04 22:03:09 +01:00
2010-08-22 12:39:39 -07:00
uint32 period = uint32 ((( mapDiff -> resetTime * sWorld . getRate ( RATE_INSTANCE_RESET_TIME )) / DAY ) * DAY );
2010-02-04 22:03:09 +01:00
if ( period < DAY )
period = DAY ;
2010-01-06 20:43:41 +03:00
uint64 next_reset = (( now + timeLeft + MINUTE ) / DAY * DAY ) + period + diff ;
2010-02-04 22:03:09 +01:00
SetResetTimeFor ( mapid , difficulty , next_reset );
2010-05-01 22:25:14 +02:00
ScheduleReset ( true , time_t ( next_reset - 3600 ), InstResetEvent ( 1 , mapid , difficulty , 0 ));
2010-04-07 18:09:10 +02:00
2008-10-02 16:23:55 -05:00
// update it in the DB
2010-01-06 20:43:41 +03:00
CharacterDatabase . PExecute ( "UPDATE instance_reset SET resettime = '" UI64FMTD "' WHERE mapid = '%d' AND difficulty = '%d'" , next_reset , mapid , difficulty );
2008-10-02 16:23:55 -05:00
}
2009-10-17 15:51:44 -07:00
2009-12-17 11:18:07 +01:00
// note: this isn't fast but it's meant to be executed very rarely
2010-06-25 00:18:01 +02:00
Map const * map = sMapMgr . CreateBaseMap ( mapid ); // _not_ include difficulty
2008-10-02 16:23:55 -05:00
MapInstanced :: InstancedMaps & instMaps = (( MapInstanced * ) map ) -> GetInstancedMaps ();
MapInstanced :: InstancedMaps :: iterator mitr ;
2009-10-17 16:20:24 -07:00
for ( mitr = instMaps . begin (); mitr != instMaps . end (); ++ mitr )
2008-10-02 16:23:55 -05:00
{
2009-04-27 18:42:35 -05:00
Map * map2 = mitr -> second ;
2010-04-07 19:14:10 +02:00
if ( ! map2 -> IsDungeon ()) continue ;
if ( warn ) (( InstanceMap * ) map2 ) -> SendResetWarnings ( timeLeft );
2009-04-27 18:42:35 -05:00
else (( InstanceMap * ) map2 ) -> Reset ( INSTANCE_RESET_GLOBAL );
2008-10-02 16:23:55 -05:00
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
// TODO: delete creature/gameobject respawn times even if the maps are not loaded
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
uint32 InstanceSaveManager :: GetNumBoundPlayersTotal ()
{
uint32 ret = 0 ;
2009-10-17 16:20:24 -07:00
for ( InstanceSaveHashMap :: iterator itr = m_instanceSaveById . begin (); itr != m_instanceSaveById . end (); ++ itr )
2008-10-02 16:23:55 -05:00
ret += itr -> second -> GetPlayerCount ();
return ret ;
}
2009-10-17 15:51:44 -07:00
2008-10-02 16:23:55 -05:00
uint32 InstanceSaveManager :: GetNumBoundGroupsTotal ()
{
uint32 ret = 0 ;
2009-10-17 16:20:24 -07:00
for ( InstanceSaveHashMap :: iterator itr = m_instanceSaveById . begin (); itr != m_instanceSaveById . end (); ++ itr )
2008-10-02 16:23:55 -05:00
ret += itr -> second -> GetGroupCount ();
return ret ;
}