aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorShauren <shauren.trinity@gmail.com>2018-07-22 13:22:35 +0200
committerShauren <shauren.trinity@gmail.com>2022-10-04 00:19:38 +0200
commit17665c929c3a9fb7fe75dd680648129bc1c1f874 (patch)
treea489cb742b7c5f3d7850d26157b3ac480aa00633 /src
parentad2df01b2c25ca6264096b8b8324dc8136ebd48b (diff)
Core/Instances: Instance lock rewrite (WIP)
Diffstat (limited to 'src')
-rw-r--r--src/server/game/DataStores/DB2Structure.h9
-rw-r--r--src/server/game/DataStores/DBCEnums.h45
-rw-r--r--src/server/game/Entities/Player/Player.cpp90
-rw-r--r--src/server/game/Entities/Player/Player.h17
-rw-r--r--src/server/game/Entities/Unit/Unit.cpp25
-rw-r--r--src/server/game/Groups/Group.h19
-rw-r--r--src/server/game/Handlers/CalendarHandler.cpp86
-rw-r--r--src/server/game/Handlers/MiscHandler.cpp17
-rw-r--r--src/server/game/Instances/InstanceLockMgr.cpp475
-rw-r--r--src/server/game/Instances/InstanceLockMgr.h285
-rw-r--r--src/server/game/Instances/InstanceScript.cpp57
-rw-r--r--src/server/game/Instances/InstanceScript.h36
-rw-r--r--src/server/game/Maps/Map.cpp256
-rw-r--r--src/server/game/Maps/Map.h13
-rw-r--r--src/server/game/Maps/MapManager.cpp113
-rw-r--r--src/server/game/Maps/MapManager.h7
-rw-r--r--src/server/game/Server/Packets/CalendarPackets.cpp2
-rw-r--r--src/server/game/Server/Packets/CalendarPackets.h2
-rw-r--r--src/server/game/Server/WorldSession.h5
-rw-r--r--src/server/game/World/World.cpp3
-rw-r--r--src/server/scripts/Commands/cs_misc.cpp13
-rw-r--r--src/server/scripts/Kalimdor/CavernsOfTime/CullingOfStratholme/boss_mal_ganis.cpp4
-rw-r--r--src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp17
-rw-r--r--src/server/worldserver/Main.cpp2
-rw-r--r--src/server/worldserver/worldserver.conf.dist1
25 files changed, 1192 insertions, 407 deletions
diff --git a/src/server/game/DataStores/DB2Structure.h b/src/server/game/DataStores/DB2Structure.h
index e7b4578dac9..e200bbba3d8 100644
--- a/src/server/game/DataStores/DB2Structure.h
+++ b/src/server/game/DataStores/DB2Structure.h
@@ -2555,6 +2555,7 @@ struct MapEntry
}
bool IsDynamicDifficultyMap() const { return (Flags[0] & MAP_FLAG_CAN_TOGGLE_DIFFICULTY) != 0; }
+ bool IsFlexLocking() const { return (Flags[0] & MAP_FLAG_FLEX_LOCKING) != 0; }
bool IsGarrison() const { return (Flags[0] & MAP_FLAG_GARRISON) != 0; }
bool IsSplitByFaction() const { return ID == 609 || ID == 2175; }
};
@@ -2584,11 +2585,15 @@ struct MapDifficultyEntry
int32 ContentTuningID;
uint32 MapID;
+ bool HasResetSchedule() const { return ResetInterval != MAP_DIFFICULTY_RESET_ANYTIME; }
+ bool IsUsingEncounterLocks() const { return (Flags & MAP_DIFFICULTY_FLAG_LOCK_TO_ENCOUNTER) != 0; }
+ bool IsExtendable() const { return (Flags & MAP_DIFFICULTY_FLAG_CANNOT_EXTEND) == 0; }
+
uint32 GetRaidDuration() const
{
- if (ResetInterval == 1)
+ if (ResetInterval == MAP_DIFFICULTY_RESET_DAILY)
return 86400;
- if (ResetInterval == 2)
+ if (ResetInterval == MAP_DIFFICULTY_RESET_WEEKLY)
return 604800;
return 0;
}
diff --git a/src/server/game/DataStores/DBCEnums.h b/src/server/game/DataStores/DBCEnums.h
index 88561e25be2..64161bb3334 100644
--- a/src/server/game/DataStores/DBCEnums.h
+++ b/src/server/game/DataStores/DBCEnums.h
@@ -781,24 +781,6 @@ enum class GlobalCurve : int32
#define MAX_ITEM_PROTO_SOCKETS 3
#define MAX_ITEM_PROTO_STATS 10
-enum MapTypes // Lua_IsInInstance
-{
- MAP_COMMON = 0, // none
- MAP_INSTANCE = 1, // party
- MAP_RAID = 2, // raid
- MAP_BATTLEGROUND = 3, // pvp
- MAP_ARENA = 4, // arena
- MAP_SCENARIO = 5 // scenario
-};
-
-enum MapFlags
-{
- MAP_FLAG_CAN_TOGGLE_DIFFICULTY = 0x0100,
- MAP_FLAG_FLEX_LOCKING = 0x8000, // All difficulties share completed encounters lock, not bound to a single instance id
- // heroic difficulty flag overrides it and uses instance id bind
- MAP_FLAG_GARRISON = 0x4000000
-};
-
enum GlyphSlotType
{
GLYPH_SLOT_MAJOR = 0,
@@ -1019,9 +1001,34 @@ enum ItemSpecStat
ITEM_SPEC_STAT_NONE = 40
};
+enum MapTypes // Lua_IsInInstance
+{
+ MAP_COMMON = 0, // none
+ MAP_INSTANCE = 1, // party
+ MAP_RAID = 2, // raid
+ MAP_BATTLEGROUND = 3, // pvp
+ MAP_ARENA = 4, // arena
+ MAP_SCENARIO = 5 // scenario
+};
+
+enum MapFlags
+{
+ MAP_FLAG_CAN_TOGGLE_DIFFICULTY = 0x00000100,
+ MAP_FLAG_FLEX_LOCKING = 0x00008000,
+ MAP_FLAG_GARRISON = 0x04000000
+};
+
enum MapDifficultyFlags : uint8
{
- MAP_DIFFICULTY_FLAG_CANNOT_EXTEND = 0x10
+ MAP_DIFFICULTY_FLAG_LOCK_TO_ENCOUNTER = 0x02, // Lock to single encounters
+ MAP_DIFFICULTY_FLAG_CANNOT_EXTEND = 0x10
+};
+
+enum MapDifficultyResetInterval : uint8
+{
+ MAP_DIFFICULTY_RESET_ANYTIME = 0,
+ MAP_DIFFICULTY_RESET_DAILY = 1,
+ MAP_DIFFICULTY_RESET_WEEKLY = 2
};
enum class ModifierTreeType : int32
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index e4a985aa4c8..5b4d2f42231 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -66,6 +66,7 @@
#include "GameTime.h"
#include "Guild.h"
#include "GuildMgr.h"
+#include "InstanceLockMgr.h"
#include "InstancePackets.h"
#include "InstanceSaveMgr.h"
#include "InstanceScript.h"
@@ -265,7 +266,6 @@ Player::Player(WorldSession* session) : Unit(true), m_sceneMgr(this)
m_dungeonDifficulty = DIFFICULTY_NORMAL;
m_raidDifficulty = DIFFICULTY_NORMAL_RAID;
m_legacyRaidDifficulty = DIFFICULTY_10_N;
- m_prevMapDifficulty = DIFFICULTY_NORMAL_RAID;
m_lastPotionId = 0;
@@ -1176,7 +1176,7 @@ void Player::Update(uint32 p_time)
{
// Player left the instance
if (_pendingBindId == GetInstanceId())
- BindToInstance();
+ ConfirmPendingBind();
SetPendingBind(0, 0);
}
else
@@ -4521,8 +4521,8 @@ Corpse* Player::CreateCorpse()
corpse->UpdatePositionData();
corpse->SetZoneScript();
- // we do not need to save corpses for BG/arenas
- if (!GetMap()->IsBattlegroundOrArena())
+ // we do not need to save corpses for instances
+ if (!GetMap()->Instanceable())
corpse->SaveToDB();
return corpse;
@@ -17309,7 +17309,7 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol
ObjectGuid transGUID = ObjectGuid::Create<HighGuid::Transport>(fields.transguid);
Transport* transport = nullptr;
- if (Map* transportMap = sMapMgr->CreateMap(mapId, this, instanceId))
+ if (Map* transportMap = sMapMgr->CreateMap(mapId, this))
{
if (Transport* transportOnMap = transportMap->GetTransport(transGUID))
{
@@ -17317,7 +17317,7 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol
{
mapId = transportOnMap->GetExpectedMapId();
instanceId = 0;
- transportMap = sMapMgr->CreateMap(mapId, this, instanceId);
+ transportMap = sMapMgr->CreateMap(mapId, this);
if (transportMap)
transport = transportMap->GetTransport(transGUID);
}
@@ -17430,7 +17430,7 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol
// NOW player must have valid map
// load the player's map here if it's not already loaded
if (!map)
- map = sMapMgr->CreateMap(mapId, this, instanceId);
+ map = sMapMgr->CreateMap(mapId, this);
AreaTriggerStruct const* areaTrigger = nullptr;
bool check = false;
@@ -19127,7 +19127,7 @@ void Player::UnbindInstance(BoundInstancesMap::mapped_type::iterator& itr, Bound
}
if (itr->second.perm)
- GetSession()->SendCalendarRaidLockout(itr->second.save, false);
+ GetSession()->SendCalendarRaidLockoutRemoved(itr->second.save);
itr->second.save->RemovePlayer(this); // save can become invalid
difficultyItr->second.erase(itr++);
@@ -19200,20 +19200,14 @@ InstancePlayerBind* Player::BindToInstance(InstanceSave* save, bool permanent, B
return nullptr;
}
-void Player::BindToInstance()
+void Player::ConfirmPendingBind()
{
- InstanceSave* mapSave = sInstanceSaveMgr->GetInstanceSave(_pendingBindId);
- if (!mapSave) //it seems sometimes mapSave is nullptr, but I did not check why
+ InstanceMap* map = GetMap()->ToInstanceMap();
+ if (!map || map->GetInstanceId() != _pendingBindId)
return;
- WorldPackets::Instance::InstanceSaveCreated data;
- data.Gm = IsGameMaster();
- SendDirectMessage(data.Write());
if (!IsGameMaster())
- {
- BindToInstance(mapSave, true, EXTEND_STATE_KEEP);
- GetSession()->SendCalendarRaidLockout(mapSave, true);
- }
+ map->CreateInstanceLockForPlayer(this);
}
void Player::SetPendingBind(uint32 instanceId, uint32 bindTimer)
@@ -19224,40 +19218,26 @@ void Player::SetPendingBind(uint32 instanceId, uint32 bindTimer)
void Player::SendRaidInfo()
{
- WorldPackets::Instance::InstanceInfo instanceInfo;
-
- time_t now = GameTime::GetGameTime();
-
- for (auto difficultyItr = m_boundInstances.begin(); difficultyItr != m_boundInstances.end(); ++difficultyItr)
- {
- for (auto itr = difficultyItr->second.begin(); itr != difficultyItr->second.end(); ++itr)
- {
- InstancePlayerBind const& bind = itr->second;
- if (bind.perm)
- {
- InstanceSave* save = itr->second.save;
+ InstanceResetTimePoint now = GameTime::GetSystemTime();
- WorldPackets::Instance::InstanceLock lockInfos;
+ std::vector<InstanceLock const*> instanceLocks = sInstanceLockMgr.GetInstanceLocksForPlayer(GetGUID());
- lockInfos.InstanceID = save->GetInstanceId();
- lockInfos.MapID = save->GetMapId();
- lockInfos.DifficultyID = save->GetDifficultyID();
- if (bind.extendState != EXTEND_STATE_EXTENDED)
- lockInfos.TimeRemaining = save->GetResetTime() - now;
- else
- lockInfos.TimeRemaining = sInstanceSaveMgr->GetSubsequentResetTime(save->GetMapId(), save->GetDifficultyID(), save->GetResetTime()) - now;
+ WorldPackets::Instance::InstanceInfo instanceInfo;
+ instanceInfo.LockList.reserve(instanceLocks.size());
- lockInfos.CompletedMask = 0;
- if (Map* map = sMapMgr->FindMap(save->GetMapId(), save->GetInstanceId()))
- if (InstanceScript* instanceScript = ((InstanceMap*)map)->GetInstanceScript())
- lockInfos.CompletedMask = instanceScript->GetCompletedEncounterMask();
+ for (InstanceLock const* instanceLock : instanceLocks)
+ {
+ instanceInfo.LockList.emplace_back();
- lockInfos.Locked = bind.extendState != EXTEND_STATE_EXPIRED;
- lockInfos.Extended = bind.extendState == EXTEND_STATE_EXTENDED;
+ WorldPackets::Instance::InstanceLock& lockInfos = instanceInfo.LockList.back();
+ lockInfos.InstanceID = instanceLock->GetInstanceId();
+ lockInfos.MapID = instanceLock->GetMapId();
+ lockInfos.DifficultyID = instanceLock->GetDifficultyId();
+ lockInfos.TimeRemaining = int32(std::chrono::duration_cast<Seconds>(instanceLock->GetEffectiveExpiryTime() - now).count());
+ lockInfos.CompletedMask = instanceLock->GetData()->CompletedEncountersMask;
- instanceInfo.LockList.push_back(lockInfos);
- }
- }
+ lockInfos.Locked = !instanceLock->IsExpired();
+ lockInfos.Extended = instanceLock->IsExtended();
}
SendDirectMessage(instanceInfo.Write());
@@ -23811,20 +23791,12 @@ void Player::SendInitialPacketsAfterAddToMap()
if (GetMap()->IsRaid())
{
- m_prevMapDifficulty = GetMap()->GetDifficultyID();
- DifficultyEntry const* difficulty = sDifficultyStore.AssertEntry(m_prevMapDifficulty);
- SendRaidDifficulty((difficulty->Flags & DIFFICULTY_FLAG_LEGACY) != 0, m_prevMapDifficulty);
+ Difficulty mapDifficulty = GetMap()->GetDifficultyID();
+ DifficultyEntry const* difficulty = sDifficultyStore.AssertEntry(mapDifficulty);
+ SendRaidDifficulty((difficulty->Flags & DIFFICULTY_FLAG_LEGACY) != 0, mapDifficulty);
}
else if (GetMap()->IsNonRaidDungeon())
- {
- m_prevMapDifficulty = GetMap()->GetDifficultyID();
- SendDungeonDifficulty(m_prevMapDifficulty);
- }
- else if (!GetMap()->Instanceable())
- {
- DifficultyEntry const* difficulty = sDifficultyStore.AssertEntry(m_prevMapDifficulty);
- SendRaidDifficulty((difficulty->Flags & DIFFICULTY_FLAG_LEGACY) != 0);
- }
+ SendDungeonDifficulty(GetMap()->GetDifficultyID());
PhasingHandler::OnMapChange(this);
diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h
index a524674505a..6837371869d 100644
--- a/src/server/game/Entities/Player/Player.h
+++ b/src/server/game/Entities/Player/Player.h
@@ -741,7 +741,7 @@ enum class ItemSearchCallbackResult
Continue
};
-enum TransferAbortReason
+enum TransferAbortReason : uint32
{
TRANSFER_ABORT_NONE = 0,
TRANSFER_ABORT_ERROR = 1,
@@ -2539,7 +2539,19 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
uint32 m_HomebindTimer;
bool m_InstanceValid;
// permanent binds and solo binds by difficulty
+ uint32 GetRecentInstanceId(uint32 mapId) const
+ {
+ auto itr = m_recentInstances.find(mapId);
+ return itr != m_recentInstances.end() ? itr->second : 0;
+ }
+
+ void SetRecentInstance(uint32 mapId, uint32 instanceId)
+ {
+ m_recentInstances[mapId] = instanceId;
+ }
+
BoundInstancesMap m_boundInstances;
+ std::unordered_map<uint32 /*mapId*/, uint32 /*instanceId*/> m_recentInstances;
InstancePlayerBind* GetBoundInstance(uint32 mapid, Difficulty difficulty, bool withExpired = false);
InstancePlayerBind const* GetBoundInstance(uint32 mapid, Difficulty difficulty) const;
BoundInstancesMap::iterator GetBoundInstances(Difficulty difficulty) { return m_boundInstances.find(difficulty); }
@@ -2547,7 +2559,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
void UnbindInstance(uint32 mapid, Difficulty difficulty, bool unload = false);
void UnbindInstance(BoundInstancesMap::mapped_type::iterator& itr, BoundInstancesMap::iterator& difficultyItr, bool unload = false);
InstancePlayerBind* BindToInstance(InstanceSave* save, bool permanent, BindExtensionState extendState = EXTEND_STATE_NORMAL, bool load = false);
- void BindToInstance();
+ void ConfirmPendingBind();
void SetPendingBind(uint32 instanceId, uint32 bindTimer);
bool HasPendingBind() const { return _pendingBindId > 0; }
void SendRaidInfo();
@@ -2961,7 +2973,6 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
Difficulty m_dungeonDifficulty;
Difficulty m_raidDifficulty;
Difficulty m_legacyRaidDifficulty;
- Difficulty m_prevMapDifficulty;
uint32 m_atLoginFlags;
diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp
index 78552b3da0f..ff04ffcde5a 100644
--- a/src/server/game/Entities/Unit/Unit.cpp
+++ b/src/server/game/Entities/Unit/Unit.cpp
@@ -10785,31 +10785,6 @@ void Unit::SetMeleeAnimKitId(uint16 animKitId)
summoner->ToGameObject()->AI()->SummonedCreatureDies(creature, attacker);
}
}
-
- // Dungeon specific stuff, only applies to players killing creatures
- if (creature->GetInstanceId())
- {
- Map* instanceMap = creature->GetMap();
-
- /// @todo do instance binding anyway if the charmer/owner is offline
- if (instanceMap->IsDungeon() && ((attacker && attacker->GetCharmerOrOwnerPlayerOrPlayerItself()) || attacker == victim))
- {
- if (instanceMap->IsRaidOrHeroicDungeon())
- {
- if (creature->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_INSTANCE_BIND)
- instanceMap->ToInstanceMap()->PermBindAllPlayers();
- }
- else
- {
- // the reset time is set but not added to the scheduler
- // until the players leave the instance
- time_t resettime = GameTime::GetGameTime() + 2 * HOUR;
- if (InstanceSave* save = sInstanceSaveMgr->GetInstanceSave(creature->GetInstanceId()))
- if (save->GetResetTime() < resettime)
- save->SetResetTime(resettime);
- }
- }
- }
}
// outdoor pvp things, do these after setting the death state, else the player activity notify won't work... doh...
diff --git a/src/server/game/Groups/Group.h b/src/server/game/Groups/Group.h
index 84d36794519..e3236b7eb6d 100644
--- a/src/server/game/Groups/Group.h
+++ b/src/server/game/Groups/Group.h
@@ -345,7 +345,25 @@ class TC_GAME_API Group
void LinkMember(GroupReference* pRef);
void DelinkMember(ObjectGuid guid);
+ uint32 GetInstanceId(MapEntry const* mapEntry) const { return 0; }
InstanceGroupBind* BindToInstance(InstanceSave* save, bool permanent, bool load = false);
+ ObjectGuid GetRecentInstanceOwner(uint32 mapId) const
+ {
+ auto itr = m_recentInstances.find(mapId);
+ return itr != m_recentInstances.end() ? itr->second.first : m_leaderGuid;
+ }
+
+ uint32 GetRecentInstanceId(uint32 mapId) const
+ {
+ auto itr = m_recentInstances.find(mapId);
+ return itr != m_recentInstances.end() ? itr->second.second : 0;
+ }
+
+ void SetRecentInstance(uint32 mapId, ObjectGuid instanceOwner, uint32 instanceId)
+ {
+ m_recentInstances[mapId] = { instanceOwner, instanceId };
+ }
+
void UnbindInstance(uint32 mapid, uint8 difficulty, bool unload = false);
InstanceGroupBind* GetBoundInstance(Player* player);
InstanceGroupBind* GetBoundInstance(Map* aMap);
@@ -391,6 +409,7 @@ class TC_GAME_API Group
ObjectGuid m_looterGuid;
ObjectGuid m_masterLooterGuid;
BoundInstancesMap m_boundInstances;
+ std::unordered_map<uint32 /*mapId*/, std::pair<ObjectGuid /*instanceOwner*/, uint32 /*instanceId*/>> m_recentInstances;
uint8* m_subGroupsCounts;
ObjectGuid m_guid;
uint32 m_dbStoreId; // Represents the ID used in database (Can be reused by other groups if group was disbanded)
diff --git a/src/server/game/Handlers/CalendarHandler.cpp b/src/server/game/Handlers/CalendarHandler.cpp
index ecff70974cc..f7ec9de1d99 100644
--- a/src/server/game/Handlers/CalendarHandler.cpp
+++ b/src/server/game/Handlers/CalendarHandler.cpp
@@ -43,6 +43,7 @@ Copied events should probably have a new owner
#include "GameTime.h"
#include "Guild.h"
#include "GuildMgr.h"
+#include "InstanceLockMgr.h"
#include "InstanceSaveMgr.h"
#include "Log.h"
#include "ObjectAccessor.h"
@@ -91,27 +92,16 @@ void WorldSession::HandleCalendarGetCalendar(WorldPackets::Calendar::CalendarGet
packet.Events.push_back(eventInfo);
}
- for (DifficultyEntry const* difficulty : sDifficultyStore)
+ for (InstanceLock const* lock : sInstanceLockMgr.GetInstanceLocksForPlayer(_player->GetGUID()))
{
- auto boundInstances = _player->GetBoundInstances(Difficulty(difficulty->ID));
- if (boundInstances != _player->m_boundInstances.end())
- {
- for (auto const& boundInstance : boundInstances->second)
- {
- if (boundInstance.second.perm)
- {
- WorldPackets::Calendar::CalendarSendCalendarRaidLockoutInfo lockoutInfo;
-
- InstanceSave const* save = boundInstance.second.save;
- lockoutInfo.MapID = save->GetMapId();
- lockoutInfo.DifficultyID = save->GetDifficultyID();
- lockoutInfo.ExpireTime = save->GetResetTime() - currTime;
- lockoutInfo.InstanceID = save->GetInstanceId(); // instance save id as unique instance copy id
-
- packet.RaidLockouts.push_back(lockoutInfo);
- }
- }
- }
+ WorldPackets::Calendar::CalendarSendCalendarRaidLockoutInfo lockoutInfo;
+
+ lockoutInfo.MapID = lock->GetMapId();
+ lockoutInfo.DifficultyID = lock->GetDifficultyId();
+ lockoutInfo.ExpireTime = int32(std::chrono::duration_cast<Seconds>(lock->GetEffectiveExpiryTime() - GameTime::GetSystemTime()).count());
+ lockoutInfo.InstanceID = lock->GetInstanceId();
+
+ packet.RaidLockouts.push_back(lockoutInfo);
}
SendPacket(packet.Write());
@@ -574,41 +564,19 @@ void WorldSession::HandleSetSavedInstanceExtend(WorldPackets::Calendar::SetSaved
player->BindToInstance(instanceBind->save, true, newState, false);
}
-
- /*
- InstancePlayerBind* instanceBind = _player->GetBoundInstance(setSavedInstanceExtend.MapID, Difficulty(setSavedInstanceExtend.DifficultyID));
- if (!instanceBind || !instanceBind->save)
- return;
-
- InstanceSave* save = instanceBind->save;
- // http://www.wowwiki.com/Instance_Lock_Extension
- // SendCalendarRaidLockoutUpdated(save);
- */
}
// ----------------------------------- SEND ------------------------------------
-void WorldSession::SendCalendarRaidLockout(InstanceSave const* save, bool add)
+void WorldSession::SendCalendarRaidLockoutAdded(InstanceLock const* lock)
{
- time_t currTime = GameTime::GetGameTime();
- if (add)
- {
- WorldPackets::Calendar::CalendarRaidLockoutAdded calendarRaidLockoutAdded;
- calendarRaidLockoutAdded.InstanceID = save->GetInstanceId();
- calendarRaidLockoutAdded.ServerTime = uint32(currTime);
- calendarRaidLockoutAdded.MapID = int32(save->GetMapId());
- calendarRaidLockoutAdded.DifficultyID = save->GetDifficultyID();
- calendarRaidLockoutAdded.TimeRemaining = uint32(save->GetResetTime() - currTime);
- SendPacket(calendarRaidLockoutAdded.Write());
- }
- else
- {
- WorldPackets::Calendar::CalendarRaidLockoutRemoved calendarRaidLockoutRemoved;
- calendarRaidLockoutRemoved.InstanceID = save->GetInstanceId();
- calendarRaidLockoutRemoved.MapID = int32(save->GetMapId());
- calendarRaidLockoutRemoved.DifficultyID = save->GetDifficultyID();
- SendPacket(calendarRaidLockoutRemoved.Write());
- }
+ WorldPackets::Calendar::CalendarRaidLockoutAdded calendarRaidLockoutAdded;
+ calendarRaidLockoutAdded.InstanceID = lock->GetInstanceId();
+ calendarRaidLockoutAdded.ServerTime = uint32(GameTime::GetGameTime());
+ calendarRaidLockoutAdded.MapID = int32(lock->GetMapId());
+ calendarRaidLockoutAdded.DifficultyID = lock->GetDifficultyId();
+ calendarRaidLockoutAdded.TimeRemaining = int32(std::chrono::duration_cast<Seconds>(lock->GetExpiryTime() - GameTime::GetSystemTime()).count());
+ SendPacket(calendarRaidLockoutAdded.Write());
}
void WorldSession::SendCalendarRaidLockoutUpdated(InstanceSave const* save)
@@ -630,3 +598,21 @@ void WorldSession::SendCalendarRaidLockoutUpdated(InstanceSave const* save)
SendPacket(packet.Write());
}
+
+void WorldSession::SendCalendarRaidLockoutRemoved(InstanceSave const* save)
+{
+ WorldPackets::Calendar::CalendarRaidLockoutRemoved calendarRaidLockoutRemoved;
+ calendarRaidLockoutRemoved.InstanceID = save->GetInstanceId();
+ calendarRaidLockoutRemoved.MapID = int32(save->GetMapId());
+ calendarRaidLockoutRemoved.DifficultyID = save->GetDifficultyID();
+ SendPacket(calendarRaidLockoutRemoved.Write());
+}
+
+void WorldSession::SendCalendarRaidLockoutRemoved(InstanceLock const* lock)
+{
+ WorldPackets::Calendar::CalendarRaidLockoutRemoved calendarRaidLockoutRemoved;
+ calendarRaidLockoutRemoved.InstanceID = lock->GetInstanceId();
+ calendarRaidLockoutRemoved.MapID = int32(lock->GetMapId());
+ calendarRaidLockoutRemoved.DifficultyID = lock->GetDifficultyId();
+ SendPacket(calendarRaidLockoutRemoved.Write());
+}
diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp
index 9cb0e65fed4..fa65c980f93 100644
--- a/src/server/game/Handlers/MiscHandler.cpp
+++ b/src/server/game/Handlers/MiscHandler.cpp
@@ -599,14 +599,13 @@ void WorldSession::HandleAreaTriggerOpcode(WorldPackets::AreaTrigger::AreaTrigge
TC_LOG_DEBUG("maps", "MAP: Player '%s' does not have a corpse in instance map %d and cannot enter", player->GetName().c_str(), at->target_mapId);
break;
case Map::CANNOT_ENTER_INSTANCE_BIND_MISMATCH:
- if (MapEntry const* entry = sMapStore.LookupEntry(at->target_mapId))
- {
- char const* mapName = entry->MapName[player->GetSession()->GetSessionDbcLocale()];
- TC_LOG_DEBUG("maps", "MAP: Player '%s' cannot enter instance map '%s' because their permanent bind is incompatible with their group's", player->GetName().c_str(), mapName);
- // is there a special opcode for this?
- // @todo figure out how to get player localized difficulty string (e.g. "10 player", "Heroic" etc)
- ChatHandler(player->GetSession()).PSendSysMessage(player->GetSession()->GetTrinityString(LANG_INSTANCE_BIND_MISMATCH), mapName);
- }
+ player->SendTransferAborted(at->target_mapId, TRANSFER_ABORT_LOCKED_TO_DIFFERENT_INSTANCE);
+ TC_LOG_DEBUG("maps", "MAP: Player '%s' cannot enter instance map %d because their permanent bind is incompatible with their group's", player->GetName().c_str(), at->target_mapId);
+ reviveAtTrigger = true;
+ break;
+ case Map::CANNOT_ENTER_ALREADY_COMPLETED_ENCOUNTER:
+ player->SendTransferAborted(at->target_mapId, TRANSFER_ABORT_ALREADY_COMPLETED_ENCOUNTER);
+ TC_LOG_DEBUG("maps", "MAP: Player '%s' cannot enter instance map %d because their permanent bind is incompatible with their group's", player->GetName().c_str(), at->target_mapId);
reviveAtTrigger = true;
break;
case Map::CANNOT_ENTER_TOO_MANY_INSTANCES:
@@ -1071,7 +1070,7 @@ void WorldSession::HandleInstanceLockResponse(WorldPackets::Instance::InstanceLo
}
if (packet.AcceptLock)
- _player->BindToInstance();
+ _player->ConfirmPendingBind();
else
_player->RepopAtGraveyard();
diff --git a/src/server/game/Instances/InstanceLockMgr.cpp b/src/server/game/Instances/InstanceLockMgr.cpp
new file mode 100644
index 00000000000..007d994133a
--- /dev/null
+++ b/src/server/game/Instances/InstanceLockMgr.cpp
@@ -0,0 +1,475 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * 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 "InstanceLockMgr.h"
+#include "DatabaseEnv.h"
+#include "DB2Stores.h"
+#include "Errors.h"
+#include "GameTime.h"
+#include "Log.h"
+#include "MapManager.h"
+#include "Player.h" // for TransferAbortReason
+#include "World.h"
+
+InstanceLockData::InstanceLockData() = default;
+InstanceLockData::~InstanceLockData() = default;
+
+InstanceLock::InstanceLock(uint32 mapId, Difficulty difficultyId, InstanceResetTimePoint expiryTime, uint32 instanceId)
+ : _mapId(mapId), _difficultyId(difficultyId), _instanceId(instanceId), _expiryTime(expiryTime), _extended(false)
+{
+}
+
+InstanceLock::~InstanceLock() = default;
+
+bool InstanceLock::IsExpired() const
+{
+ return _expiryTime < GameTime::GetSystemTime();
+}
+
+InstanceResetTimePoint InstanceLock::GetEffectiveExpiryTime() const
+{
+ if (!IsExtended())
+ return GetExpiryTime();
+
+ MapDb2Entries entries{ _mapId, _difficultyId };
+
+ // return next reset time
+ if (IsExpired())
+ return sInstanceLockMgr.GetNextResetTime(entries);
+
+ // if not expired, return expiration time + 1 reset period
+ return GetExpiryTime() + Seconds(entries.MapDifficulty->GetRaidDuration());
+}
+
+SharedInstanceLockData::SharedInstanceLockData() = default;
+
+SharedInstanceLockData::~SharedInstanceLockData()
+{
+ // Cleanup database
+ if (InstanceId)
+ sInstanceLockMgr.OnSharedInstanceLockDataDelete(InstanceId);
+}
+
+SharedInstanceLock::SharedInstanceLock(uint32 mapId, Difficulty difficultyId, InstanceResetTimePoint expiryTime, uint32 instanceId,
+ std::shared_ptr<SharedInstanceLockData> sharedData)
+ : InstanceLock(mapId, difficultyId, expiryTime, instanceId), _sharedData(std::move(sharedData))
+{
+}
+
+MapDb2Entries::MapDb2Entries(uint32 mapId, Difficulty difficulty)
+ : Map(sMapStore.AssertEntry(mapId)), MapDifficulty(ASSERT_NOTNULL(sDB2Manager.GetMapDifficultyData(mapId, difficulty)))
+{
+}
+
+MapDb2Entries::MapDb2Entries(MapEntry const* map, MapDifficultyEntry const* mapDifficulty)
+ : Map(map), MapDifficulty(mapDifficulty)
+{
+}
+
+InstanceLockKey MapDb2Entries::GetKey() const
+{
+ return { MapDifficulty->MapID, MapDifficulty->LockID };
+}
+
+bool MapDb2Entries::IsInstanceIdBound() const
+{
+ return !Map->IsFlexLocking() && !MapDifficulty->IsUsingEncounterLocks();
+}
+
+InstanceLockUpdateEvent::InstanceLockUpdateEvent(InstanceLockUpdateEvent&& other) noexcept = default;
+InstanceLockUpdateEvent& InstanceLockUpdateEvent::operator=(InstanceLockUpdateEvent&&) noexcept = default;
+InstanceLockUpdateEvent::~InstanceLockUpdateEvent() = default;
+
+InstanceLockMgr::InstanceLockMgr() = default;
+
+InstanceLockMgr::~InstanceLockMgr() = default;
+
+void InstanceLockMgr::Load()
+{
+ std::unordered_map<uint32, std::shared_ptr<SharedInstanceLockData>> instanceLockDataById;
+
+ // 0 1 2
+ if (QueryResult result = CharacterDatabase.Query("SELECT instanceId, data, completedEncountersMask FROM instance"))
+ {
+ do
+ {
+ Field* fields = result->Fetch();
+ uint32 instanceId = fields[0].GetUInt32();
+
+ std::shared_ptr<SharedInstanceLockData> data = std::make_shared<SharedInstanceLockData>();
+ data->Data = fields[1].GetString();
+ data->CompletedEncountersMask = fields[2].GetUInt32();
+ data->InstanceId = instanceId;
+
+ instanceLockDataById[instanceId] = std::move(data);
+
+ } while (result->NextRow());
+ }
+
+ // 0 1 2 3 4 5 6 7 8
+ if (QueryResult result = CharacterDatabase.Query("SELECT guid, mapId, lockId, instanceId, difficulty, data, completedEncountersMask, expiryTime, extended FROM character_instance_lock"))
+ {
+ do
+ {
+ Field* fields = result->Fetch();
+
+ ObjectGuid playerGuid = ObjectGuid::Create<HighGuid::Player>(fields[0].GetUInt64());
+ uint32 mapId = fields[1].GetUInt32();
+ uint32 lockId = fields[2].GetUInt32();
+ uint32 instanceId = fields[3].GetUInt32();
+ Difficulty difficulty = Difficulty(fields[4].GetUInt8());
+ InstanceResetTimePoint expiryTime = std::chrono::system_clock::from_time_t(time_t(fields[7].GetUInt64()));
+
+ // Mark instance id as being used
+ sMapMgr->RegisterInstanceId(instanceId);
+
+ InstanceLock* instanceLock;
+ if (MapDb2Entries{ mapId, difficulty }.IsInstanceIdBound())
+ {
+ auto sharedDataItr = instanceLockDataById.find(instanceId);
+ if (sharedDataItr == instanceLockDataById.end())
+ {
+ TC_LOG_ERROR("instance.locks", "Missing instance data for instance id based lock (id %u)", instanceId);
+ CharacterDatabase.PQuery("DELETE FROM character_instance_lock WHERE instanceId = %u", instanceId);
+ continue;
+ }
+
+ instanceLock = new SharedInstanceLock(mapId, difficulty, expiryTime, instanceId, sharedDataItr->second);
+ _instanceLockDataById[instanceId] = std::weak_ptr<SharedInstanceLockData>(sharedDataItr->second);
+ }
+ else
+ instanceLock = new InstanceLock(mapId, difficulty, expiryTime, instanceId);
+
+ instanceLock->GetData()->Data = fields[5].GetString();
+ instanceLock->GetData()->CompletedEncountersMask = fields[6].GetUInt32();
+ instanceLock->SetExtended(fields[8].GetBool());
+
+ _instanceLocksByPlayer[playerGuid][InstanceLockKey{ mapId, lockId }].reset(instanceLock);
+
+ } while (result->NextRow());
+ }
+}
+
+void InstanceLockMgr::Unload()
+{
+ _unloading = true;
+ _instanceLocksByPlayer.clear();
+ _instanceLockDataById.clear();
+}
+
+TransferAbortReason InstanceLockMgr::CanJoinInstanceLock(ObjectGuid const& playerGuid, MapDb2Entries const& entries, InstanceLock const* instanceLock) const
+{
+ if (!entries.MapDifficulty->HasResetSchedule())
+ return TRANSFER_ABORT_NONE;
+
+ InstanceLock const* playerInstanceLock = FindActiveInstanceLock(playerGuid, entries);
+ if (!playerInstanceLock)
+ return TRANSFER_ABORT_NONE;
+
+ if (entries.Map->IsFlexLocking())
+ {
+ // compare completed encounters - if instance has any encounters unkilled in players lock then cannot enter
+ if (playerInstanceLock->GetData()->CompletedEncountersMask & ~instanceLock->GetData()->CompletedEncountersMask)
+ return TRANSFER_ABORT_ALREADY_COMPLETED_ENCOUNTER;
+
+ return TRANSFER_ABORT_NONE;
+ }
+
+ if (!entries.MapDifficulty->IsUsingEncounterLocks() && playerInstanceLock->GetInstanceId() && playerInstanceLock->GetInstanceId() != instanceLock->GetInstanceId())
+ return TRANSFER_ABORT_LOCKED_TO_DIFFERENT_INSTANCE;
+
+ return TRANSFER_ABORT_NONE;
+}
+
+InstanceLock* InstanceLockMgr::FindInstanceLock(LockMap const& locks, ObjectGuid const& playerGuid, MapDb2Entries const& entries)
+{
+ auto playerLocksItr = locks.find(playerGuid);
+ if (playerLocksItr == locks.end())
+ return nullptr;
+
+ auto lockItr = playerLocksItr->second.find(entries.GetKey());
+ if (lockItr == playerLocksItr->second.end())
+ return nullptr;
+
+ return lockItr->second.get();
+}
+
+InstanceLock* InstanceLockMgr::FindActiveInstanceLock(ObjectGuid const& playerGuid, MapDb2Entries const& entries) const
+{
+ return FindActiveInstanceLock(playerGuid, entries, false, true);
+}
+
+InstanceLock* InstanceLockMgr::FindActiveInstanceLock(ObjectGuid const& playerGuid, MapDb2Entries const& entries, bool ignoreTemporary, bool ignoreExpired) const
+{
+ InstanceLock* lock = FindInstanceLock(_instanceLocksByPlayer, playerGuid, entries);
+
+ // Ignore expired and not extended locks
+ if (lock && (!lock->IsExpired() || lock->IsExtended() || !ignoreExpired))
+ return lock;
+
+ if (ignoreTemporary)
+ return nullptr;
+
+ return FindInstanceLock(_temporaryInstanceLocksByPlayer, playerGuid, entries);
+}
+
+std::vector<InstanceLock const*> InstanceLockMgr::GetInstanceLocksForPlayer(ObjectGuid const& playerGuid) const
+{
+ std::vector<InstanceLock const*> locks;
+ auto playerLocksItr = _instanceLocksByPlayer.find(playerGuid);
+ if (playerLocksItr != _instanceLocksByPlayer.end())
+ {
+ locks.reserve(playerLocksItr->second.size());
+ std::transform(playerLocksItr->second.begin(), playerLocksItr->second.end(), std::back_inserter(locks),
+ [](PlayerLockMap::value_type const& p) { return p.second.get(); });
+ }
+
+ return locks;
+}
+
+InstanceLock* InstanceLockMgr::CreateInstanceLockForNewInstance(ObjectGuid const& playerGuid, MapDb2Entries const& entries, uint32 instanceId)
+{
+ if (!entries.MapDifficulty->HasResetSchedule())
+ return nullptr;
+
+ InstanceLock* instanceLock;
+ if (entries.IsInstanceIdBound())
+ {
+ std::shared_ptr<SharedInstanceLockData> sharedData = std::make_shared<SharedInstanceLockData>();
+ _instanceLockDataById[instanceId] = sharedData;
+ instanceLock = new SharedInstanceLock(entries.MapDifficulty->MapID, Difficulty(entries.MapDifficulty->DifficultyID),
+ GetNextResetTime(entries), 0, std::move(sharedData));
+ }
+ else
+ instanceLock = new InstanceLock(entries.MapDifficulty->MapID, Difficulty(entries.MapDifficulty->DifficultyID),
+ GetNextResetTime(entries), 0);
+
+ _temporaryInstanceLocksByPlayer[playerGuid][entries.GetKey()].reset(instanceLock);
+ TC_LOG_DEBUG("instance.locks", "[%u-%s | %u-%s] Created new temporary instance lock for %s in instance %u",
+ entries.Map->ID, entries.Map->MapName[sWorld->GetDefaultDbcLocale()],
+ uint32(entries.MapDifficulty->DifficultyID), sDifficultyStore.AssertEntry(entries.MapDifficulty->DifficultyID)->Name[sWorld->GetDefaultDbcLocale()],
+ playerGuid.ToString().c_str(), instanceId);
+ return instanceLock;
+}
+
+InstanceLock* InstanceLockMgr::UpdateInstanceLockForPlayer(CharacterDatabaseTransaction trans, ObjectGuid const& playerGuid,
+ MapDb2Entries const& entries, InstanceLockUpdateEvent&& updateEvent)
+{
+ InstanceLock* instanceLock = FindActiveInstanceLock(playerGuid, entries, true, true);
+ if (!instanceLock)
+ {
+ // Move lock from temporary storage if it exists there
+ // This is to avoid destroying expired locks before any boss is killed in a fresh lock
+ // player can still change his mind, exit instance and reactivate old lock
+ auto playerLocksItr = _temporaryInstanceLocksByPlayer.find(playerGuid);
+ if (playerLocksItr != _temporaryInstanceLocksByPlayer.end())
+ {
+ auto lockItr = playerLocksItr->second.find(entries.GetKey());
+ if (lockItr != playerLocksItr->second.end())
+ {
+ instanceLock = lockItr->second.release();
+ _instanceLocksByPlayer[playerGuid][entries.GetKey()].reset(instanceLock);
+
+ playerLocksItr->second.erase(lockItr);
+ if (playerLocksItr->second.empty())
+ _temporaryInstanceLocksByPlayer.erase(playerLocksItr);
+
+ TC_LOG_DEBUG("instance.locks", "[%u-%s | %u-%s] Promoting temporary lock to permanent for %s in instance %u",
+ entries.Map->ID, entries.Map->MapName[sWorld->GetDefaultDbcLocale()],
+ uint32(entries.MapDifficulty->DifficultyID), sDifficultyStore.AssertEntry(entries.MapDifficulty->DifficultyID)->Name[sWorld->GetDefaultDbcLocale()],
+ playerGuid.ToString().c_str(), updateEvent.InstanceId);
+ }
+ }
+ }
+
+ if (!instanceLock)
+ {
+ if (entries.IsInstanceIdBound())
+ {
+ auto sharedDataItr = _instanceLockDataById.find(updateEvent.InstanceId);
+ ASSERT(sharedDataItr != _instanceLockDataById.end());
+ ASSERT(!sharedDataItr->second.expired());
+
+ instanceLock = new SharedInstanceLock(entries.MapDifficulty->MapID, Difficulty(entries.MapDifficulty->DifficultyID),
+ GetNextResetTime(entries), updateEvent.InstanceId, sharedDataItr->second.lock());
+ ASSERT(static_cast<SharedInstanceLock*>(instanceLock)->GetSharedData()->InstanceId == updateEvent.InstanceId);
+ }
+ else
+ instanceLock = new InstanceLock(entries.MapDifficulty->MapID, Difficulty(entries.MapDifficulty->DifficultyID),
+ GetNextResetTime(entries), updateEvent.InstanceId);
+
+ _instanceLocksByPlayer[playerGuid][entries.GetKey()].reset(instanceLock);
+ TC_LOG_DEBUG("instance.locks", "[%u-%s | %u-%s] Created new instance lock for %s in instance %u",
+ entries.Map->ID, entries.Map->MapName[sWorld->GetDefaultDbcLocale()],
+ uint32(entries.MapDifficulty->DifficultyID), sDifficultyStore.AssertEntry(entries.MapDifficulty->DifficultyID)->Name[sWorld->GetDefaultDbcLocale()],
+ playerGuid.ToString().c_str(), updateEvent.InstanceId);
+ }
+ else
+ {
+ if (entries.IsInstanceIdBound())
+ {
+ ASSERT(!instanceLock->GetInstanceId() || instanceLock->GetInstanceId() == updateEvent.InstanceId);
+ auto sharedDataItr = _instanceLockDataById.find(updateEvent.InstanceId);
+ ASSERT(sharedDataItr != _instanceLockDataById.end());
+ ASSERT(sharedDataItr->second.lock().get() == static_cast<SharedInstanceLock*>(instanceLock)->GetSharedData());
+ }
+
+ instanceLock->SetInstanceId(updateEvent.InstanceId);
+ }
+
+ instanceLock->GetData()->Data = std::move(updateEvent.NewData);
+ if (updateEvent.CompletedEncounter)
+ {
+ instanceLock->GetData()->CompletedEncountersMask |= 1u << updateEvent.CompletedEncounter->Bit;
+ TC_LOG_DEBUG("instance.locks", "[%u-%s | %u-%s] Instance lock for %s in instance %u gains completed encounter [%u-%s]",
+ entries.Map->ID, entries.Map->MapName[sWorld->GetDefaultDbcLocale()],
+ uint32(entries.MapDifficulty->DifficultyID), sDifficultyStore.AssertEntry(entries.MapDifficulty->DifficultyID)->Name[sWorld->GetDefaultDbcLocale()],
+ playerGuid.ToString().c_str(), updateEvent.InstanceId,
+ updateEvent.CompletedEncounter->ID, updateEvent.CompletedEncounter->Name[sWorld->GetDefaultDbcLocale()]);
+ }
+
+ // Synchronize map completed encounters into players completed encounters for UI
+ if (!entries.MapDifficulty->IsUsingEncounterLocks())
+ instanceLock->GetData()->CompletedEncountersMask |= updateEvent.InstanceCompletedEncountersMask;
+
+ if (instanceLock->IsExpired())
+ {
+ ASSERT(instanceLock->IsExtended(), "Instance lock must have been extended to create instance map from it");
+ instanceLock->SetExpiryTime(GetNextResetTime(entries));
+ instanceLock->SetExtended(false);
+ TC_LOG_DEBUG("instance.locks", "[%u-%s | %u-%s] Expired instance lock for %s in instance %u is now active",
+ entries.Map->ID, entries.Map->MapName[sWorld->GetDefaultDbcLocale()],
+ uint32(entries.MapDifficulty->DifficultyID), sDifficultyStore.AssertEntry(entries.MapDifficulty->DifficultyID)->Name[sWorld->GetDefaultDbcLocale()],
+ playerGuid.ToString().c_str(), updateEvent.InstanceId);
+ }
+
+ // TODO: DB SAVE IN TRANSACTION
+ trans->PAppend("DELETE FROM character_instance_lock WHERE guid=" UI64FMTD " AND mapId=%u AND lockId=%d",
+ playerGuid.GetCounter(),
+ entries.MapDifficulty->MapID,
+ entries.MapDifficulty->LockID);
+ std::string escapedData = instanceLock->GetData()->Data;
+ CharacterDatabase.EscapeString(escapedData);
+ trans->PAppend("INSERT INTO character_instance_lock (guid, mapId, lockId, instanceId, difficulty, data, completedEncountersMask, entranceWorldSafeLocId, expiryTime, extended) VALUES (" UI64FMTD ", %u, %d, %u, %d, \"%s\", %u, %u, " UI64FMTD ", %d)",
+ playerGuid.GetCounter(),
+ entries.MapDifficulty->MapID,
+ entries.MapDifficulty->LockID,
+ instanceLock->GetInstanceId(),
+ entries.MapDifficulty->DifficultyID,
+ escapedData.c_str(),
+ instanceLock->GetData()->CompletedEncountersMask,
+ instanceLock->GetData()->EntranceWorldSafeLocId,
+ uint64(std::chrono::system_clock::to_time_t(instanceLock->GetExpiryTime())),
+ instanceLock->IsExtended() ? 1 : 0);
+
+ return instanceLock;
+}
+
+void InstanceLockMgr::UpdateSharedInstanceLock(CharacterDatabaseTransaction trans, InstanceLockUpdateEvent&& updateEvent)
+{
+ auto sharedDataItr = _instanceLockDataById.find(updateEvent.InstanceId);
+ ASSERT(sharedDataItr != _instanceLockDataById.end());
+ std::shared_ptr<SharedInstanceLockData> sharedData = sharedDataItr->second.lock();
+ ASSERT(sharedData && sharedData->InstanceId == updateEvent.InstanceId);
+ sharedData->Data = std::move(updateEvent.NewData);
+ if (updateEvent.CompletedEncounter)
+ {
+ sharedData->CompletedEncountersMask |= 1u << updateEvent.CompletedEncounter->Bit;
+ TC_LOG_DEBUG("instance.locks", "Instance %u gains completed encounter [%u-%s]",
+ updateEvent.InstanceId, updateEvent.CompletedEncounter->ID, updateEvent.CompletedEncounter->Name[sWorld->GetDefaultDbcLocale()]);
+ }
+
+ trans->PAppend("DELETE FROM instance2 WHERE instanceId=%u",
+ sharedData->InstanceId);
+ std::string escapedData = sharedData->Data;
+ CharacterDatabase.EscapeString(escapedData);
+ trans->PAppend("INSERT INTO instance2 (instanceId, data, completedEncountersMask, entranceWorldSafeLocId) VALUES (%u, \"%s\", %u, %u)",
+ sharedData->InstanceId,
+ escapedData.c_str(),
+ sharedData->CompletedEncountersMask,
+ sharedData->EntranceWorldSafeLocId);
+}
+
+void InstanceLockMgr::OnSharedInstanceLockDataDelete(uint32 instanceId)
+{
+ if (_unloading)
+ return;
+
+ _instanceLockDataById.erase(instanceId);
+ CharacterDatabase.PExecute("DELETE FROM instance2 WHERE instanceId=%u", instanceId);
+ TC_LOG_DEBUG("instance.locks", "Deleting instance %u as it is no longer referenced by any player", instanceId);
+}
+
+void InstanceLockMgr::UpdateInstanceLockExtensionForPlayer(ObjectGuid const& playerGuid, MapDb2Entries const& entries, bool extended)
+{
+ if (InstanceLock* instanceLock = FindActiveInstanceLock(playerGuid, entries, true, false))
+ {
+ instanceLock->SetExtended(extended);
+ CharacterDatabase.PExecute("UPDATE character_instance_lock SET extended = %d WHERE guid = " UI64FMTD " AND mapId = %u AND lockId = %d",
+ extended ? 1 : 0,
+ playerGuid.GetCounter(),
+ entries.MapDifficulty->MapID,
+ entries.MapDifficulty->LockID);
+
+ TC_LOG_DEBUG("instance.locks", "[%u-%s | %u-%s] Instance lock for %s is %s extended",
+ entries.Map->ID, entries.Map->MapName[sWorld->GetDefaultDbcLocale()],
+ uint32(entries.MapDifficulty->DifficultyID), sDifficultyStore.AssertEntry(entries.MapDifficulty->DifficultyID)->Name[sWorld->GetDefaultDbcLocale()],
+ playerGuid.ToString().c_str(), extended ? "now" : "no longer");
+ }
+}
+
+#include "Config.h"
+
+InstanceResetTimePoint InstanceLockMgr::GetNextResetTime(MapDb2Entries const& entries)
+{
+ tm dateTime = *GameTime::GetDateAndTime();
+ dateTime.tm_sec = 0;
+ dateTime.tm_min = 0;
+ int32 resetHour = sConfigMgr->GetIntDefault("ResetSchedule.DailyHour", 9);
+ switch (entries.MapDifficulty->ResetInterval)
+ {
+ case MAP_DIFFICULTY_RESET_DAILY:
+ {
+ if (dateTime.tm_hour >= resetHour)
+ ++dateTime.tm_mday;
+
+ dateTime.tm_hour = resetHour;
+ break;
+ }
+ case MAP_DIFFICULTY_RESET_WEEKLY:
+ {
+ int32 resetDay = sConfigMgr->GetIntDefault("ResetSchedule.WeeklyDay", 2);
+ int32 daysAdjust = resetDay - dateTime.tm_wday;
+ if (dateTime.tm_wday > resetDay || (dateTime.tm_wday == resetDay && dateTime.tm_hour >= resetHour))
+ daysAdjust += 7; // passed it for current week, grab time from next week
+
+ dateTime.tm_hour = resetHour;
+ dateTime.tm_mday += daysAdjust;
+ break;
+ }
+ default:
+ break;
+ }
+
+ return std::chrono::system_clock::from_time_t(mktime(&dateTime));
+}
+
+InstanceLockMgr& InstanceLockMgr::Instance()
+{
+ static InstanceLockMgr instance;
+ return instance;
+}
diff --git a/src/server/game/Instances/InstanceLockMgr.h b/src/server/game/Instances/InstanceLockMgr.h
new file mode 100644
index 00000000000..faf0bd584f6
--- /dev/null
+++ b/src/server/game/Instances/InstanceLockMgr.h
@@ -0,0 +1,285 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * 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 InstanceLockMgr_h__
+#define InstanceLockMgr_h__
+
+#include "Define.h"
+#include "DatabaseEnvFwd.h"
+#include "Duration.h"
+#include "Hash.h"
+#include "ObjectGuid.h"
+#include <unordered_map>
+
+/*
+ * - Resetting instance difficulties with reset schedule from UI seems only possible either when its completed or in group (and only with LockID)
+ * - All difficulties of all maps with ResetInterval share locks - KEY is (MapID, LockID)
+ *
+ * DATABASE
+ * character_instance_lock
+ * `guid` bigint(20) unsigned NOT NULL,
+ * `mapId` int(10) unsigned NOT NULL,
+ * `lockId` int(10) unsigned NOT NULL,
+ * `instanceId` int(10) unsigned, REFERENCES instance for instanceId based locks
+ * `difficulty` tinyint(3) unsigned,
+ * `data` text, ALWAYS FILLED (also might not match instance data for instanceId based locks)
+ * `completedEncountersMask` int(10) unsigned, ALWAYS FILLED (also might not match instance data for instanceId based locks)
+ * `expiryTime` bigint(20) unsigned,
+ * `extended` tinyint(1) unsigned,
+ * PRIMARY KEY (`guid`,`mapId`,`lockId`),
+ *
+ * instance
+ * `instanceId` int(10) unsigned NOT NULL,
+ * `data` text, FILLED ONLY FOR ID BASED LOCKS
+ * `completedEncountersMask` int(10) unsigned, FILLED ONLY FOR ID BASED LOCKS
+ * PRIMARY KEY (`instanceId`)
+ */
+
+struct DungeonEncounterEntry;
+struct MapEntry;
+struct MapDifficultyEntry;
+enum Difficulty : uint8;
+enum TransferAbortReason : uint32;
+
+#define INSTANCE_ID_HIGH_MASK 0x1F440000
+#define INSTANCE_ID_LFG_MASK 0x00000001
+#define INSTANCE_ID_NORMAL_MASK 0x00010000
+
+using InstanceResetTimePoint = std::chrono::system_clock::time_point;
+
+struct InstanceLockData
+{
+ InstanceLockData();
+ virtual ~InstanceLockData();
+
+ InstanceLockData(InstanceLockData const&) = delete;
+ InstanceLockData(InstanceLockData&&) = delete;
+ InstanceLockData& operator=(InstanceLockData const&) = delete;
+ InstanceLockData& operator=(InstanceLockData&&) = delete;
+
+ std::string Data;
+ uint32 CompletedEncountersMask = 0;
+};
+
+class TC_GAME_API InstanceLock
+{
+public:
+ InstanceLock(uint32 mapId, Difficulty difficultyId, InstanceResetTimePoint expiryTime, uint32 instanceId);
+ virtual ~InstanceLock();
+
+ InstanceLock(InstanceLock const&) = delete;
+ InstanceLock(InstanceLock&&) = delete;
+ InstanceLock& operator=(InstanceLock const&) = delete;
+ InstanceLock& operator=(InstanceLock&&) = delete;
+
+ uint32 GetMapId() const { return _mapId; }
+
+ Difficulty GetDifficultyId() const { return _difficultyId; }
+
+ uint32 GetInstanceId() const { return _instanceId; }
+ virtual void SetInstanceId(uint32 instanceId) { _instanceId = instanceId; }
+
+ InstanceResetTimePoint GetExpiryTime() const { return _expiryTime; }
+ void SetExpiryTime(InstanceResetTimePoint expiryTime) { _expiryTime = expiryTime; }
+ bool IsExpired() const;
+
+ bool IsExtended() const { return _extended; }
+ void SetExtended(bool extended) { _extended = extended; }
+
+ InstanceLockData* GetData() { return &_data; }
+ InstanceLockData const* GetData() const { return &_data; }
+
+ virtual InstanceLockData const* GetInstanceInitializationData() const { return &_data; }
+
+ InstanceResetTimePoint GetEffectiveExpiryTime() const;
+
+private:
+ uint32 _mapId;
+ Difficulty _difficultyId;
+ uint32 _instanceId;
+ std::chrono::system_clock::time_point _expiryTime;
+ bool _extended;
+ InstanceLockData _data;
+};
+
+struct SharedInstanceLockData : InstanceLockData
+{
+ SharedInstanceLockData();
+ ~SharedInstanceLockData();
+
+ SharedInstanceLockData(SharedInstanceLockData const&) = delete;
+ SharedInstanceLockData(SharedInstanceLockData&&) = delete;
+ SharedInstanceLockData& operator=(SharedInstanceLockData const&) = delete;
+ SharedInstanceLockData& operator=(SharedInstanceLockData&&) = delete;
+
+ uint32 InstanceId = 0;
+};
+
+class TC_GAME_API SharedInstanceLock : public InstanceLock
+{
+public:
+ SharedInstanceLock(uint32 mapId, Difficulty difficultyId, InstanceResetTimePoint expiryTime, uint32 instanceId, std::shared_ptr<SharedInstanceLockData> sharedData);
+
+ void SetInstanceId(uint32 instanceId) override
+ {
+ InstanceLock::SetInstanceId(instanceId);
+ _sharedData->InstanceId = instanceId;
+ }
+
+ InstanceLockData const* GetInstanceInitializationData() const override { return _sharedData.get(); }
+
+ SharedInstanceLockData* GetSharedData() { return _sharedData.get(); }
+ SharedInstanceLockData const* GetSharedData() const { return _sharedData.get(); }
+
+private:
+ /**
+ Instance id based locks have two states
+ One shared by everyone, which is the real state used by instance
+ and one for each player that shows in UI that might have less encounters completed
+ */
+ std::shared_ptr<SharedInstanceLockData> _sharedData;
+};
+
+using InstanceLockKey = std::pair<uint32 /*MapDifficultyEntry::MapID*/, uint32 /*MapDifficultyEntry::LockID*/>;
+
+struct TC_GAME_API MapDb2Entries
+{
+ MapDb2Entries(uint32 mapId, Difficulty difficulty);
+ MapDb2Entries(MapEntry const* map, MapDifficultyEntry const* mapDifficulty);
+
+ InstanceLockKey GetKey() const;
+ bool IsInstanceIdBound() const;
+
+ MapEntry const* Map;
+ MapDifficultyEntry const* MapDifficulty;
+};
+
+struct InstanceLockUpdateEvent
+{
+ InstanceLockUpdateEvent(uint32 instanceId, std::string newData, uint32 instanceCompletedEncountersMask, DungeonEncounterEntry const* completedEncounter)
+ : InstanceId(instanceId), NewData(std::move(newData)), InstanceCompletedEncountersMask(instanceCompletedEncountersMask), CompletedEncounter(completedEncounter) { }
+ InstanceLockUpdateEvent(InstanceLockUpdateEvent const& other) = delete;
+ InstanceLockUpdateEvent(InstanceLockUpdateEvent&& other) noexcept;
+
+ InstanceLockUpdateEvent& operator=(InstanceLockUpdateEvent const&) = delete;
+ InstanceLockUpdateEvent& operator=(InstanceLockUpdateEvent&&) noexcept;
+ ~InstanceLockUpdateEvent();
+
+ uint32 InstanceId;
+ std::string NewData;
+ uint32 InstanceCompletedEncountersMask;
+ DungeonEncounterEntry const* CompletedEncounter;
+};
+
+class TC_GAME_API InstanceLockMgr
+{
+public:
+ void Load();
+ void Unload();
+
+ /**
+ @brief Checks if player is allowed to enter instance map
+ @param playerGuid Guid of player who is trying to enter instance map
+ @param entries Map.db2 + MapDifficulty.db2 data for instance
+ @param instanceLock InstanceLock used during map creation
+ @return Failure reason to show in client or TRANSFER_ABORT_NONE when player is allowed to enter
+ */
+ TransferAbortReason CanJoinInstanceLock(ObjectGuid const& playerGuid, MapDb2Entries const& entries, InstanceLock const* instanceLock) const;
+
+ /**
+ @brief Attempts to find InstanceLock for given instance owner. This will also find expired but extended locks
+ @param playerGuid Guid of player owning instance. Player's own guid if not in group, otherwise group leader's
+ @param entries Map.db2 + MapDifficulty.db2 data for instance
+ @return InstanceLock previously loaded from database
+ */
+ InstanceLock* FindActiveInstanceLock(ObjectGuid const& playerGuid, MapDb2Entries const& entries) const;
+
+ /**
+ @brief Retrieves all existing instance locks for a player
+ @param playerGuid Guid of player whose locks are requested
+ @return All locks (current and expired) for player
+ */
+ std::vector<InstanceLock const*> GetInstanceLocksForPlayer(ObjectGuid const& playerGuid) const;
+
+ /**
+ @brief Creates new InstanceLock when instance map is created
+ @param playerGuid Guid of player owning instance. Player's own guid if not in group, otherwise group leader's
+ @param entries Map.db2 + MapDifficulty.db2 data for instance
+ @param instanceId Persistent instance id
+ @return New InstanceLock with clear state and _instanceId not set
+ */
+ InstanceLock* CreateInstanceLockForNewInstance(ObjectGuid const& playerGuid, MapDb2Entries const& entries, uint32 instanceId);
+
+ /**
+ @brief Updates existing instance lock for player with new completed encounter and instance state
+ @param trans Database transaction
+ @param playerGuid Guid of player who will become locked to instance
+ @param entries Map.db2 + MapDifficulty.db2 data for instance
+ @param updateEvent New instance lock data
+ @return Updated InstanceLock for player
+ */
+ InstanceLock* UpdateInstanceLockForPlayer(CharacterDatabaseTransaction trans, ObjectGuid const& playerGuid, MapDb2Entries const& entries, InstanceLockUpdateEvent&& updateEvent);
+
+ /**
+ @brief Updates existing instance id based lock shared state with new completed encounter and instance state
+ @param trans Database transaction
+ @param updateEvent New instance lock data
+ */
+ void UpdateSharedInstanceLock(CharacterDatabaseTransaction trans, InstanceLockUpdateEvent&& updateEvent);
+
+ /**
+ @brief Handles last reference to shared instance state being removed to clean up leftover database data
+ @param instanceId Id of the instance being deleted
+ */
+ void OnSharedInstanceLockDataDelete(uint32 instanceId);
+
+ /**
+ @brief Updates existing instance lock for player with requested extension state
+ @param playerGuid Guid of player whose lock is extended
+ @param entries Map.db2 + MapDifficulty.db2 data for instance
+ @param extended New instance lock extension state
+ */
+ void UpdateInstanceLockExtensionForPlayer(ObjectGuid const& playerGuid, MapDb2Entries const& entries, bool extended);
+
+ static InstanceLockMgr& Instance();
+
+ static InstanceResetTimePoint GetNextResetTime(MapDb2Entries const& entries);
+
+private:
+ InstanceLockMgr();
+ InstanceLockMgr(InstanceLockMgr const& right) = delete;
+ InstanceLockMgr(InstanceLockMgr&& right) = delete;
+ InstanceLockMgr& operator=(InstanceLockMgr const& right) = delete;
+ InstanceLockMgr& operator=(InstanceLockMgr&& right) = delete;
+
+ ~InstanceLockMgr();
+
+ using PlayerLockMap = std::unordered_map<InstanceLockKey, std::unique_ptr<InstanceLock>>;
+ using LockMap = std::unordered_map<ObjectGuid, PlayerLockMap>;
+
+ static InstanceLock* FindInstanceLock(LockMap const& locks, ObjectGuid const& playerGuid, MapDb2Entries const& entries);
+ InstanceLock* FindActiveInstanceLock(ObjectGuid const& playerGuid, MapDb2Entries const& entries, bool ignoreTemporary, bool ignoreExpired) const;
+
+ LockMap _temporaryInstanceLocksByPlayer; // locks stored here before any boss gets killed
+ LockMap _instanceLocksByPlayer;
+ std::unordered_map<uint32, std::weak_ptr<SharedInstanceLockData>> _instanceLockDataById;
+ bool _unloading = false;
+};
+
+#define sInstanceLockMgr InstanceLockMgr::Instance()
+
+#endif // InstanceLockMgr_h__
diff --git a/src/server/game/Instances/InstanceScript.cpp b/src/server/game/Instances/InstanceScript.cpp
index 4623522ccf9..358ce8d60c0 100644
--- a/src/server/game/Instances/InstanceScript.cpp
+++ b/src/server/game/Instances/InstanceScript.cpp
@@ -21,6 +21,7 @@
#include "CreatureAI.h"
#include "CreatureAIImpl.h"
#include "DatabaseEnv.h"
+#include "DB2Stores.h"
#include "GameEventSender.h"
#include "GameObject.h"
#include "Group.h"
@@ -51,6 +52,16 @@ BossBoundaryData::~BossBoundaryData()
delete it->Boundary;
}
+DungeonEncounterEntry const* BossInfo::GetDungeonEncounterForDifficulty(Difficulty difficulty) const
+{
+ auto itr = std::find_if(DungeonEncounters.begin(), DungeonEncounters.end(), [difficulty](DungeonEncounterEntry const* dungeonEncounter)
+ {
+ return dungeonEncounter && (dungeonEncounter->DifficultyID == 0 || Difficulty(dungeonEncounter->DifficultyID) == difficulty);
+ });
+
+ return itr != DungeonEncounters.end() ? *itr : nullptr;
+}
+
InstanceScript::InstanceScript(InstanceMap* map) : instance(map), completedEncounters(0), _instanceSpawnGroups(sObjectMgr->GetInstanceSpawnGroupsForMap(map->GetId())),
_entranceId(0), _temporaryEntranceId(0), _combatResurrectionTimer(0), _combatResurrectionCharges(0), _combatResurrectionTimerStarted(false)
{
@@ -217,6 +228,13 @@ void InstanceScript::LoadObjectData(ObjectData const* data, ObjectInfoMap& objec
}
}
+void InstanceScript::LoadDungeonEncounterData(uint32 bossId, std::array<uint32, MAX_DUNGEON_ENCOUNTERS_PER_BOSS> const& dungeonEncounterIds)
+{
+ if (bossId < bosses.size())
+ for (std::size_t i = 0; i < MAX_DUNGEON_ENCOUNTERS_PER_BOSS; ++i)
+ bosses[bossId].DungeonEncounters[i] = sDungeonEncounterStore.LookupEntry(dungeonEncounterIds[i]);
+}
+
void InstanceScript::UpdateDoorState(GameObject* door)
{
DoorInfoMapBounds range = doors.equal_range(door->GetEntry());
@@ -397,6 +415,7 @@ bool InstanceScript::SetBossState(uint32 id, EncounterState state)
if (minion->isWorldBoss() && minion->IsAlive())
return false;
+ DungeonEncounterEntry const* dungeonEncounter = nullptr;
switch (state)
{
case IN_PROGRESS:
@@ -413,9 +432,18 @@ bool InstanceScript::SetBossState(uint32 id, EncounterState state)
break;
}
case FAIL:
+ ResetCombatResurrections();
+ SendEncounterEnd();
+ break;
case DONE:
ResetCombatResurrections();
SendEncounterEnd();
+ dungeonEncounter = bossInfo->GetDungeonEncounterForDifficulty(instance->GetDifficultyID());
+ if (dungeonEncounter)
+ {
+ DoUpdateCriteria(CriteriaType::DefeatDungeonEncounter, dungeonEncounter->ID);
+ SendBossKillCredit(dungeonEncounter->ID);
+ }
break;
default:
break;
@@ -423,6 +451,8 @@ bool InstanceScript::SetBossState(uint32 id, EncounterState state)
bossInfo->state = state;
SaveToDB();
+ if (state == DONE)
+ instance->UpdateInstanceLock(dungeonEncounter, { id, state });
}
for (uint32 type = 0; type < MAX_DOOR_TYPES; ++type)
@@ -493,7 +523,7 @@ bool InstanceScript::ReadSaveDataHeaders(std::istringstream& data)
void InstanceScript::ReadSaveDataBossStates(std::istringstream& data)
{
uint32 bossId = 0;
- for (std::vector<BossInfo>::iterator i = bosses.begin(); i != bosses.end(); ++i, ++bossId)
+ for (; bossId < bosses.size(); ++bossId)
{
uint32 buff;
data >> buff;
@@ -521,6 +551,31 @@ std::string InstanceScript::GetSaveData()
return saveStream.str();
}
+std::string InstanceScript::UpdateSaveData(std::string const& oldData, UpdateSaveDataEvent const& event)
+{
+ if (!instance->GetMapDifficulty()->IsUsingEncounterLocks())
+ return GetSaveData();
+
+ std::size_t position = (headers.size() + event.BossId) * 2;
+ std::string newData = oldData;
+ if (position >= oldData.length())
+ {
+ // Initialize blank data
+ std::ostringstream saveStream;
+ WriteSaveDataHeaders(saveStream);
+ for (std::size_t i = 0; i < bosses.size(); ++i)
+ saveStream << uint32(NOT_STARTED) << ' ';
+
+ WriteSaveDataMore(saveStream);
+
+ newData = saveStream.str();
+ }
+
+ newData[position] = uint32(event.NewState) + '0';
+
+ return newData;
+}
+
void InstanceScript::WriteSaveDataHeaders(std::ostringstream& data)
{
for (char header : headers)
diff --git a/src/server/game/Instances/InstanceScript.h b/src/server/game/Instances/InstanceScript.h
index 07e2cddf916..8e34702361b 100644
--- a/src/server/game/Instances/InstanceScript.h
+++ b/src/server/game/Instances/InstanceScript.h
@@ -21,6 +21,7 @@
#include "ZoneScript.h"
#include "Common.h"
#include "Duration.h"
+#include <array>
#include <iosfwd>
#include <map>
#include <set>
@@ -38,9 +39,11 @@ class InstanceMap;
class ModuleReference;
class Player;
class Unit;
+struct DungeonEncounterEntry;
struct InstanceSpawnGroupInfo;
enum class CriteriaType : uint8;
enum class CriteriaStartEvent : uint8;
+enum Difficulty : uint8;
enum EncounterCreditType : uint8;
enum EncounterFrameType
@@ -77,6 +80,14 @@ enum DoorType
MAX_DOOR_TYPES
};
+static constexpr uint32 MAX_DUNGEON_ENCOUNTERS_PER_BOSS = 4;
+
+struct DungeonEncounterData
+{
+ uint32 BossId;
+ std::array<uint32, MAX_DUNGEON_ENCOUNTERS_PER_BOSS> DungeonEncounterId;
+};
+
struct DoorData
{
uint32 entry, bossId;
@@ -118,11 +129,15 @@ typedef std::vector<AreaBoundary const*> CreatureBoundary;
struct BossInfo
{
- BossInfo() : state(TO_BE_DECIDED) { }
+ BossInfo() : state(TO_BE_DECIDED) { DungeonEncounters.fill(nullptr); }
+
+ DungeonEncounterEntry const* GetDungeonEncounterForDifficulty(Difficulty difficulty) const;
+
EncounterState state;
GuidSet door[MAX_DOOR_TYPES];
GuidSet minion;
CreatureBoundary boundary;
+ std::array<DungeonEncounterEntry const*, MAX_DUNGEON_ENCOUNTERS_PER_BOSS> DungeonEncounters;
};
struct DoorInfo
@@ -139,6 +154,12 @@ struct MinionInfo
BossInfo* bossInfo;
};
+struct UpdateSaveDataEvent
+{
+ uint32 BossId;
+ EncounterState NewState;
+};
+
typedef std::multimap<uint32 /*entry*/, DoorInfo> DoorInfoMap;
typedef std::pair<DoorInfoMap::const_iterator, DoorInfoMap::const_iterator> DoorInfoMapBounds;
@@ -164,10 +185,12 @@ class TC_GAME_API InstanceScript : public ZoneScript
// When save is needed, this function generates the data
virtual std::string GetSaveData();
+ virtual std::string UpdateSaveData(std::string const& oldData, UpdateSaveDataEvent const& event);
+
void SaveToDB();
virtual void Update(uint32 /*diff*/) { }
- void UpdateCombatResurrection(uint32 /*diff*/);
+ void UpdateCombatResurrection(uint32 diff);
// Used by the map's CannotEnter function.
// This is to prevent players from entering during boss encounters.
@@ -289,6 +312,12 @@ class TC_GAME_API InstanceScript : public ZoneScript
void LoadDoorData(DoorData const* data);
void LoadMinionData(MinionData const* data);
void LoadObjectData(ObjectData const* creatureData, ObjectData const* gameObjectData);
+ template<typename T>
+ void LoadDungeonEncounterData(T const& encounters)
+ {
+ for (DungeonEncounterData const& encounter : encounters)
+ LoadDungeonEncounterData(encounter.BossId, encounter.DungeonEncounterId);
+ }
void AddObject(Creature* obj, bool add);
void AddObject(GameObject* obj, bool add);
@@ -318,6 +347,7 @@ class TC_GAME_API InstanceScript : public ZoneScript
private:
static void LoadObjectData(ObjectData const* creatureData, ObjectInfoMap& objectInfo);
+ void LoadDungeonEncounterData(uint32 bossId, std::array<uint32, MAX_DUNGEON_ENCOUNTERS_PER_BOSS> const& dungeonEncounterIds);
void UpdateEncounterState(EncounterCreditType type, uint32 creditEntry, Unit* source);
std::vector<char> headers;
@@ -327,7 +357,7 @@ class TC_GAME_API InstanceScript : public ZoneScript
ObjectInfoMap _creatureInfo;
ObjectInfoMap _gameObjectInfo;
ObjectGuidMap _objectGuids;
- uint32 completedEncounters; // completed encounter mask, bit indexes are DungeonEncounter.dbc boss numbers, used for packets
+ uint32 completedEncounters; // DEPRECATED, REMOVE
std::vector<InstanceSpawnGroupInfo> const* const _instanceSpawnGroups;
std::unordered_set<uint32> _activatedAreaTriggers;
uint32 _entranceId;
diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp
index d828a3f7cbd..a30e752f555 100644
--- a/src/server/game/Maps/Map.cpp
+++ b/src/server/game/Maps/Map.cpp
@@ -31,6 +31,7 @@
#include "GridNotifiersImpl.h"
#include "GridStates.h"
#include "Group.h"
+#include "InstanceLockMgr.h"
#include "InstancePackets.h"
#include "InstanceSaveMgr.h"
#include "InstanceScenario.h"
@@ -1754,7 +1755,7 @@ bool Map::getObjectHitPos(PhaseShift const& phaseShift, float x1, float y1, floa
return result;
}
-Map::EnterState Map::PlayerCannotEnter(uint32 mapid, Player* player, bool loginCheck)
+Map::EnterState Map::PlayerCannotEnter(uint32 mapid, Player* player, bool /*loginCheck*/)
{
MapEntry const* entry = sMapStore.LookupEntry(mapid);
if (!entry)
@@ -1809,26 +1810,19 @@ Map::EnterState Map::PlayerCannotEnter(uint32 mapid, Player* player, bool loginC
TC_LOG_DEBUG("maps", "Map::CanPlayerEnter - player '%s' is dead but does not have a corpse!", player->GetName().c_str());
}
- //Get instance where player's group is bound & its map
- if (!loginCheck && group)
+ if (entry->Instanceable())
{
- InstanceGroupBind* boundInstance = group->GetBoundInstance(entry);
- if (boundInstance && boundInstance->save)
- if (Map* boundMap = sMapMgr->FindMap(mapid, boundInstance->save->GetInstanceId()))
+ //Get instance where player's group is bound & its map
+ if (uint32 instanceIdToCheck = sMapMgr->FindInstanceIdForPlayer(mapid, player))
+ {
+ if (Map* boundMap = sMapMgr->FindMap(mapid, instanceIdToCheck))
if (EnterState denyReason = boundMap->CannotEnter(player))
return denyReason;
- }
- // players are only allowed to enter 5 instances per hour
- if (entry->IsDungeon() && (!player->GetGroup() || (player->GetGroup() && !player->GetGroup()->isLFGGroup())))
- {
- uint32 instanceIdToCheck = 0;
- if (InstanceSave* save = player->GetInstanceSave(mapid))
- instanceIdToCheck = save->GetInstanceId();
-
- // instanceId can never be 0 - will not be found
- if (!player->CheckInstanceCount(instanceIdToCheck) && !player->isDead())
- return CANNOT_ENTER_TOO_MANY_INSTANCES;
+ // players are only allowed to enter 10 instances per hour
+ if (entry->IsDungeon() && !player->CheckInstanceCount(instanceIdToCheck) && !player->isDead())
+ return Map::CANNOT_ENTER_TOO_MANY_INSTANCES;
+ }
}
return CAN_ENTER;
@@ -2773,10 +2767,10 @@ template TC_GAME_API void Map::RemoveFromMap(Conversation*, bool);
/* ******* Dungeon Instance Maps ******* */
-InstanceMap::InstanceMap(uint32 id, time_t expiry, uint32 InstanceId, Difficulty SpawnMode, TeamId InstanceTeam)
+InstanceMap::InstanceMap(uint32 id, time_t expiry, uint32 InstanceId, Difficulty SpawnMode, TeamId InstanceTeam, InstanceLock* instanceLock)
: Map(id, expiry, InstanceId, SpawnMode),
m_resetAfterUnload(false), m_unloadWhenEmpty(false),
- i_data(nullptr), i_script_id(0), i_scenario(nullptr)
+ i_data(nullptr), i_script_id(0), i_scenario(nullptr), i_instanceLock(instanceLock)
{
//lets initialize visibility distance for dungeons
InstanceMap::InitVisibilityDistance();
@@ -2830,11 +2824,16 @@ Map::EnterState InstanceMap::CannotEnter(Player* player)
if (!player->IsLoading() && IsRaid() && GetInstanceScript() && GetInstanceScript()->IsEncounterInProgress())
return CANNOT_ENTER_ZONE_IN_COMBAT;
- // cannot enter if player is permanent saved to a different instance id
- if (InstancePlayerBind* playerBind = player->GetBoundInstance(GetId(), GetDifficultyID()))
- if (playerBind->perm && playerBind->save)
- if (playerBind->save->GetInstanceId() != GetInstanceId())
- return CANNOT_ENTER_INSTANCE_BIND_MISMATCH;
+ if (i_instanceLock)
+ {
+ // cannot enter if player is permanent saved to a different instance id
+ TransferAbortReason lockError = sInstanceLockMgr.CanJoinInstanceLock(player->GetGUID(), { GetEntry(), GetMapDifficulty() }, i_instanceLock);
+ if (lockError == TRANSFER_ABORT_LOCKED_TO_DIFFERENT_INSTANCE)
+ return CANNOT_ENTER_INSTANCE_BIND_MISMATCH;
+
+ if (lockError == TRANSFER_ABORT_ALREADY_COMPLETED_ENCOUNTER)
+ return CANNOT_ENTER_ALREADY_COMPLETED_ENCOUNTER;
+ }
return Map::CannotEnter(player);
}
@@ -2844,85 +2843,27 @@ Map::EnterState InstanceMap::CannotEnter(Player* player)
*/
bool InstanceMap::AddPlayerToMap(Player* player, bool initPlayer /*= true*/)
{
- Group* group = player->GetGroup();
-
// increase current instances (hourly limit)
- if (!group || !group->isLFGGroup())
- player->AddInstanceEnterTime(GetInstanceId(), GameTime::GetGameTime());
-
- // get or create an instance save for the map
- InstanceSave* mapSave = sInstanceSaveMgr->GetInstanceSave(GetInstanceId());
- if (!mapSave)
- {
- TC_LOG_DEBUG("maps", "InstanceMap::Add: creating instance save for map %d spawnmode %d with instance id %d", GetId(), GetDifficultyID(), GetInstanceId());
- mapSave = sInstanceSaveMgr->AddInstanceSave(GetId(), GetInstanceId(), GetDifficultyID(), 0, 0, true);
- }
+ player->AddInstanceEnterTime(GetInstanceId(), GameTime::GetGameTime());
- ASSERT(mapSave);
-
- // check for existing instance binds
- InstancePlayerBind* playerBind = player->GetBoundInstance(GetId(), GetDifficultyID());
- if (playerBind && playerBind->perm)
- {
- // cannot enter other instances if bound permanently
- if (playerBind->save != mapSave)
- {
- TC_LOG_ERROR("maps", "InstanceMap::Add: player %s %s is permanently bound to instance %s %d, %d, %d, %d, %d, %d but he is being put into instance %s %d, %d, %d, %d, %d, %d", player->GetName().c_str(), player->GetGUID().ToString().c_str(), GetMapName(), playerBind->save->GetMapId(), playerBind->save->GetInstanceId(), static_cast<uint32>(playerBind->save->GetDifficultyID()), playerBind->save->GetPlayerCount(), playerBind->save->GetGroupCount(), playerBind->save->CanReset(), GetMapName(), mapSave->GetMapId(), mapSave->GetInstanceId(), static_cast<uint32>(mapSave->GetDifficultyID()), mapSave->GetPlayerCount(), mapSave->GetGroupCount(), mapSave->CanReset());
- return false;
- }
- }
- else
+ MapDb2Entries entries{ GetEntry(), GetMapDifficulty() };
+ if (entries.MapDifficulty->HasResetSchedule() && i_instanceLock && i_instanceLock->GetData()->CompletedEncountersMask)
{
- if (group)
+ if (!entries.MapDifficulty->IsUsingEncounterLocks())
{
- // solo saves should have been reset when the map was loaded
- InstanceGroupBind* groupBind = group->GetBoundInstance(this);
- if (playerBind && playerBind->save != mapSave)
+ InstanceLock const* playerLock = sInstanceLockMgr.FindActiveInstanceLock(player->GetGUID(), entries);
+ if (!playerLock || (playerLock->IsExpired() && playerLock->IsExtended()) ||
+ playerLock->GetData()->CompletedEncountersMask != i_instanceLock->GetData()->CompletedEncountersMask)
{
- TC_LOG_ERROR("maps", "InstanceMap::Add: player %s %s is being put into instance %s %d, %d, %d, %d, %d, %d but he is in group %s and is bound to instance %d, %d, %d, %d, %d, %d!", player->GetName().c_str(), player->GetGUID().ToString().c_str(), GetMapName(), mapSave->GetMapId(), mapSave->GetInstanceId(), static_cast<uint32>(mapSave->GetDifficultyID()), mapSave->GetPlayerCount(), mapSave->GetGroupCount(), mapSave->CanReset(), group->GetLeaderGUID().ToString().c_str(), playerBind->save->GetMapId(), playerBind->save->GetInstanceId(), static_cast<uint32>(playerBind->save->GetDifficultyID()), playerBind->save->GetPlayerCount(), playerBind->save->GetGroupCount(), playerBind->save->CanReset());
- if (groupBind)
- TC_LOG_ERROR("maps", "InstanceMap::Add: the group is bound to the instance %s %d, %d, %d, %d, %d, %d", GetMapName(), groupBind->save->GetMapId(), groupBind->save->GetInstanceId(), static_cast<uint32>(groupBind->save->GetDifficultyID()), groupBind->save->GetPlayerCount(), groupBind->save->GetGroupCount(), groupBind->save->CanReset());
- //ABORT();
- return false;
+ WorldPackets::Instance::PendingRaidLock pendingRaidLock;
+ pendingRaidLock.TimeUntilLock = 60000;
+ pendingRaidLock.CompletedMask = i_instanceLock->GetData()->CompletedEncountersMask;
+ pendingRaidLock.Extending = playerLock && playerLock->IsExtended();
+ pendingRaidLock.WarningOnly = entries.Map->IsFlexLocking(); // events it triggers: 1 : INSTANCE_LOCK_WARNING 0 : INSTANCE_LOCK_STOP / INSTANCE_LOCK_START
+ player->GetSession()->SendPacket(pendingRaidLock.Write());
+ if (!entries.Map->IsFlexLocking())
+ player->SetPendingBind(GetInstanceId(), 60000);
}
- // bind to the group or keep using the group save
- if (!groupBind)
- group->BindToInstance(mapSave, false);
- else
- {
- // cannot jump to a different instance without resetting it
- if (groupBind->save != mapSave)
- {
- TC_LOG_ERROR("maps", "InstanceMap::Add: player %s %s is being put into instance %d, %d, %d but he is in group %s which is bound to instance %d, %d, %d!", player->GetName().c_str(), player->GetGUID().ToString().c_str(), mapSave->GetMapId(), mapSave->GetInstanceId(), static_cast<uint32>(mapSave->GetDifficultyID()), group->GetLeaderGUID().ToString().c_str(), groupBind->save->GetMapId(), groupBind->save->GetInstanceId(), static_cast<uint32>(groupBind->save->GetDifficultyID()));
- TC_LOG_ERROR("maps", "MapSave players: %d, group count: %d", mapSave->GetPlayerCount(), mapSave->GetGroupCount());
- if (groupBind->save)
- TC_LOG_ERROR("maps", "GroupBind save players: %d, group count: %d", groupBind->save->GetPlayerCount(), groupBind->save->GetGroupCount());
- else
- TC_LOG_ERROR("maps", "GroupBind save NULL");
- return false;
- }
- // if the group/leader is permanently bound to the instance
- // players also become permanently bound when they enter
- if (groupBind->perm)
- {
- WorldPackets::Instance::PendingRaidLock pendingRaidLock;
- pendingRaidLock.TimeUntilLock = 60000;
- pendingRaidLock.CompletedMask = i_data ? i_data->GetCompletedEncounterMask() : 0;
- pendingRaidLock.Extending = false;
- pendingRaidLock.WarningOnly = false; // events it throws: 1 : INSTANCE_LOCK_WARNING 0 : INSTANCE_LOCK_STOP / INSTANCE_LOCK_START
- player->SendDirectMessage(pendingRaidLock.Write());
- player->SetPendingBind(mapSave->GetInstanceId(), 60000);
- }
- }
- }
- else
- {
- // set up a solo bind or continue using it
- if (!playerBind)
- player->BindToInstance(mapSave, false);
- else
- // cannot jump to a different instance without resetting it
- ASSERT(playerBind->save == mapSave);
}
}
@@ -2983,7 +2924,7 @@ void InstanceMap::RemovePlayerFromMap(Player* player, bool remove)
sInstanceSaveMgr->UnloadInstanceSave(GetInstanceId());
}
-void InstanceMap::CreateInstanceData(bool load)
+void InstanceMap::CreateInstanceData()
{
if (i_data != nullptr)
return;
@@ -2998,24 +2939,18 @@ void InstanceMap::CreateInstanceData(bool load)
if (!i_data)
return;
- if (load)
+ if (i_instanceLock)
{
- /// @todo make a global storage for this
- CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_INSTANCE);
- stmt->setUInt16(0, uint16(GetId()));
- stmt->setUInt32(1, i_InstanceId);
- PreparedQueryResult result = CharacterDatabase.Query(stmt);
-
- if (result)
+ MapDb2Entries entries{ GetEntry(), GetMapDifficulty() };
+ if (entries.IsInstanceIdBound() || IsRaid() || entries.MapDifficulty->IsRestoringDungeonState() || !i_owningGroupRef.isValid())
{
- Field* fields = result->Fetch();
- std::string data = fields[0].GetString();
- i_data->SetCompletedEncountersMask(fields[1].GetUInt32());
- i_data->SetEntranceLocation(fields[2].GetUInt32());
- if (!data.empty())
+ InstanceLockData const* lockData = i_instanceLock->GetInstanceInitializationData();
+ i_data->SetCompletedEncountersMask(lockData->CompletedEncountersMask);
+ i_data->SetEntranceLocation(lockData->EntranceWorldSafeLocId);
+ if (!lockData->Data.empty())
{
TC_LOG_DEBUG("maps", "Loading instance data for `%s` with id %u", sObjectMgr->GetScriptName(i_script_id).c_str(), i_InstanceId);
- i_data->Load(data.c_str());
+ i_data->Load(lockData->Data.c_str());
}
}
}
@@ -3082,51 +3017,74 @@ std::string const& InstanceMap::GetScriptName() const
return sObjectMgr->GetScriptName(i_script_id);
}
-void InstanceMap::PermBindAllPlayers()
+void InstanceMap::UpdateInstanceLock(DungeonEncounterEntry const* dungeonEncounter, UpdateSaveDataEvent const& updateSaveDataEvent)
{
- if (!IsDungeon())
- return;
-
- InstanceSave* save = sInstanceSaveMgr->GetInstanceSave(GetInstanceId());
- if (!save)
+ if (i_instanceLock)
{
- TC_LOG_ERROR("maps", "Cannot bind players to instance map (Name: %s, Entry: %u, Difficulty: %u, ID: %u) because no instance save is available!", GetMapName(), GetId(), static_cast<uint32>(GetDifficultyID()), GetInstanceId());
- return;
- }
+ uint32 instanceCompletedEncounters = i_instanceLock->GetData()->CompletedEncountersMask;
+ if (dungeonEncounter)
+ instanceCompletedEncounters |= 1u << dungeonEncounter->Bit;
- // perm bind all players that are currently inside the instance
- for (MapRefManager::iterator itr = m_mapRefManager.begin(); itr != m_mapRefManager.end(); ++itr)
- {
- Player* player = itr->GetSource();
- // never instance bind GMs with GM mode enabled
- if (player->IsGameMaster())
- continue;
+ MapDb2Entries entries{ GetEntry(), GetMapDifficulty() };
+
+ CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
+
+ if (entries.IsInstanceIdBound())
+ sInstanceLockMgr.UpdateSharedInstanceLock(trans,
+ { GetInstanceId(), i_data->GetSaveData(), instanceCompletedEncounters, dungeonEncounter });
- InstancePlayerBind* bind = player->GetBoundInstance(save->GetMapId(), save->GetDifficultyID());
- if (bind && bind->perm)
+ for (MapReference& mapReference : m_mapRefManager)
{
- if (bind->save && bind->save->GetInstanceId() != save->GetInstanceId())
- {
- TC_LOG_ERROR("maps", "Player (%s, Name: %s) is in instance map (Name: %s, Entry: %u, Difficulty: %u, ID: %u) that is being bound, but already has a save for the map on ID %u!", player->GetGUID().ToString().c_str(), player->GetName().c_str(), GetMapName(), save->GetMapId(), static_cast<uint32>(save->GetDifficultyID()), save->GetInstanceId(), bind->save->GetInstanceId());
- }
- else if (!bind->save)
+ Player* player = mapReference.GetSource();
+ // never instance bind GMs with GM mode enabled
+ if (player->IsGameMaster())
+ continue;
+
+ InstanceLock const* playerLock = sInstanceLockMgr.FindActiveInstanceLock(player->GetGUID(), entries);
+ std::string const* oldData = nullptr;
+ if (playerLock)
+ oldData = &playerLock->GetData()->Data;
+
+ bool isNewLock = !playerLock || !playerLock->GetData()->CompletedEncountersMask || playerLock->IsExpired();
+
+ InstanceLock const* newLock = sInstanceLockMgr.UpdateInstanceLockForPlayer(trans, player->GetGUID(), entries,
+ { GetInstanceId(), i_data->UpdateSaveData(oldData ? *oldData : "", updateSaveDataEvent), instanceCompletedEncounters, dungeonEncounter });
+
+ if (isNewLock)
{
- TC_LOG_ERROR("maps", "Player (%s, Name: %s) is in instance map (Name: %s, Entry: %u, Difficulty: %u, ID: %u) that is being bound, but already has a bind (without associated save) for the map!", player->GetGUID().ToString().c_str(), player->GetName().c_str(), GetMapName(), save->GetMapId(), static_cast<uint32>(save->GetDifficultyID()), save->GetInstanceId());
+ WorldPackets::Instance::InstanceSaveCreated data;
+ data.Gm = player->IsGameMaster();
+ player->SendDirectMessage(data.Write());
+
+ player->GetSession()->SendCalendarRaidLockoutAdded(newLock);
}
}
- else
- {
- player->BindToInstance(save, true);
- WorldPackets::Instance::InstanceSaveCreated data;
- data.Gm = player->IsGameMaster();
- player->SendDirectMessage(data.Write());
- player->GetSession()->SendCalendarRaidLockout(save, true);
-
- // if group leader is in instance, group also gets bound
- if (Group* group = player->GetGroup())
- if (group->GetLeaderGUID() == player->GetGUID())
- group->BindToInstance(save, true);
- }
+
+ CharacterDatabase.CommitTransaction(trans);
+ }
+}
+
+void InstanceMap::CreateInstanceLockForPlayer(Player* player)
+{
+ MapDb2Entries entries{ GetEntry(), GetMapDifficulty() };
+ InstanceLock const* playerLock = sInstanceLockMgr.FindActiveInstanceLock(player->GetGUID(), entries);
+
+ bool isNewLock = !playerLock || !playerLock->GetData()->CompletedEncountersMask || playerLock->IsExpired();
+
+ CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
+
+ InstanceLock const* newLock = sInstanceLockMgr.UpdateInstanceLockForPlayer(trans, player->GetGUID(), entries,
+ { GetInstanceId(), i_data->GetSaveData(), i_instanceLock->GetData()->CompletedEncountersMask, nullptr });
+
+ CharacterDatabase.CommitTransaction(trans);
+
+ if (isNewLock)
+ {
+ WorldPackets::Instance::InstanceSaveCreated data;
+ data.Gm = player->IsGameMaster();
+ player->SendDirectMessage(data.Write());
+
+ player->GetSession()->SendCalendarRaidLockoutAdded(newLock);
}
}
diff --git a/src/server/game/Maps/Map.h b/src/server/game/Maps/Map.h
index f031b1832fd..e3d622f49b0 100644
--- a/src/server/game/Maps/Map.h
+++ b/src/server/game/Maps/Map.h
@@ -49,6 +49,7 @@ class BattlegroundMap;
class CreatureGroup;
class GameObjectModel;
class Group;
+class InstanceLock;
class InstanceMap;
class InstanceSave;
class InstanceScript;
@@ -63,6 +64,8 @@ class Unit;
class Weather;
class WorldObject;
class WorldPacket;
+struct DungeonEncounterEntry;
+struct UpdateSaveDataEvent;
struct MapDifficultyEntry;
struct MapEntry;
struct Position;
@@ -268,6 +271,7 @@ class TC_GAME_API Map : public GridRefManager<NGridType>
CANNOT_ENTER_NOT_IN_RAID, // Target instance is a raid instance and the player is not in a raid group
CANNOT_ENTER_CORPSE_IN_DIFFERENT_INSTANCE, // Player is dead and their corpse is not in target instance
CANNOT_ENTER_INSTANCE_BIND_MISMATCH, // Player's permanent instance save is not compatible with their group's current instance bind
+ CANNOT_ENTER_ALREADY_COMPLETED_ENCOUNTER, // Player is locked to encounter that wasn't defeated in the instance yet
CANNOT_ENTER_TOO_MANY_INSTANCES, // Player has entered too many instances recently
CANNOT_ENTER_MAX_PLAYERS, // Target map already has the maximum number of players allowed
CANNOT_ENTER_ZONE_IN_COMBAT, // A boss encounter is currently in progress on the target map
@@ -796,12 +800,12 @@ enum InstanceResetMethod
class TC_GAME_API InstanceMap : public Map
{
public:
- InstanceMap(uint32 id, time_t, uint32 InstanceId, Difficulty SpawnMode, TeamId InstanceTeam);
+ InstanceMap(uint32 id, time_t, uint32 InstanceId, Difficulty SpawnMode, TeamId InstanceTeam, InstanceLock* instanceLock);
~InstanceMap();
bool AddPlayerToMap(Player* player, bool initPlayer = true) override;
void RemovePlayerFromMap(Player*, bool) override;
void Update(uint32) override;
- void CreateInstanceData(bool load);
+ void CreateInstanceData();
bool Reset(uint8 method);
uint32 GetScriptId() const { return i_script_id; }
std::string const& GetScriptName() const;
@@ -810,7 +814,9 @@ class TC_GAME_API InstanceMap : public Map
InstanceScenario* GetInstanceScenario() { return i_scenario; }
InstanceScenario const* GetInstanceScenario() const { return i_scenario; }
void SetInstanceScenario(InstanceScenario* scenario) { i_scenario = scenario; }
- void PermBindAllPlayers();
+ InstanceLock const* GetInstanceLock() const { return i_instanceLock; }
+ void UpdateInstanceLock(DungeonEncounterEntry const* dungeonEncounter, UpdateSaveDataEvent const& updateSaveDataEvent);
+ void CreateInstanceLockForPlayer(Player* player);
void UnloadAll() override;
EnterState CannotEnter(Player* player) override;
void SendResetWarnings(uint32 timeLeft) const;
@@ -833,6 +839,7 @@ class TC_GAME_API InstanceMap : public Map
InstanceScript* i_data;
uint32 i_script_id;
InstanceScenario* i_scenario;
+ InstanceLock* i_instanceLock;
};
class TC_GAME_API BattlegroundMap : public Map
diff --git a/src/server/game/Maps/MapManager.cpp b/src/server/game/Maps/MapManager.cpp
index 3f5d54b4721..a89324eb1a1 100644
--- a/src/server/game/Maps/MapManager.cpp
+++ b/src/server/game/Maps/MapManager.cpp
@@ -22,6 +22,7 @@
#include "DB2Stores.h"
#include "GarrisonMap.h"
#include "Group.h"
+#include "InstanceLockMgr.h"
#include "InstanceSaveMgr.h"
#include "Log.h"
#include "Map.h"
@@ -81,7 +82,7 @@ Map* MapManager::CreateWorldMap(uint32 mapId, uint32 instanceId)
return map;
}
-InstanceMap* MapManager::CreateInstance(uint32 mapId, uint32 instanceId, InstanceSave* save, Difficulty difficulty, TeamId team)
+InstanceMap* MapManager::CreateInstance(uint32 mapId, uint32 instanceId, InstanceLock* instanceLock, Difficulty difficulty, TeamId team, Group* group)
{
// make sure we have a valid map id
MapEntry const* entry = sMapStore.LookupEntry(mapId);
@@ -94,18 +95,17 @@ InstanceMap* MapManager::CreateInstance(uint32 mapId, uint32 instanceId, Instanc
// some instances only have one difficulty
sDB2Manager.GetDownscaledMapDifficultyData(mapId, difficulty);
- TC_LOG_DEBUG("maps", "MapInstanced::CreateInstance: %s map instance %d for %d created with difficulty %u", save ? "" : "new ", instanceId, mapId, static_cast<uint32>(difficulty));
+ TC_LOG_DEBUG("maps", "MapInstanced::CreateInstance: %smap instance %d for %d created with difficulty %s",
+ instanceLock && instanceLock->GetInstanceId() ? "" : "new ", instanceId, mapId, sDifficultyStore.AssertEntry(difficulty)->Name[sWorld->GetDefaultDbcLocale()]);
- InstanceMap* map = new InstanceMap(mapId, i_gridCleanUpDelay, instanceId, difficulty, team);
+ InstanceMap* map = new InstanceMap(mapId, i_gridCleanUpDelay, instanceId, difficulty, team, instanceLock);
ASSERT(map->IsDungeon());
map->LoadRespawnTimes();
map->LoadCorpseData();
- bool load_data = save != nullptr;
- map->CreateInstanceData(load_data);
- if (InstanceScenario* instanceScenario = sScenarioMgr->CreateInstanceScenario(map, team))
- map->SetInstanceScenario(instanceScenario);
+ map->CreateInstanceData();
+ map->SetInstanceScenario(sScenarioMgr->CreateInstanceScenario(map, team));
if (sWorld->getBoolConfig(CONFIG_INSTANCEMAP_LOAD_GRIDS))
map->LoadAllCells();
@@ -136,7 +136,7 @@ GarrisonMap* MapManager::CreateGarrison(uint32 mapId, uint32 instanceId, Player*
- create the instance if it's not created already
- the player is not actually added to the instance (only in InstanceMap::Add)
*/
-Map* MapManager::CreateMap(uint32 mapId, Player* player, uint32 loginInstanceId /*= 0*/)
+Map* MapManager::CreateMap(uint32 mapId, Player* player)
{
if (!player)
return nullptr;
@@ -172,63 +172,50 @@ Map* MapManager::CreateMap(uint32 mapId, Player* player, uint32 loginInstanceId
}
else if (entry->IsDungeon())
{
- InstancePlayerBind* pBind = player->GetBoundInstance(mapId, player->GetDifficultyID(entry));
- InstanceSave* pSave = pBind ? pBind->save : nullptr;
-
- // priority:
- // 1. player's permanent bind
- // 2. player's current instance id if this is at login
- // 3. group's current bind
- // 4. player's current bind
- if (!pBind || !pBind->perm)
+ Group* group = player->GetGroup();
+ Difficulty difficulty = group ? group->GetDifficultyID(entry) : player->GetDifficultyID(entry);
+ MapDb2Entries entries{ entry, sDB2Manager.GetDownscaledMapDifficultyData(mapId, difficulty) };
+ ObjectGuid instanceOwnerGuid = group ? group->GetRecentInstanceOwner(mapId) : player->GetGUID();
+ InstanceLock* instanceLock = sInstanceLockMgr.FindActiveInstanceLock(instanceOwnerGuid, entries);
+ if (instanceLock)
{
- if (loginInstanceId) // if the player has a saved instance id on login, we either use this instance or relocate him out (return null)
- {
- map = FindMap_i(mapId, loginInstanceId);
- if (!map && pSave && pSave->GetInstanceId() == loginInstanceId)
- {
- map = CreateInstance(mapId, loginInstanceId, pSave, pSave->GetDifficultyID(), player->GetTeamId());
- i_maps[{ map->GetId(), map->GetInstanceId() }] = map;
- }
- return map;
- }
+ newInstanceId = instanceLock->GetInstanceId();
- InstanceGroupBind* groupBind = nullptr;
- Group* group = player->GetGroup();
- // use the player's difficulty setting (it may not be the same as the group's)
- if (group)
- {
- groupBind = group->GetBoundInstance(entry);
- if (groupBind)
- {
- // solo saves should be reset when entering a group's instance
- player->UnbindInstance(mapId, player->GetDifficultyID(entry));
- pSave = groupBind->save;
- }
- }
- }
- if (pSave)
- {
- // solo/perm/group
- newInstanceId = pSave->GetInstanceId();
- map = FindMap_i(mapId, newInstanceId);
- // it is possible that the save exists but the map doesn't
- if (!map)
- map = CreateInstance(mapId, newInstanceId, pSave, pSave->GetDifficultyID(), player->GetTeamId());
+ // Reset difficulty to the one used in instance lock
+ if (!entries.Map->IsFlexLocking())
+ difficulty = instanceLock->GetDifficultyId();
}
else
{
- Difficulty diff = player->GetGroup() ? player->GetGroup()->GetDifficultyID(entry) : player->GetDifficultyID(entry);
+ // Try finding instance id for normal dungeon
+ if (!entries.MapDifficulty->HasResetSchedule())
+ newInstanceId = group ? group->GetRecentInstanceId(mapId) : player->GetRecentInstanceId(mapId);
+
+ // If not found or instance is not a normal dungeon, generate new one
+ if (!newInstanceId)
+ newInstanceId = GenerateInstanceId();
- // if no instanceId via group members or instance saves is found
- // the instance will be created for the first time
+ instanceLock = sInstanceLockMgr.CreateInstanceLockForNewInstance(instanceOwnerGuid, entries, newInstanceId);
+ }
+
+ // it is possible that the save exists but the map doesn't
+ map = FindMap_i(mapId, newInstanceId);
+
+ // is is also possible that instance id is already in use by another group for boss-based locks
+ if (!entries.IsInstanceIdBound() && instanceLock && map && map->ToInstanceMap()->GetInstanceLock() != instanceLock)
+ {
newInstanceId = GenerateInstanceId();
+ instanceLock->SetInstanceId(newInstanceId);
+ map = nullptr;
+ }
- //Seems it is now possible, but I do not know if it should be allowed
- //ASSERT(!FindInstanceMap(NewInstanceId));
- map = FindMap_i(mapId, newInstanceId);
- if (!map)
- map = CreateInstance(mapId, newInstanceId, nullptr, diff, player->GetTeamId());
+ if (!map)
+ {
+ map = CreateInstance(mapId, newInstanceId, instanceLock, difficulty, player->GetTeamId(), group);
+ if (group)
+ group->SetRecentInstance(mapId, instanceOwnerGuid, newInstanceId);
+ else
+ player->SetRecentInstance(mapId, newInstanceId);
}
}
else if (entry->IsGarrison())
@@ -352,10 +339,14 @@ void MapManager::InitInstanceIds()
{
_nextInstanceId = 1;
- if (QueryResult result = CharacterDatabase.Query("SELECT IFNULL(MAX(id), 0) FROM instance"))
- _freeInstanceIds->resize((*result)[0].GetUInt64() + 2, true); // make space for one extra to be able to access [_nextInstanceId] index in case all slots are taken
- else
- _freeInstanceIds->resize(_nextInstanceId + 1, true);
+ uint64 maxExistingInstanceId = 0;
+ if (QueryResult result = CharacterDatabase.Query("SELECT IFNULL(MAX(instanceId), 0) FROM instance"))
+ maxExistingInstanceId = std::max(maxExistingInstanceId, (*result)[0].GetUInt64());
+
+ if (QueryResult result = CharacterDatabase.Query("SELECT IFNULL(MAX(instanceId), 0) FROM character_instance_lock"))
+ maxExistingInstanceId = std::max(maxExistingInstanceId, (*result)[0].GetUInt64());
+
+ _freeInstanceIds->resize(maxExistingInstanceId + 2, true); // make space for one extra to be able to access [_nextInstanceId] index in case all slots are taken
// never allow 0 id
_freeInstanceIds->set(0, false);
diff --git a/src/server/game/Maps/MapManager.h b/src/server/game/Maps/MapManager.h
index 304ca859c81..474972707a8 100644
--- a/src/server/game/Maps/MapManager.h
+++ b/src/server/game/Maps/MapManager.h
@@ -30,8 +30,9 @@
class Battleground;
class BattlegroundMap;
class GarrisonMap;
+class Group;
+class InstanceLock;
class InstanceMap;
-class InstanceSave;
class Map;
class Player;
enum Difficulty : uint8;
@@ -49,7 +50,7 @@ class TC_GAME_API MapManager
static MapManager* instance();
- Map* CreateMap(uint32 mapId, Player* player, uint32 loginInstanceId = 0);
+ Map* CreateMap(uint32 mapId, Player* player);
Map* FindMap(uint32 mapId, uint32 instanceId) const;
void Initialize();
@@ -136,7 +137,7 @@ class TC_GAME_API MapManager
Map* FindMap_i(uint32 mapId, uint32 instanceId) const;
Map* CreateWorldMap(uint32 mapId, uint32 instanceId);
- InstanceMap* CreateInstance(uint32 mapId, uint32 instanceId, InstanceSave* save, Difficulty difficulty, TeamId team);
+ InstanceMap* CreateInstance(uint32 mapId, uint32 instanceId, InstanceLock* instanceLock, Difficulty difficulty, TeamId team, Group* group);
BattlegroundMap* CreateBattleground(uint32 mapId, uint32 instanceId, Battleground* bg);
GarrisonMap* CreateGarrison(uint32 mapId, uint32 instanceId, Player* owner);
diff --git a/src/server/game/Server/Packets/CalendarPackets.cpp b/src/server/game/Server/Packets/CalendarPackets.cpp
index 870761ae3c6..1e42362a480 100644
--- a/src/server/game/Server/Packets/CalendarPackets.cpp
+++ b/src/server/game/Server/Packets/CalendarPackets.cpp
@@ -39,7 +39,7 @@ ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Calendar::CalendarSendCal
data << uint64(lockoutInfo.InstanceID);
data << int32(lockoutInfo.MapID);
data << uint32(lockoutInfo.DifficultyID);
- data << uint32(lockoutInfo.ExpireTime);
+ data << int32(lockoutInfo.ExpireTime);
return data;
}
diff --git a/src/server/game/Server/Packets/CalendarPackets.h b/src/server/game/Server/Packets/CalendarPackets.h
index 1f292ca1468..4ee6ddbfc3a 100644
--- a/src/server/game/Server/Packets/CalendarPackets.h
+++ b/src/server/game/Server/Packets/CalendarPackets.h
@@ -174,7 +174,7 @@ namespace WorldPackets
uint64 InstanceID = 0;
int32 MapID = 0;
uint32 DifficultyID = 0;
- time_t ExpireTime = time_t(0);
+ int32 ExpireTime = 0;
};
struct CalendarSendCalendarEventInfo
diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h
index 05e376b8e70..4433c91fa31 100644
--- a/src/server/game/Server/WorldSession.h
+++ b/src/server/game/Server/WorldSession.h
@@ -43,6 +43,7 @@
class BlackMarketEntry;
class CollectionMgr;
class Creature;
+class InstanceLock;
class InstanceSave;
class Item;
class LoginQueryHolder;
@@ -1682,8 +1683,10 @@ class TC_GAME_API WorldSession
void HandleCalendarGetNumPending(WorldPackets::Calendar::CalendarGetNumPending& calendarGetNumPending);
void HandleCalendarEventSignup(WorldPackets::Calendar::CalendarEventSignUp& calendarEventSignUp);
- void SendCalendarRaidLockout(InstanceSave const* save, bool add);
+ void SendCalendarRaidLockoutAdded(InstanceLock const* lock);
void SendCalendarRaidLockoutUpdated(InstanceSave const* save);
+ void SendCalendarRaidLockoutRemoved(InstanceSave const* save);
+ void SendCalendarRaidLockoutRemoved(InstanceLock const* lock);
void HandleSetSavedInstanceExtend(WorldPackets::Calendar::SetSavedInstanceExtend& setSavedInstanceExtend);
// Void Storage
diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp
index afdb02cb3cd..cda9e94cf83 100644
--- a/src/server/game/World/World.cpp
+++ b/src/server/game/World/World.cpp
@@ -58,6 +58,7 @@
#include "GridNotifiersImpl.h"
#include "GroupMgr.h"
#include "GuildMgr.h"
+#include "InstanceLockMgr.h"
#include "InstanceSaveMgr.h"
#include "IPLocation.h"
#include "Language.h"
@@ -1879,6 +1880,8 @@ void World::SetInitialWorldSettings()
TC_LOG_INFO("server.loading", "Loading instances...");
sInstanceSaveMgr->LoadInstances();
+ sInstanceLockMgr.Load();
+
TC_LOG_INFO("server.loading", "Loading Localization strings...");
uint32 oldMSTime = getMSTime();
sObjectMgr->LoadCreatureLocales();
diff --git a/src/server/scripts/Commands/cs_misc.cpp b/src/server/scripts/Commands/cs_misc.cpp
index 38fda7ba51c..68b6d1b4d3e 100644
--- a/src/server/scripts/Commands/cs_misc.cpp
+++ b/src/server/scripts/Commands/cs_misc.cpp
@@ -419,19 +419,6 @@ public:
}
}
- // if the player or the player's group is bound to another instance
- // the player will not be bound to another one
- InstancePlayerBind* bind = _player->GetBoundInstance(target->GetMapId(), target->GetDifficultyID(map->GetEntry()));
- if (!bind)
- {
- Group* group = _player->GetGroup();
- // if no bind exists, create a solo bind
- InstanceGroupBind* gBind = group ? group->GetBoundInstance(target) : nullptr; // if no bind exists, create a solo bind
- if (!gBind)
- if (InstanceSave* save = sInstanceSaveMgr->GetInstanceSave(target->GetInstanceId()))
- _player->BindToInstance(save, !save->CanReset());
- }
-
if (map->IsRaid())
{
_player->SetRaidDifficultyID(target->GetRaidDifficultyID());
diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/CullingOfStratholme/boss_mal_ganis.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/CullingOfStratholme/boss_mal_ganis.cpp
index c56dbef37d6..29e3025b92f 100644
--- a/src/server/scripts/Kalimdor/CavernsOfTime/CullingOfStratholme/boss_mal_ganis.cpp
+++ b/src/server/scripts/Kalimdor/CavernsOfTime/CullingOfStratholme/boss_mal_ganis.cpp
@@ -82,10 +82,6 @@ class boss_mal_ganis : public CreatureScript
if (_defeated)
return;
_defeated = true;
-
- // @todo hack most likely
- if (InstanceMap* map = instance->instance->ToInstanceMap())
- map->PermBindAllPlayers();
}
}
diff --git a/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp b/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp
index b681d068a1f..6b71835447b 100644
--- a/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp
+++ b/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp
@@ -106,6 +106,22 @@ DoorData const doorData[] =
{ 0, 0, DOOR_TYPE_ROOM } // END
};
+DungeonEncounterData const encounters[] =
+{
+ { DATA_LORD_MARROWGAR, {{ 1101 }} },
+ { DATA_LADY_DEATHWHISPER, {{ 1100 }} },
+ { DATA_ICECROWN_GUNSHIP_BATTLE, {{ 1099 }} },
+ { DATA_DEATHBRINGER_SAURFANG, {{ 1096 }} },
+ { DATA_FESTERGUT, {{ 1097 }} },
+ { DATA_ROTFACE, {{ 1104 }} },
+ { DATA_PROFESSOR_PUTRICIDE, {{ 1102 }} },
+ { DATA_BLOOD_PRINCE_COUNCIL, {{ 1095 }} },
+ { DATA_BLOOD_QUEEN_LANA_THEL, {{ 1103 }} },
+ { DATA_VALITHRIA_DREAMWALKER, {{ 1098 }} },
+ { DATA_SINDRAGOSA, {{ 1105 }} },
+ { DATA_THE_LICH_KING, {{ 1106 }} }
+};
+
// this doesnt have to only store questgivers, also can be used for related quest spawns
struct WeeklyQuest
{
@@ -144,6 +160,7 @@ class instance_icecrown_citadel : public InstanceMapScript
{
SetHeaders(DataHeader);
SetBossNumber(EncounterCount);
+ LoadDungeonEncounterData(encounters);
LoadBossBoundaries(boundaries);
LoadDoorData(doorData);
HeroicAttempts = MaxHeroicAttempts;
diff --git a/src/server/worldserver/Main.cpp b/src/server/worldserver/Main.cpp
index e226287b58c..efc3f3cb298 100644
--- a/src/server/worldserver/Main.cpp
+++ b/src/server/worldserver/Main.cpp
@@ -32,6 +32,7 @@
#include "DatabaseLoader.h"
#include "DeadlineTimer.h"
#include "GitRevision.h"
+#include "InstanceLockMgr.h"
#include "InstanceSaveMgr.h"
#include "IoContext.h"
#include "MapManager.h"
@@ -320,6 +321,7 @@ extern int main(int argc, char** argv)
sOutdoorPvPMgr->Die(); // unload it before MapManager
sMapMgr->UnloadAll(); // unload all grids (including locked in memory)
sTerrainMgr.UnloadAll();
+ sInstanceLockMgr.Unload();
});
// Start the Remote Access port (acceptor) if enabled
diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist
index fd92d54234b..05bec77ed07 100644
--- a/src/server/worldserver/worldserver.conf.dist
+++ b/src/server/worldserver/worldserver.conf.dist
@@ -4134,6 +4134,7 @@ Logger.mmaps=3,Server
#Logger.entities.vehicle=3,Console Server
#Logger.gameevent=3,Console Server
#Logger.guild=3,Console Server
+#Logger.instance.locks=3,Console Server
#Logger.lfg=3,Console Server
#Logger.loot=3,Console Server
#Logger.maps.script=3,Console Server