First Commit

For Azeroth!
This commit is contained in:
Yehonal
2016-06-26 10:39:44 +02:00
commit e8e94a0a66
3777 changed files with 1419268 additions and 0 deletions

View File

@@ -0,0 +1,774 @@
/*
* Copyright (C)
* Copyright (C)
*
* 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/>.
*/
#include "Common.h"
#include "Player.h"
#include "GridNotifiers.h"
#include "Log.h"
#include "CellImpl.h"
#include "Map.h"
#include "MapManager.h"
#include "MapInstanced.h"
#include "InstanceSaveMgr.h"
#include "Timer.h"
#include "GridNotifiersImpl.h"
#include "Config.h"
#include "Transport.h"
#include "ObjectMgr.h"
#include "World.h"
#include "Group.h"
#include "InstanceScript.h"
#include "ScriptMgr.h"
uint16 InstanceSaveManager::ResetTimeDelay[] = {3600, 900, 300, 60, 0};
PlayerBindStorage InstanceSaveManager::playerBindStorage;
BoundInstancesMap InstanceSaveManager::emptyBoundInstancesMap;
InstanceSaveManager::~InstanceSaveManager()
{
lock_instLists = true;
// pussywizard: crashes on calling function in destructor (PlayerUnbindInstance), completely not needed anyway
/*for (InstanceSaveHashMap::iterator itr = m_instanceSaveById.begin(); itr != m_instanceSaveById.end(); ++itr)
{
InstanceSave* save = itr->second;
InstanceSave::PlayerListType &pList = save->m_playerList;
while (!pList.empty())
PlayerUnbindInstance(*(pList.begin()), save->GetMapId(), save->GetDifficulty(), false);
delete save;
}*/
}
/*
- adding instance into manager
*/
InstanceSave* InstanceSaveManager::AddInstanceSave(uint32 mapId, uint32 instanceId, Difficulty difficulty, bool startup /*=false*/)
{
ASSERT(!GetInstanceSave(instanceId));
const MapEntry* entry = sMapStore.LookupEntry(mapId);
if (!entry)
{
sLog->outError("InstanceSaveManager::AddInstanceSave: wrong mapid = %d, instanceid = %d!", mapId, instanceId);
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);
return NULL;
}
time_t resetTime, extendedResetTime;
if (entry->map_type == MAP_RAID || difficulty > DUNGEON_DIFFICULTY_NORMAL)
{
resetTime = GetResetTimeFor(mapId, difficulty);
extendedResetTime = GetExtendedResetTimeFor(mapId, difficulty);
}
else
{
resetTime = time(NULL) + 3*DAY; // normals expire after 3 days even if someone is still bound to them, cleared on startup
extendedResetTime = 0;
}
InstanceSave* save = new InstanceSave(mapId, instanceId, difficulty, resetTime, extendedResetTime);
if (!startup)
save->InsertToDB();
m_instanceSaveById[instanceId] = save;
return save;
}
InstanceSave* InstanceSaveManager::GetInstanceSave(uint32 InstanceId)
{
InstanceSaveHashMap::iterator itr = m_instanceSaveById.find(InstanceId);
return itr != m_instanceSaveById.end() ? itr->second : NULL;
}
bool InstanceSaveManager::DeleteInstanceSaveIfNeeded(uint32 InstanceId, bool skipMapCheck)
{
return DeleteInstanceSaveIfNeeded(GetInstanceSave(InstanceId), skipMapCheck);
}
bool InstanceSaveManager::DeleteInstanceSaveIfNeeded(InstanceSave* save, bool skipMapCheck)
{
// pussywizard: save is removed only when there are no more players bound AND the map doesn't exist
// pussywizard: this function is called when unbinding a player and when unloading a map
if (!lock_instLists && save && save->m_playerList.empty() && (skipMapCheck || !sMapMgr->FindMap(save->GetMapId(), save->GetInstanceId())))
{
// delete save from storage:
InstanceSaveHashMap::iterator itr = m_instanceSaveById.find(save->GetInstanceId());
ASSERT(itr != m_instanceSaveById.end() && itr->second == save);
m_instanceSaveById.erase(itr);
// delete save from db:
// character_instance is deleted when unbinding a certain player
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INSTANCE_BY_INSTANCE);
stmt->setUInt32(0, save->GetInstanceId());
CharacterDatabase.Execute(stmt);
// clear respawn times (if map is loaded do it just to be sure, if already unloaded it won't do it by itself)
Map::DeleteRespawnTimesInDB(save->GetMapId(), save->GetInstanceId());
delete save;
return true;
}
return false;
}
InstanceSave::InstanceSave(uint16 MapId, uint32 InstanceId, Difficulty difficulty, time_t resetTime, time_t extendedResetTime)
: m_resetTime(resetTime), m_extendedResetTime(extendedResetTime), m_instanceid(InstanceId), m_mapid(MapId), m_difficulty(MapId == 631 || MapId == 724 ? Difficulty(difficulty%2) : difficulty), m_canReset(true), m_instanceData(""), m_completedEncounterMask(0)
{
}
InstanceSave::~InstanceSave()
{
ASSERT(m_playerList.empty());
}
void InstanceSave::InsertToDB()
{
std::string data;
uint32 completedEncounters = 0;
Map* map = sMapMgr->FindMap(GetMapId(), m_instanceid);
if (map)
{
ASSERT(map->IsDungeon());
if (InstanceScript* instanceScript = map->ToInstanceMap()->GetInstanceScript())
{
data = instanceScript->GetSaveData();
completedEncounters = instanceScript->GetCompletedEncounterMask();
// pussywizard:
SetInstanceData(data);
SetCompletedEncounterMask(completedEncounters);
}
}
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_INSTANCE_SAVE);
stmt->setUInt32(0, m_instanceid);
stmt->setUInt16(1, GetMapId());
stmt->setUInt32(2, uint32(GetResetTimeForDB()));
stmt->setUInt8(3, uint8(GetDifficulty()));
stmt->setUInt32(4, completedEncounters);
stmt->setString(5, data);
CharacterDatabase.Execute(stmt);
}
time_t InstanceSave::GetResetTimeForDB()
{
// only save the reset time for normal instances
const MapEntry* entry = sMapStore.LookupEntry(GetMapId());
if (!entry || entry->map_type == MAP_RAID || GetDifficulty() == DUNGEON_DIFFICULTY_HEROIC)
return 0;
else
return GetResetTime();
}
// to cache or not to cache, that is the question
InstanceTemplate const* InstanceSave::GetTemplate()
{
return sObjectMgr->GetInstanceTemplate(m_mapid);
}
MapEntry const* InstanceSave::GetMapEntry()
{
return sMapStore.LookupEntry(m_mapid);
}
void InstanceSave::AddPlayer(uint32 guidLow)
{
TRINITY_GUARD(ACE_Thread_Mutex, _lock);
m_playerList.push_back(guidLow);
}
bool InstanceSave::RemovePlayer(uint32 guidLow, InstanceSaveManager* ism)
{
TRINITY_GUARD(ACE_Thread_Mutex, _lock);
m_playerList.remove(guidLow);
// ism passed as an argument to avoid calling via singleton (might result in a deadlock)
return ism->DeleteInstanceSaveIfNeeded(this->GetInstanceId(), false);
}
void InstanceSaveManager::LoadInstances()
{
uint32 oldMSTime = getMSTime();
// Delete character_instance for non-existent character
CharacterDatabase.DirectExecute("DELETE ci.* FROM character_instance AS ci LEFT JOIN characters AS c ON ci.guid = c.guid WHERE c.guid IS NULL");
// Delete expired normal instances (normals expire after 3 days even if someone is still bound to them, cleared on startup)
CharacterDatabase.DirectExecute("DELETE FROM instance WHERE resettime > 0 AND resettime < UNIX_TIMESTAMP()");
// Delete instance with no binds
CharacterDatabase.DirectExecute("DELETE i.* FROM instance AS i LEFT JOIN character_instance AS ci ON i.id = ci.instance WHERE ci.guid IS NULL");
// Delete creature_respawn, gameobject_respawn and creature_instance for non-existent instance
CharacterDatabase.DirectExecute("DELETE FROM creature_respawn WHERE instanceId > 0 AND instanceId NOT IN (SELECT id FROM instance)");
CharacterDatabase.DirectExecute("DELETE FROM gameobject_respawn WHERE instanceId > 0 AND instanceId NOT IN (SELECT id FROM instance)");
CharacterDatabase.DirectExecute("DELETE tmp.* FROM character_instance AS tmp LEFT JOIN instance ON tmp.instance = instance.id WHERE tmp.instance > 0 AND instance.id IS NULL");
// Clean invalid references to instance
CharacterDatabase.DirectExecute("UPDATE corpse SET instanceId = 0 WHERE instanceId > 0 AND instanceId NOT IN (SELECT id FROM instance)");
CharacterDatabase.DirectExecute("UPDATE characters AS tmp LEFT JOIN instance ON tmp.instance_id = instance.id SET tmp.instance_id = 0 WHERE tmp.instance_id > 0 AND instance.id IS NULL");
// Initialize instance id storage (Needs to be done after the trash has been clean out)
sMapMgr->InitInstanceIds();
// Load reset times and clean expired instances
LoadResetTimes();
// pussywizard
LoadInstanceSaves();
LoadCharacterBinds();
sLog->outString(">> Loaded instances and binds in %u ms", GetMSTimeDiffToNow(oldMSTime));
sLog->outString();
}
void InstanceSaveManager::LoadResetTimes()
{
time_t now = time(NULL);
time_t today = (now / DAY) * DAY;
// load the global respawn times for raid/heroic instances
uint32 diff = sWorld->getIntConfig(CONFIG_INSTANCE_RESET_TIME_HOUR) * HOUR;
QueryResult result = CharacterDatabase.Query("SELECT mapid, difficulty, resettime FROM instance_reset");
if (result)
{
do
{
Field* fields = result->Fetch();
uint32 mapid = fields[0].GetUInt16();
Difficulty difficulty = Difficulty(fields[1].GetUInt8());
uint64 resettime = fields[2].GetUInt32();
MapDifficulty const* mapDiff = GetMapDifficultyData(mapid, difficulty);
if (!mapDiff)
{
sLog->outError("InstanceSaveManager::LoadResetTimes: invalid mapid(%u)/difficulty(%u) pair in instance_reset!", mapid, difficulty);
CharacterDatabase.DirectPExecute("DELETE FROM instance_reset WHERE mapid = '%u' AND difficulty = '%u'", mapid, difficulty);
continue;
}
SetResetTimeFor(mapid, difficulty, resettime);
} while (result->NextRow());
}
// 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
for (MapDifficultyMap::const_iterator itr = sMapDifficultyMap.begin(); itr != sMapDifficultyMap.end(); ++itr)
{
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)
continue;
// the reset_delay must be at least one day
uint32 period = mapDiff->resetTime;
if (period < DAY)
period = DAY;
time_t t = GetResetTimeFor(mapid, difficulty);
if (!t)
{
// initialize the reset time
t = today + period + diff;
SetResetTimeFor(mapid, difficulty, t);
CharacterDatabase.DirectPExecute("INSERT INTO instance_reset VALUES ('%u', '%u', '%u')", mapid, difficulty, (uint32)t);
}
SetExtendedResetTimeFor(mapid, difficulty, t + period);
// schedule the global reset/warning
uint8 type;
for (type = 1; type < 5; ++type)
if (now + ResetTimeDelay[type-1] < t)
break;
ScheduleReset(t - ResetTimeDelay[type-1], InstResetEvent(type, mapid, difficulty));
}
}
void InstanceSaveManager::LoadInstanceSaves()
{
QueryResult result = CharacterDatabase.Query("SELECT id, map, resettime, difficulty, completedEncounters, data FROM instance ORDER BY id ASC");
if (result)
{
do
{
Field* fields = result->Fetch();
uint32 instanceId = fields[0].GetUInt32();
uint32 mapId = fields[1].GetUInt16();
time_t resettime = time_t(fields[2].GetUInt32());
uint8 difficulty = fields[3].GetUInt8();
uint32 completedEncounters = fields[4].GetUInt32();
std::string instanceData = fields[5].GetString();
// Mark instance id as being used
sMapMgr->RegisterInstanceId(instanceId);
InstanceSave* save = AddInstanceSave(mapId, instanceId, Difficulty(difficulty), true);
if (save)
{
save->SetCompletedEncounterMask(completedEncounters);
save->SetInstanceData(instanceData);
if (resettime > 0)
save->SetResetTime(resettime);
}
}
while (result->NextRow());
}
}
void InstanceSaveManager::LoadCharacterBinds()
{
lock_instLists = true;
QueryResult result = CharacterDatabase.Query("SELECT guid, instance, permanent, extended FROM character_instance");
if (result)
{
do
{
Field* fields = result->Fetch();
uint32 guid = fields[0].GetUInt32();
uint32 instanceId = fields[1].GetUInt32();
bool perm = fields[2].GetBool();
bool extended = fields[3].GetBool();
if (InstanceSave* save = GetInstanceSave(instanceId))
{
PlayerCreateBoundInstancesMaps(guid);
InstancePlayerBind& bind = playerBindStorage[guid]->m[save->GetDifficulty()][save->GetMapId()];
if (bind.save) // pussywizard: another bind for the same map and difficulty! may happen because of mysql thread races
{
if (bind.perm) // already loaded perm -> delete currently checked one from db
{
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_GUID);
stmt->setUInt32(0, guid);
stmt->setUInt32(1, instanceId);
CharacterDatabase.Execute(stmt);
continue;
}
else // override temp bind by newest one
{
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_GUID);
stmt->setUInt32(0, guid);
stmt->setUInt32(1, bind.save->GetInstanceId());
CharacterDatabase.Execute(stmt);
bind.save->RemovePlayer(guid, this);
}
}
bind.save = save;
bind.perm = perm;
bind.extended = extended;
save->AddPlayer(guid);
if (perm)
save->SetCanReset(false);
}
}
while (result->NextRow());
}
lock_instLists = false;
}
void InstanceSaveManager::ScheduleReset(time_t time, InstResetEvent event)
{
m_resetTimeQueue.insert(std::pair<time_t, InstResetEvent>(time, event));
}
void InstanceSaveManager::Update()
{
time_t now = time(NULL);
time_t t;
bool resetOccurred = false;
while (!m_resetTimeQueue.empty())
{
t = m_resetTimeQueue.begin()->first;
if (t >= now)
break;
InstResetEvent &event = m_resetTimeQueue.begin()->second;
if (event.type)
{
// global reset/warning for a certain map
time_t resetTime = GetResetTimeFor(event.mapid, event.difficulty);
bool warn = event.type < 5;
_ResetOrWarnAll(event.mapid, event.difficulty, warn, resetTime);
if (warn)
{
// schedule the next warning/reset
++event.type;
ScheduleReset(resetTime - ResetTimeDelay[event.type-1], event);
}
else
resetOccurred = true;
}
m_resetTimeQueue.erase(m_resetTimeQueue.begin());
}
// pussywizard: send updated calendar and raid info
if (resetOccurred)
{
sLog->outString("Instance ID reset occurred, sending updated calendar and raid info to all players!");
WorldPacket dummy;
for (SessionMap::const_iterator itr = sWorld->GetAllSessions().begin(); itr != sWorld->GetAllSessions().end(); ++itr)
if (Player* plr = itr->second->GetPlayer())
{
itr->second->HandleCalendarGetCalendar(dummy);
plr->SendRaidInfo();
}
}
}
void InstanceSaveManager::_ResetSave(InstanceSaveHashMap::iterator &itr)
{
lock_instLists = true;
InstanceSave::PlayerListType &pList = itr->second->m_playerList;
for (InstanceSave::PlayerListType::iterator iter = pList.begin(), iter2; iter != pList.end(); )
{
iter2 = iter++;
PlayerUnbindInstanceNotExtended(*iter2, itr->second->GetMapId(), itr->second->GetDifficulty(), ObjectAccessor::GetObjectInOrOutOfWorld(MAKE_NEW_GUID(*iter2, 0, HIGHGUID_PLAYER), (Player*)NULL));
}
// delete stuff if no players left (noone extended id)
if (pList.empty())
{
// delete character_instance per id, delete instance per id
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE);
stmt->setUInt32(0, itr->second->GetInstanceId());
CharacterDatabase.Execute(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INSTANCE_BY_INSTANCE);
stmt->setUInt32(0, itr->second->GetInstanceId());
CharacterDatabase.Execute(stmt);
// clear respawn times if the map is already unloaded and won't do it by itself
if (!sMapMgr->FindMap(itr->second->GetMapId(), itr->second->GetInstanceId()))
Map::DeleteRespawnTimesInDB(itr->second->GetMapId(), itr->second->GetInstanceId());
delete itr->second;
m_instanceSaveById.erase(itr);
}
else
{
// delete character_instance per id where extended = 0, transtaction with set extended = 0, transaction is used to avoid mysql thread races
SQLTransaction trans = CharacterDatabase.BeginTransaction();
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_NOT_EXTENDED);
stmt->setUInt32(0, itr->second->GetInstanceId());
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_INSTANCE_SET_NOT_EXTENDED);
stmt->setUInt32(0, itr->second->GetInstanceId());
trans->Append(stmt);
CharacterDatabase.CommitTransaction(trans);
// update reset time and extended reset time for instance save
itr->second->SetResetTime(GetResetTimeFor(itr->second->GetMapId(), itr->second->GetDifficulty()));
itr->second->SetExtendedResetTime(GetExtendedResetTimeFor(itr->second->GetMapId(), itr->second->GetDifficulty()));
}
lock_instLists = false;
}
void InstanceSaveManager::_ResetOrWarnAll(uint32 mapid, Difficulty difficulty, bool warn, time_t resetTime)
{
// global reset for all instances of the given map
MapEntry const* mapEntry = sMapStore.LookupEntry(mapid);
if (!mapEntry->Instanceable())
return;
time_t now = time(NULL);
if (!warn)
{
MapDifficulty const* mapDiff = GetMapDifficultyData(mapid, difficulty);
if (!mapDiff || !mapDiff->resetTime)
{
sLog->outError("InstanceSaveManager::ResetOrWarnAll: not valid difficulty or no reset delay for map %d", mapid);
return;
}
// calculate the next reset time
uint32 diff = sWorld->getIntConfig(CONFIG_INSTANCE_RESET_TIME_HOUR) * HOUR;
uint32 period = mapDiff->resetTime;
if (period < DAY)
period = DAY;
uint32 next_reset = uint32(((resetTime + MINUTE) / DAY * DAY) + period + diff);
SetResetTimeFor(mapid, difficulty, next_reset);
SetExtendedResetTimeFor(mapid, difficulty, next_reset + period);
ScheduleReset(time_t(next_reset-3600), InstResetEvent(1, mapid, difficulty));
// update it in the DB
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GLOBAL_INSTANCE_RESETTIME);
stmt->setUInt32(0, next_reset);
stmt->setUInt16(1, uint16(mapid));
stmt->setUInt8(2, uint8(difficulty));
CharacterDatabase.Execute(stmt);
// remove all binds to instances of the given map and delete from db (delete per instance id, no mass deletion!)
// do this after new reset time is calculated
for (InstanceSaveHashMap::iterator itr = m_instanceSaveById.begin(), itr2; itr != m_instanceSaveById.end(); )
{
itr2 = itr++;
if (itr2->second->GetMapId() == mapid && itr2->second->GetDifficulty() == difficulty)
_ResetSave(itr2);
}
}
// now loop all existing maps to warn / reset
Map const* map = sMapMgr->CreateBaseMap(mapid);
MapInstanced::InstancedMaps &instMaps = ((MapInstanced*)map)->GetInstancedMaps();
MapInstanced::InstancedMaps::iterator mitr;
uint32 timeLeft;
for (mitr = instMaps.begin(); mitr != instMaps.end(); ++mitr)
{
Map* map2 = mitr->second;
if (!map2->IsDungeon() || map2->GetDifficulty() != difficulty)
continue;
if (warn)
{
if (now >= resetTime)
timeLeft = 0;
else
timeLeft = uint32(resetTime - now);
map2->ToInstanceMap()->SendResetWarnings(timeLeft);
}
else
{
InstanceSave* save = GetInstanceSave(map2->GetInstanceId());
map2->ToInstanceMap()->Reset(INSTANCE_RESET_GLOBAL, (save ? &(save->m_playerList) : NULL));
}
}
}
InstancePlayerBind* InstanceSaveManager::PlayerBindToInstance(uint32 guidLow, InstanceSave* save, bool permanent, Player* player /*= NULL*/)
{
InstancePlayerBind& bind = playerBindStorage[guidLow]->m[save->GetDifficulty()][save->GetMapId()];
ASSERT(!bind.perm || permanent); // ensure there's no changing permanent to temporary, this can be done only by unbinding
if (bind.save)
{
if (save != bind.save || permanent != bind.perm)
{
bind.extended = false;
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_INSTANCE);
stmt->setUInt32(0, save->GetInstanceId());
stmt->setBool(1, permanent);
stmt->setUInt32(2, guidLow);
stmt->setUInt32(3, bind.save->GetInstanceId());
CharacterDatabase.Execute(stmt);
}
}
else
{
// pussywizard: protect against mysql thread races!
// pussywizard: CHANGED MY MIND! DON'T SLOW DOWN THIS QUERY! HANDLE ONLY DURING LOADING FROM DB!
// example: enter instance -> bind -> update old id to new id -> exit -> delete new id
// if delete by new id is executed before update, then we end up with shit in db
/*SQLTransaction trans = CharacterDatabase.BeginTransaction();
// ensure any shit for that map+difficulty is deleted!
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_GUID_MAP_DIFF); // DELETE ci FROM character_instance ci JOIN instance i ON ci.instance = i.id WHERE ci.guid = ? AND i.map = ? AND i.difficulty = ?
stmt->setUInt32(0, guidLow);
stmt->setUInt16(1, uint16(save->GetMapId()));
stmt->setUInt8(2, uint8(save->GetDifficulty()));
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_INSTANCE);
stmt->setUInt32(0, guidLow);
stmt->setUInt32(1, save->GetInstanceId());
stmt->setBool(2, permanent);
trans->Append(stmt);
CharacterDatabase.CommitTransaction(trans);*/
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_INSTANCE);
stmt->setUInt32(0, guidLow);
stmt->setUInt32(1, save->GetInstanceId());
stmt->setBool(2, permanent);
CharacterDatabase.Execute(stmt);
}
if (bind.save != save)
{
if (bind.save)
bind.save->RemovePlayer(guidLow, this);
save->AddPlayer(guidLow);
}
if (permanent)
{
save->SetCanReset(false);
if (!bind.perm && player) // temporary changing to permanent
player->GetSession()->SendCalendarRaidLockout(save, true);
}
bind.save = save;
bind.perm = permanent;
if (player)
sScriptMgr->OnPlayerBindToInstance(player, save->GetDifficulty(), save->GetMapId(), permanent);
return &bind;
}
void InstanceSaveManager::PlayerUnbindInstance(uint32 guidLow, uint32 mapid, Difficulty difficulty, bool deleteFromDB, Player* player /*= NULL*/)
{
BoundInstancesMapWrapper* w = playerBindStorage[guidLow];
BoundInstancesMap::iterator itr = w->m[difficulty].find(mapid);
if (itr != w->m[difficulty].end())
{
if (deleteFromDB)
{
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_GUID);
stmt->setUInt32(0, guidLow);
stmt->setUInt32(1, itr->second.save->GetInstanceId());
CharacterDatabase.Execute(stmt);
}
if (itr->second.perm && player)
player->GetSession()->SendCalendarRaidLockout(itr->second.save, false);
InstanceSave* tmp = itr->second.save;
w->m[difficulty].erase(itr);
tmp->RemovePlayer(guidLow, this);
}
}
void InstanceSaveManager::PlayerUnbindInstanceNotExtended(uint32 guidLow, uint32 mapid, Difficulty difficulty, Player* player /*= NULL*/)
{
BoundInstancesMapWrapper* w = playerBindStorage[guidLow];
BoundInstancesMap::iterator itr = w->m[difficulty].find(mapid);
if (itr != w->m[difficulty].end())
{
if (itr->second.extended)
itr->second.extended = false;
else
{
if (itr->second.perm && player)
player->GetSession()->SendCalendarRaidLockout(itr->second.save, false);
InstanceSave* tmp = itr->second.save;
w->m[difficulty].erase(itr);
tmp->RemovePlayer(guidLow, this);
}
}
}
InstancePlayerBind* InstanceSaveManager::PlayerGetBoundInstance(uint32 guidLow, uint32 mapid, Difficulty difficulty)
{
Difficulty difficulty_fixed = (mapid == 631 || mapid == 724 ? Difficulty(difficulty%2) : difficulty);
MapDifficulty const* mapDiff = GetDownscaledMapDifficultyData(mapid, difficulty_fixed);
if (!mapDiff)
return NULL;
BoundInstancesMapWrapper* w = NULL;
PlayerBindStorage::const_iterator itr = playerBindStorage.find(guidLow);
if (itr != playerBindStorage.end())
w = itr->second;
else
return NULL;
BoundInstancesMap::iterator itr2 = w->m[difficulty_fixed].find(mapid);
if (itr2 != w->m[difficulty_fixed].end())
return &itr2->second;
else
return NULL;
}
bool InstanceSaveManager::PlayerIsPermBoundToInstance(uint32 guidLow, uint32 mapid, Difficulty difficulty)
{
if (InstancePlayerBind* bind = PlayerGetBoundInstance(guidLow, mapid, difficulty))
if (bind->perm)
return true;
return false;
}
BoundInstancesMap const& InstanceSaveManager::PlayerGetBoundInstances(uint32 guidLow, Difficulty difficulty)
{
PlayerBindStorage::iterator itr = playerBindStorage.find(guidLow);
if (itr != playerBindStorage.end())
return itr->second->m[difficulty];
return emptyBoundInstancesMap;
}
void InstanceSaveManager::PlayerCreateBoundInstancesMaps(uint32 guidLow)
{
if (playerBindStorage.find(guidLow) == playerBindStorage.end())
playerBindStorage[guidLow] = new BoundInstancesMapWrapper;
}
InstanceSave* InstanceSaveManager::PlayerGetInstanceSave(uint32 guidLow, uint32 mapid, Difficulty difficulty)
{
InstancePlayerBind* pBind = PlayerGetBoundInstance(guidLow, mapid, difficulty);
return (pBind ? pBind->save : NULL);
}
uint32 InstanceSaveManager::PlayerGetDestinationInstanceId(Player* player, uint32 mapid, Difficulty difficulty)
{
// returning 0 means a new instance will be created
// non-zero implicates that InstanceSave exists
InstancePlayerBind* ipb = PlayerGetBoundInstance(player->GetGUIDLow(), mapid, difficulty);
if (ipb && ipb->perm) // 1. self perm
return ipb->save->GetInstanceId();
if (Group* g = player->GetGroup())
{
if (InstancePlayerBind* ilb = PlayerGetBoundInstance(GUID_LOPART(g->GetLeaderGUID()), mapid, difficulty)) // 2. leader temp/perm
return ilb->save->GetInstanceId();
return 0; // 3. in group, no leader bind
}
return ipb ? ipb->save->GetInstanceId() : 0; // 4. self temp
}
void InstanceSaveManager::CopyBinds(uint32 from, uint32 to, Player* toPlr)
{
if (from == to)
return;
for (uint8 d = 0; d < MAX_DIFFICULTY; ++d)
{
BoundInstancesMap const& bi = PlayerGetBoundInstances(from, Difficulty(d));
for (BoundInstancesMap::const_iterator itr = bi.begin(); itr != bi.end(); ++itr)
if (!PlayerGetBoundInstance(to, itr->first, Difficulty(d)))
PlayerBindToInstance(to, itr->second.save, false, toPlr);
}
}
void InstanceSaveManager::UnbindAllFor(InstanceSave* save)
{
InstanceSave::PlayerListType &pList = save->m_playerList;
while (!pList.empty())
PlayerUnbindInstance(*(pList.begin()), save->GetMapId(), save->GetDifficulty(), true, ObjectAccessor::GetObjectInOrOutOfWorld(MAKE_NEW_GUID(*(pList.begin()), 0, HIGHGUID_PLAYER), (Player*)NULL));
}

View File

@@ -0,0 +1,204 @@
/*
* Copyright (C)
* Copyright (C)
*
* 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/>.
*/
#ifndef _INSTANCESAVEMGR_H
#define _INSTANCESAVEMGR_H
#include "Define.h"
#include <ace/Singleton.h>
#include <ace/Null_Mutex.h>
#include <ace/Thread_Mutex.h>
#include <list>
#include <map>
#include "UnorderedMap.h"
#include "DatabaseEnv.h"
#include "DBCEnums.h"
#include "ObjectDefines.h"
struct InstanceTemplate;
struct MapEntry;
class Player;
class Group;
class InstanceSaveManager;
struct InstancePlayerBind
{
InstanceSave* save;
bool perm : 1;
bool extended : 1;
InstancePlayerBind() : save(NULL), perm(false), extended(false) {}
};
typedef UNORDERED_MAP< uint32 /*mapId*/, InstancePlayerBind > BoundInstancesMap;
struct BoundInstancesMapWrapper
{
BoundInstancesMap m[MAX_DIFFICULTY];
};
typedef UNORDERED_MAP< uint32 /*guidLow*/, BoundInstancesMapWrapper* > PlayerBindStorage;
class InstanceSave
{
friend class InstanceSaveManager;
public:
InstanceSave(uint16 MapId, uint32 InstanceId, Difficulty difficulty, time_t resetTime, time_t extendedResetTime);
~InstanceSave();
uint32 GetInstanceId() const { return m_instanceid; }
uint32 GetMapId() const { return m_mapid; }
Difficulty GetDifficulty() const { return m_difficulty; }
/* Saved when the instance is generated for the first time */
void InsertToDB();
// pussywizard: deleting is done internally when there are no binds left
std::string GetInstanceData() const { return m_instanceData; }
void SetInstanceData(std::string str) { m_instanceData = str; }
uint32 GetCompletedEncounterMask() const { return m_completedEncounterMask; }
void SetCompletedEncounterMask(uint32 mask) { m_completedEncounterMask = mask; }
// pussywizard: for normal instances this corresponds to 0, for raid/heroic instances this caches the global reset time for the map
time_t GetResetTime() const { return m_resetTime; }
time_t GetExtendedResetTime() const { return m_extendedResetTime; }
time_t GetResetTimeForDB();
void SetResetTime(time_t resetTime) { m_resetTime = resetTime; }
void SetExtendedResetTime(time_t extendedResetTime) { m_extendedResetTime = extendedResetTime; }
bool CanReset() const { return m_canReset; }
void SetCanReset(bool canReset) { m_canReset = canReset; }
InstanceTemplate const* GetTemplate();
MapEntry const* GetMapEntry();
void AddPlayer(uint32 guidLow);
bool RemovePlayer(uint32 guidLow, InstanceSaveManager* ism);
typedef std::list<uint32> PlayerListType;
private:
PlayerListType m_playerList;
time_t m_resetTime;
time_t m_extendedResetTime;
uint32 m_instanceid;
uint32 m_mapid;
Difficulty m_difficulty;
bool m_canReset;
std::string m_instanceData;
uint32 m_completedEncounterMask;
ACE_Thread_Mutex _lock;
};
typedef UNORDERED_MAP<uint32 /*PAIR32(map, difficulty)*/, time_t /*resetTime*/> ResetTimeByMapDifficultyMap;
class InstanceSaveManager
{
friend class ACE_Singleton<InstanceSaveManager, ACE_Thread_Mutex>;
friend class InstanceSave;
private:
InstanceSaveManager() : lock_instLists(false) {};
~InstanceSaveManager();
public:
typedef UNORDERED_MAP<uint32 /*InstanceId*/, InstanceSave*> InstanceSaveHashMap;
struct InstResetEvent
{
uint8 type; // 0 - unused, 1-4 warnings about pending reset, 5 - reset
Difficulty difficulty:8;
uint16 mapid;
InstResetEvent() : type(0), difficulty(DUNGEON_DIFFICULTY_NORMAL), mapid(0) {}
InstResetEvent(uint8 t, uint32 _mapid, Difficulty d)
: type(t), difficulty(d), mapid(_mapid) {}
};
typedef std::multimap<time_t /*resetTime*/, InstResetEvent> ResetTimeQueue;
void LoadInstances();
void LoadResetTimes();
void LoadInstanceSaves();
void LoadCharacterBinds();
time_t GetResetTimeFor(uint32 mapid, Difficulty d) const
{
ResetTimeByMapDifficultyMap::const_iterator itr = m_resetTimeByMapDifficulty.find(MAKE_PAIR32(mapid, d));
return itr != m_resetTimeByMapDifficulty.end() ? itr->second : 0;
}
time_t GetExtendedResetTimeFor(uint32 mapid, Difficulty d) const
{
ResetTimeByMapDifficultyMap::const_iterator itr = m_resetExtendedTimeByMapDifficulty.find(MAKE_PAIR32(mapid, d));
return itr != m_resetExtendedTimeByMapDifficulty.end() ? itr->second : 0;
}
void SetResetTimeFor(uint32 mapid, Difficulty d, time_t t)
{
m_resetTimeByMapDifficulty[MAKE_PAIR32(mapid, d)] = t;
}
void SetExtendedResetTimeFor(uint32 mapid, Difficulty d, time_t t)
{
m_resetExtendedTimeByMapDifficulty[MAKE_PAIR32(mapid, d)] = t;
}
ResetTimeByMapDifficultyMap const& GetResetTimeMap() const
{
return m_resetTimeByMapDifficulty;
}
void ScheduleReset(time_t time, InstResetEvent event);
void Update();
InstanceSave* AddInstanceSave(uint32 mapId, uint32 instanceId, Difficulty difficulty, bool startup = false);
bool DeleteInstanceSaveIfNeeded(uint32 InstanceId, bool skipMapCheck);
bool DeleteInstanceSaveIfNeeded(InstanceSave* save, bool skipMapCheck);
InstanceSave* GetInstanceSave(uint32 InstanceId);
InstancePlayerBind* PlayerBindToInstance(uint32 guidLow, InstanceSave* save, bool permanent, Player* player = NULL);
void PlayerUnbindInstance(uint32 guidLow, uint32 mapid, Difficulty difficulty, bool deleteFromDB, Player* player = NULL);
void PlayerUnbindInstanceNotExtended(uint32 guidLow, uint32 mapid, Difficulty difficulty, Player* player = NULL);
InstancePlayerBind* PlayerGetBoundInstance(uint32 guidLow, uint32 mapid, Difficulty difficulty);
bool PlayerIsPermBoundToInstance(uint32 guidLow, uint32 mapid, Difficulty difficulty);
BoundInstancesMap const& PlayerGetBoundInstances(uint32 guidLow, Difficulty difficulty);
void PlayerCreateBoundInstancesMaps(uint32 guidLow);
InstanceSave* PlayerGetInstanceSave(uint32 guidLow, uint32 mapid, Difficulty difficulty);
uint32 PlayerGetDestinationInstanceId(Player* player, uint32 mapid, Difficulty difficulty);
void CopyBinds(uint32 from, uint32 to, Player* toPlr = NULL);
void UnbindAllFor(InstanceSave* save);
protected:
static uint16 ResetTimeDelay[];
static PlayerBindStorage playerBindStorage;
static BoundInstancesMap emptyBoundInstancesMap;
private:
void _ResetOrWarnAll(uint32 mapid, Difficulty difficulty, bool warn, time_t resetTime);
void _ResetSave(InstanceSaveHashMap::iterator &itr);
bool lock_instLists;
InstanceSaveHashMap m_instanceSaveById;
ResetTimeByMapDifficultyMap m_resetTimeByMapDifficulty;
ResetTimeByMapDifficultyMap m_resetExtendedTimeByMapDifficulty;
ResetTimeQueue m_resetTimeQueue;
};
#define sInstanceSaveMgr ACE_Singleton<InstanceSaveManager, ACE_Thread_Mutex>::instance()
#endif

View File

@@ -0,0 +1,448 @@
/*
* Copyright (C)
* Copyright (C)
*
* 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/>.
*/
#include "Creature.h"
#include "CreatureAI.h"
#include "DatabaseEnv.h"
#include "GameObject.h"
#include "Group.h"
#include "InstanceScript.h"
#include "LFGMgr.h"
#include "Log.h"
#include "Map.h"
#include "Player.h"
#include "Pet.h"
#include "WorldSession.h"
#include "Opcodes.h"
#include "Spell.h"
void InstanceScript::SaveToDB()
{
std::string data = GetSaveData();
//if (data.empty()) // pussywizard: encounterMask can be updated and theres no reason to not save
// return;
// pussywizard:
InstanceSave* save = sInstanceSaveMgr->GetInstanceSave(instance->GetInstanceId());
if (save)
save->SetInstanceData(data);
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_INSTANCE_SAVE_DATA);
stmt->setString(0, data);
stmt->setUInt32(1, instance->GetInstanceId());
CharacterDatabase.Execute(stmt);
}
void InstanceScript::HandleGameObject(uint64 GUID, bool open, GameObject* go)
{
if (!go)
go = instance->GetGameObject(GUID);
if (go)
go->SetGoState(open ? GO_STATE_ACTIVE : GO_STATE_READY);
else
;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: InstanceScript: HandleGameObject failed");
}
bool InstanceScript::IsEncounterInProgress() const
{
for (std::vector<BossInfo>::const_iterator itr = bosses.begin(); itr != bosses.end(); ++itr)
if (itr->state == IN_PROGRESS)
return true;
return false;
}
void InstanceScript::LoadMinionData(const MinionData* data)
{
while (data->entry)
{
if (data->bossId < bosses.size())
minions.insert(std::make_pair(data->entry, MinionInfo(&bosses[data->bossId])));
++data;
}
;//sLog->outDebug(LOG_FILTER_TSCR, "InstanceScript::LoadMinionData: " UI64FMTD " minions loaded.", uint64(minions.size()));
}
void InstanceScript::LoadDoorData(const DoorData* data)
{
while (data->entry)
{
if (data->bossId < bosses.size())
doors.insert(std::make_pair(data->entry, DoorInfo(&bosses[data->bossId], data->type, BoundaryType(data->boundary))));
++data;
}
;//sLog->outDebug(LOG_FILTER_TSCR, "InstanceScript::LoadDoorData: " UI64FMTD " doors loaded.", uint64(doors.size()));
}
void InstanceScript::UpdateMinionState(Creature* minion, EncounterState state)
{
switch (state)
{
case NOT_STARTED:
if (!minion->IsAlive())
minion->Respawn();
else if (minion->IsInCombat())
minion->AI()->EnterEvadeMode();
break;
case IN_PROGRESS:
if (!minion->IsAlive())
minion->Respawn();
else if (!minion->GetVictim())
minion->AI()->DoZoneInCombat(NULL, 100.0f);
break;
default:
break;
}
}
void InstanceScript::UpdateDoorState(GameObject* door)
{
DoorInfoMapBounds range = doors.equal_range(door->GetEntry());
if (range.first == range.second)
return;
// xinef: doors can be assigned to few bosses, if any of them demands doors closed - they should be closed (added & operator for assigment)
bool open = true;
for (; range.first != range.second && open; ++range.first)
{
DoorInfo const& info = range.first->second;
switch (info.type)
{
case DOOR_TYPE_ROOM:
open &= (info.bossInfo->state != IN_PROGRESS) ? true : false;
break;
case DOOR_TYPE_PASSAGE:
open &= (info.bossInfo->state == DONE) ? true : false;
break;
case DOOR_TYPE_SPAWN_HOLE:
open &= (info.bossInfo->state == IN_PROGRESS) ? true : false;
break;
default:
break;
}
}
door->SetGoState(open ? GO_STATE_ACTIVE : GO_STATE_READY);
}
void InstanceScript::AddDoor(GameObject* door, bool add)
{
DoorInfoMapBounds range = doors.equal_range(door->GetEntry());
if (range.first == range.second)
return;
for (; range.first != range.second; ++range.first)
{
DoorInfo const& data = range.first->second;
if (add)
{
data.bossInfo->door[data.type].insert(door);
switch (data.boundary)
{
default:
case BOUNDARY_NONE:
break;
case BOUNDARY_N:
case BOUNDARY_S:
data.bossInfo->boundary[data.boundary] = door->GetPositionX();
break;
case BOUNDARY_E:
case BOUNDARY_W:
data.bossInfo->boundary[data.boundary] = door->GetPositionY();
break;
case BOUNDARY_NW:
case BOUNDARY_SE:
data.bossInfo->boundary[data.boundary] = door->GetPositionX() + door->GetPositionY();
break;
case BOUNDARY_NE:
case BOUNDARY_SW:
data.bossInfo->boundary[data.boundary] = door->GetPositionX() - door->GetPositionY();
break;
}
}
else
data.bossInfo->door[data.type].erase(door);
}
if (add)
UpdateDoorState(door);
}
void InstanceScript::AddMinion(Creature* minion, bool add)
{
MinionInfoMap::iterator itr = minions.find(minion->GetEntry());
if (itr == minions.end())
return;
if (add)
itr->second.bossInfo->minion.insert(minion);
else
itr->second.bossInfo->minion.erase(minion);
}
bool InstanceScript::SetBossState(uint32 id, EncounterState state)
{
if (id < bosses.size())
{
BossInfo* bossInfo = &bosses[id];
if (bossInfo->state == TO_BE_DECIDED) // loading
{
bossInfo->state = state;
//sLog->outError("Inialize boss %u state as %u.", id, (uint32)state);
return false;
}
else
{
if (bossInfo->state == state)
return false;
if (state == DONE)
for (MinionSet::iterator i = bossInfo->minion.begin(); i != bossInfo->minion.end(); ++i)
if ((*i)->isWorldBoss() && (*i)->IsAlive())
return false;
bossInfo->state = state;
SaveToDB();
}
for (uint32 type = 0; type < MAX_DOOR_TYPES; ++type)
for (DoorSet::iterator i = bossInfo->door[type].begin(); i != bossInfo->door[type].end(); ++i)
UpdateDoorState(*i);
for (MinionSet::iterator i = bossInfo->minion.begin(); i != bossInfo->minion.end(); ++i)
UpdateMinionState(*i, state);
return true;
}
return false;
}
std::string InstanceScript::LoadBossState(const char * data)
{
if (!data)
return NULL;
std::istringstream loadStream(data);
uint32 buff;
uint32 bossId = 0;
for (std::vector<BossInfo>::iterator i = bosses.begin(); i != bosses.end(); ++i, ++bossId)
{
loadStream >> buff;
if (buff < TO_BE_DECIDED)
SetBossState(bossId, (EncounterState)buff);
}
return loadStream.str();
}
std::string InstanceScript::GetBossSaveData()
{
std::ostringstream saveStream;
for (std::vector<BossInfo>::iterator i = bosses.begin(); i != bosses.end(); ++i)
saveStream << (uint32)i->state << ' ';
return saveStream.str();
}
void InstanceScript::DoUseDoorOrButton(uint64 uiGuid, uint32 uiWithRestoreTime, bool bUseAlternativeState)
{
if (!uiGuid)
return;
GameObject* go = instance->GetGameObject(uiGuid);
if (go)
{
if (go->GetGoType() == GAMEOBJECT_TYPE_DOOR || go->GetGoType() == GAMEOBJECT_TYPE_BUTTON)
{
if (go->getLootState() == GO_READY)
go->UseDoorOrButton(uiWithRestoreTime, bUseAlternativeState);
else if (go->getLootState() == GO_ACTIVATED)
go->ResetDoorOrButton();
}
else
sLog->outError("SD2: Script call DoUseDoorOrButton, but gameobject entry %u is type %u.", go->GetEntry(), go->GetGoType());
}
}
void InstanceScript::DoRespawnGameObject(uint64 uiGuid, uint32 uiTimeToDespawn)
{
if (GameObject* go = instance->GetGameObject(uiGuid))
{
//not expect any of these should ever be handled
if (go->GetGoType() == GAMEOBJECT_TYPE_FISHINGNODE || go->GetGoType() == GAMEOBJECT_TYPE_DOOR ||
go->GetGoType() == GAMEOBJECT_TYPE_BUTTON || go->GetGoType() == GAMEOBJECT_TYPE_TRAP)
return;
if (go->isSpawned())
return;
go->SetRespawnTime(uiTimeToDespawn);
}
}
void InstanceScript::DoUpdateWorldState(uint32 uiStateId, uint32 uiStateData)
{
Map::PlayerList const& lPlayers = instance->GetPlayers();
if (!lPlayers.isEmpty())
{
for (Map::PlayerList::const_iterator itr = lPlayers.begin(); itr != lPlayers.end(); ++itr)
if (Player* player = itr->GetSource())
player->SendUpdateWorldState(uiStateId, uiStateData);
}
else
;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: DoUpdateWorldState attempt send data but no players in map.");
}
// Send Notify to all players in instance
void InstanceScript::DoSendNotifyToInstance(char const* format, ...)
{
InstanceMap::PlayerList const& players = instance->GetPlayers();
if (!players.isEmpty())
{
va_list ap;
va_start(ap, format);
char buff[1024];
vsnprintf(buff, 1024, format, ap);
va_end(ap);
for (Map::PlayerList::const_iterator i = players.begin(); i != players.end(); ++i)
if (Player* player = i->GetSource())
player->GetSession()->SendNotification("%s", buff);
}
}
// Update Achievement Criteria for all players in instance
void InstanceScript::DoUpdateAchievementCriteria(AchievementCriteriaTypes type, uint32 miscValue1 /*= 0*/, uint32 miscValue2 /*= 0*/, Unit* unit /*= NULL*/)
{
Map::PlayerList const &PlayerList = instance->GetPlayers();
if (!PlayerList.isEmpty())
for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i)
if (Player* player = i->GetSource())
player->UpdateAchievementCriteria(type, miscValue1, miscValue2, unit);
}
// Start timed achievement for all players in instance
void InstanceScript::DoStartTimedAchievement(AchievementCriteriaTimedTypes type, uint32 entry)
{
Map::PlayerList const &PlayerList = instance->GetPlayers();
if (!PlayerList.isEmpty())
for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i)
if (Player* player = i->GetSource())
player->StartTimedAchievement(type, entry);
}
// Stop timed achievement for all players in instance
void InstanceScript::DoStopTimedAchievement(AchievementCriteriaTimedTypes type, uint32 entry)
{
Map::PlayerList const &PlayerList = instance->GetPlayers();
if (!PlayerList.isEmpty())
for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i)
if (Player* player = i->GetSource())
player->RemoveTimedAchievement(type, entry);
}
// Remove Auras due to Spell on all players in instance
void InstanceScript::DoRemoveAurasDueToSpellOnPlayers(uint32 spell)
{
Map::PlayerList const& PlayerList = instance->GetPlayers();
if (!PlayerList.isEmpty())
{
for (Map::PlayerList::const_iterator itr = PlayerList.begin(); itr != PlayerList.end(); ++itr)
{
if (Player* player = itr->GetSource())
{
player->RemoveAurasDueToSpell(spell);
if (Pet* pet = player->GetPet())
pet->RemoveAurasDueToSpell(spell);
}
}
}
}
// Cast spell on all players in instance
void InstanceScript::DoCastSpellOnPlayers(uint32 spell)
{
Map::PlayerList const &PlayerList = instance->GetPlayers();
if (!PlayerList.isEmpty())
for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i)
if (Player* player = i->GetSource())
player->CastSpell(player, spell, true);
}
bool InstanceScript::CheckAchievementCriteriaMeet(uint32 criteria_id, Player const* /*source*/, Unit const* /*target*/ /*= NULL*/, uint32 /*miscvalue1*/ /*= 0*/)
{
sLog->outError("Achievement system call InstanceScript::CheckAchievementCriteriaMeet but instance script for map %u not have implementation for achievement criteria %u",
instance->GetId(), criteria_id);
return false;
}
void InstanceScript::SetCompletedEncountersMask(uint32 newMask, bool save)
{
if (completedEncounters == newMask)
return;
completedEncounters = newMask;
// pussywizard:
if (save)
{
InstanceSave* iSave = sInstanceSaveMgr->GetInstanceSave(instance->GetInstanceId());
if (iSave)
iSave->SetCompletedEncounterMask(completedEncounters);
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_INSTANCE_SAVE_ENCOUNTERMASK);
stmt->setUInt32(0, completedEncounters);
stmt->setUInt32(1, instance->GetInstanceId());
CharacterDatabase.Execute(stmt);
}
}
void InstanceScript::SendEncounterUnit(uint32 type, Unit* unit /*= NULL*/, uint8 param1 /*= 0*/, uint8 param2 /*= 0*/)
{
// size of this packet is at most 15 (usually less)
WorldPacket data(SMSG_UPDATE_INSTANCE_ENCOUNTER_UNIT, 15);
data << uint32(type);
switch (type)
{
case ENCOUNTER_FRAME_ENGAGE:
case ENCOUNTER_FRAME_DISENGAGE:
case ENCOUNTER_FRAME_UPDATE_PRIORITY:
data.append(unit->GetPackGUID());
data << uint8(param1);
break;
case ENCOUNTER_FRAME_ADD_TIMER:
case ENCOUNTER_FRAME_ENABLE_OBJECTIVE:
case ENCOUNTER_FRAME_DISABLE_OBJECTIVE:
data << uint8(param1);
break;
case ENCOUNTER_FRAME_UPDATE_OBJECTIVE:
data << uint8(param1);
data << uint8(param2);
break;
case ENCOUNTER_FRAME_REFRESH_FRAMES:
default:
break;
}
instance->SendToPlayers(&data);
}

View File

@@ -0,0 +1,257 @@
/*
* Copyright (C)
* Copyright (C)
*
* 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/>.
*/
#ifndef TRINITY_INSTANCE_DATA_H
#define TRINITY_INSTANCE_DATA_H
#include "ZoneScript.h"
#include "World.h"
#include "ObjectMgr.h"
//#include "GameObject.h"
//#include "Map.h"
#define OUT_SAVE_INST_DATA ;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: Saving Instance Data for Instance %s (Map %d, Instance Id %d)", instance->GetMapName(), instance->GetId(), instance->GetInstanceId())
#define OUT_SAVE_INST_DATA_COMPLETE ;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: Saving Instance Data for Instance %s (Map %d, Instance Id %d) completed.", instance->GetMapName(), instance->GetId(), instance->GetInstanceId())
#define OUT_LOAD_INST_DATA(a) ;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: Loading Instance Data for Instance %s (Map %d, Instance Id %d). Input is '%s'", instance->GetMapName(), instance->GetId(), instance->GetInstanceId(), a)
#define OUT_LOAD_INST_DATA_COMPLETE ;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: Instance Data Load for Instance %s (Map %d, Instance Id: %d) is complete.", instance->GetMapName(), instance->GetId(), instance->GetInstanceId())
#define OUT_LOAD_INST_DATA_FAIL sLog->outError("TSCR: Unable to load Instance Data for Instance %s (Map %d, Instance Id: %d).", instance->GetMapName(), instance->GetId(), instance->GetInstanceId())
class Map;
class Unit;
class Player;
class GameObject;
class Creature;
typedef std::set<GameObject*> DoorSet;
typedef std::set<Creature*> MinionSet;
enum EncounterFrameType
{
ENCOUNTER_FRAME_ENGAGE = 0,
ENCOUNTER_FRAME_DISENGAGE = 1,
ENCOUNTER_FRAME_UPDATE_PRIORITY = 2,
ENCOUNTER_FRAME_ADD_TIMER = 3,
ENCOUNTER_FRAME_ENABLE_OBJECTIVE = 4,
ENCOUNTER_FRAME_UPDATE_OBJECTIVE = 5,
ENCOUNTER_FRAME_DISABLE_OBJECTIVE = 6,
ENCOUNTER_FRAME_REFRESH_FRAMES = 7, // Xinef: can be used to refresh frames after unit was destroyed from client and send back (phase changes)
};
enum EncounterState
{
NOT_STARTED = 0,
IN_PROGRESS = 1,
FAIL = 2,
DONE = 3,
SPECIAL = 4,
TO_BE_DECIDED = 5,
};
enum DoorType
{
DOOR_TYPE_ROOM = 0, // Door can open if encounter is not in progress
DOOR_TYPE_PASSAGE = 1, // Door can open if encounter is done
DOOR_TYPE_SPAWN_HOLE = 2, // Door can open if encounter is in progress, typically used for spawning places
MAX_DOOR_TYPES,
};
enum BoundaryType
{
BOUNDARY_NONE = 0,
BOUNDARY_N,
BOUNDARY_S,
BOUNDARY_E,
BOUNDARY_W,
BOUNDARY_NE,
BOUNDARY_NW,
BOUNDARY_SE,
BOUNDARY_SW,
BOUNDARY_MAX_X = BOUNDARY_N,
BOUNDARY_MIN_X = BOUNDARY_S,
BOUNDARY_MAX_Y = BOUNDARY_W,
BOUNDARY_MIN_Y = BOUNDARY_E,
};
typedef std::map<BoundaryType, float> BossBoundaryMap;
struct DoorData
{
uint32 entry, bossId;
DoorType type;
uint32 boundary;
};
struct MinionData
{
uint32 entry, bossId;
};
struct BossInfo
{
BossInfo() : state(TO_BE_DECIDED) {}
EncounterState state;
DoorSet door[MAX_DOOR_TYPES];
MinionSet minion;
BossBoundaryMap boundary;
};
struct DoorInfo
{
explicit DoorInfo(BossInfo* _bossInfo, DoorType _type, BoundaryType _boundary)
: bossInfo(_bossInfo), type(_type), boundary(_boundary) {}
BossInfo* bossInfo;
DoorType type;
BoundaryType boundary;
};
struct MinionInfo
{
explicit MinionInfo(BossInfo* _bossInfo) : bossInfo(_bossInfo) {}
BossInfo* bossInfo;
};
typedef std::multimap<uint32 /*entry*/, DoorInfo> DoorInfoMap;
typedef std::pair<DoorInfoMap::const_iterator, DoorInfoMap::const_iterator> DoorInfoMapBounds;
typedef std::map<uint32 /*entry*/, MinionInfo> MinionInfoMap;
class InstanceScript : public ZoneScript
{
public:
explicit InstanceScript(Map* map) : instance(map), completedEncounters(0) {}
virtual ~InstanceScript() {}
Map* instance;
//On creation, NOT load.
virtual void Initialize() {}
//On load
virtual void Load(char const* data) { LoadBossState(data); }
//When save is needed, this function generates the data
virtual std::string GetSaveData() { return GetBossSaveData(); }
void SaveToDB();
virtual void Update(uint32 /*diff*/) {}
//Used by the map's CanEnter function.
//This is to prevent players from entering during boss encounters.
virtual bool IsEncounterInProgress() const;
//Called when a player successfully enters the instance.
virtual void OnPlayerEnter(Player* /*player*/) {}
virtual void OnPlayerAreaUpdate(Player* /*player*/, uint32 /*oldArea*/, uint32 /*newArea*/) {}
//Handle open / close objects
//use HandleGameObject(0, boolen, GO); in OnObjectCreate in instance scripts
//use HandleGameObject(GUID, boolen, NULL); in any other script
void HandleGameObject(uint64 guid, bool open, GameObject* go = NULL);
//change active state of doors or buttons
void DoUseDoorOrButton(uint64 guid, uint32 withRestoreTime = 0, bool useAlternativeState = false);
//Respawns a GO having negative spawntimesecs in gameobject-table
void DoRespawnGameObject(uint64 guid, uint32 timeToDespawn = MINUTE);
//sends world state update to all players in instance
void DoUpdateWorldState(uint32 worldstateId, uint32 worldstateValue);
// Send Notify to all players in instance
void DoSendNotifyToInstance(char const* format, ...);
// Update Achievement Criteria for all players in instance
void DoUpdateAchievementCriteria(AchievementCriteriaTypes type, uint32 miscValue1 = 0, uint32 miscValue2 = 0, Unit* unit = NULL);
// Start/Stop Timed Achievement Criteria for all players in instance
void DoStartTimedAchievement(AchievementCriteriaTimedTypes type, uint32 entry);
void DoStopTimedAchievement(AchievementCriteriaTimedTypes type, uint32 entry);
// Remove Auras due to Spell on all players in instance
void DoRemoveAurasDueToSpellOnPlayers(uint32 spell);
// Cast spell on all players in instance
void DoCastSpellOnPlayers(uint32 spell);
virtual bool SetBossState(uint32 id, EncounterState state);
EncounterState GetBossState(uint32 id) const { return id < bosses.size() ? bosses[id].state : TO_BE_DECIDED; }
BossBoundaryMap const* GetBossBoundary(uint32 id) const { return id < bosses.size() ? &bosses[id].boundary : NULL; }
BossInfo const* GetBossInfo(uint32 id) const { return &bosses[id]; }
// Achievement criteria additional requirements check
// NOTE: not use this if same can be checked existed requirement types from AchievementCriteriaRequirementType
virtual bool CheckAchievementCriteriaMeet(uint32 /*criteria_id*/, Player const* /*source*/, Unit const* /*target*/ = NULL, uint32 /*miscvalue1*/ = 0);
// Checks boss requirements (one boss required to kill other)
virtual bool CheckRequiredBosses(uint32 /*bossId*/, Player const* /*player*/ = NULL) const { return true; }
void SetCompletedEncountersMask(uint32 newMask, bool save);
// Returns completed encounters mask for packets
uint32 GetCompletedEncounterMask() const { return completedEncounters; }
void SendEncounterUnit(uint32 type, Unit* unit = NULL, uint8 param1 = 0, uint8 param2 = 0);
virtual void FillInitialWorldStates(WorldPacket& /*data*/) {}
protected:
void SetBossNumber(uint32 number) { bosses.resize(number); }
void LoadDoorData(DoorData const* data);
void LoadMinionData(MinionData const* data);
void AddDoor(GameObject* door, bool add);
void AddMinion(Creature* minion, bool add);
void UpdateDoorState(GameObject* door);
void UpdateMinionState(Creature* minion, EncounterState state);
std::string LoadBossState(char const* data);
std::string GetBossSaveData();
private:
std::vector<BossInfo> bosses;
DoorInfoMap doors;
MinionInfoMap minions;
uint32 completedEncounters; // completed encounter mask, bit indexes are DungeonEncounter.dbc boss numbers, used for packets
};
template<class AI, class T>
AI* GetInstanceAI(T* obj, char const* scriptName)
{
if (InstanceMap* instance = obj->GetMap()->ToInstanceMap())
if (instance->GetInstanceScript())
if (instance->GetScriptId() == sObjectMgr->GetScriptId(scriptName))
return new AI(obj);
return NULL;
};
template<class AI, class T>
AI* GetInstanceAI(T* obj)
{
if (InstanceMap* instance = obj->GetMap()->ToInstanceMap())
if (instance->GetInstanceScript())
return new AI(obj);
return NULL;
};
#endif