diff options
author | Shauren <shauren.trinity@gmail.com> | 2018-07-22 13:22:35 +0200 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2022-10-04 00:19:38 +0200 |
commit | 17665c929c3a9fb7fe75dd680648129bc1c1f874 (patch) | |
tree | a489cb742b7c5f3d7850d26157b3ac480aa00633 /src | |
parent | ad2df01b2c25ca6264096b8b8324dc8136ebd48b (diff) |
Core/Instances: Instance lock rewrite (WIP)
Diffstat (limited to 'src')
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 |