From 00690192b4acc5228e8dddd4c88844ff9e0dced9 Mon Sep 17 00:00:00 2001 From: hater Date: Wed, 21 Feb 2024 03:02:38 -0500 Subject: [PATCH] retail like lockouts with infinite money glitch; fix respawns --- data/sql/custom/db_characters/20230101_01.sql | 5 +- data/sql/custom/db_characters/20230918_01.sql | 8 +- .../Implementation/CharacterDatabase.cpp | 45 +- .../Implementation/CharacterDatabase.h | 30 +- src/server/game/DungeonFinding/LFGMgr.cpp | 417 ---------- src/server/game/DungeonFinding/LFGMgr.h | 3 - src/server/game/DungeonFinding/LFGScripts.cpp | 31 +- src/server/game/DungeonFinding/LFGScripts.h | 1 - .../game/Entities/GameObject/GameObject.cpp | 119 +-- .../game/Entities/GameObject/GameObject.h | 12 - src/server/game/Entities/Object/Object.h | 4 +- .../game/Entities/Object/ObjectDefines.h | 6 + src/server/game/Entities/Player/Player.cpp | 31 +- src/server/game/Entities/Player/Player.h | 50 +- .../game/Entities/Player/PlayerMisc.cpp | 150 +--- .../game/Entities/Player/PlayerStorage.cpp | 356 +++++++-- .../game/Globals/AreaTriggerDataStore.cpp | 10 - src/server/game/Globals/ObjectMgr.cpp | 91 +-- src/server/game/Globals/ObjectMgr.h | 10 +- src/server/game/Groups/Group.cpp | 401 +++++++--- src/server/game/Groups/Group.h | 32 +- src/server/game/Groups/GroupMgr.cpp | 115 ++- src/server/game/Groups/GroupMgr.h | 12 + src/server/game/Handlers/CalendarHandler.cpp | 88 +-- src/server/game/Handlers/CharacterHandler.cpp | 4 + src/server/game/Handlers/LFGHandler.cpp | 2 - src/server/game/Handlers/MiscHandler.cpp | 139 +--- src/server/game/Handlers/MovementHandler.cpp | 19 +- src/server/game/Instances/InstanceSaveMgr.cpp | 742 ++++++++---------- src/server/game/Instances/InstanceSaveMgr.h | 194 +++-- src/server/game/Instances/InstanceScript.cpp | 2 +- src/server/game/Instances/InstanceScript.h | 6 +- src/server/game/Maps/Map.cpp | 255 +++--- src/server/game/Maps/Map.h | 20 +- src/server/game/Maps/MapInstanced.cpp | 71 +- src/server/game/Maps/MapInstanced.h | 2 +- src/server/game/Maps/MapMgr.cpp | 66 +- src/server/game/Maps/MapMgr.h | 19 +- .../Scripting/ScriptDefines/PlayerScript.cpp | 4 +- .../Scripting/ScriptDefines/PlayerScript.h | 2 +- src/server/game/Scripting/ScriptMgr.h | 2 +- src/server/game/Spells/Auras/SpellAuras.cpp | 18 - src/server/game/World/World.cpp | 6 +- src/server/scripts/Commands/cs_instance.cpp | 64 +- src/server/scripts/Commands/cs_misc.cpp | 14 +- .../Deadmines/instance_deadmines.cpp | 28 - .../Gnomeregan/instance_gnomeregan.cpp | 13 - .../Scholomance/instance_scholomance.cpp | 3 - .../Stratholme/instance_stratholme.cpp | 77 -- 49 files changed, 1707 insertions(+), 2092 deletions(-) diff --git a/data/sql/custom/db_characters/20230101_01.sql b/data/sql/custom/db_characters/20230101_01.sql index ad4f79947c3205..d5a4ce6314e20a 100644 --- a/data/sql/custom/db_characters/20230101_01.sql +++ b/data/sql/custom/db_characters/20230101_01.sql @@ -1,3 +1,4 @@ +DROP TABLE IF EXiSTS `character_accountwide_reputation`; CREATE TABLE `acore_characters`.`character_accountwide_reputation` ( `accountId` INT UNSIGNED NOT NULL, `factionGroup` INT UNSIGNED NOT NULL, @@ -5,18 +6,20 @@ CREATE TABLE `acore_characters`.`character_accountwide_reputation` ( `rep` INT UNSIGNED NULL, PRIMARY KEY (`accountId`, `factionGroup`, `factionId`)); - +DROP TABLE IF EXiSTS `character_accountwide_taxi`; CREATE TABLE `acore_characters`.`character_accountwide_taxi` ( `accountId` INT UNSIGNED NOT NULL, `faction` INT UNSIGNED NOT NULL, `node` INT UNSIGNED NOT NULL, PRIMARY KEY (`accountId`, `faction`, `node`)); +DROP TABLE IF EXiSTS `character_accountwide_title`; CREATE TABLE `acore_characters`.`character_accountwide_title` ( `accountId` INT UNSIGNED NOT NULL, `title` INT UNSIGNED NOT NULL, PRIMARY KEY (`accountId`, `title`)); +DROP TABLE IF EXiSTS `character_accountwide_mount`; CREATE TABLE `acore_characters`.`character_accountwide_mount` ( `accountId` INT UNSIGNED NOT NULL, `spellId` INT UNSIGNED NOT NULL, diff --git a/data/sql/custom/db_characters/20230918_01.sql b/data/sql/custom/db_characters/20230918_01.sql index 0a6340a83b3d32..8b7260e264c615 100644 --- a/data/sql/custom/db_characters/20230918_01.sql +++ b/data/sql/custom/db_characters/20230918_01.sql @@ -32,4 +32,10 @@ CREATE TABLE `forge_character_transmogsets` ( `tabard` int unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`guid`,`setid`), KEY `Character` (`guid`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; \ No newline at end of file +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; + +ALTER table acore_characters.`character_instance` +add column `entranceId` int not null default 0; + +ALTER table acore_characters.`instance` +add column `entranceId` int not null default 0 after `completedEncounters`; \ No newline at end of file diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 2e94f5f932fa95..5bbe0e533701a2 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -261,7 +261,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_DEL_TUTORIALS, "DELETE FROM account_tutorial WHERE accountId = ?", CONNECTION_ASYNC); // Instance saves - PrepareStatement(CHAR_INS_INSTANCE_SAVE, "INSERT INTO instance (id, map, resettime, difficulty, completedEncounters, data) VALUES (?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_INSTANCE_SAVE, "INSERT INTO instance (id, map, resettime, difficulty, completedEncounters, data, entranceId) VALUES (?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_INSTANCE_SAVE_DATA, "UPDATE instance SET data=? WHERE id=?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_INSTANCE_SAVE_ENCOUNTERMASK, "UPDATE instance SET completedEncounters=? WHERE id=?", CONNECTION_ASYNC); @@ -363,6 +363,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_INS_GROUP, "INSERT INTO `groups` (guid, leaderGuid, lootMethod, looterGuid, lootThreshold, icon1, icon2, icon3, icon4, icon5, icon6, icon7, icon8, groupType, difficulty, raidDifficulty, masterLooterGuid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_REP_GROUP_MEMBER, "REPLACE INTO group_member (guid, memberGuid, memberFlags, subgroup, roles) VALUES(?, ?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_GROUP_MEMBER, "DELETE FROM group_member WHERE memberGuid = ? AND guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_GROUP_INSTANCE_PERM_BINDING, "DELETE FROM group_instance WHERE guid = ? AND `instance` = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_GROUP_LEADER, "UPDATE `groups` SET leaderGuid = ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_GROUP_TYPE, "UPDATE `groups` SET groupType = ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_GROUP_MEMBER_SUBGROUP, "UPDATE group_member SET subgroup = ? WHERE memberGuid = ?", CONNECTION_ASYNC); @@ -381,7 +382,15 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_DEL_INVALID_ACHIEVMENT, "DELETE FROM character_achievement WHERE achievement = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_ADDON, "INSERT INTO addons (name, crc) VALUES (?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_INVALID_PET_SPELL, "DELETE FROM pet_spell WHERE spell = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_UPD_GLOBAL_INSTANCE_RESETTIME, "UPDATE instance_reset SET resettime = ? WHERE mapid = ? AND difficulty = ?", CONNECTION_ASYNC); + + PrepareStatement(CHAR_DEL_GROUP_INSTANCE_BY_INSTANCE, "DELETE FROM group_instance WHERE instance = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_GROUP_INSTANCE_BY_GUID, "DELETE FROM group_instance WHERE guid = ? AND instance = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_REP_GROUP_INSTANCE, "REPLACE INTO group_instance (guid, instance, permanent) VALUES (?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_INSTANCE_RESETTIME, "UPDATE instance SET resettime = ? WHERE id = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_GLOBAL_INSTANCE_RESETTIME, "INSERT INTO instance_reset (mapid, difficulty, resettime) VALUES (?, ?, ?)", CONNECTION_SYNCH); + PrepareStatement(CHAR_DEL_GLOBAL_INSTANCE_RESETTIME, "DELETE FROM instance_reset WHERE mapid = ? AND difficulty = ?", CONNECTION_SYNCH); + PrepareStatement(CHAR_UPD_GLOBAL_INSTANCE_RESETTIME, "UPDATE instance_reset SET resettime = ? WHERE mapid = ? AND difficulty = ?", CONNECTION_BOTH); + PrepareStatement(CHAR_UPD_CHAR_ONLINE, "UPDATE characters SET online = 1 WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_CHAR_NAME_AT_LOGIN, "UPDATE characters set name = ?, at_login = ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_WORLDSTATE, "UPDATE worldstates SET value = ? WHERE entry = ?", CONNECTION_ASYNC); @@ -390,9 +399,9 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_NOT_EXTENDED, "DELETE FROM character_instance WHERE instance = ? AND extended = 0", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_CHAR_INSTANCE_SET_NOT_EXTENDED, "UPDATE character_instance SET extended = 0 WHERE instance = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_GUID, "DELETE FROM character_instance WHERE guid = ? AND instance = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_UPD_CHAR_INSTANCE, "UPDATE character_instance SET instance = ?, permanent = ?, extended = 0 WHERE guid = ? AND instance = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_CHAR_INSTANCE, "UPDATE character_instance SET instance = ?, permanent = ?, extended = ? WHERE guid = ? AND instance = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_CHAR_INSTANCE_EXTENDED, "UPDATE character_instance SET extended = ? WHERE guid = ? AND instance = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_INS_CHAR_INSTANCE, "INSERT INTO character_instance (guid, instance, permanent, extended) VALUES (?, ?, ?, 0)", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_CHAR_INSTANCE, "INSERT INTO character_instance (guid, instance, permanent, extended) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_ARENA_LOG_FIGHT, "INSERT INTO log_arena_fights VALUES (?, NOW(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_ARENA_LOG_MEMBERSTATS, "INSERT INTO log_arena_memberstats VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_GENDER_AND_APPEARANCE, "UPDATE characters SET gender = ?, skin = ?, face = ?, hairStyle = ?, hairColor = ?, facialStyle = ? WHERE guid = ?", CONNECTION_ASYNC); @@ -420,6 +429,10 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_SEL_CHAR_CLASS_LVL_AT_LOGIN, "SELECT class, level, at_login, knownTitles FROM characters WHERE guid = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_CHAR_CUSTOMIZE_INFO, "SELECT name, race, class, gender, at_login FROM characters WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHAR_RACE_OR_FACTION_CHANGE_INFOS, "SELECT at_login, knownTitles, money FROM characters WHERE guid = ?", CONNECTION_ASYNC); + + PrepareStatement(CHAR_SEL_INSTANCE, "SELECT data, completedEncounters, entranceId FROM instance WHERE map = ? AND id = ?", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_PERM_BIND_BY_INSTANCE, "SELECT guid FROM character_instance WHERE instance = ? and permanent = 1", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_CHAR_AT_LOGIN_TITLES_MONEY, "SELECT at_login, knownTitles, money FROM characters WHERE guid = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_CHAR_COD_ITEM_MAIL, "SELECT id, messageType, mailTemplateId, sender, subject, body, money, has_items FROM mail WHERE receiver = ? AND has_items <> 0 AND cod <> 0", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_CHAR_SOCIAL, "SELECT DISTINCT guid FROM character_social WHERE friend = ?", CONNECTION_SYNCH); @@ -456,6 +469,13 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_DEL_GROUP_MEMBER_ALL, "DELETE FROM group_member WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_CHAR_GIFT, "INSERT INTO character_gifts (guid, item_guid, entry, flags) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_INSTANCE_BY_INSTANCE, "DELETE FROM instance WHERE id = ?", CONNECTION_ASYNC); + + PrepareStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE, "DELETE FROM character_instance WHERE instance = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_EXPIRED_CHAR_INSTANCE_BY_MAP_DIFF, "DELETE FROM character_instance USING character_instance LEFT JOIN instance ON character_instance.instance = id WHERE (extended = 0 or permanent = 0) and map = ? and difficulty = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_GROUP_INSTANCE_BY_MAP_DIFF, "DELETE FROM group_instance USING group_instance LEFT JOIN instance ON group_instance.`instance` = id WHERE group_instance.`map` = ? and group_instance.difficulty = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_EXPIRED_INSTANCE_BY_MAP_DIFF, "DELETE FROM instance WHERE map = ? and difficulty = ? and (SELECT guid FROM character_instance WHERE extended != 0 AND instance = id LIMIT 1) IS NULL", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_EXPIRE_CHAR_INSTANCE_BY_MAP_DIFF, "UPDATE character_instance LEFT JOIN instance ON character_instance.instance = id SET extended = extended-1 WHERE map = ? and difficulty = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_MAIL_ITEM_BY_ID, "DELETE FROM mail_items WHERE mail_id = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_PETITION, "INSERT INTO petition (ownerguid, petitionguid, name, type) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_PETITION_BY_GUID, "DELETE FROM petition WHERE petitionguid = ?", CONNECTION_ASYNC); @@ -869,6 +889,9 @@ void CharacterDatabaseConnection::DoPrepareStatements() ")" , CONNECTION_ASYNC); + // hater: retail lockout + PrepareStatement(CHAR_SEL_CHARACTER_INSTANCE, "SELECT id, permanent, map, difficulty, extended, resettime, character_instance.entranceId FROM character_instance LEFT JOIN instance ON instance = id WHERE guid = ?", CONNECTION_ASYNC); + // QuestTracker PrepareStatement(CHAR_INS_QUEST_TRACK, "INSERT INTO quest_tracker (id, character_guid, quest_accept_time, core_hash, core_revision) VALUES (?, ?, NOW(), ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_QUEST_TRACK_GM_COMPLETE, "UPDATE quest_tracker SET completed_by_gm = 1 WHERE id = ? AND character_guid = ? ORDER BY quest_accept_time DESC LIMIT 1", CONNECTION_ASYNC); @@ -894,13 +917,13 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_REP_CHAR_SETTINGS, "REPLACE INTO character_settings (guid, source, data) VALUES (?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_SETTINGS, "DELETE FROM character_settings WHERE guid = ?", CONNECTION_ASYNC); - // Instance saved data. Stores the states of gameobjects in instances to be loaded on server start - PrepareStatement(CHAR_SELECT_INSTANCE_SAVED_DATA, "SELECT id, guid, state FROM instance_saved_go_state_data", CONNECTION_SYNCH); - PrepareStatement(CHAR_UPDATE_INSTANCE_SAVED_DATA, "UPDATE instance_saved_go_state_data SET state = ? WHERE guid = ? AND id = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_INSERT_INSTANCE_SAVED_DATA, "INSERT INTO instance_saved_go_state_data (id, guid, state) VALUES (?, ?, ?)", CONNECTION_ASYNC); - PrepareStatement(CHAR_DELETE_INSTANCE_SAVED_DATA, "DELETE FROM instance_saved_go_state_data WHERE id = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_SANITIZE_INSTANCE_SAVED_DATA, "DELETE FROM instance_saved_go_state_data WHERE id NOT IN (SELECT instance.id FROM instance)", CONNECTION_ASYNC); - + // Scenario + /* + PrepareStatement(CHAR_SEL_SCENARIO_INSTANCE_CRITERIA_FOR_INSTANCE, "SELECT criteria, counter, date FROM instance_scenario_progress WHERE id = ?", CONNECTION_SYNCH); + PrepareStatement(CHAR_DEL_SCENARIO_INSTANCE_CRITERIA, "DELETE FROM instance_scenario_progress WHERE id = ? AND criteria = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_SCENARIO_INSTANCE_CRITERIA, "INSERT INTO instance_scenario_progress (id, criteria, counter, date) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_SCENARIO_INSTANCE_CRITERIA_FOR_INSTANCE, "DELETE FROM instance_scenario_progress WHERE id = ?", CONNECTION_ASYNC); + */ PrepareStatement(CHAR_DEL_CHAR_SPELL_CHARGES, "DELETE FROM character_spell_charges WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_SPELLCHARGES, "SELECT classMask0, classMask1, classMask2, maxCharges, currentCharges, maxDuration, currentDuration, chargeAura FROM character_spell_charges WHERE guid = ?", CONNECTION_ASYNC); } diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index 6f0c15f16dfe94..8dc8012f879a78 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -285,6 +285,7 @@ enum CharacterDatabaseStatements : uint32 CHAR_INS_GROUP, CHAR_REP_GROUP_MEMBER, CHAR_DEL_GROUP_MEMBER, + CHAR_DEL_GROUP_INSTANCE_PERM_BINDING, CHAR_UPD_GROUP_LEADER, CHAR_UPD_GROUP_TYPE, CHAR_UPD_GROUP_MEMBER_SUBGROUP, @@ -302,7 +303,15 @@ enum CharacterDatabaseStatements : uint32 CHAR_DEL_INVALID_ACHIEVMENT, CHAR_INS_ADDON, CHAR_DEL_INVALID_PET_SPELL, + + CHAR_DEL_GROUP_INSTANCE_BY_INSTANCE, + CHAR_DEL_GROUP_INSTANCE_BY_GUID, + CHAR_REP_GROUP_INSTANCE, + CHAR_UPD_INSTANCE_RESETTIME, + CHAR_INS_GLOBAL_INSTANCE_RESETTIME, + CHAR_DEL_GLOBAL_INSTANCE_RESETTIME, CHAR_UPD_GLOBAL_INSTANCE_RESETTIME, + CHAR_UPD_CHAR_ONLINE, CHAR_UPD_CHAR_NAME_AT_LOGIN, CHAR_UPD_WORLDSTATE, @@ -347,6 +356,10 @@ enum CharacterDatabaseStatements : uint32 CHAR_SEL_CHAR_CLASS_LVL_AT_LOGIN, CHAR_SEL_CHAR_CUSTOMIZE_INFO, CHAR_SEL_CHAR_RACE_OR_FACTION_CHANGE_INFOS, + + CHAR_SEL_INSTANCE, + CHAR_SEL_PERM_BIND_BY_INSTANCE, + CHAR_SEL_CHAR_AT_LOGIN_TITLES_MONEY, CHAR_SEL_CHAR_COD_ITEM_MAIL, CHAR_SEL_CHAR_SOCIAL, @@ -378,6 +391,12 @@ enum CharacterDatabaseStatements : uint32 CHAR_DEL_GROUP_MEMBER_ALL, CHAR_INS_CHAR_GIFT, CHAR_DEL_INSTANCE_BY_INSTANCE, + + CHAR_DEL_EXPIRED_CHAR_INSTANCE_BY_MAP_DIFF, + CHAR_DEL_GROUP_INSTANCE_BY_MAP_DIFF, + CHAR_DEL_EXPIRED_INSTANCE_BY_MAP_DIFF, + CHAR_UPD_EXPIRE_CHAR_INSTANCE_BY_MAP_DIFF, + CHAR_DEL_MAIL_ITEM_BY_ID, CHAR_INS_PETITION, CHAR_DEL_PETITION_BY_GUID, @@ -503,6 +522,8 @@ enum CharacterDatabaseStatements : uint32 CHAR_UPD_CUSTOM_ITEM, + CHAR_SEL_CHARACTER_INSTANCE, + CHAR_INS_QUEST_TRACK, CHAR_UPD_QUEST_TRACK_GM_COMPLETE, CHAR_UPD_QUEST_TRACK_COMPLETE_TIME, @@ -524,11 +545,10 @@ enum CharacterDatabaseStatements : uint32 CHAR_REP_CHAR_SETTINGS, CHAR_DEL_CHAR_SETTINGS, - CHAR_SELECT_INSTANCE_SAVED_DATA, - CHAR_UPDATE_INSTANCE_SAVED_DATA, - CHAR_INSERT_INSTANCE_SAVED_DATA, - CHAR_DELETE_INSTANCE_SAVED_DATA, - CHAR_SANITIZE_INSTANCE_SAVED_DATA, + /*CHAR_SEL_SCENARIO_INSTANCE_CRITERIA_FOR_INSTANCE, + CHAR_DEL_SCENARIO_INSTANCE_CRITERIA, + CHAR_INS_SCENARIO_INSTANCE_CRITERIA, + CHAR_DEL_SCENARIO_INSTANCE_CRITERIA_FOR_INSTANCE,*/ CHAR_DEL_CHAR_SPELL_CHARGES, CHAR_SEL_CHARACTER_SPELLCHARGES, diff --git a/src/server/game/DungeonFinding/LFGMgr.cpp b/src/server/game/DungeonFinding/LFGMgr.cpp index 25c0ff694e84b5..504fa6f6b5e90c 100644 --- a/src/server/game/DungeonFinding/LFGMgr.cpp +++ b/src/server/game/DungeonFinding/LFGMgr.cpp @@ -262,13 +262,7 @@ namespace lfg } if (reload) - { CachedDungeonMapStore.clear(); - // Recalculate locked dungeons - for (LfgPlayerDataContainer::const_iterator it = PlayersStore.begin(); it != PlayersStore.end(); ++it) - if (Player* player = ObjectAccessor::FindConnectedPlayer(it->first)) - InitializeLockedDungeons(player, nullptr); - } } void LFGMgr::Update(uint32 tdiff, uint8 task) @@ -376,127 +370,9 @@ namespace lfg UpdateProposal(proposalId, guid, true); } } - - UpdateRaidBrowser(tdiff); } } - /** - Generate the dungeon lock map for a given player - - @param[in] player Player we need to initialize the lock status map - */ - void LFGMgr::InitializeLockedDungeons(Player* player, Group const* group) - { - ObjectGuid guid = player->GetGUID(); - - uint8 level = player->GetLevel(); - uint8 expansion = player->GetSession()->Expansion(); - LfgDungeonSet const& dungeons = GetDungeonsByRandom(0); - LfgLockMap lock; - - bool onlySeasonalBosses = m_options == LFG_OPTION_ENABLE_SEASONAL_BOSSES; - - float avgItemLevel = player->GetAverageItemLevelForDF(); - - for (LfgDungeonSet::const_iterator it = dungeons.begin(); it != dungeons.end(); ++it) - { - LFGDungeonData const* dungeon = GetLFGDungeon(*it); - if (!dungeon) // should never happen - We provide a list from sLFGDungeonStore - continue; - MapEntry const* mapEntry = sMapStore.LookupEntry(dungeon->map); - DungeonProgressionRequirements const* ar = sObjectMgr->GetAccessRequirement(dungeon->map, Difficulty(dungeon->difficulty)); - - uint32 lockData = 0; - if (dungeon->expansion > expansion || (onlySeasonalBosses && !dungeon->seasonal)) - lockData = LFG_LOCKSTATUS_INSUFFICIENT_EXPANSION; - else if (DisableMgr::IsDisabledFor(DISABLE_TYPE_MAP, dungeon->map, player)) - lockData = LFG_LOCKSTATUS_RAID_LOCKED; - else if (DisableMgr::IsDisabledFor(DISABLE_TYPE_LFG_MAP, dungeon->map, player)) - lockData = LFG_LOCKSTATUS_RAID_LOCKED; - else if (dungeon->difficulty > DUNGEON_DIFFICULTY_NORMAL && (!mapEntry || !mapEntry->IsRaid()) && sInstanceSaveMgr->PlayerIsPermBoundToInstance(player->GetGUID(), dungeon->map, Difficulty(dungeon->difficulty))) - lockData = LFG_LOCKSTATUS_RAID_LOCKED; - else if ((dungeon->minlevel > level && !sWorld->getBoolConfig(CONFIG_DUNGEON_ACCESS_REQUIREMENTS_LFG_DBC_LEVEL_OVERRIDE)) || (sWorld->getBoolConfig(CONFIG_DUNGEON_ACCESS_REQUIREMENTS_LFG_DBC_LEVEL_OVERRIDE) && ar && ar->levelMin > 0 && ar->levelMin > level)) - lockData = LFG_LOCKSTATUS_TOO_LOW_LEVEL; - else if ((dungeon->maxlevel < level && !sWorld->getBoolConfig(CONFIG_DUNGEON_ACCESS_REQUIREMENTS_LFG_DBC_LEVEL_OVERRIDE)) || (sWorld->getBoolConfig(CONFIG_DUNGEON_ACCESS_REQUIREMENTS_LFG_DBC_LEVEL_OVERRIDE) && ar && ar->levelMax > 0 && ar->levelMax < level)) - lockData = LFG_LOCKSTATUS_TOO_HIGH_LEVEL; - else if (dungeon->seasonal && !IsSeasonActive(dungeon->id)) - lockData = LFG_LOCKSTATUS_NOT_IN_SEASON; - else if (ar) - { - // Check required items - for (const ProgressionRequirement* itemRequirement : ar->items) - { - if (!itemRequirement->checkLeaderOnly || !group || group->GetLeaderGUID() == player->GetGUID()) - { - if (itemRequirement->faction == TEAM_NEUTRAL || itemRequirement->faction == player->GetTeamId(true)) - { - if (!player->HasItemCount(itemRequirement->id, 1)) - { - lockData = LFG_LOCKSTATUS_MISSING_ITEM; - break; - } - } - } - } - - //Check for quests - for (const ProgressionRequirement* questRequirement : ar->quests) - { - if (!questRequirement->checkLeaderOnly || !group || group->GetLeaderGUID() == player->GetGUID()) - { - if (questRequirement->faction == TEAM_NEUTRAL || questRequirement->faction == player->GetTeamId(true)) - { - if (!player->GetQuestRewardStatus(questRequirement->id)) - { - lockData = LFG_LOCKSTATUS_QUEST_NOT_COMPLETED; - break; - } - } - } - } - - //Check for ilvl - if (ar->reqItemLevel && (float)ar->reqItemLevel > avgItemLevel) - { - lockData = LFG_LOCKSTATUS_TOO_LOW_GEAR_SCORE; - } - - //Check if player has the required achievements - for (const ProgressionRequirement* achievementRequirement : ar->achievements) - { - if (!achievementRequirement->checkLeaderOnly || !group || group->GetLeaderGUID() == player->GetGUID()) - { - if (achievementRequirement->faction == TEAM_NEUTRAL || achievementRequirement->faction == player->GetTeamId(true)) - { - if (!player->HasAchieved(achievementRequirement->id)) - { - lockData = LFG_LOCKSTATUS_MISSING_ACHIEVEMENT; - break; - } - } - } - } - } - - sScriptMgr->OnInitializeLockedDungeons(player, level, lockData, dungeon); - - /* TODO VoA closed if WG is not under team control (LFG_LOCKSTATUS_RAID_LOCKED) - lockData = LFG_LOCKSTATUS_TOO_LOW_GEAR_SCORE; - lockData = LFG_LOCKSTATUS_TOO_HIGH_GEAR_SCORE; - lockData = LFG_LOCKSTATUS_ATTUNEMENT_TOO_LOW_LEVEL; - lockData = LFG_LOCKSTATUS_ATTUNEMENT_TOO_HIGH_LEVEL; - */ - - if (lockData) - lock[dungeon->Entry()] = lockData; - } - - sScriptMgr->OnAfterInitializeLockedDungeons(player); - - SetLockedDungeons(guid, lock); - } - /** Adds the player/group to lfg queue. If player is in a group then it is the leader of the group tying to join the group. Join conditions are checked before adding @@ -974,261 +850,6 @@ namespace lfg player->GetSession()->SendPacket(&data); } - void LFGMgr::UpdateRaidBrowser(uint32 diff) - { - for (uint8 team = 0; team < 2; ++team) - { - if (m_raidBrowserUpdateTimer[team] > diff) - m_raidBrowserUpdateTimer[team] -= diff; - else - m_raidBrowserUpdateTimer[team] = 0; - } - - if (GetMSTimeDiff(GameTime::GetGameTimeMS(), GetTimeMS()) > 98ms) // prevent lagging - { - return; - } - - ObjectGuid guid, groupGuid, instanceGuid; - uint8 level, Class, race, talents[3]; - float iLevel, mp5, mp5combat, baseAP, rangedAP; - int32 spellDamage, spellHeal; - uint32 dungeonId, encounterMask, maxPower; - uint32 deletedCounter, groupCounter, playerCounter; - ByteBuffer buffer_deleted, buffer_groups, buffer_players; - std::string emptyComment; - GuidSet deletedGroups, deletedGroupsToErase; - RBInternalInfoMap copy; - - for (uint8 team = 0; team < 2; ++team) - { - if (m_raidBrowserLastUpdatedDungeonId[team] == 0) // new loop - { - if (m_raidBrowserUpdateTimer[team] > 0) // allowed only with some time interval - continue; - else // reset timer - m_raidBrowserUpdateTimer[team] = 5000; - } - - RBUsedDungeonsSet::const_iterator neitr, titr; - for (neitr = RBUsedDungeonsStore[team].begin(); neitr != RBUsedDungeonsStore[team].end(); ) - { - titr = neitr++; - dungeonId = (*titr); - - // go to next dungeon than previously (one dungeon updated in one LFGMgr::UpdateRaidBrowser) - if (dungeonId <= m_raidBrowserLastUpdatedDungeonId[team]) - continue; - m_raidBrowserLastUpdatedDungeonId[team] = dungeonId; - - RBEntryInfoMap& entryInfoMap = RaidBrowserStore[team][dungeonId]; - LFGDungeonData const* dungeonData = GetLFGDungeon(dungeonId); // checked if exists before inserting to the container - RBInternalInfoMap& currInternalInfoMap = RBInternalInfoStoreCurr[team][dungeonId]; - for (RBEntryInfoMap::const_iterator sitr = entryInfoMap.begin(); sitr != entryInfoMap.end(); ++sitr) - { - guid = sitr->first; - groupGuid.Clear(); - Player* p = ObjectAccessor::FindConnectedPlayer(guid); - ASSERT(p); - if (sitr->second.roles == PLAYER_ROLE_LEADER) - { - ASSERT(p->GetGroup()); - groupGuid = p->GetGroup()->GetGUID(); - } - encounterMask = 0; - instanceGuid.Clear(); - if (InstancePlayerBind* bind = sInstanceSaveMgr->PlayerGetBoundInstance(guid, dungeonData->map, dungeonData->difficulty)) - if (bind->perm) - { - instanceGuid = ObjectGuid::Create(bind->save->GetInstanceId()); - encounterMask = bind->save->GetCompletedEncounterMask(); - } - - talents[0] = 0; - talents[1] = 0; - talents[2] = 0; - p->GetTalentTreePoints(talents); - spellDamage = p->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_ALL); - spellHeal = p->SpellBaseHealingBonusDone(SPELL_SCHOOL_MASK_ALL); - mp5 = p->GetFloatValue(UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER); - mp5combat = p->GetFloatValue(UNIT_FIELD_POWER_REGEN_INTERRUPTED_FLAT_MODIFIER); - baseAP = p->GetTotalAttackPowerValue(BASE_ATTACK); - rangedAP = p->GetTotalAttackPowerValue(RANGED_ATTACK); - maxPower = 0; - if (p->getClass() == CLASS_DRUID) - maxPower = p->GetMaxPower(POWER_MANA); - else - maxPower = (p->getPowerType() == POWER_RAGE || p->getPowerType() == POWER_RUNIC_POWER) ? p->GetMaxPower(p->getPowerType()) / 10 : p->GetMaxPower(p->getPowerType()); - - currInternalInfoMap[sitr->first] = RBInternalInfo(guid, sitr->second.comment, !groupGuid.IsEmpty(), groupGuid, sitr->second.roles, encounterMask, instanceGuid, - 1, p->GetLevel(), p->getClass(), p->getRace(), p->GetAverageItemLevel(), - talents, p->GetAreaId(), p->GetArmor(), (uint32)std::max(0, spellDamage), (uint32)std::max(0, spellHeal), - p->GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + static_cast(CR_CRIT_MELEE)), p->GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + static_cast(CR_CRIT_RANGED)), p->GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + static_cast(CR_CRIT_SPELL)), std::max(0.0f, mp5), std::max(0.0f, mp5combat), - std::max(baseAP, rangedAP), (uint32)p->GetStat(STAT_AGILITY), p->GetMaxHealth(), maxPower, p->GetDefenseSkillValue(), - p->GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + static_cast(CR_DODGE)), p->GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + static_cast(CR_BLOCK)), p->GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + static_cast(CR_PARRY)), p->GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + static_cast(CR_HASTE_SPELL)), p->GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + static_cast(CR_MASTERY))); - - if (!groupGuid) - continue; - for (Group::member_citerator mitr = p->GetGroup()->GetMemberSlots().begin(); mitr != p->GetGroup()->GetMemberSlots().end(); ++mitr) - { - if (mitr->guid == sitr->first) // leader already added - continue; - - guid = mitr->guid; - level = 1; - Class = 0; - race = 0; - iLevel = 0.0f; - talents[0] = 0; - talents[1] = 0; - talents[2] = 0; - if (CharacterCacheEntry const* gpd = sCharacterCache->GetCharacterCacheByGuid(mitr->guid)) - { - level = gpd->Level; - Class = gpd->Class; - race = gpd->Race; - } - Player* mplr = ObjectAccessor::FindConnectedPlayer(guid); - if (mplr) - { - iLevel = mplr->GetAverageItemLevel(); - mplr->GetTalentTreePoints(talents); - } - currInternalInfoMap[mitr->guid] = RBInternalInfo(guid, emptyComment, false, groupGuid, 0, 0, ObjectGuid::Empty, - (mplr ? 1 : 0), level, Class, race, iLevel, - talents, 0, 0, 0, 0, - 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0); - } - } - - copy.clear(); - copy = currInternalInfoMap; // will be saved as prev at the end - - // compare prev with curr to build difference packet - deletedCounter = 0; - groupCounter = 0; - playerCounter = 0; - buffer_deleted.clear(); - buffer_groups.clear(); - buffer_players.clear(); - deletedGroups.clear(); - deletedGroupsToErase.clear(); - - RBInternalInfoMap& prevInternalInfoMap = RBInternalInfoStorePrev[team][dungeonId]; - RBInternalInfoMap::iterator iter, iterTmp; - for (RBInternalInfoMap::const_iterator sitr = prevInternalInfoMap.begin(); sitr != prevInternalInfoMap.end(); ++sitr) - { - iter = currInternalInfoMap.find(sitr->first); - if (iter == currInternalInfoMap.end()) // was -> isn't - { - if (sitr->second.isGroupLeader) - deletedGroups.insert(sitr->second.groupGuid); - ++deletedCounter; - buffer_deleted << sitr->second.guid; - } - else // was -> is - { - if (sitr->second.isGroupLeader) // was a leader - { - if (!iter->second.isGroupLeader) // leader -> no longer a leader - deletedGroups.insert(sitr->second.groupGuid); - else if (sitr->second.groupGuid != iter->second.groupGuid) // leader -> leader of another group - { - deletedGroups.insert(sitr->second.groupGuid); - deletedGroupsToErase.insert(iter->second.groupGuid); - ++groupCounter; - RBPacketAppendGroup(iter->second, buffer_groups); - } - else if (sitr->second.comment != iter->second.comment || sitr->second.encounterMask != iter->second.encounterMask || sitr->second.instanceGuid != iter->second.instanceGuid) // leader -> nothing changed - { - ++groupCounter; - RBPacketAppendGroup(iter->second, buffer_groups); - } - } - else if (iter->second.isGroupLeader) // wasn't a leader -> is a leader - { - deletedGroupsToErase.insert(iter->second.groupGuid); - ++groupCounter; - RBPacketAppendGroup(iter->second, buffer_groups); - } - - if (!iter->second._online) // if offline, copy previous stats (itemLevel, talents, area, etc.) - { - iterTmp = copy.find(sitr->first); // copied container is for building a full packet, so modify it there (currInternalInfoMap is erased) - iterTmp->second.CopyStats(sitr->second); - if (!sitr->second.PlayerSameAs(iterTmp->second)) // player info changed - { - ++playerCounter; - RBPacketAppendPlayer(iterTmp->second, buffer_players); - } - } - else if (!sitr->second.PlayerSameAs(iter->second)) // player info changed - { - ++playerCounter; - RBPacketAppendPlayer(iter->second, buffer_players); - } - currInternalInfoMap.erase(iter); - } - } - // left entries (new) - for (RBInternalInfoMap::const_iterator sitr = currInternalInfoMap.begin(); sitr != currInternalInfoMap.end(); ++sitr) - { - if (sitr->second.isGroupLeader) - { - deletedGroupsToErase.insert(sitr->second.groupGuid); - ++groupCounter; - RBPacketAppendGroup(sitr->second, buffer_groups); - } - ++playerCounter; - RBPacketAppendPlayer(sitr->second, buffer_players); - } - - if (!deletedGroupsToErase.empty()) - { - for (ObjectGuid const& toErase : deletedGroupsToErase) - { - deletedGroups.erase(toErase); - } - } - - if (!deletedGroups.empty()) - { - for (ObjectGuid const& deletedGroup : deletedGroups) - { - ++deletedCounter; - buffer_deleted << deletedGroup; - } - } - - WorldPacket differencePacket(SMSG_UPDATE_LFG_LIST, 1000); - RBPacketBuildDifference(differencePacket, dungeonId, deletedCounter, buffer_deleted, groupCounter, buffer_groups, playerCounter, buffer_players); - WorldPacket fullPacket(SMSG_UPDATE_LFG_LIST, 1000); - RBPacketBuildFull(fullPacket, dungeonId, copy); - - RBCacheStore[team][dungeonId] = fullPacket; - prevInternalInfoMap = copy; - currInternalInfoMap.clear(); - - if (entryInfoMap.empty()) - RBUsedDungeonsStore[team].erase(titr); - - // send difference packet to browsing players - for (RBSearchersMap::const_iterator sitr = RBSearchersStore[team].begin(); sitr != RBSearchersStore[team].end(); ++sitr) - if (sitr->second == dungeonId) - if (Player* p = ObjectAccessor::FindConnectedPlayer(sitr->first)) - p->GetSession()->SendPacket(&differencePacket); - - break; // one dungeon updated in one LFGMgr::UpdateRaidBrowser - } - - // already updated all in this time interval - if (neitr == RBUsedDungeonsStore[team].end()) - m_raidBrowserLastUpdatedDungeonId[team] = 0; - } - } - void LFGMgr::RBPacketAppendGroup(const RBInternalInfo& info, ByteBuffer& buffer) { buffer << info.groupGuid; @@ -1744,33 +1365,6 @@ namespace lfg } } - if (player->GetMapId() == uint32(dungeon->map)) - { - // check instance id with leader - if (!leaderTeleportIncluded) - { - if (InstancePlayerBind* ilb = sInstanceSaveMgr->PlayerGetBoundInstance(grp->GetLeaderGUID(), dungeon->map, player->GetDungeonDifficulty())) - { - if (player->GetInstanceId() == ilb->save->GetInstanceId()) - { - // Do not teleport if in the same map and instance as leader - continue; - } - } - } - - // Remove bind to that map - sInstanceSaveMgr->PlayerUnbindInstance(player->GetGUID(), dungeon->map, player->GetDungeonDifficulty(), true); - } - else - { - // RDF removes all binds to that map - if (randomDungeon && !sInstanceSaveMgr->PlayerIsPermBoundToInstance(player->GetGUID(), dungeon->map, player->GetDungeonDifficulty())) - { - sInstanceSaveMgr->PlayerUnbindInstance(player->GetGUID(), dungeon->map, player->GetDungeonDifficulty(), true); - } - } - playersTeleported.push_back(player); } } @@ -1780,11 +1374,6 @@ namespace lfg TeleportPlayer(player, false, teleportLocation); } - if (randomDungeon) - grp->AddLfgRandomInstanceFlag(); - if (Difficulty(dungeon->difficulty) == DUNGEON_DIFFICULTY_HEROIC) - grp->AddLfgHeroicFlag(); - // Update group info grp->SendUpdate(); } @@ -2260,12 +1849,6 @@ namespace lfg continue; } - // Remove Dungeon Finder Cooldown if still exists - if (player->HasAura(LFG_SPELL_DUNGEON_COOLDOWN)) - { - player->RemoveAurasDueToSpell(LFG_SPELL_DUNGEON_COOLDOWN); - } - // Xinef: Update achievements, set correct amount of randomly grouped players if (dungeon->difficulty == DUNGEON_DIFFICULTY_HEROIC) if (uint8 count = GetRandomPlayersCount(player->GetGUID())) diff --git a/src/server/game/DungeonFinding/LFGMgr.h b/src/server/game/DungeonFinding/LFGMgr.h index 0eb27e71c03a08..3fa77101466da9 100644 --- a/src/server/game/DungeonFinding/LFGMgr.h +++ b/src/server/game/DungeonFinding/LFGMgr.h @@ -490,8 +490,6 @@ namespace lfg // LFGScripts /// Get leader of the group (using internal data) ObjectGuid GetLeader(ObjectGuid guid); - /// Initializes locked dungeons for given player (called at login or level change) - void InitializeLockedDungeons(Player* player, Group const* group = nullptr); /// Sets player team void SetTeam(ObjectGuid guid, TeamId teamId); /// Sets player group @@ -548,7 +546,6 @@ namespace lfg void LfrSearchAdd(Player* p, uint32 dungeonId); void LfrSearchRemove(Player* p); void SendRaidBrowserCachedList(Player* player, uint32 dungeonId); - void UpdateRaidBrowser(uint32 diff); void LfrSetComment(Player* p, std::string comment); void SendRaidBrowserJoinedPacket(Player* p, LfgDungeonSet& dungeons, std::string comment); void RBPacketAppendGroup(const RBInternalInfo& info, ByteBuffer& buffer); diff --git a/src/server/game/DungeonFinding/LFGScripts.cpp b/src/server/game/DungeonFinding/LFGScripts.cpp index cabd4543f454ac..fcfda8d82ca9a0 100644 --- a/src/server/game/DungeonFinding/LFGScripts.cpp +++ b/src/server/game/DungeonFinding/LFGScripts.cpp @@ -33,10 +33,7 @@ namespace lfg void LFGPlayerScript::OnLevelChanged(Player* player, uint8 /*oldLevel*/) { - if (!sLFGMgr->isOptionEnabled(LFG_OPTION_ENABLE_DUNGEON_FINDER | LFG_OPTION_ENABLE_RAID_BROWSER | LFG_OPTION_ENABLE_SEASONAL_BOSSES)) - return; - sLFGMgr->InitializeLockedDungeons(player, player->GetGroup()); } void LFGPlayerScript::OnLogout(Player* player) @@ -61,33 +58,7 @@ namespace lfg void LFGPlayerScript::OnLogin(Player* player) { - if (!sLFGMgr->isOptionEnabled(LFG_OPTION_ENABLE_DUNGEON_FINDER | LFG_OPTION_ENABLE_RAID_BROWSER | LFG_OPTION_ENABLE_SEASONAL_BOSSES)) - return; - - // Temporal: Trying to determine when group data and LFG data gets desynched - ObjectGuid guid = player->GetGUID(); - ObjectGuid gguid = sLFGMgr->GetGroup(guid); - - Group const* group = player->GetGroup(); - if (group) - { - ObjectGuid gguid2 = group->GetGUID(); - if (gguid != gguid2) - { - sLFGMgr->SetupGroupMember(guid, group->GetGUID()); - } - } - - sLFGMgr->InitializeLockedDungeons(player, group); - sLFGMgr->SetTeam(player->GetGUID(), player->GetTeamId()); - /// @todo - Restore LfgPlayerData and send proper status to player if it was in a group - } - - void LFGPlayerScript::OnBindToInstance(Player* player, Difficulty difficulty, uint32 mapId, bool /*permanent*/) - { - MapEntry const* mapEntry = sMapStore.LookupEntry(mapId); - if (mapEntry->IsDungeon() && difficulty > DUNGEON_DIFFICULTY_NORMAL) - sLFGMgr->InitializeLockedDungeons(player, player->GetGroup()); + } void LFGPlayerScript::OnMapChanged(Player* player) diff --git a/src/server/game/DungeonFinding/LFGScripts.h b/src/server/game/DungeonFinding/LFGScripts.h index 8173fb1bcc72e3..10d971ee4bc4a1 100644 --- a/src/server/game/DungeonFinding/LFGScripts.h +++ b/src/server/game/DungeonFinding/LFGScripts.h @@ -37,7 +37,6 @@ namespace lfg void OnLevelChanged(Player* player, uint8 oldLevel) override; void OnLogout(Player* player) override; void OnLogin(Player* player) override; - void OnBindToInstance(Player* player, Difficulty difficulty, uint32 mapId, bool permanent) override; void OnMapChanged(Player* player) override; }; diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index 5145bd97e71ffa..e9b5f1d0d4a4c0 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -333,29 +333,7 @@ bool GameObject::Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* map, u // GAMEOBJECT_BYTES_1, index at 0, 1, 2 and 3 SetGoType(GameobjectTypes(goinfo->type)); - if (IsInstanceGameobject()) - { - switch (GetStateSavedOnInstance()) - { - case 0: - SetGoState(GO_STATE_READY); - SwitchDoorOrButton(true); - break; - case 1: - SetGoState(GO_STATE_READY); - break; - case 2: - SetGoState(GO_STATE_ACTIVE_ALTERNATIVE); - break; - default: - SetGoState(go_state); - break; - } - } - else - { - SetGoState(go_state); - } + SetGoState(go_state); SetGoArtKit(artKit); @@ -2500,41 +2478,6 @@ void GameObject::SetGoState(GOState state) else if (state == GO_STATE_READY) EnableCollision(!startOpen);*/ } - /* Whenever a gameobject inside an instance changes - * save it's state on the database to be loaded properly - * on server restart or crash. - */ - if (IsInstanceGameobject() && IsAbleToSaveOnDb()) - { - // Save the gameobject state on the Database - if (!FindStateSavedOnInstance()) - { - SaveInstanceData(GameobjectStateToInt(&state)); - } - else - { - UpdateInstanceData(GameobjectStateToInt(&state)); - } - } -} - -bool GameObject::IsInstanceGameobject() -{ - // Avoid checking for unecessary gameobjects whose - // states don't matter for the dungeon progression - if (!ValidateGameobjectType()) - { - return false; - } - - if (auto* map = FindMap()) - { - if (map->IsDungeon() || map->IsRaid()) - { - return true; - } - } - return false; } bool GameObject::ValidateGameobjectType() @@ -2582,66 +2525,6 @@ bool GameObject::IsAbleToSaveOnDb() return m_saveStateOnDb; } -void GameObject::UpdateSaveToDb(bool enable) -{ - m_saveStateOnDb = enable; - - if (enable) - { - SavingStateOnDB(); - } -} - -void GameObject::SavingStateOnDB() -{ - if (IsInstanceGameobject()) - { - GOState param = GetGoState(); - if (!FindStateSavedOnInstance()) - { - SaveInstanceData(GameobjectStateToInt(¶m)); - } - } -} - -void GameObject::SaveInstanceData(uint8 state) -{ - uint32 id = GetInstanceId(); - uint32 guid = GetSpawnId(); - - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INSERT_INSTANCE_SAVED_DATA); - stmt->SetData(0, id); - stmt->SetData(1, guid); - stmt->SetData(2, state); - CharacterDatabase.Execute(stmt); - - sObjectMgr->NewInstanceSavedGameobjectState(id, guid, state); -} - -void GameObject::UpdateInstanceData(uint8 state) -{ - uint32 id = GetInstanceId(); - uint32 guid = GetSpawnId(); - - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPDATE_INSTANCE_SAVED_DATA); - stmt->SetData(0, state); - stmt->SetData(1, guid); - stmt->SetData(2, id); - CharacterDatabase.Execute(stmt); - - sObjectMgr->SetInstanceSavedGameobjectState(id, guid, state); -} - -uint8 GameObject::GetStateSavedOnInstance() -{ - return sObjectMgr->GetInstanceSavedGameobjectState(GetInstanceId(), GetSpawnId()); -} - -bool GameObject::FindStateSavedOnInstance() -{ - return sObjectMgr->FindInstanceSavedGameobjectState(GetInstanceId(), GetSpawnId()); -} - void GameObject::SetDisplayId(uint32 displayid) { SetUInt32Value(GAMEOBJECT_DISPLAYID, displayid); diff --git a/src/server/game/Entities/GameObject/GameObject.h b/src/server/game/Entities/GameObject/GameObject.h index d7ba58e8fd5689..9e688ab543a174 100644 --- a/src/server/game/Entities/GameObject/GameObject.h +++ b/src/server/game/Entities/GameObject/GameObject.h @@ -350,12 +350,7 @@ class GameObject : public WorldObject, public GridObject, public Mov static std::unordered_map gameObjectToEventFlag; // Gameobject -> event flag - void SaveInstanceData(uint8 state); - void UpdateInstanceData(uint8 state); - bool FindStateSavedOnInstance(); bool ValidateGameobjectType(); - uint8 GetStateSavedOnInstance(); - bool IsInstanceGameobject(); uint8 GameobjectStateToInt(GOState* state); /* A check to verify if this object is available to be saved on the DB when @@ -363,13 +358,6 @@ class GameObject : public WorldObject, public GridObject, public Mov */ bool IsAbleToSaveOnDb(); - /* Enable or Disable the ability to save on the database this gameobject's state - * whenever it changes - */ - void UpdateSaveToDb(bool enable); - - void SavingStateOnDB(); - std::string GetDebugInfo() const override; protected: bool AIM_Initialize(); diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index b16ca150aa7915..b23b15150c66d5 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -418,6 +418,8 @@ class WorldObject : public Object, public WorldLocation Position GetFirstCollisionPosition(float dist, float angle); Position GetRandomNearPosition(float radius); + uint32 GetInstanceId() const { return m_InstanceId; } + void GetContactPoint(WorldObject const* obj, float& x, float& y, float& z, float distance2d = CONTACT_DISTANCE) const; void GetChargeContactPoint(WorldObject const* obj, float& x, float& y, float& z, float distance2d = CONTACT_DISTANCE) const; @@ -430,8 +432,6 @@ class WorldObject : public Object, public WorldLocation void GetRandomPoint(const Position& srcPos, float distance, float& rand_x, float& rand_y, float& rand_z) const; [[nodiscard]] Position GetRandomPoint(const Position& srcPos, float distance) const; - [[nodiscard]] uint32 GetInstanceId() const { return m_InstanceId; } - virtual void SetPhaseMask(uint32 newPhaseMask, bool update); [[nodiscard]] uint32 GetPhaseMask() const { return m_phaseMask; } [[nodiscard]] bool InSamePhase(uint32 phasemask) const { return m_useCombinedPhases ? GetPhaseMask() & phasemask : GetPhaseMask() == phasemask; } diff --git a/src/server/game/Entities/Object/ObjectDefines.h b/src/server/game/Entities/Object/ObjectDefines.h index 55c4826339ab93..48503bdd631c33 100644 --- a/src/server/game/Entities/Object/ObjectDefines.h +++ b/src/server/game/Entities/Object/ObjectDefines.h @@ -56,6 +56,7 @@ inline uint16 MAKE_PAIR16(uint8 l, uint8 h); inline uint32 MAKE_PAIR32(uint16 l, uint16 h); inline uint16 PAIR32_HIPART(uint32 x); inline uint16 PAIR32_LOPART(uint32 x); +inline uint64 MAKE_PAIR64(uint32 l, uint32 h); enum class VisibilityDistanceType : uint8 { @@ -69,6 +70,11 @@ enum class VisibilityDistanceType : uint8 Max }; +uint64 MAKE_PAIR64(uint32 l, uint32 h) +{ + return uint64(l | (uint64(h) << 32)); +} + uint32 PAIR64_HIPART(uint64 x) { return (uint32)((x >> 32) & UI64LIT(0x00000000FFFFFFFF)); diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 450d72a0f0ffc9..7bbb9690b341bb 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -5053,7 +5053,11 @@ void Player::RepopAtGraveyard() if (GetMap()->IsDungeon()) { ResurrectPlayer(0.5f); SpawnCorpseBones(); - script->DoNearTeleportPlayers(script->_challengeEntranceLoc); // TODO: lol + if (script->_challengeEntranceLoc.GetPositionX() == 0) { + if (WorldSafeLocsEntry const* entranceSafeLocEntry = sObjectMgr->GetWorldSafeLoc(script->instance->GetId(), script->GetEntranceLocation())) + script->_challengeEntranceLoc.Relocate(entranceSafeLocEntry->Loc); + } + NearTeleportTo(script->_challengeEntranceLoc); return; } @@ -12155,20 +12159,13 @@ void Player::SendTransferAborted(uint32 mapid, TransferAbortReason reason, uint8 void Player::SendInstanceResetWarning(uint32 mapid, Difficulty difficulty, uint32 time, bool onEnterMap) { - // pussywizard: - InstancePlayerBind* bind = sInstanceSaveMgr->PlayerGetBoundInstance(GetGUID(), mapid, difficulty); - if (bind && bind->extended) - { - if (!onEnterMap) // extended id player shouldn't be warned about lock expiration - return; - time += (bind->save->GetExtendedResetTime() - bind->save->GetResetTime()); // add lockout period to the time left - } - // type of warning, based on the time remaining until reset uint32 type; - if (time > 3600) + if (onEnterMap) type = RAID_INSTANCE_WELCOME; - else if (time > 900) + else if (time > 21600) + type = RAID_INSTANCE_WELCOME; + else if (time > 3600) type = RAID_INSTANCE_WARNING_HOURS; else if (time > 300) type = RAID_INSTANCE_WARNING_MIN; @@ -12180,11 +12177,11 @@ void Player::SendInstanceResetWarning(uint32 mapid, Difficulty difficulty, uint3 data << uint32(mapid); data << uint32(difficulty); // difficulty data << uint32(time); - if (type == RAID_INSTANCE_WELCOME) - { - data << uint8(bind && bind->perm); // is locked - data << uint8(bind && bind->extended); // is extended, ignored if prev field is 0 - } + if (InstancePlayerBind const* bind = GetBoundInstance(mapid, difficulty)) + data << uint8(bind && bind->perm); + else + data << uint8(false); + data << uint8(false); GetSession()->SendPacket(&data); } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 8e0bd54e4cccb6..f2c9ac54d37f8a 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -923,6 +923,7 @@ enum PlayerLoginQueryIndex PLAYER_LOGIN_QUERY_LOAD_PET_SLOTS = 37, PLAYER_LOGIN_QUERY_LOAD_SPELL_CHARGES = 38, PLAYER_LOGIN_QUERY_LOAD_TRANSMOG, + PLAYER_LOGIN_QUERY_LOAD_BOUND_INSTANCES, MAX_PLAYER_LOGIN_QUERY }; @@ -938,6 +939,29 @@ enum PlayerDelayedOperations DELAYED_END }; +enum BindExtensionState +{ + EXTEND_STATE_EXPIRED = 0, + EXTEND_STATE_NORMAL = 1, + EXTEND_STATE_EXTENDED = 2, + EXTEND_STATE_KEEP = 255 // special state: keep current save type +}; +struct InstancePlayerBind +{ + InstanceSave* save; + /* permanent PlayerInstanceBinds are created in Raid/Heroic instances for players + that aren't already permanently bound when they are inside when a boss is killed + or when they enter an instance that the group leader is permanently bound to. */ + bool perm; + /* extend state listing: + EXPIRED - doesn't affect anything unless manually re-extended by player + NORMAL - standard state + EXTENDED - won't be promoted to EXPIRED at next reset period, will instead be promoted to NORMAL */ + BindExtensionState extendState; + + InstancePlayerBind() : save(nullptr), perm(false), extendState(EXTEND_STATE_NORMAL) { } +}; + enum PlayerCharmedAISpells { SPELL_T_STUN, @@ -1951,8 +1975,12 @@ class Player : public Unit, public GridObject [[nodiscard]] Difficulty GetDungeonDifficulty() const { return m_dungeonDifficulty; } [[nodiscard]] Difficulty GetRaidDifficulty() const { return m_raidDifficulty; } [[nodiscard]] Difficulty GetStoredRaidDifficulty() const { return m_raidMapDifficulty; } // only for use in difficulty packet after exiting to raid map - void SetDungeonDifficulty(Difficulty dungeon_difficulty) { m_dungeonDifficulty = dungeon_difficulty; } - void SetRaidDifficulty(Difficulty raid_difficulty) { m_raidDifficulty = raid_difficulty; } + void SetDungeonDifficulty(Difficulty dungeon_difficulty) { + m_dungeonDifficulty = dungeon_difficulty; + } + void SetRaidDifficulty(Difficulty raid_difficulty) { + m_raidDifficulty = raid_difficulty; + } void StoreRaidMapDifficulty() { m_raidMapDifficulty = GetMap()->GetDifficulty(); } bool UpdateSkill(uint32 skill_id, uint32 step); @@ -2049,7 +2077,7 @@ class Player : public Unit, public GridObject void SendDungeonDifficulty(bool IsInGroup); void SendRaidDifficulty(bool IsInGroup, int32 forcedDifficulty = -1); - static void ResetInstances(ObjectGuid guid, uint8 method, bool isRaid); + void ResetInstances(uint8 method, bool isRaid, bool isLegacy = false); void SendResetInstanceSuccess(uint32 MapId); void SendResetInstanceFailed(uint32 reason, uint32 MapId); void SendResetFailedNotify(uint32 mapid); @@ -2467,23 +2495,36 @@ class Player : public Unit, public GridObject /*** INSTANCE SYSTEM ***/ /*********************************************************/ + typedef std::unordered_map> BoundInstancesMap; + void UpdateHomebindTime(uint32 time); uint32 m_HomebindTimer; bool m_InstanceValid; + // permanent binds and solo binds by difficulty + BoundInstancesMap m_boundInstances; + 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); } + InstanceSave* GetInstanceSave(uint32 mapid); + 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 SetPendingBind(uint32 instanceId, uint32 bindTimer) { _pendingBindId = instanceId; _pendingBindTimer = bindTimer; } [[nodiscard]] bool HasPendingBind() const { return _pendingBindId > 0; } [[nodiscard]] uint32 GetPendingBind() const { return _pendingBindId; } void SendRaidInfo(); + + void SendSavedInstances(); void PrettyPrintRequirementsQuestList(const std::vector& missingQuests) const; void PrettyPrintRequirementsAchievementsList(const std::vector& missingAchievements) const; void PrettyPrintRequirementsItemsList(const std::vector& missingItems) const; bool Satisfy(DungeonProgressionRequirements const* ar, uint32 target_map, bool report = false); bool CheckInstanceLoginValid(); + bool CheckInstanceValidity(bool /*isLogin*/); [[nodiscard]] bool CheckInstanceCount(uint32 instanceId) const; - void AddInstanceEnterTime(uint32 instanceId, time_t enterTime) { if (_instanceResetTimes.find(instanceId) == _instanceResetTimes.end()) @@ -2795,6 +2836,7 @@ class Player : public Unit, public GridObject void _LoadEntryPointData(PreparedQueryResult result); void _LoadGlyphs(PreparedQueryResult result); void _LoadTalents(PreparedQueryResult result); + void _LoadBoundInstances(PreparedQueryResult result); void _LoadInstanceTimeRestrictions(PreparedQueryResult result); void _LoadBrewOfTheMonth(PreparedQueryResult result); void _LoadCharacterSettings(PreparedQueryResult result); diff --git a/src/server/game/Entities/Player/PlayerMisc.cpp b/src/server/game/Entities/Player/PlayerMisc.cpp index 0e75ff4550e9aa..431535b234842e 100644 --- a/src/server/game/Entities/Player/PlayerMisc.cpp +++ b/src/server/game/Entities/Player/PlayerMisc.cpp @@ -192,134 +192,56 @@ void Player::SendResetFailedNotify(uint32 mapid) } /// Reset all solo instances and optionally send a message on success for each -void Player::ResetInstances(ObjectGuid guid, uint8 method, bool isRaid) +void Player::ResetInstances(uint8 method, bool isRaid, bool isLegacy) { - switch (method) - { - case INSTANCE_RESET_ALL: - { - Player* p = ObjectAccessor::FindConnectedPlayer(guid); - if (!p || p->GetDifficulty(false) != DUNGEON_DIFFICULTY_NORMAL) - break; - std::vector toUnbind; - BoundInstancesMap const& m_boundInstances = sInstanceSaveMgr->PlayerGetBoundInstances(p->GetGUID(), Difficulty(DUNGEON_DIFFICULTY_NORMAL)); - for (BoundInstancesMap::const_iterator itr = m_boundInstances.begin(); itr != m_boundInstances.end(); ++itr) - { - InstanceSave* instanceSave = itr->second.save; - MapEntry const* entry = sMapStore.LookupEntry(itr->first); - if (!entry || entry->IsRaid() || !instanceSave->CanReset()) - { - continue; - } + // method can be INSTANCE_RESET_ALL, INSTANCE_RESET_CHANGE_DIFFICULTY, INSTANCE_RESET_GROUP_JOIN - Map* map = sMapMgr->FindMap(instanceSave->GetMapId(), instanceSave->GetInstanceId()); - if (!map || map->ToInstanceMap()->Reset(method)) - { - p->SendResetInstanceSuccess(instanceSave->GetMapId()); - toUnbind.push_back(instanceSave); - } - else - { - p->SendResetInstanceFailed(0, instanceSave->GetMapId()); - } + // we assume that when the difficulty changes, all instances that can be reset will be + for (auto diffCategory : m_boundInstances) { + Difficulty diff = diffCategory.first; + auto difficultyItr = m_boundInstances.find(diff); + if (difficultyItr == m_boundInstances.end()) + return; - sInstanceSaveMgr->DeleteInstanceSavedData(instanceSave->GetInstanceId()); - } - for (std::vector::const_iterator itr = toUnbind.begin(); itr != toUnbind.end(); ++itr) + for (auto itr = difficultyItr->second.begin(); itr != difficultyItr->second.end();) + { + InstanceSave* p = itr->second.save; + MapEntry const* entry = sMapStore.LookupEntry(itr->first); + if (!entry || entry->IsRaid() != isRaid || !p->CanReset()) { - sInstanceSaveMgr->UnbindAllFor(*itr); + ++itr; + continue; } - } - break; - case INSTANCE_RESET_CHANGE_DIFFICULTY: - { - Player* p = ObjectAccessor::FindConnectedPlayer(guid); - if (!p) - break; - std::vector toUnbind; - BoundInstancesMap const& m_boundInstances = sInstanceSaveMgr->PlayerGetBoundInstances(p->GetGUID(), p->GetDifficulty(isRaid)); - for (BoundInstancesMap::const_iterator itr = m_boundInstances.begin(); itr != m_boundInstances.end(); ++itr) + + if (method == INSTANCE_RESET_ALL) { - InstanceSave* instanceSave = itr->second.save; - MapEntry const* entry = sMapStore.LookupEntry(itr->first); - if (!entry || entry->IsRaid() != isRaid || !instanceSave->CanReset()) + // the "reset all instances" method can only reset normal maps + if (entry->IsRaid() || diff == Difficulty::DUNGEON_DIFFICULTY_HEROIC) { + ++itr; continue; } + } - Map* map = sMapMgr->FindMap(instanceSave->GetMapId(), instanceSave->GetInstanceId()); - if (!map || map->ToInstanceMap()->Reset(method)) + // if the map is loaded, reset it + Map* map = sMapMgr->FindMap(p->GetMapId(), p->GetInstanceId()); + if (map && map->IsDungeon()) + if (!map->ToInstanceMap()->Reset(method)) { - p->SendResetInstanceSuccess(instanceSave->GetMapId()); - toUnbind.push_back(instanceSave); - } - else - { - p->SendResetInstanceFailed(0, instanceSave->GetMapId()); + ++itr; + continue; } - sInstanceSaveMgr->DeleteInstanceSavedData(instanceSave->GetInstanceId()); - } - for (std::vector::const_iterator itr = toUnbind.begin(); itr != toUnbind.end(); ++itr) - sInstanceSaveMgr->UnbindAllFor(*itr); - } - break; - case INSTANCE_RESET_GROUP_JOIN: - { - Player* p = ObjectAccessor::FindConnectedPlayer(guid); - if (!p) - break; - for (uint8 d = 0; d < MAX_DIFFICULTY; ++d) - { - std::vector toUnbind; - BoundInstancesMap const& m_boundInstances = sInstanceSaveMgr->PlayerGetBoundInstances(p->GetGUID(), Difficulty(d)); - for (BoundInstancesMap::const_iterator itr = m_boundInstances.begin(); itr != m_boundInstances.end(); ++itr) - { - if (itr->second.perm) - continue; - InstanceSave* instanceSave = itr->second.save; - Map* map = sMapMgr->FindMap(instanceSave->GetMapId(), instanceSave->GetInstanceId()); - if (!map || p->FindMap() != map) - { - //p->SendResetInstanceSuccess(instanceSave->GetMapId()); - toUnbind.push_back(instanceSave); - } - //else - // p->SendResetInstanceFailed(0, instanceSave->GetMapId()); - - sInstanceSaveMgr->DeleteInstanceSavedData(instanceSave->GetInstanceId()); - } - for (std::vector::const_iterator itr = toUnbind.begin(); itr != toUnbind.end(); ++itr) - sInstanceSaveMgr->PlayerUnbindInstance(p->GetGUID(), (*itr)->GetMapId(), (*itr)->GetDifficulty(), true, p); - } - } - break; - case INSTANCE_RESET_GROUP_LEAVE: - { - Player* p = ObjectAccessor::FindConnectedPlayer(guid); - for (uint8 d = 0; d < MAX_DIFFICULTY; ++d) - { - std::vector toUnbind; - BoundInstancesMap const& m_boundInstances = sInstanceSaveMgr->PlayerGetBoundInstances(guid, Difficulty(d)); - for (BoundInstancesMap::const_iterator itr = m_boundInstances.begin(); itr != m_boundInstances.end(); ++itr) - { - if (itr->second.perm) - continue; - InstanceSave* instanceSave = itr->second.save; - Map* map = sMapMgr->FindMap(instanceSave->GetMapId(), instanceSave->GetInstanceId()); - if (!p || !map || p->FindMap() != map) - { - //p->SendResetInstanceSuccess(instanceSave->GetMapId()); - toUnbind.push_back(instanceSave); - } - //else - // p->SendResetInstanceFailed(0, instanceSave->GetMapId()); - } - for (std::vector::const_iterator itr = toUnbind.begin(); itr != toUnbind.end(); ++itr) - sInstanceSaveMgr->PlayerUnbindInstance(guid, (*itr)->GetMapId(), (*itr)->GetDifficulty(), true, p); - } + // since this is a solo instance there should not be any players inside + if (method == INSTANCE_RESET_ALL || method == INSTANCE_RESET_CHANGE_DIFFICULTY) + SendResetInstanceSuccess(p->GetMapId()); + + p->DeleteFromDB(); + difficultyItr->second.erase(itr++); + + // the following should remove the instance save from the manager and delete it as well + p->RemovePlayer(this); } - break; } } diff --git a/src/server/game/Entities/Player/PlayerStorage.cpp b/src/server/game/Entities/Player/PlayerStorage.cpp index cdce701bf0f0e1..d9ef77d2644c6f 100644 --- a/src/server/game/Entities/Player/PlayerStorage.cpp +++ b/src/server/game/Entities/Player/PlayerStorage.cpp @@ -5138,9 +5138,6 @@ bool Player::LoadFromDB(ObjectGuid playerGuid, CharacterDatabaseQueryHolder cons //Other way is to saves m_team into characters table. SetFactionForRace(getRace(true)); - // pussywizard: create empty instance bind containers if necessary - sInstanceSaveMgr->PlayerCreateBoundInstancesMaps(playerGuid); - // load home bind and check in same time class/race pair, it used later for restore broken positions if (!_LoadHomeBind(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_HOME_BIND))) return false; @@ -5179,6 +5176,7 @@ bool Player::LoadFromDB(ObjectGuid playerGuid, CharacterDatabaseQueryHolder cons SetUInt16Value(PLAYER_FIELD_KILLS, 0, fields[49].Get()); SetUInt16Value(PLAYER_FIELD_KILLS, 1, fields[50].Get()); + _LoadBoundInstances(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_BOUND_INSTANCES)); _LoadInstanceTimeRestrictions(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_INSTANCE_LOCK_TIMES)); _LoadEntryPointData(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_ENTRY_POINT)); @@ -5187,32 +5185,12 @@ bool Player::LoadFromDB(ObjectGuid playerGuid, CharacterDatabaseQueryHolder cons Map* map = nullptr; - // pussywizard: group changed difficulty when player was offline, teleport to the enterance of new difficulty - if (mapEntry && ((mapEntry->IsNonRaidDungeon() && dungeonDiff != GetDungeonDifficulty()) || (mapEntry->IsRaid() && raidDiff != GetRaidDifficulty()))) - { - bool fixed = false; - if (uint32 destInstId = sInstanceSaveMgr->PlayerGetDestinationInstanceId(this, mapId, GetDifficulty(mapEntry->IsRaid()))) - { - instanceId = destInstId; - if (AreaTriggerTeleport const* at = sObjectMgr->GetMapEntranceTrigger(mapId)) - { - Relocate(at->target_X, at->target_Y, at->target_Z, at->target_Orientation); - fixed = true; - } - } - if (!fixed) - { - RelocateToHomebind(); - mapEntry = sMapStore.LookupEntry(mapId); - } - } - if (!mapEntry || !IsPositionValid()) { LOG_ERROR("entities.player", "Player (guidlow {}) have invalid coordinates (MapId: {} X: {} Y: {} Z: {} O: {}). Teleport to default race/class locations.", guid, mapId, GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation()); RelocateToHomebind(); } - // Player was saved in Arena or Bg + // Player was saved in Arena or Bg else if (mapEntry->IsBattlegroundOrArena()) { // xinef: resurrect player, cant log in dead without corpse @@ -5244,7 +5222,7 @@ bool Player::LoadFromDB(ObjectGuid playerGuid, CharacterDatabaseQueryHolder cons } } } - // currently we do not support transport in bg + // currently we do not support transport in bg else if (transLowGUID != 0) { // transLowGUID > 0 ---> motion transport guid @@ -5305,7 +5283,7 @@ bool Player::LoadFromDB(ObjectGuid playerGuid, CharacterDatabaseQueryHolder cons RelocateToHomebind(); } } - // currently we do not support taxi in instance + // currently we do not support taxi in instance else if (!taxi_nodes.empty()) { instanceId = 0; @@ -5330,11 +5308,9 @@ bool Player::LoadFromDB(ObjectGuid playerGuid, CharacterDatabaseQueryHolder cons // check whether player was unbound or is bound to another instance if (instanceId) - { - InstanceSave* save = sInstanceSaveMgr->PlayerGetInstanceSave(GetGUID(), mapId, GetDifficulty(mapEntry->IsRaid())); - if (!save || save->GetInstanceId() != instanceId) - instanceId = 0; - } + if (InstanceSave* save = GetInstanceSave(mapId)) + if (save->GetInstanceId() != instanceId) + instanceId = 0; } // if the player is in an instance and it has been reset in the meantime teleport him to the entrance @@ -6624,6 +6600,232 @@ void Player::_LoadGroup() RemovePlayerFlag(PLAYER_FLAGS_GROUP_LEADER); } +void Player::_LoadBoundInstances(PreparedQueryResult result) +{ + m_boundInstances.clear(); + + Group* group = GetGroup(); + + // 0 1 2 3 4 5 6 + // SELECT id, permanent, map, difficulty, extended, resettime, entranceId FROM character_instance LEFT JOIN instance ON instance = id WHERE guid = ? + if (result) + { + do + { + Field* fields = result->Fetch(); + + bool perm = fields[1].Get(); + uint32 mapId = fields[2].Get(); + uint32 instanceId = fields[0].Get(); + uint8 difficulty = fields[3].Get(); + BindExtensionState extendState = BindExtensionState(fields[4].Get()); + + time_t resetTime = time_t(fields[5].Get()); + // the resettime for normal instances is only saved when the InstanceSave is unloaded + // so the value read from the DB may be wrong here but only if the InstanceSave is loaded + // and in that case it is not used + + uint32 entranceId = fields[6].Get(); + + bool deleteInstance = false; + + MapEntry const* mapEntry = sMapStore.LookupEntry(mapId); + std::string mapname = mapEntry ? mapEntry->name[sWorld->GetDefaultDbcLocale()] : "Unknown"; + + if (!mapEntry || !mapEntry->IsDungeon()) + { + LOG_ERROR("entities.player", "Player::_LoadBoundInstances: Player '%s' (%s) has bind to not existed or not dungeon map %d (%s)", + GetName().c_str(), GetGUID().ToString().c_str(), mapId, mapname.c_str()); + deleteInstance = true; + } + else if (difficulty) + { + LOG_ERROR("entities.player", "Player::_LoadBoundInstances: player '%s' (%s) has bind to not existed difficulty %d instance for map %u (%s)", + GetName().c_str(), GetGUID().ToString().c_str(), difficulty, mapId, mapname.c_str()); + deleteInstance = true; + } + else + { + MapDifficulty const* mapDiff = GetMapDifficultyData(mapId, Difficulty(difficulty)); + if (!mapDiff) + { + LOG_ERROR("entities.player", "Player::_LoadBoundInstances: player '%s' (%s) has bind to not existed difficulty %d instance for map %u (%s)", + GetName().c_str(), GetGUID().ToString().c_str(), difficulty, mapId, mapname.c_str()); + deleteInstance = true; + } + else if (!perm && group) + { + LOG_ERROR("entities.player", "Player::_LoadBoundInstances: player '%s' (%s) is in group %s but has a non-permanent character bind to map %d (%s), %d, %d", + GetName().c_str(), GetGUID().ToString().c_str(), group->GetGUID().ToString().c_str(), mapId, mapname.c_str(), instanceId, difficulty); + deleteInstance = true; + } + } + + if (deleteInstance) + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_GUID); + + stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(1, instanceId); + + CharacterDatabase.Execute(stmt); + + continue; + } + + // since non permanent binds are always solo bind, they can always be reset + if (InstanceSave* save = sInstanceSaveMgr->AddInstanceSave(mapId, instanceId, Difficulty(difficulty), resetTime, entranceId, !perm, true)) + BindToInstance(save, perm, extendState, true); + } while (result->NextRow()); + } +} + +InstancePlayerBind* Player::GetBoundInstance(uint32 mapid, Difficulty difficulty, bool withExpired) +{ + // some instances only have one difficulty + MapDifficulty const* mapDiff = GetDownscaledMapDifficultyData(mapid, difficulty); + if (!mapDiff) + return nullptr; + + auto difficultyItr = m_boundInstances.find(difficulty); + if (difficultyItr == m_boundInstances.end()) + return nullptr; + + auto itr = difficultyItr->second.find(mapid); + if (itr != difficultyItr->second.end()) + if (itr->second.extendState || withExpired) + return &itr->second; + return nullptr; +} + +InstancePlayerBind const* Player::GetBoundInstance(uint32 mapid, Difficulty difficulty) const +{ + // some instances only have one difficulty + MapDifficulty const* mapDiff = GetDownscaledMapDifficultyData(mapid, difficulty); + if (!mapDiff) + return nullptr; + + auto difficultyItr = m_boundInstances.find(difficulty); + if (difficultyItr == m_boundInstances.end()) + return nullptr; + + auto itr = difficultyItr->second.find(mapid); + if (itr != difficultyItr->second.end()) + return &itr->second; + + return nullptr; +} + +InstanceSave* Player::GetInstanceSave(uint32 mapid) +{ + MapEntry const* mapEntry = sMapStore.LookupEntry(mapid); + InstancePlayerBind* pBind = GetBoundInstance(mapid, GetDifficulty(mapEntry->IsRaid())); + InstanceSave* pSave = pBind ? pBind->save : nullptr; + if (!pBind || !pBind->perm) + if (Group* group = GetGroup()) + if (InstanceGroupBind* groupBind = group->GetBoundInstance(GetDifficulty(mapEntry->IsRaid()), mapid)) + pSave = groupBind->save; + + return pSave; +} + +void Player::UnbindInstance(uint32 mapid, Difficulty difficulty, bool unload) +{ + auto difficultyItr = m_boundInstances.find(difficulty); + if (difficultyItr != m_boundInstances.end()) + { + auto itr = difficultyItr->second.find(mapid); + if (itr != difficultyItr->second.end()) + UnbindInstance(itr, difficultyItr, unload); + } +} + +void Player::UnbindInstance(BoundInstancesMap::mapped_type::iterator& itr, BoundInstancesMap::iterator& difficultyItr, bool unload) +{ + if (itr != difficultyItr->second.end()) + { + if (!unload) + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_GUID); + + stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(1, itr->second.save->GetInstanceId()); + + CharacterDatabase.Execute(stmt); + } + + if (itr->second.perm) + GetSession()->SendCalendarRaidLockout(itr->second.save, false); + + itr->second.save->RemovePlayer(this); // save can become invalid + difficultyItr->second.erase(itr++); + } +} + +InstancePlayerBind* Player::BindToInstance(InstanceSave* save, bool permanent, BindExtensionState extendState, bool load) +{ + if (save) + { + InstancePlayerBind& bind = m_boundInstances[save->GetDifficultyID()][save->GetMapId()]; + if (extendState == EXTEND_STATE_KEEP) // special flag, keep the player's current extend state when updating for new boss down + { + if (save == bind.save) + extendState = bind.extendState; + else + extendState = EXTEND_STATE_NORMAL; + } + if (!load) + { + if (bind.save) + { + // update the save when the group kills a boss + if (permanent != bind.perm || save != bind.save || extendState != bind.extendState) + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_INSTANCE); + + stmt->SetData(0, save->GetInstanceId()); + stmt->SetData(1, permanent); + stmt->SetData(2, extendState); + stmt->SetData(3, GetGUID().GetCounter()); + stmt->SetData(4, bind.save->GetInstanceId()); + + CharacterDatabase.Execute(stmt); + } + } + else + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_INSTANCE); + + stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(1, save->GetInstanceId()); + stmt->SetData(2, permanent); + stmt->SetData(3, extendState); + + CharacterDatabase.Execute(stmt); + } + } + + if (bind.save != save) + { + if (bind.save) + bind.save->RemovePlayer(this); + save->AddPlayer(this); + } + + save->SetCanReset(true); + + bind.save = save; + bind.perm = permanent; + bind.extendState = extendState; + if (!load) + LOG_DEBUG("maps", "Player::BindToInstance: Player '{}' ({}) is now bound to map (ID: {}, Instance {}, Difficulty {})", GetName().c_str(), GetGUID().ToString().c_str(), save->GetMapId(), save->GetInstanceId(), save->GetDifficultyID()); + sScriptMgr->OnPlayerBindToInstance(this, save->GetDifficultyID(), save->GetMapId(), permanent, uint8(extendState)); + return &bind; + } + + return nullptr; +} + void Player::BindToInstance() { InstanceSave* mapSave = sInstanceSaveMgr->GetInstanceSave(_pendingBindId); @@ -6631,9 +6833,14 @@ void Player::BindToInstance() return; WorldPacket data(SMSG_INSTANCE_SAVE_CREATED, 4); - data << uint32(0); + data << uint32(IsGameMaster()); GetSession()->SendPacket(&data); - sInstanceSaveMgr->PlayerBindToInstance(this->GetGUID(), mapSave, true, this); + + if (!IsGameMaster()) + { + BindToInstance(mapSave, true, EXTEND_STATE_KEEP); + GetSession()->SendCalendarRaidLockout(mapSave, true); + } } void Player::SendRaidInfo() @@ -6647,20 +6854,20 @@ void Player::SendRaidInfo() time_t now = GameTime::GetGameTime().count(); - for (uint8 i = 0; i < MAX_DIFFICULTY; ++i) + for (auto difficultyItr = m_boundInstances.begin(); difficultyItr != m_boundInstances.end(); ++difficultyItr) { - BoundInstancesMap const& m_boundInstances = sInstanceSaveMgr->PlayerGetBoundInstances(GetGUID(), Difficulty(i)); - for (BoundInstancesMap::const_iterator itr = m_boundInstances.begin(); itr != m_boundInstances.end(); ++itr) + for (auto itr = difficultyItr->second.begin(); itr != difficultyItr->second.end(); ++itr) { - if (itr->second.perm) + InstancePlayerBind const& bind = itr->second; + if (bind.perm) { InstanceSave* save = itr->second.save; - time_t resetTime = itr->second.extended ? save->GetExtendedResetTime() : save->GetResetTime(); + time_t resetTime = save->GetResetTime(); data << uint32(save->GetMapId()); // map id - data << uint32(save->GetDifficulty()); // difficulty + data << uint32(save->GetDifficultyID()); // difficulty data << ObjectGuid::Create(save->GetInstanceId()); // instance id data << uint8(1); // expired = 0 - data << uint8(itr->second.extended ? 1 : 0);// extended = 1 + data << uint8(itr->second.extendState ? 1 : 0);// extended = 1 data << uint32(resetTime >= now ? resetTime - now : 0); // reset time ++counter; } @@ -6678,12 +6885,12 @@ void Player::SendSavedInstances() bool hasBeenSaved = false; WorldPacket data; - for (uint8 i = 0; i < MAX_DIFFICULTY; ++i) + for (BoundInstancesMap::const_iterator difficultyItr = m_boundInstances.begin(); difficultyItr != m_boundInstances.end(); ++difficultyItr) { - BoundInstancesMap const& m_boundInstances = sInstanceSaveMgr->PlayerGetBoundInstances(GetGUID(), Difficulty(i)); - for (BoundInstancesMap::const_iterator itr = m_boundInstances.begin(); itr != m_boundInstances.end(); ++itr) + for (auto itr = difficultyItr->second.begin(); itr != difficultyItr->second.end(); ++itr) { - if (itr->second.perm) // only permanent binds are sent + InstancePlayerBind const& bind = itr->second; + if (bind.perm) // only permanent binds are sent { hasBeenSaved = true; break; @@ -6699,12 +6906,12 @@ void Player::SendSavedInstances() if (!hasBeenSaved) return; - for (uint8 i = 0; i < MAX_DIFFICULTY; ++i) + for (BoundInstancesMap::const_iterator difficultyItr = m_boundInstances.begin(); difficultyItr != m_boundInstances.end(); ++difficultyItr) { - BoundInstancesMap const& m_boundInstances = sInstanceSaveMgr->PlayerGetBoundInstances(GetGUID(), Difficulty(i)); - for (BoundInstancesMap::const_iterator itr = m_boundInstances.begin(); itr != m_boundInstances.end(); ++itr) + for (auto itr = difficultyItr->second.begin(); itr != difficultyItr->second.end(); ++itr) { - if (itr->second.perm) + InstancePlayerBind const& bind = itr->second; + if (bind.perm) // only permanent binds are sent { data.Initialize(SMSG_UPDATE_LAST_INSTANCE); data << uint32(itr->second.save->GetMapId()); @@ -7097,6 +7304,59 @@ bool Player::CheckInstanceLoginValid() return sMapMgr->PlayerCannotEnter(GetMap()->GetId(), this, true) == Map::CAN_ENTER; } +bool Player::CheckInstanceValidity(bool /*isLogin*/) +{ + // game masters' instances are always valid + if (IsGameMaster()) + return true; + + // non-instances are always valid + Map* map = FindMap(); + if (!map || !map->IsDungeon()) + return true; + + // raid instances require the player to be in a raid group to be valid + if (map->IsRaid() && !sWorld->getBoolConfig(CONFIG_INSTANCE_IGNORE_RAID) && (map->GetEntry()->Expansion() >= sWorld->getIntConfig(CONFIG_EXPANSION))) + if (!GetGroup() || !GetGroup()->isRaidGroup()) + return false; + + if (Group* group = GetGroup()) + { + // check if player's group is bound to this instance + InstanceGroupBind* bind = group->GetBoundInstance(map->GetDifficulty(), map->GetId()); + if (!bind || !bind->save || bind->save->GetInstanceId() != map->GetInstanceId()) + return false; + + Map::PlayerList const& players = map->GetPlayers(); + if (!players.IsEmpty()) + for (Map::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it) + { + if (Player* otherPlayer = it->GetSource()) + { + if (otherPlayer->IsGameMaster()) + continue; + if (!otherPlayer->m_InstanceValid) // ignore players that currently have a homebind timer active + continue; + if (group != otherPlayer->GetGroup()) + return false; + } + } + } + else + { + // instance is invalid if we are not grouped and there are other players + if (map->GetPlayersCountExceptGMs() > 1) + return false; + + // check if the player is bound to this instance + InstancePlayerBind* bind = GetBoundInstance(map->GetId(), map->GetDifficulty()); + if (!bind || !bind->save || bind->save->GetInstanceId() != map->GetInstanceId()) + return false; + } + + return true; +} + bool Player::CheckInstanceCount(uint32 instanceId) const { if (_instanceResetTimes.size() < sWorld->getIntConfig(CONFIG_MAX_INSTANCES_PER_HOUR)) diff --git a/src/server/game/Globals/AreaTriggerDataStore.cpp b/src/server/game/Globals/AreaTriggerDataStore.cpp index 88e513c822fb0d..73c13ac6685c96 100644 --- a/src/server/game/Globals/AreaTriggerDataStore.cpp +++ b/src/server/game/Globals/AreaTriggerDataStore.cpp @@ -62,16 +62,6 @@ void AreaTriggerDataStore::LoadAreaTriggerTemplates() continue; } - if (actionType == AREATRIGGER_ACTION_TELEPORT) - { - if (!sObjectMgr->GetWorldSafeLoc(action.Param)) - { - LOG_ERROR("sql.sql", "Table `areatrigger_template_actions` has invalid entry ({},{}) with TargetType=Teleport and Param ({}) not a valid world safe loc entry", - areaTriggerId.Id, uint32(areaTriggerId.IsServerSide), action.Param); - continue; - } - } - action.TargetType = AreaTriggerActionUserTypes(targetType); action.ActionType = AreaTriggerActionTypes(actionType); diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index bfa3b95ebb18d8..4fc76a2d111349 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -10511,72 +10511,6 @@ uint32 ObjectMgr::GetQuestMoneyReward(uint8 level, uint32 questMoneyDifficulty) return 0; } -void ObjectMgr::LoadInstanceSavedGameobjectStateData() -{ - uint32 oldMSTime = getMSTime(); - - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SELECT_INSTANCE_SAVED_DATA); - PreparedQueryResult result = CharacterDatabase.Query(stmt); - - if (!result) - { - // There's no gameobject with this GUID saved on the DB - LOG_INFO("sql.sql", ">> Loaded 0 Instance saved gameobject state data. DB table `instance_saved_go_state_data` is empty."); - return; - } - - Field* fields; - uint32 count = 0; - do - { - fields = result->Fetch(); - GameobjectInstanceSavedStateList.push_back({ fields[0].Get(), fields[1].Get(), fields[2].Get() }); - count++; - } while (result->NextRow()); - - LOG_INFO("server.loading", ">> Loaded {} instance saved gameobject state data in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); - LOG_INFO("server.loading", " "); -} - -uint8 ObjectMgr::GetInstanceSavedGameobjectState(uint32 id, uint32 guid) -{ - for (auto it = GameobjectInstanceSavedStateList.begin(); it != GameobjectInstanceSavedStateList.end(); it++) - { - if (it->m_guid == guid && it->m_instance == id) - { - return it->m_state; - } - } - return 3; // Any state higher than 2 to get the default state -} - -bool ObjectMgr::FindInstanceSavedGameobjectState(uint32 id, uint32 guid) -{ - for (auto it = GameobjectInstanceSavedStateList.begin(); it != GameobjectInstanceSavedStateList.end(); it++) - { - if (it->m_guid == guid && it->m_instance == id) - { - return true; - } - } - return false; -} - -void ObjectMgr::SetInstanceSavedGameobjectState(uint32 id, uint32 guid, uint8 state) -{ - for (auto it = GameobjectInstanceSavedStateList.begin(); it != GameobjectInstanceSavedStateList.end(); it++) - { - if (it->m_guid == guid && it->m_instance == id) - { - it->m_state = state; - } - } -} -void ObjectMgr::NewInstanceSavedGameobjectState(uint32 id, uint32 guid, uint8 state) -{ - GameobjectInstanceSavedStateList.push_back({ id, guid, state }); -} - void ObjectMgr::SendServerMail(Player* player, uint32 id, uint32 reqLevel, uint32 reqPlayTime, uint32 rewardMoneyA, uint32 rewardMoneyH, uint32 rewardItemA, uint32 rewardItemCountA, uint32 rewardItemH, uint32 rewardItemCountH, std::string subject, std::string body, uint8 active) const { if (active) @@ -10892,26 +10826,28 @@ void ObjectMgr::LoadWorldSafeLocs() uint32 oldMSTime = getMSTime(); // 0 1 2 3 4 5 - if (QueryResult result = WorldDatabase.Query("SELECT ID, MapID, LocX, LocY, LocZ, Facing FROM world_safe_locs")) + if (QueryResult result = WorldDatabase.Query("SELECT ID, Map, LocX, LocY, LocZ, Facing FROM world_safe_locs")) { do { Field* fields = result->Fetch(); uint32 id = fields[0].Get(); - WorldLocation loc(fields[1].Get(), fields[2].Get(), fields[3].Get(), fields[4].Get(), fields[5].Get()); + uint32 map = fields[1].Get(); + WorldLocation loc(map, fields[2].Get(), fields[3].Get(), fields[4].Get(), fields[5].Get()); if (!MapMgr::IsValidMapCoord(loc)) { - LOG_ERROR("sql.sql", "World location (ID: %u) has a invalid position MapID: %u %s, skipped", id, loc.GetMapId(), loc.ToString().c_str()); + LOG_ERROR("sql.sql", "World location (ID: {}) has a invalid position MapID: {} {}, skipped", id, loc.GetMapId(), loc.ToString().c_str()); continue; } - WorldSafeLocsEntry* worldSafeLocs = _worldSafeLocs[id]; + WorldSafeLocsEntry* worldSafeLocs = new WorldSafeLocsEntry(); worldSafeLocs->ID = id; worldSafeLocs->Loc.WorldRelocate(loc); + _worldSafeLocs[map][id] = worldSafeLocs; } while (result->NextRow()); - LOG_INFO("server.loading", ">> Loaded {} world locations %u ms", _worldSafeLocs.size(), GetMSTimeDiffToNow(oldMSTime)); + LOG_INFO("server.loading", ">> Loaded {} world locations {} ms", _worldSafeLocs.size(), GetMSTimeDiffToNow(oldMSTime)); } else LOG_INFO("server.loading", ">> Loaded 0 world locations. DB table `world_safe_locs` is empty."); @@ -10974,11 +10910,16 @@ MapChallengeModeEntry* ObjectMgr::GetChallengeMode(uint32 id) const return nullptr; } -WorldSafeLocsEntry* ObjectMgr::GetWorldSafeLoc(uint32 id) const +WorldSafeLocsEntry* ObjectMgr::GetWorldSafeLoc(uint32 map, uint32 id) const { - auto out = _worldSafeLocs.find(id); - if (out != _worldSafeLocs.end()) - return out->second; + auto out = _worldSafeLocs.find(map); + if (out != _worldSafeLocs.end()) { + auto loc = out->second.find(id); + if (loc != out->second.end()) + return loc->second; + else + return nullptr; + } else return nullptr; } diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index 35a90c60686504..14e8297be6ae51 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -1521,14 +1521,8 @@ class ObjectMgr [[nodiscard]] uint32 GetQuestMoneyReward(uint8 level, uint32 questMoneyDifficulty) const; void SendServerMail(Player* player, uint32 id, uint32 reqLevel, uint32 reqPlayTime, uint32 rewardMoneyA, uint32 rewardMoneyH, uint32 rewardItemA, uint32 rewardItemCountA, uint32 rewardItemH, uint32 rewardItemCountH, std::string subject, std::string body, uint8 active) const; - void LoadInstanceSavedGameobjectStateData(); - bool FindInstanceSavedGameobjectState(uint32 id, uint32 guid); - uint8 GetInstanceSavedGameobjectState(uint32 id, uint32 guid); - void SetInstanceSavedGameobjectState(uint32 id, uint32 guid, uint8 state); - void NewInstanceSavedGameobjectState(uint32 id, uint32 guid, uint8 state); - // hater: m+ - WorldSafeLocsEntry* GetWorldSafeLoc(uint32 id) const; + WorldSafeLocsEntry* GetWorldSafeLoc(uint32 map, uint32 id) const; MapChallengeModeEntry* GetChallengeMode(uint32 id) const; void LoadWorldSafeLocs(); void LoadMythicLevelScale(); @@ -1937,7 +1931,7 @@ class ObjectMgr std::set _hasDifficultyEntries[MAX_DIFFICULTY - 1]; // already loaded creatures with difficulty 1 values, used in CheckCreatureTemplate // hater: m+ - std::unordered_map _worldSafeLocs; + std::unordered_map< uint32 /*map id*/, std::unordered_map> _worldSafeLocs; std::unordered_map _cacheChallengeMode; std::unordered_map> _cacheMythicMinionValues; typedef std::unordered_map> InstanceDifficultyMultiplierContainer; diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp index e2208fa849a7e7..e6a9698ae1b64c 100644 --- a/src/server/game/Groups/Group.cpp +++ b/src/server/game/Groups/Group.cpp @@ -136,12 +136,15 @@ bool Group::Create(Player* leader) m_dungeonDifficulty = leader->GetDungeonDifficulty(); m_raidDifficulty = leader->GetRaidDifficulty(); + m_dbStoreId = sGroupMgr->GenerateNewGroupDbStoreId(); + sGroupMgr->RegisterGroupDbStoreId(m_dbStoreId, this); + // Store group in database CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_GROUP); uint8 index = 0; - stmt->SetData(index++, lowguid); + stmt->SetData(index++, m_dbStoreId); stmt->SetData(index++, m_leaderGuid.GetCounter()); stmt->SetData(index++, uint8(m_lootMethod)); stmt->SetData(index++, m_looterGuid.GetCounter()); @@ -173,27 +176,14 @@ bool Group::Create(Player* leader) bool Group::LoadGroupFromDB(Field* fields) { - ObjectGuid::LowType groupLowGuid = fields[16].Get(); - m_guid = ObjectGuid::Create(groupLowGuid); + m_dbStoreId = fields[17].Get(); + m_guid = ObjectGuid::Create(sGroupMgr->GenerateGroupId()); m_leaderGuid = ObjectGuid::Create(fields[0].Get()); // group leader not exist if (!sCharacterCache->GetCharacterNameByGuid(m_leaderGuid, m_leaderName)) - { - CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GROUP); - stmt->SetData(0, groupLowGuid); - trans->Append(stmt); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GROUP_MEMBER_ALL); - stmt->SetData(0, groupLowGuid); - trans->Append(stmt); - CharacterDatabase.CommitTransaction(trans); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_LFG_DATA); - stmt->SetData(0, groupLowGuid); - CharacterDatabase.Execute(stmt); return false; - } m_lootMethod = LootMethod(fields[1].Get()); m_looterGuid = ObjectGuid::Create(fields[2].Get()); @@ -271,7 +261,7 @@ void Group::ConvertToLFG(bool restricted /*= true*/) CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GROUP_TYPE); stmt->SetData(0, uint8(m_groupType)); - stmt->SetData(1, GetGUID().GetCounter()); + stmt->SetData(1, m_dbStoreId); CharacterDatabase.Execute(stmt); } @@ -300,7 +290,7 @@ void Group::ConvertToRaid() CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GROUP_TYPE); stmt->SetData(0, uint8(m_groupType)); - stmt->SetData(1, GetGUID().GetCounter()); + stmt->SetData(1, m_dbStoreId); CharacterDatabase.Execute(stmt); } @@ -437,7 +427,7 @@ bool Group::AddMember(Player* player) player->SetGroup(this, subGroup); // if the same group invites the player back, cancel the homebind timer - _cancelHomebindIfInstance(player); + player->m_InstanceValid = player->CheckInstanceValidity(false); if (!isRaidGroup()) // reset targetIcons for non-raid-groups { @@ -448,7 +438,7 @@ bool Group::AddMember(Player* player) if (!isBGGroup() && !isBFGroup()) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_GROUP_MEMBER); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, m_dbStoreId); stmt->SetData(1, member.guid.GetCounter()); stmt->SetData(2, member.flags); stmt->SetData(3, member.group); @@ -464,23 +454,22 @@ bool Group::AddMember(Player* player) if (!IsLeader(player->GetGUID()) && !isBGGroup() && !isBFGroup()) { - Player::ResetInstances(player->GetGUID(), INSTANCE_RESET_GROUP_JOIN, false); + // reset the new member's instances, unless he is currently in one of them + // including raid/heroic instances that they are not permanently bound to! + player->ResetInstances(INSTANCE_RESET_GROUP_JOIN, false, false); + player->ResetInstances(INSTANCE_RESET_GROUP_JOIN, true, false); - if (player->GetDungeonDifficulty() != GetDungeonDifficulty()) + if (player->GetDungeonDifficulty() != GetDungeonDifficultyID()) { - player->SetDungeonDifficulty(GetDungeonDifficulty()); - player->SendDungeonDifficulty(true); + player->SetDungeonDifficulty(GetDungeonDifficultyID()); + player->SendDungeonDifficulty(false); } - if (player->GetRaidDifficulty() != GetRaidDifficulty()) + if (player->GetRaidDifficulty() != GetRaidDifficultyID()) { - player->SetRaidDifficulty(GetRaidDifficulty()); - player->SendRaidDifficulty(true); + player->SetRaidDifficulty(GetRaidDifficultyID()); + player->SendRaidDifficulty(false); } } - else if (IsLeader(player->GetGUID()) && isLFGGroup()) // pussywizard - { - Player::ResetInstances(player->GetGUID(), INSTANCE_RESET_GROUP_JOIN, false); - } player->SetGroupUpdateFlag(GROUP_UPDATE_FULL); UpdatePlayerOutOfRange(player); @@ -597,6 +586,7 @@ bool Group::RemoveMember(ObjectGuid guid, const RemoveMethod& method /*= GROUP_R stmt->SetData(0, guid.GetCounter()); stmt->SetData(1, GetGUID().GetCounter()); CharacterDatabase.Execute(stmt); + DelinkMember(guid); } // Remove player from loot rolls @@ -666,8 +656,6 @@ bool Group::RemoveMember(ObjectGuid guid, const RemoveMethod& method /*= GROUP_R } _homebindIfInstance(player); - if (!isBGGroup() && !isBFGroup()) - Player::ResetInstances(guid, INSTANCE_RESET_GROUP_LEAVE, false); sScriptMgr->OnGroupRemoveMember(this, guid, method, kicker, reason); @@ -725,14 +713,40 @@ void Group::ChangeLeader(ObjectGuid newLeaderGuid) if (!isBGGroup() && !isBFGroup()) { CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + // Update the group leader + // Remove the groups permanent instance bindings + for (auto difficultyItr = m_boundInstances.begin(); difficultyItr != m_boundInstances.end(); ++difficultyItr) + { + for (auto itr = difficultyItr->second.begin(); itr != difficultyItr->second.end();) + { + // Do not unbind saves of instances that already had map created (a newLeader entered) + // forcing a new instance with another leader requires group disbanding (confirmed on retail) + if (itr->second.perm && !sMapMgr->FindMap(itr->first, itr->second.save->GetInstanceId())) + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GROUP_INSTANCE_PERM_BINDING); + stmt->SetData(0, m_dbStoreId); + stmt->SetData(1, itr->second.save->GetInstanceId()); + trans->Append(stmt); + + itr->second.save->RemoveGroup(this); + difficultyItr->second.erase(itr++); + } + else + ++itr; + } + } + // Copy the permanent binds from the new leader to the group + Group::ConvertLeaderInstancesToGroup(newLeader, this, true); + // Update the group leader CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GROUP_LEADER); + stmt->SetData(0, newLeader->GetGUID().GetCounter()); - stmt->SetData(1, GetGUID().GetCounter()); + stmt->SetData(1, m_dbStoreId); + trans->Append(stmt); - CharacterDatabase.CommitTransaction(trans); - sInstanceSaveMgr->CopyBinds(m_leaderGuid, newLeaderGuid, newLeader); + CharacterDatabase.CommitTransaction(trans); } if (Player* oldLeader = ObjectAccessor::FindConnectedPlayer(m_leaderGuid)) @@ -750,6 +764,43 @@ void Group::ChangeLeader(ObjectGuid newLeaderGuid) sScriptMgr->OnGroupChangeLeader(this, newLeaderGuid, m_leaderGuid); // This hook should be executed at the end - Not used anywhere in the original core } +/// convert the player's binds to the group +void Group::ConvertLeaderInstancesToGroup(Player* player, Group* group, bool switchLeader) +{ + // copy all binds to the group, when changing leader it's assumed the character + // will not have any solo binds + for (auto difficultyItr = player->m_boundInstances.begin(); difficultyItr != player->m_boundInstances.end(); ++difficultyItr) + { + for (auto itr = difficultyItr->second.begin(); itr != difficultyItr->second.end();) + { + if (!switchLeader || !group->GetBoundInstance(itr->second.save->GetDifficultyID(), itr->first)) + if (itr->second.extendState) // not expired + group->BindToInstance(itr->second.save, itr->second.perm, false); + + // permanent binds are not removed + if (switchLeader && !itr->second.perm) + { + // increments itr in call + player->UnbindInstance(itr, difficultyItr, false); + } + else + ++itr; + } + } + + /* if group leader is in a non-raid dungeon map and nobody is actually bound to this map then the group can "take over" the instance * + * (example: two-player group disbanded by disconnect where the player reconnects within 60 seconds and the group is reformed) */ + if (Map* playerMap = player->GetMap()) + if (!switchLeader && playerMap->IsNonRaidDungeon()) + if (InstanceSave* save = sInstanceSaveMgr->GetInstanceSave(playerMap->GetInstanceId())) + if (save->GetGroupCount() == 0 && save->GetPlayerCount() == 0) + { + LOG_DEBUG("maps", "Group::ConvertLeaderInstancesToGroup: Group for player {} is taking over unbound instance map {} with Id {}", player->GetName().c_str(), playerMap->GetId(), playerMap->GetInstanceId()); + // if nobody is saved to this, then the save wasn't permanent + group->BindToInstance(save, false, false); + } +} + void Group::Disband(bool hideDestroy /* = false */) { sScriptMgr->OnGroupDisband(this); @@ -759,25 +810,17 @@ void Group::Disband(bool hideDestroy /* = false */) for (member_citerator citr = m_memberSlots.begin(); citr != m_memberSlots.end(); ++citr) { - if (!isBGGroup() && !isBFGroup()) - { - sCharacterCache->ClearCharacterGroup(citr->guid); - } player = ObjectAccessor::FindConnectedPlayer(citr->guid); + if (!player) + continue; + if (player && !instanceId && !isBGGroup() && !isBFGroup()) { instanceId = player->GetInstanceId(); } - _homebindIfInstance(player); - if (!isBGGroup() && !isBFGroup()) - Player::ResetInstances(citr->guid, INSTANCE_RESET_GROUP_LEAVE, false); - - if (!player) - continue; - //we cannot call _removeMember because it would invalidate member iterator //if we are removing player from battleground raid if (isBGGroup() || isBFGroup()) @@ -814,6 +857,8 @@ void Group::Disband(bool hideDestroy /* = false */) data << m_guid << uint32(m_counter) << uint32(0) << uint64(0); player->GetSession()->SendPacket(&data); } + + _homebindIfInstance(player); } RollId.clear(); m_memberSlots.clear(); @@ -825,22 +870,24 @@ void Group::Disband(bool hideDestroy /* = false */) CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GROUP); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, m_dbStoreId); trans->Append(stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GROUP_MEMBER_ALL); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, m_dbStoreId); trans->Append(stmt); CharacterDatabase.CommitTransaction(trans); + ResetInstances(INSTANCE_RESET_GROUP_DISBAND, false, false, player); + ResetInstances(INSTANCE_RESET_GROUP_DISBAND, true, false, player); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_LFG_DATA); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, m_dbStoreId); CharacterDatabase.Execute(stmt); - } - // Cleaning up instance saved data for gameobjects when a group is disbanded - sInstanceSaveMgr->DeleteInstanceSavedData(instanceId); + sGroupMgr->FreeGroupDbStoreId(this); + } sGroupMgr->RemoveGroup(this); delete this; @@ -2051,90 +2098,201 @@ void Group::SetRaidDifficulty(Difficulty difficulty) } } -void Group::ResetInstances(uint8 method, bool isRaid, Player* leader) +Difficulty Group::GetDifficultyID(MapEntry const* mapEntry) const +{ + if (!mapEntry->IsRaid()) + return m_dungeonDifficulty; + + return m_raidDifficulty; +} + +void Group::ResetInstances(uint8 method, bool isRaid, bool isLegacy, Player* SendMsgTo) { if (isBGGroup() || isBFGroup() || isLFGGroup()) return; - switch (method) - { - case INSTANCE_RESET_ALL: + // method can be INSTANCE_RESET_ALL, INSTANCE_RESET_CHANGE_DIFFICULTY, INSTANCE_RESET_GROUP_DISBAND + + for (auto diffCategory : m_boundInstances) { + Difficulty diff = diffCategory.first; + for (auto itr = diffCategory.second.begin(); itr != diffCategory.second.end();) + { + InstanceSave* instanceSave = itr->second.save; + MapEntry const* entry = sMapStore.LookupEntry(itr->first); + if (!entry || entry->IsRaid() != isRaid || (!instanceSave->CanReset() && method != INSTANCE_RESET_GROUP_DISBAND)) { - if (leader->GetDifficulty(false) != DUNGEON_DIFFICULTY_NORMAL) - break; - std::vector toUnbind; - BoundInstancesMap const& m_boundInstances = sInstanceSaveMgr->PlayerGetBoundInstances(leader->GetGUID(), Difficulty(DUNGEON_DIFFICULTY_NORMAL)); - for (BoundInstancesMap::const_iterator itr = m_boundInstances.begin(); itr != m_boundInstances.end(); ++itr) + ++itr; + continue; + } + + if (method == INSTANCE_RESET_ALL) + { + // the "reset all instances" method can only reset normal maps + if (entry->IsRaid() || diff == Difficulty::RAID_DIFFICULTY_25MAN_NORMAL) { - InstanceSave* instanceSave = itr->second.save; - MapEntry const* entry = sMapStore.LookupEntry(itr->first); - if (!entry || entry->IsRaid() || !instanceSave->CanReset()) - continue; + ++itr; + continue; + } + } - Map* map = sMapMgr->FindMap(instanceSave->GetMapId(), instanceSave->GetInstanceId()); - if (!map || map->ToInstanceMap()->Reset(method)) - { - leader->SendResetInstanceSuccess(instanceSave->GetMapId()); - toUnbind.push_back(instanceSave); - } - else + bool isEmpty = true; + // if the map is loaded, reset it + Map* map = sMapMgr->FindMap(instanceSave->GetMapId(), instanceSave->GetInstanceId()); + if (map && map->IsDungeon() && !(method == INSTANCE_RESET_GROUP_DISBAND && !instanceSave->CanReset())) + { + if (instanceSave->CanReset()) + isEmpty = ((InstanceMap*)map)->Reset(method); + else + isEmpty = !map->HavePlayers(); + } + + if (SendMsgTo) + { + if (!isEmpty) + SendMsgTo->SendResetInstanceFailed(0, instanceSave->GetMapId()); + else if (sConfigMgr->GetBoolDefault("InstancesResetAnnounce", false)) + { + if (Group* group = SendMsgTo->GetGroup()) { - leader->SendResetInstanceFailed(0, instanceSave->GetMapId()); + for (GroupReference* groupRef = group->GetFirstMember(); groupRef != nullptr; groupRef = groupRef->next()) + if (Player* player = groupRef->GetSource()) + player->SendResetInstanceSuccess(instanceSave->GetMapId()); } - sInstanceSaveMgr->DeleteInstanceSavedData(instanceSave->GetInstanceId()); + else + SendMsgTo->SendResetInstanceSuccess(instanceSave->GetMapId()); } - for (std::vector::const_iterator itr = toUnbind.begin(); itr != toUnbind.end(); ++itr) - sInstanceSaveMgr->UnbindAllFor(*itr); + else + SendMsgTo->SendResetInstanceSuccess(instanceSave->GetMapId()); } - break; - case INSTANCE_RESET_CHANGE_DIFFICULTY: + + if (isEmpty || method == INSTANCE_RESET_GROUP_DISBAND || method == INSTANCE_RESET_CHANGE_DIFFICULTY) { - std::vector toUnbind; - BoundInstancesMap const& m_boundInstances = sInstanceSaveMgr->PlayerGetBoundInstances(leader->GetGUID(), leader->GetDifficulty(isRaid)); - for (BoundInstancesMap::const_iterator itr = m_boundInstances.begin(); itr != m_boundInstances.end(); ++itr) + // do not reset the instance, just unbind if others are permanently bound to it + if (isEmpty && instanceSave->CanReset()) + instanceSave->DeleteFromDB(); + else { - InstanceSave* instanceSave = itr->second.save; - MapEntry const* entry = sMapStore.LookupEntry(itr->first); - if (!entry || entry->IsRaid() != isRaid || !instanceSave->CanReset()) - continue; + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GROUP_INSTANCE_BY_INSTANCE); - Map* map = sMapMgr->FindMap(instanceSave->GetMapId(), instanceSave->GetInstanceId()); - if (!map || map->ToInstanceMap()->Reset(method)) - { - leader->SendResetInstanceSuccess(instanceSave->GetMapId()); - toUnbind.push_back(instanceSave); - } - else - { - leader->SendResetInstanceFailed(0, instanceSave->GetMapId()); - } + stmt->SetData(0, instanceSave->GetInstanceId()); - sInstanceSaveMgr->DeleteInstanceSavedData(instanceSave->GetInstanceId()); + CharacterDatabase.Execute(stmt); } - for (std::vector::const_iterator itr = toUnbind.begin(); itr != toUnbind.end(); ++itr) - sInstanceSaveMgr->UnbindAllFor(*itr); + + + itr = diffCategory.second.erase(itr); + // this unloads the instance save unless online players are bound to it + // (eg. permanent binds or GM solo binds) + instanceSave->RemoveGroup(this); } - break; + else + ++itr; + } } } -void Group::_homebindIfInstance(Player* player) +InstanceGroupBind* Group::GetBoundInstance(Player* player) { - if (player && !player->IsGameMaster() && player->FindMap() && sMapStore.LookupEntry(player->GetMapId())->IsDungeon()) - player->m_InstanceValid = false; + uint32 mapid = player->GetMapId(); + MapEntry const* mapEntry = sMapStore.LookupEntry(mapid); + return GetBoundInstance(mapEntry); +} + +InstanceGroupBind* Group::GetBoundInstance(Map* aMap) +{ + return GetBoundInstance(aMap->GetEntry()); +} + +InstanceGroupBind* Group::GetBoundInstance(MapEntry const* mapEntry) +{ + if (!mapEntry || !mapEntry->IsDungeon()) + return nullptr; + + Difficulty difficulty = GetDifficultyID(mapEntry); + return GetBoundInstance(difficulty, mapEntry->MapID); +} + +InstanceGroupBind* Group::GetBoundInstance(Difficulty difficulty, uint32 mapId) +{ + // some instances only have one difficulty + GetDownscaledMapDifficultyData(mapId, difficulty); + + auto difficultyItr = m_boundInstances.find(difficulty); + if (difficultyItr == m_boundInstances.end()) + return nullptr; + + auto itr = difficultyItr->second.find(mapId); + if (itr != difficultyItr->second.end()) + return &itr->second; + else + return nullptr; +} + +InstanceGroupBind* Group::BindToInstance(InstanceSave* save, bool permanent, bool load) +{ + if (!save || isBGGroup() || isBFGroup()) + return nullptr; + + InstanceGroupBind& bind = m_boundInstances[save->GetDifficultyID()][save->GetMapId()]; + if (!load && (!bind.save || permanent != bind.perm || save != bind.save)) + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_GROUP_INSTANCE); + + stmt->SetData(0, m_dbStoreId); + stmt->SetData(1, save->GetInstanceId()); + stmt->SetData(2, permanent); + + CharacterDatabase.Execute(stmt); + } + + if (bind.save != save) + { + if (bind.save) + bind.save->RemoveGroup(this); + save->AddGroup(this); + } + + bind.save = save; + bind.perm = permanent; + if (!load) + LOG_DEBUG("maps", "Group::BindToInstance: %s, storage id: %u is now bound to map %d, instance %d, difficulty %d", + GetGUID().ToString().c_str(), m_dbStoreId, save->GetMapId(), save->GetInstanceId(), save->GetDifficultyID()); + + return &bind; } -void Group::_cancelHomebindIfInstance(Player* player) +void Group::UnbindInstance(uint32 mapid, uint8 difficulty, bool unload) { - // if player is reinvited to group and in the instance - cancel homebind timer - if (!player->FindMap() || !player->FindMap()->IsDungeon()) + auto difficultyItr = m_boundInstances.find(Difficulty(difficulty)); + if (difficultyItr == m_boundInstances.end()) return; - InstancePlayerBind* bind = sInstanceSaveMgr->PlayerGetBoundInstance(player->GetGUID(), player->FindMap()->GetId(), player->GetDifficulty(player->FindMap()->IsRaid())); - if (bind && bind->save->GetInstanceId() == player->GetInstanceId()) - player->m_InstanceValid = true; + + auto itr = difficultyItr->second.find(mapid); + if (itr != difficultyItr->second.end()) + { + if (!unload) + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GROUP_INSTANCE_BY_GUID); + + stmt->SetData(0, m_dbStoreId); + stmt->SetData(1, itr->second.save->GetInstanceId()); + + CharacterDatabase.Execute(stmt); + } + + itr->second.save->RemoveGroup(this); // save can become invalid + difficultyItr->second.erase(itr); + } } +void Group::_homebindIfInstance(Player* player) +{ + if (player && !player->IsGameMaster() && player->FindMap() && sMapStore.LookupEntry(player->GetMapId())->IsDungeon()) + player->m_InstanceValid = false; +} + + void Group::BroadcastGroupUpdate(void) { // FG: HACK: force flags update on group leave - for values update hack @@ -2413,6 +2571,31 @@ void Group::LinkMember(GroupReference* pRef) m_memberMgr.insertFirst(pRef); } +void Group::DelinkMember(ObjectGuid guid) +{ + GroupReference* ref = m_memberMgr.getFirst(); + while (ref) + { + GroupReference* nextRef = ref->next(); + if (ref->GetSource()->GetGUID() == guid) + { + ref->unlink(); + break; + } + ref = nextRef; + } +} + +Group::BoundInstancesMap::iterator Group::GetBoundInstances(Difficulty difficulty) +{ + return m_boundInstances.find(difficulty); +} + +Group::BoundInstancesMap::iterator Group::GetBoundInstanceEnd() +{ + return m_boundInstances.end(); +} + void Group::_initRaidSubGroupsCounter() { // Sub group counters initialization diff --git a/src/server/game/Groups/Group.h b/src/server/game/Groups/Group.h index 87317f3763c070..4bd6e2dbb87924 100644 --- a/src/server/game/Groups/Group.h +++ b/src/server/game/Groups/Group.h @@ -163,6 +163,15 @@ class Roll : public LootValidatorRef uint8 rollVoteMask; }; +struct InstanceGroupBind +{ + InstanceSave* save; + bool perm; + /* permanent InstanceGroupBinds exist if the leader has a permanent + PlayerInstanceBind for the same instance. */ + InstanceGroupBind() : save(nullptr), perm(false) { } +}; + /** request member stats checken **/ /** todo: uninvite people that not accepted invite **/ class Group @@ -178,6 +187,7 @@ class Group }; typedef std::list MemberSlotList; typedef MemberSlotList::const_iterator member_citerator; + typedef std::unordered_map> BoundInstancesMap; protected: typedef MemberSlotList::iterator member_witerator; @@ -200,6 +210,7 @@ class Group bool AddMember(Player* player); bool RemoveMember(ObjectGuid guid, const RemoveMethod& method = GROUP_REMOVEMETHOD_DEFAULT, ObjectGuid kicker = ObjectGuid::Empty, const char* reason = nullptr); void ChangeLeader(ObjectGuid guid); + static void ConvertLeaderInstancesToGroup(Player* player, Group* group, bool switchLeader); void SetLootMethod(LootMethod method); void SetLooterGuid(ObjectGuid guid); void SetMasterLooterGuid(ObjectGuid guid); @@ -225,6 +236,8 @@ class Group ObjectGuid GetMasterLooterGuid() const; ItemQualities GetLootThreshold() const; + uint32 GetDbStoreId() const { return m_dbStoreId; } + // member manipulation methods bool IsMember(ObjectGuid guid) const; bool IsLeader(ObjectGuid guid) const; @@ -265,8 +278,10 @@ class Group Difficulty GetRaidDifficulty() const; void SetDungeonDifficulty(Difficulty difficulty); void SetRaidDifficulty(Difficulty difficulty); - uint16 InInstance(); - void ResetInstances(uint8 method, bool isRaid, Player* leader); + Difficulty GetDifficultyID(MapEntry const* mapEntry) const; + Difficulty GetDungeonDifficultyID() const { return m_dungeonDifficulty; } + Difficulty GetRaidDifficultyID() const { return m_raidDifficulty; } + void ResetInstances(uint8 method, bool isRaid, bool isLegacy, Player* SendMsgTo); // -no description- //void SendInit(WorldSession* session); @@ -302,6 +317,7 @@ class Group void ResetMaxEnchantingLevel(); void LinkMember(GroupReference* pRef); + void DelinkMember(ObjectGuid guid); // FG: evil hacks void BroadcastGroupUpdate(void); @@ -322,9 +338,17 @@ class Group DataMap CustomData; + InstanceGroupBind* BindToInstance(InstanceSave* save, bool permanent, bool load = false); + void UnbindInstance(uint32 mapid, uint8 difficulty, bool unload = false); + InstanceGroupBind* GetBoundInstance(Player* player); + InstanceGroupBind* GetBoundInstance(Map* aMap); + InstanceGroupBind* GetBoundInstance(MapEntry const* mapEntry); + InstanceGroupBind* GetBoundInstance(Difficulty difficulty, uint32 mapId); + BoundInstancesMap::iterator GetBoundInstances(Difficulty difficulty); + BoundInstancesMap::iterator GetBoundInstanceEnd(); + protected: void _homebindIfInstance(Player* player); - void _cancelHomebindIfInstance(Player* player); void _initRaidSubGroupsCounter(); member_citerator _getMemberCSlot(ObjectGuid Guid) const; @@ -348,12 +372,14 @@ class Group ItemQualities m_lootThreshold; ObjectGuid m_looterGuid; ObjectGuid m_masterLooterGuid; + BoundInstancesMap m_boundInstances; Rolls RollId; uint8* m_subGroupsCounts; ObjectGuid m_guid; uint32 m_counter; // used only in SMSG_GROUP_LIST uint32 m_maxEnchantingLevel; uint8 m_lfgGroupFlags; + uint32 m_dbStoreId; // Represents the ID used in database (Can be reused by other groups if group was disbanded) // Xinef: change difficulty prevention uint32 _difficultyChangePreventionTime; diff --git a/src/server/game/Groups/GroupMgr.cpp b/src/server/game/Groups/GroupMgr.cpp index 58eabc93b3b928..8c94212963c3d0 100644 --- a/src/server/game/Groups/GroupMgr.cpp +++ b/src/server/game/Groups/GroupMgr.cpp @@ -33,6 +33,55 @@ GroupMgr::~GroupMgr() delete itr->second; } +uint32 GroupMgr::GenerateNewGroupDbStoreId() +{ + uint32 newStorageId = NextGroupDbStoreId; + + for (uint32 i = ++NextGroupDbStoreId; i < 0xFFFFFFFF; ++i) + { + if ((i < GroupDbStore.size() && GroupDbStore[i] == nullptr) || i >= GroupDbStore.size()) + { + NextGroupDbStoreId = i; + break; + } + } + + if (newStorageId == NextGroupDbStoreId) + { + LOG_ERROR("misc", "Group storage ID overflow!! Can't continue, shutting down server. "); + World::StopNow(ERROR_EXIT_CODE); + } + + return newStorageId; +} + +void GroupMgr::RegisterGroupDbStoreId(uint32 storageId, Group* group) +{ + // Allocate space if necessary. + if (storageId >= uint32(GroupDbStore.size())) + GroupDbStore.resize(storageId + 1); + + GroupDbStore[storageId] = group; +} + +void GroupMgr::FreeGroupDbStoreId(Group* group) +{ + uint32 storageId = group->GetDbStoreId(); + + if (storageId < NextGroupDbStoreId) + NextGroupDbStoreId = storageId; + + GroupDbStore[storageId] = nullptr; +} + +Group* GroupMgr::GetGroupByDbStoreId(uint32 storageId) const +{ + if (storageId < GroupDbStore.size()) + return GroupDbStore[storageId]; + + return nullptr; +} + GroupMgr* GroupMgr::instance() { static GroupMgr instance; @@ -108,12 +157,6 @@ void GroupMgr::LoadGroups() // Delete all groups with less than 2 members CharacterDatabase.DirectExecute("DELETE FROM `groups` WHERE guid NOT IN (SELECT guid FROM group_member GROUP BY guid HAVING COUNT(guid) > 1)"); - // Delete invalid lfg_data - CharacterDatabase.DirectExecute("DELETE lfg_data FROM lfg_data LEFT JOIN `groups` ON lfg_data.guid = groups.guid WHERE groups.guid IS NULL OR groups.groupType <> 12"); - // CharacterDatabase.DirectExecute("DELETE `groups` FROM `groups` LEFT JOIN lfg_data ON groups.guid = lfg_data.guid WHERE groups.groupType=12 AND lfg_data.guid IS NULL"); // group should be left so binds are cleared when disbanded - - InitGroupIds(); - // 0 1 2 3 4 5 6 7 8 9 QueryResult result = CharacterDatabase.Query("SELECT g.leaderGuid, g.lootMethod, g.looterGuid, g.lootThreshold, g.icon1, g.icon2, g.icon3, g.icon4, g.icon5, g.icon6" // 10 11 12 13 14 15 16 17 18 @@ -131,14 +174,15 @@ void GroupMgr::LoadGroups() { Field* fields = result->Fetch(); Group* group = new Group; - if (!group->LoadGroupFromDB(fields)) - { - delete group; - continue; - } + group->LoadGroupFromDB(fields); AddGroup(group); - RegisterGroupId(group->GetGUID().GetCounter()); + // Get the ID used for storing the group in the database and register it in the pool. + uint32 storageId = group->GetDbStoreId(); + + RegisterGroupDbStoreId(storageId, group); + if (storageId == NextGroupDbStoreId) + NextGroupDbStoreId++; ++count; } while (result->NextRow()); @@ -154,6 +198,7 @@ void GroupMgr::LoadGroups() // Delete all rows from group_member with no group CharacterDatabase.DirectExecute("DELETE FROM group_member WHERE guid NOT IN (SELECT guid FROM `groups`)"); + CharacterDatabase.DirectExecute("DELETE FROM group_instance WHERE guid NOT IN (SELECT guid FROM `groups`)"); // Delete all members that does not exist CharacterDatabase.DirectExecute("DELETE FROM group_member WHERE memberGuid NOT IN (SELECT guid FROM characters)"); @@ -170,10 +215,12 @@ void GroupMgr::LoadGroups() do { Field* fields = result->Fetch(); - Group* group = GetGroupByGUID(fields[0].Get()); + Group* group = GetGroupByDbStoreId(fields[0].Get()); if (group) group->LoadMemberFromDB(fields[1].Get(), fields[2].Get(), fields[3].Get(), fields[4].Get()); + else + LOG_ERROR("misc", "GroupMgr::LoadGroups: Consistency failed, can't find group (storage id: {})", fields[0].Get()); ++count; } while (result->NextRow()); @@ -182,4 +229,46 @@ void GroupMgr::LoadGroups() LOG_INFO("server.loading", " "); } } + + LOG_INFO("server.loading", "Loading Group instance saves..."); + { + uint32 oldMSTime = getMSTime(); + + // 0 1 2 3 4 5 6 + QueryResult result = CharacterDatabase.Query("SELECT gi.guid, i.map, gi.instance, gi.permanent, i.difficulty, i.resettime, i.entranceId, " + "(select COUNT(1) FROM `character_instance` ci LEFT JOIN `groups` g ON ci.guid = g.leaderGuid WHERE ci.`instance` = gi.`instance` AND ci.`permanent` = 1 LIMIT 1)" + "FROM group_instance gi LEFT JOIN instance i ON gi.instance = i.id ORDER BY guid"); + + if (!result) + { + LOG_INFO("server.loading", ">> Loaded 0 group-instance saves. DB table `group_instance` is empty!"); + return; + } + + uint32 count = 0; + do + { + Field* fields = result->Fetch(); + Group* group = GetGroupByDbStoreId(fields[0].Get()); + // group will never be NULL (we have run consistency sql's before loading) + + MapEntry const* mapEntry = sMapStore.LookupEntry(fields[1].Get()); + if (!mapEntry || !mapEntry->IsDungeon()) + { + LOG_INFO("sql.sql", "Incorrect entry in group_instance table : no dungeon map %d", fields[1].Get()); + continue; + } + + uint32 diff = fields[4].Get(); + auto difficultyEntry = GetMapDifficultyData(mapEntry->MapID, Difficulty(diff)); + if (!difficultyEntry) + continue; + + InstanceSave* save = sInstanceSaveMgr->AddInstanceSave(mapEntry->MapID, fields[2].Get(), Difficulty(diff), time_t(fields[5].Get()), fields[6].Get(), fields[7].Get() == 0, true); + group->BindToInstance(save, fields[3].Get(), true); + ++count; + } while (result->NextRow()); + + LOG_INFO("server.loading", ">> Loaded %u group-instance saves in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); + } } diff --git a/src/server/game/Groups/GroupMgr.h b/src/server/game/Groups/GroupMgr.h index 72358121acbd3b..c0fa382affc7fc 100644 --- a/src/server/game/Groups/GroupMgr.h +++ b/src/server/game/Groups/GroupMgr.h @@ -30,6 +30,7 @@ class GroupMgr static GroupMgr* instance(); typedef std::map GroupContainer; + typedef std::vector GroupDbContainer; Group* GetGroupByGUID(ObjectGuid::LowType guid) const; @@ -37,15 +38,26 @@ class GroupMgr void RegisterGroupId(ObjectGuid::LowType groupId); ObjectGuid::LowType GenerateGroupId(); + uint32 GenerateNewGroupDbStoreId(); + void RegisterGroupDbStoreId(uint32 storageId, Group* group); + void FreeGroupDbStoreId(Group* group); + void SetNextGroupDbStoreId(uint32 storageId) { NextGroupDbStoreId = storageId; }; + Group* GetGroupByDbStoreId(uint32 storageId) const; + void SetGroupDbStoreSize(uint32 newSize) { GroupDbStore.resize(newSize); } + void LoadGroups(); void AddGroup(Group* group); void RemoveGroup(Group* group); + void Update(uint32 diff); + protected: typedef std::vector GroupIds; GroupIds _groupIds; ObjectGuid::LowType _nextGroupId; + uint32 NextGroupDbStoreId; GroupContainer GroupStore; + GroupDbContainer GroupDbStore; }; #define sGroupMgr GroupMgr::instance() diff --git a/src/server/game/Handlers/CalendarHandler.cpp b/src/server/game/Handlers/CalendarHandler.cpp index 9f72af9c069cda..a93b4f9b5e08d3 100644 --- a/src/server/game/Handlers/CalendarHandler.cpp +++ b/src/server/game/Handlers/CalendarHandler.cpp @@ -101,18 +101,19 @@ void WorldSession::HandleCalendarGetCalendar(WorldPacket& /*recvData*/) uint32 boundCounter = 0; for (uint8 i = 0; i < MAX_DIFFICULTY; ++i) { - BoundInstancesMap const& m_boundInstances = sInstanceSaveMgr->PlayerGetBoundInstances(_player->GetGUID(), Difficulty(i)); - for (BoundInstancesMap::const_iterator itr = m_boundInstances.begin(); itr != m_boundInstances.end(); ++itr) - { - if (itr->second.perm) + auto boundInstances = _player->GetBoundInstances(Difficulty(i)); + if (boundInstances != _player->m_boundInstances.end()) { + for (auto const& boundInstance : boundInstances->second) { - InstanceSave const* save = itr->second.save; - time_t resetTime = itr->second.extended ? save->GetExtendedResetTime() : save->GetResetTime(); - dataBuffer << uint32(save->GetMapId()); - dataBuffer << uint32(save->GetDifficulty()); - dataBuffer << uint32(resetTime >= currTime ? resetTime - currTime : 0); - dataBuffer << ObjectGuid::Create(save->GetInstanceId()); // instance save id as unique instance copy id - ++boundCounter; + if (boundInstance.second.perm) + { + InstanceSave const* save = boundInstance.second.save; + dataBuffer << uint32(save->GetMapId()); + dataBuffer << uint32(save->GetDifficultyID()); + dataBuffer << uint32(save->GetResetTime() - currTime); + dataBuffer << ObjectGuid::Create(save->GetInstanceId()); // instance save id as unique instance copy id + ++boundCounter; + } } } } @@ -120,38 +121,6 @@ void WorldSession::HandleCalendarGetCalendar(WorldPacket& /*recvData*/) data << uint32(boundCounter); data.append(dataBuffer); - // pussywizard - uint32 relationTime = sWorld->getIntConfig(CONFIG_INSTANCE_RESET_TIME_RELATIVE_TIMESTAMP) + sWorld->getIntConfig(CONFIG_INSTANCE_RESET_TIME_HOUR) * HOUR; // set point in time (default 29.12.2005) + X hours - data << uint32(relationTime); - - // Reuse variables - boundCounter = 0; - std::set sentMaps; - dataBuffer.clear(); - - ResetTimeByMapDifficultyMap const& resets = sInstanceSaveMgr->GetResetTimeMap(); - for (ResetTimeByMapDifficultyMap::const_iterator itr = resets.begin(); itr != resets.end(); ++itr) - { - uint32 mapId = PAIR32_LOPART(itr->first); - if (sentMaps.find(mapId) != sentMaps.end()) - continue; - - MapEntry const* mapEntry = sMapStore.LookupEntry(mapId); - if (!mapEntry || !mapEntry->IsRaid()) - continue; - - sentMaps.insert(mapId); - - dataBuffer << int32(mapId); - time_t period = sInstanceSaveMgr->GetExtendedResetTimeFor(mapId, (Difficulty)PAIR32_HIPART(itr->first)) - itr->second; - dataBuffer << int32(period); // pussywizard: reset time period - dataBuffer << int32(0); // pussywizard: reset time offset, needed for other than 7-day periods if not aligned with relationTime - ++boundCounter; - } - - data << uint32(boundCounter); - data.append(dataBuffer); - /// @todo: Fix this, how we do know how many and what holidays to send? data << uint32(sGameEventMgr->modifiedHolidays.size()); for (uint32 entry : sGameEventMgr->modifiedHolidays) @@ -797,20 +766,20 @@ void WorldSession::HandleSetSavedInstanceExtend(WorldPacket& recvData) if (!entry || !entry->IsRaid()) return; - InstancePlayerBind* instanceBind = sInstanceSaveMgr->PlayerGetBoundInstance(GetPlayer()->GetGUID(), mapId, Difficulty(difficulty)); - if (!instanceBind || !instanceBind->perm || (bool)toggleExtendOn == instanceBind->extended) - return; - - instanceBind->extended = (bool)toggleExtendOn; + if (Player* player = GetPlayer()) + { + InstancePlayerBind* instanceBind = player->GetBoundInstance(mapId, Difficulty(difficulty), toggleExtendOn); // include expired instances if we are toggling extend on + if (!instanceBind || !instanceBind->save || !instanceBind->perm) + return; - // update in db - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_INSTANCE_EXTENDED); - stmt->SetData(0, toggleExtendOn ? 1 : 0); - stmt->SetData(1, GetPlayer()->GetGUID().GetCounter()); - stmt->SetData(2, instanceBind->save->GetInstanceId()); - CharacterDatabase.Execute(stmt); + BindExtensionState newState; + if (!toggleExtendOn || instanceBind->extendState == EXTEND_STATE_EXPIRED) + newState = EXTEND_STATE_NORMAL; + else + newState = EXTEND_STATE_EXTENDED; - SendCalendarRaidLockoutUpdated(instanceBind->save, (bool)toggleExtendOn); + player->BindToInstance(instanceBind->save, true, newState, false); + } } // ----------------------------------- SEND ------------------------------------ @@ -828,7 +797,7 @@ void WorldSession::SendCalendarRaidLockout(InstanceSave const* save, bool add) } data << uint32(save->GetMapId()); - data << uint32(save->GetDifficulty()); + data << uint32(save->GetDifficultyID()); data << uint32(save->GetResetTime() >= currTime ? save->GetResetTime() - currTime : 0); data << ObjectGuid::Create(save->GetInstanceId()); SendPacket(&data); @@ -837,12 +806,13 @@ void WorldSession::SendCalendarRaidLockout(InstanceSave const* save, bool add) void WorldSession::SendCalendarRaidLockoutUpdated(InstanceSave const* save, bool isExtended) { time_t currTime = GameTime::GetGameTime().count(); - time_t resetTime = isExtended ? save->GetExtendedResetTime() : save->GetResetTime(); - time_t resetTimeOp = isExtended ? save->GetResetTime() : save->GetExtendedResetTime(); + time_t resetTime = save->GetResetTime(); + time_t resetTimeOp = save->GetResetTime(); + WorldPacket data(SMSG_CALENDAR_RAID_LOCKOUT_UPDATED, 4 + 4 + 4 + 4 + 8); data.AppendPackedTime(currTime); data << uint32(save->GetMapId()); - data << uint32(save->GetDifficulty()); + data << uint32(save->GetDifficultyID()); data << uint32(resetTimeOp >= currTime ? resetTimeOp - currTime : resetTimeOp); // pussywizard: old time in secs to reset data << uint32(resetTime >= currTime ? resetTime - currTime : 0); // pussywizard: new time in secs to reset SendPacket(&data); diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index a47146c37fc852..9414841ea7bf85 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -224,6 +224,10 @@ bool LoginQueryHolder::Initialize() stmt->SetData(0, m_accountId); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_TRANSMOG, stmt); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_INSTANCE); + stmt->SetData(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_BOUND_INSTANCES, stmt); + return res; } diff --git a/src/server/game/Handlers/LFGHandler.cpp b/src/server/game/Handlers/LFGHandler.cpp index a78b8864462f1f..71056f9ea9d78a 100644 --- a/src/server/game/Handlers/LFGHandler.cpp +++ b/src/server/game/Handlers/LFGHandler.cpp @@ -157,7 +157,6 @@ void WorldSession::HandleLfgPlayerLockInfoRequestOpcode(WorldPacket& /*recvData* sLFGMgr->GetRandomAndSeasonalDungeons(level, GetPlayer()->GetSession()->Expansion()); // Get player locked Dungeons - sLFGMgr->InitializeLockedDungeons(GetPlayer(), GetPlayer()->GetGroup()); // pussywizard lfg::LfgLockMap const& lock = sLFGMgr->GetLockedDungeons(guid); uint32 rsize = uint32(randomDungeons.size()); uint32 lsize = uint32(lock.size()); @@ -239,7 +238,6 @@ void WorldSession::HandleLfgPartyLockInfoRequestOpcode(WorldPacket& /*recvData* if (pguid == guid) continue; - sLFGMgr->InitializeLockedDungeons(plrg, group); // pussywizard lockMap[pguid] = sLFGMgr->GetLockedDungeons(pguid); } diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp index fc315d8d51fd20..baf67558a6233c 100644 --- a/src/server/game/Handlers/MiscHandler.cpp +++ b/src/server/game/Handlers/MiscHandler.cpp @@ -81,6 +81,8 @@ void WorldSession::HandleRepopRequestOpcode(WorldPacket& recv_data) GetPlayer()->RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT, true); GetPlayer()->BuildPlayerRepop(); GetPlayer()->RepopAtGraveyard(); + + // hater: add instance tps here } void WorldSession::HandleGossipSelectOptionOpcode(WorldPacket& recv_data) @@ -1317,10 +1319,10 @@ void WorldSession::HandleResetInstancesOpcode(WorldPacket& /*recv_data*/) if (Group* group = _player->GetGroup()) { if (group->IsLeader(_player->GetGUID())) - group->ResetInstances(INSTANCE_RESET_ALL, false, _player); + group->ResetInstances(INSTANCE_RESET_ALL, false, false, _player); } else - Player::ResetInstances(_player->GetGUID(), INSTANCE_RESET_ALL, false); + _player->ResetInstances(INSTANCE_RESET_ALL, true, false); } void WorldSession::HandleSetDungeonDifficultyOpcode(WorldPacket& recv_data) @@ -1360,7 +1362,7 @@ void WorldSession::HandleSetDungeonDifficultyOpcode(WorldPacket& recv_data) } } - group->ResetInstances(INSTANCE_RESET_CHANGE_DIFFICULTY, false, _player); + group->ResetInstances(INSTANCE_RESET_CHANGE_DIFFICULTY, false, false, _player); group->SetDungeonDifficulty(Difficulty(mode)); } } @@ -1371,8 +1373,9 @@ void WorldSession::HandleSetDungeonDifficultyOpcode(WorldPacket& recv_data) _player->SendDungeonDifficulty(group != nullptr); return; } - Player::ResetInstances(_player->GetGUID(), INSTANCE_RESET_CHANGE_DIFFICULTY, false); + _player->ResetInstances(INSTANCE_RESET_CHANGE_DIFFICULTY, false, false); _player->SetDungeonDifficulty(Difficulty(mode)); + _player->SendDungeonDifficulty(group != nullptr); } } @@ -1394,145 +1397,29 @@ void WorldSession::HandleSetRaidDifficultyOpcode(WorldPacket& recv_data) { if (group->IsLeader(_player->GetGUID())) { - std::set foundMaps; - std::set foundMapsPtr; - Map* currMap = nullptr; - - if (uint32 preventionTime = group->GetDifficultyChangePreventionTime()) - { - switch (group->GetDifficultyChangePreventionReason()) - { - case DIFFICULTY_PREVENTION_CHANGE_BOSS_KILLED: - ChatHandler(this).PSendSysMessage("Raid was in combat recently and may not change difficulty again for %u sec.", preventionTime); - break; - case DIFFICULTY_PREVENTION_CHANGE_RECENTLY_CHANGED: - default: - ChatHandler(this).PSendSysMessage("Raid difficulty has changed recently, and may not change again for %u sec.", preventionTime); - break; - } - - _player->SendRaidDifficulty(group != nullptr); - return; - } - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) { Player* groupGuy = itr->GetSource(); if (!groupGuy) continue; - if (!groupGuy->IsInWorld()) - { - _player->SendRaidDifficulty(group != nullptr); + if (!groupGuy->IsInMap(groupGuy)) return; - } - - if (IsSharedDifficultyMap(groupGuy->GetMap()->GetId()) && (uint32(mode % 2) == uint32(_player->GetRaidDifficulty() % 2)) && group->isRaidGroup()) - { - if (!currMap) - currMap = groupGuy->GetMap(); - foundMaps.insert(groupGuy->GetMap()->GetId()); - foundMapsPtr.insert(groupGuy->GetMap()); - if (foundMaps.size() > 1 || foundMapsPtr.size() > 1) - { - _player->SendRaidDifficulty(group != nullptr); - return; - } - if (!groupGuy->IsAlive() || groupGuy->IsInCombat() || groupGuy->GetVictim() || groupGuy->m_mover != groupGuy || groupGuy->IsNonMeleeSpellCast(true) || (!groupGuy->GetMotionMaster()->empty() && groupGuy->GetMotionMaster()->GetCurrentMovementGeneratorType() != IDLE_MOTION_TYPE) - || !groupGuy->movespline->Finalized() || !groupGuy->GetMap()->ToInstanceMap() || !groupGuy->GetMap()->ToInstanceMap()->GetInstanceScript() || groupGuy->GetMap()->ToInstanceMap()->GetInstanceScript()->IsEncounterInProgress() - || !groupGuy->Satisfy(sObjectMgr->GetAccessRequirement(groupGuy->GetMap()->GetId(), Difficulty(mode)), groupGuy->GetMap()->GetId(), false)) - { - _player->SendRaidDifficulty(group != nullptr); - return; - } - } - else if (groupGuy->GetGUID() == _player->GetGUID() ? groupGuy->GetMap()->IsDungeon() : groupGuy->GetMap()->IsRaid()) - { - _player->SendRaidDifficulty(group != nullptr); + if (groupGuy->GetMap()->IsRaid()) return; - } } - Map* homeMap571 = sMapMgr->CreateMap(571, nullptr); - Map* homeMap0 = sMapMgr->CreateMap(0, nullptr); - ASSERT(homeMap0 && homeMap571); - - std::map playerTeleport; - // handle here all players in the instance that are not in the group - if (currMap) - for (Map::PlayerList::const_iterator itr = currMap->GetPlayers().begin(); itr != currMap->GetPlayers().end(); ++itr) - if (Player* p = itr->GetSource()) - if (p->GetGroup() != group) - { - if (!p->IsInWorld() || !p->IsAlive() || p->IsInCombat() || p->GetVictim() || p->m_mover != p || p->IsNonMeleeSpellCast(true) || (!p->GetMotionMaster()->empty() && p->GetMotionMaster()->GetCurrentMovementGeneratorType() != IDLE_MOTION_TYPE) - || !p->movespline->Finalized() || !p->GetMap()->ToInstanceMap() || !p->GetMap()->ToInstanceMap()->GetInstanceScript() || p->GetMap()->ToInstanceMap()->GetInstanceScript()->IsEncounterInProgress()) - { - _player->SendRaidDifficulty(group != nullptr); - return; - } - playerTeleport[p]; - } - for (std::map::iterator itr = playerTeleport.begin(); itr != playerTeleport.end(); ++itr) - { - Player* p = itr->first; - Map* oldMap = p->GetMap(); - oldMap->RemovePlayerFromMap(p, false); - p->ResetMap(); - oldMap->AfterPlayerUnlinkFromMap(); - p->SetMap(homeMap0); - p->Relocate(0.0f, 0.0f, 0.0f, 0.0f); - if (!p->TeleportTo(571, 5790.20f, 2071.36f, 636.07f, 3.60f)) - p->GetSession()->KickPlayer("HandleSetRaidDifficultyOpcode 1"); - } - - bool anyoneInside = false; - playerTeleport.clear(); - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* groupGuy = itr->GetSource(); - if (!groupGuy) - continue; - - if (IsSharedDifficultyMap(groupGuy->GetMap()->GetId())) - { - anyoneInside = true; - - float x, y, z, o; - groupGuy->GetPosition(x, y, z, o); - Map* oldMap = groupGuy->GetMap(); - oldMap->RemovePlayerFromMap(groupGuy, false); - groupGuy->ResetMap(); - oldMap->AfterPlayerUnlinkFromMap(); - groupGuy->SetMap(homeMap571); - groupGuy->Relocate(5790.20f, 2071.36f, 636.07f, 3.60f); - Position dest = {x, y, z + 0.1f, o}; - playerTeleport[groupGuy] = dest; - } - } - - if (!anyoneInside) // pussywizard: don't reset if changing ICC/RS difficulty while inside - group->ResetInstances(INSTANCE_RESET_CHANGE_DIFFICULTY, true, _player); + group->ResetInstances(INSTANCE_RESET_CHANGE_DIFFICULTY, true, false, _player); group->SetRaidDifficulty(Difficulty(mode)); group->SetDifficultyChangePrevention(DIFFICULTY_PREVENTION_CHANGE_RECENTLY_CHANGED); - - for (std::map::iterator itr = playerTeleport.begin(); itr != playerTeleport.end(); ++itr) - { - itr->first->SetRaidDifficulty(Difficulty(mode)); // needed for teleport not to fail - if (!itr->first->TeleportTo(*(foundMaps.begin()), itr->second.GetPositionX(), itr->second.GetPositionY(), itr->second.GetPositionZ(), itr->second.GetOrientation())) - itr->first->GetSession()->KickPlayer("HandleSetRaidDifficultyOpcode 2"); - } } } else { - if (_player->FindMap() && _player->FindMap()->IsDungeon()) - { - _player->SendRaidDifficulty(group != nullptr); - return; - } - Player::ResetInstances(_player->GetGUID(), INSTANCE_RESET_CHANGE_DIFFICULTY, true); + _player->ResetInstances(INSTANCE_RESET_CHANGE_DIFFICULTY, true); _player->SetRaidDifficulty(Difficulty(mode)); + _player->SendRaidDifficulty(false); } } @@ -1729,7 +1616,7 @@ void WorldSession::HandleInstanceLockResponse(WorldPacket& recvPacket) uint8 accept; recvPacket >> accept; - if (!_player->HasPendingBind() || _player->GetPendingBind() != _player->GetInstanceId() || (_player->GetGroup() && _player->GetGroup()->isLFGGroup() && _player->GetGroup()->IsLfgRandomInstance())) + if (!_player->HasPendingBind()) { LOG_DEBUG("network.opcode", "InstanceLockResponse: Player {} ({}) tried to bind himself/teleport to graveyard without a pending bind!", _player->GetName(), _player->GetGUID().ToString()); diff --git a/src/server/game/Handlers/MovementHandler.cpp b/src/server/game/Handlers/MovementHandler.cpp index 07bbb39410ad45..99c19e3215129a 100644 --- a/src/server/game/Handlers/MovementHandler.cpp +++ b/src/server/game/Handlers/MovementHandler.cpp @@ -69,6 +69,10 @@ void WorldSession::HandleMoveWorldportAck() MapEntry const* mEntry = sMapStore.LookupEntry(loc.GetMapId()); InstanceTemplate const* mInstance = sObjectMgr->GetInstanceTemplate(loc.GetMapId()); + // reset instance validity, except if going to an instance inside an instance + if (GetPlayer()->m_InstanceValid == false && !mInstance) + GetPlayer()->m_InstanceValid = true; + Map* oldMap = GetPlayer()->GetMap(); if (GetPlayer()->IsInWorld()) { @@ -76,15 +80,6 @@ void WorldSession::HandleMoveWorldportAck() oldMap->RemovePlayerFromMap(GetPlayer(), false); } - // reset instance validity, except if going to an instance inside an instance - if (!GetPlayer()->m_InstanceValid && !mInstance) - { - GetPlayer()->m_InstanceValid = true; - // pussywizard: m_InstanceValid can be false only by leaving a group in an instance => so remove temp binds that could not be removed because player was still on the map! - if (!sInstanceSaveMgr->PlayerIsPermBoundToInstance(GetPlayer()->GetGUID(), oldMap->GetId(), oldMap->GetDifficulty())) - sInstanceSaveMgr->PlayerUnbindInstance(GetPlayer()->GetGUID(), oldMap->GetId(), oldMap->GetDifficulty(), true); - } - // relocate the player to the teleport destination Map* newMap = sMapMgr->CreateMap(loc.GetMapId(), GetPlayer()); // the CanEnter checks are done in TeleporTo but conditions may change @@ -233,6 +228,12 @@ void WorldSession::HandleMoveWorldportAck() uint32 timeleft = uint32(timeReset - GameTime::GetGameTime().count()); GetPlayer()->SendInstanceResetWarning(mEntry->MapID, diff, timeleft, true); } + + // check if instance is valid + if (!GetPlayer()->CheckInstanceValidity(false)) + GetPlayer()->m_InstanceValid = false; + + // instance mounting is handled in InstanceTemplate allowMount = mInstance->AllowMount; } diff --git a/src/server/game/Instances/InstanceSaveMgr.cpp b/src/server/game/Instances/InstanceSaveMgr.cpp index ec6854ede84bfa..05e03407e22b70 100644 --- a/src/server/game/Instances/InstanceSaveMgr.cpp +++ b/src/server/game/Instances/InstanceSaveMgr.cpp @@ -16,6 +16,7 @@ */ #include "InstanceSaveMgr.h" +#include "DBCStores.h" #include "Common.h" #include "Config.h" #include "GameTime.h" @@ -34,8 +35,6 @@ #include "World.h" uint16 InstanceSaveMgr::ResetTimeDelay[] = {3600, 900, 300, 60, 0}; -PlayerBindStorage InstanceSaveMgr::playerBindStorage; -BoundInstancesMap InstanceSaveMgr::emptyBoundInstancesMap; InstanceSaveMgr::~InstanceSaveMgr() { @@ -62,9 +61,10 @@ InstanceSaveMgr* InstanceSaveMgr::instance() /* - adding instance into manager */ -InstanceSave* InstanceSaveMgr::AddInstanceSave(uint32 mapId, uint32 instanceId, Difficulty difficulty, bool startup /*=false*/) +InstanceSave* InstanceSaveMgr::AddInstanceSave(uint32 mapId, uint32 instanceId, Difficulty difficulty, time_t resetTime, uint32 entranceId, bool canReset, bool startup /*=false*/) { - ASSERT(!GetInstanceSave(instanceId)); + if (InstanceSave* old_save = GetInstanceSave(instanceId)) + return old_save; MapEntry const* entry = sMapStore.LookupEntry(mapId); if (!entry) @@ -85,20 +85,23 @@ InstanceSave* InstanceSaveMgr::AddInstanceSave(uint32 mapId, uint32 instanceId, return nullptr; } - time_t resetTime, extendedResetTime; - if (entry->map_type == MAP_RAID || difficulty > DUNGEON_DIFFICULTY_NORMAL) + if (!resetTime) { - resetTime = GetResetTimeFor(mapId, difficulty); - extendedResetTime = GetExtendedResetTimeFor(mapId, difficulty); - } - else - { - resetTime = GameTime::GetGameTime().count() + static_cast(3) * DAY; // normals expire after 3 days even if someone is still bound to them, cleared on startup - extendedResetTime = 0; + // initialize reset time + // for normal instances if no creatures are killed the instance will reset in two hours + if (entry->IsRaid() || difficulty > Difficulty::DUNGEON_DIFFICULTY_HEROIC) + resetTime = GetResetTimeFor(mapId, difficulty); + else + { + resetTime = time(nullptr) + 2 * HOUR; + // normally this will be removed soon after in InstanceMap::Add, prevent error + ScheduleReset(true, resetTime, InstResetEvent(0, mapId, difficulty, instanceId)); + } } - InstanceSave* save = new InstanceSave(mapId, instanceId, difficulty, resetTime, extendedResetTime); + + InstanceSave* save = new InstanceSave(mapId, instanceId, difficulty, entranceId, resetTime, canReset); if (!startup) - save->InsertToDB(); + save->SaveToDB(); m_instanceSaveById[instanceId] = save; return save; @@ -110,58 +113,64 @@ InstanceSave* InstanceSaveMgr::GetInstanceSave(uint32 InstanceId) return itr != m_instanceSaveById.end() ? itr->second : nullptr; } -bool InstanceSaveMgr::DeleteInstanceSaveIfNeeded(uint32 InstanceId, bool skipMapCheck) +void InstanceSaveMgr::DeleteInstanceFromDB(uint32 instanceid) { - return DeleteInstanceSaveIfNeeded(GetInstanceSave(InstanceId), skipMapCheck); -} + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); -bool InstanceSaveMgr::DeleteInstanceSaveIfNeeded(InstanceSave* save, bool skipMapCheck, bool deleteSave /*= true*/) -{ - // pussywizard: save is removed only when there are no more players bound AND the map doesn't exist - // pussywizard: this function is called when unbinding a player and when unloading a map - if (!lock_instLists && save && save->m_playerList.empty() && (skipMapCheck || !sMapMgr->FindMap(save->GetMapId(), save->GetInstanceId()))) - { - // delete save from storage: - InstanceSaveHashMap::iterator itr = m_instanceSaveById.find(save->GetInstanceId()); - ASSERT(itr != m_instanceSaveById.end() && itr->second == save); - m_instanceSaveById.erase(itr); + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INSTANCE_BY_INSTANCE); + stmt->SetData(0, instanceid); + trans->Append(stmt); - // delete save from db: - // character_instance is deleted when unbinding a certain player - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INSTANCE_BY_INSTANCE); - stmt->SetData(0, save->GetInstanceId()); - CharacterDatabase.Execute(stmt); - DeleteInstanceSavedData(save->GetInstanceId()); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE); + stmt->SetData(0, instanceid); + trans->Append(stmt); - // clear respawn times (if map is loaded do it just to be sure, if already unloaded it won't do it by itself) - Map::DeleteRespawnTimesInDB(save->GetMapId(), save->GetInstanceId()); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GROUP_INSTANCE_BY_INSTANCE); + stmt->SetData(0, instanceid); + trans->Append(stmt); - sScriptMgr->OnInstanceIdRemoved(save->GetInstanceId()); + CharacterDatabase.CommitTransaction(trans); + // Respawn times should be deleted only when the map gets unloaded +} - if (deleteSave) +void InstanceSaveMgr::RemoveInstanceSave(uint32 InstanceId) +{ + InstanceSaveHashMap::iterator itr = m_instanceSaveById.find(InstanceId); + if (itr != m_instanceSaveById.end()) + { + // save the resettime for normal instances only when they get unloaded + if (time_t resettime = itr->second->GetResetTimeForDB()) { - delete save; + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_INSTANCE_RESETTIME); + + stmt->SetData(0, uint64(resettime)); + stmt->SetData(1, InstanceId); + + CharacterDatabase.Execute(stmt); } - return true; + itr->second->SetToDelete(true); + m_instanceSaveById.erase(itr); } - return false; } -InstanceSave::InstanceSave(uint16 MapId, uint32 InstanceId, Difficulty difficulty, time_t resetTime, time_t extendedResetTime) - : m_resetTime(resetTime), m_extendedResetTime(extendedResetTime), m_instanceid(InstanceId), m_mapid(MapId), m_difficulty(IsSharedDifficultyMap(MapId) ? Difficulty(difficulty % 2) : difficulty), m_canReset(true), m_instanceData(""), m_completedEncounterMask(0) +void InstanceSaveMgr::UnloadInstanceSave(uint32 InstanceId) { - sScriptMgr->OnConstructInstanceSave(this); + if (InstanceSave* save = GetInstanceSave(InstanceId)) + save->UnloadIfEmpty(); } +InstanceSave::InstanceSave(uint16 MapId, uint32 InstanceId, Difficulty difficulty, uint32 entranceId, time_t resetTime, bool canReset) + : m_resetTime(resetTime), m_instanceid(InstanceId), m_mapid(MapId), + m_difficulty(difficulty), m_entranceId(entranceId), m_canReset(canReset), m_toDelete(false) { } + InstanceSave::~InstanceSave() { - ASSERT(m_playerList.empty()); - - sScriptMgr->OnDestructInstanceSave(this); + // the players and groups must be unbound before deleting the save + ASSERT(m_playerList.empty() && m_groupList.empty()); } -void InstanceSave::InsertToDB() +void InstanceSave::SaveToDB() { std::string data; uint32 completedEncounters = 0; @@ -174,10 +183,7 @@ void InstanceSave::InsertToDB() { data = instanceScript->GetSaveData(); completedEncounters = instanceScript->GetCompletedEncounterMask(); - - // pussywizard: - SetInstanceData(data); - SetCompletedEncounterMask(completedEncounters); + m_entranceId = instanceScript->GetEntranceLocation(); } } @@ -185,9 +191,10 @@ void InstanceSave::InsertToDB() stmt->SetData(0, m_instanceid); stmt->SetData(1, GetMapId()); stmt->SetData(2, uint32(GetResetTimeForDB())); - stmt->SetData(3, uint8(GetDifficulty())); + stmt->SetData(3, uint8(GetDifficultyID())); stmt->SetData(4, completedEncounters); stmt->SetData(5, data); + stmt->SetData(6, m_entranceId); CharacterDatabase.Execute(stmt); sScriptMgr->OnInstanceSave(this); @@ -197,7 +204,7 @@ time_t InstanceSave::GetResetTimeForDB() { // only save the reset time for normal instances MapEntry const* entry = sMapStore.LookupEntry(GetMapId()); - if (!entry || entry->map_type == MAP_RAID || GetDifficulty() == DUNGEON_DIFFICULTY_HEROIC) + if (!entry || entry->map_type == MAP_RAID || GetDifficultyID() == DUNGEON_DIFFICULTY_HEROIC) return 0; else return GetResetTime(); @@ -214,46 +221,28 @@ MapEntry const* InstanceSave::GetMapEntry() return sMapStore.LookupEntry(m_mapid); } -void InstanceSave::AddPlayer(ObjectGuid guid) +void InstanceSave::DeleteFromDB() { - std::lock_guard guard(_lock); - m_playerList.push_back(guid); + InstanceSaveMgr::DeleteInstanceFromDB(GetInstanceId()); } -bool InstanceSave::RemovePlayer(ObjectGuid guid, InstanceSaveMgr* ism) +/* true if the instance save is still valid */ +bool InstanceSave::UnloadIfEmpty() { - bool deleteSave = false; + if (m_playerList.empty() && m_groupList.empty()) { - std::lock_guard lg(_lock); - m_playerList.remove(guid); - - // ism passed as an argument to avoid calling via singleton (might result in a deadlock) - deleteSave = ism->DeleteInstanceSaveIfNeeded(this, false, false); - } - - // Delete save now (avoid mutex memory corruption) - if (deleteSave) - { - delete this; - } - - return deleteSave; -} + // don't remove the save if there are still players inside the map + if (Map* map = sMapMgr->FindMap(GetMapId(), GetInstanceId())) + if (map->HavePlayers()) + return true; -void InstanceSaveMgr::SanitizeInstanceSavedData() -{ - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SANITIZE_INSTANCE_SAVED_DATA); - CharacterDatabase.Execute(stmt); -} + if (!sInstanceSaveMgr->lock_instLists) + sInstanceSaveMgr->RemoveInstanceSave(GetInstanceId()); -void InstanceSaveMgr::DeleteInstanceSavedData(uint32 instanceId) -{ - if (instanceId) - { - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DELETE_INSTANCE_SAVED_DATA); - stmt->SetData(0, instanceId); - CharacterDatabase.Execute(stmt); + return false; } + else + return true; } void InstanceSaveMgr::LoadInstances() @@ -282,14 +271,7 @@ void InstanceSaveMgr::LoadInstances() sMapMgr->InitInstanceIds(); // Load reset times and clean expired instances - LoadResetTimes(); - - // pussywizard - LoadInstanceSaves(); - LoadCharacterBinds(); - - // Sanitize pending rows on Instance_saved_data for data that wasn't deleted properly - SanitizeInstanceSavedData(); + sInstanceSaveMgr->LoadResetTimes(); LOG_INFO("server.loading", ">> Loaded Instances And Binds in {} ms", GetMSTimeDiffToNow(oldMSTime)); LOG_INFO("server.loading", " "); @@ -300,6 +282,45 @@ void InstanceSaveMgr::LoadResetTimes() time_t now = GameTime::GetGameTime().count(); time_t today = (now / DAY) * DAY; + // get the current reset times for normal instances (these may need to be updated) + // these are only kept in memory for InstanceSaves that are loaded later + // resettime = 0 in the DB for raid/heroic instances so those are skipped + typedef std::pair ResetTimeMapDiffType; + typedef std::map InstResetTimeMapDiffType; + InstResetTimeMapDiffType instResetTime; + + // index instance ids by map/difficulty pairs for fast reset warning send + typedef std::multimap ResetTimeMapDiffInstances; + typedef std::pair ResetTimeMapDiffInstancesBounds; + ResetTimeMapDiffInstances mapDiffResetInstances; + + if (QueryResult result = CharacterDatabase.Query("SELECT id, map, difficulty, resettime FROM instance ORDER BY id ASC")) + { + do + { + Field* fields = result->Fetch(); + + uint32 instanceId = fields[0].Get(); + + // Mark instance id as being used + sMapMgr->RegisterInstanceId(instanceId); + + if (time_t resettime = time_t(fields[3].Get())) + { + uint32 mapid = fields[1].Get(); + uint32 difficulty = fields[2].Get(); + + instResetTime[instanceId] = ResetTimeMapDiffType(MAKE_PAIR32(mapid, difficulty), resettime); + mapDiffResetInstances.insert(ResetTimeMapDiffInstances::value_type(MAKE_PAIR32(mapid, difficulty), instanceId)); + } + } while (result->NextRow()); + + // schedule the reset times + for (InstResetTimeMapDiffType::iterator itr = instResetTime.begin(); itr != instResetTime.end(); ++itr) + if (itr->second.second > now) + ScheduleReset(true, itr->second.second, InstResetEvent(0, PAIR32_LOPART(itr->second.first), Difficulty(PAIR32_HIPART(itr->second.first)), itr->first)); + } + // load the global respawn times for raid/heroic instances uint32 diff = sWorld->getIntConfig(CONFIG_INSTANCE_RESET_TIME_HOUR) * HOUR; QueryResult result = CharacterDatabase.Query("SELECT mapid, difficulty, resettime FROM instance_reset"); @@ -310,7 +331,7 @@ void InstanceSaveMgr::LoadResetTimes() Field* fields = result->Fetch(); uint32 mapid = fields[0].Get(); Difficulty difficulty = Difficulty(fields[1].Get()); - uint64 resettime = fields[2].Get(); + uint64 resettime = fields[2].Get(); MapDifficulty const* mapDiff = GetMapDifficultyData(mapid, difficulty); if (!mapDiff) @@ -320,7 +341,18 @@ void InstanceSaveMgr::LoadResetTimes() continue; } - SetResetTimeFor(mapid, difficulty, resettime); + // update the reset time if the hour in the configs changes + uint64 newresettime = (resettime / DAY) * DAY + diff; + if (resettime != newresettime) + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GLOBAL_INSTANCE_RESETTIME); + stmt->SetData(0, uint64(newresettime)); + stmt->SetData(1, uint16(mapid)); + stmt->SetData(2, uint8(difficulty)); + CharacterDatabase.Execute(stmt); + } + + InitializeResetTimeFor(mapid, difficulty, newresettime); } while (result->NextRow()); } @@ -358,104 +390,92 @@ void InstanceSaveMgr::LoadResetTimes() CharacterDatabase.DirectExecute("UPDATE instance_reset SET resettime = '{}' WHERE mapid = '{}' AND difficulty = '{}'", (uint32)t, mapid, difficulty); } - SetExtendedResetTimeFor(mapid, difficulty, t); + InitializeResetTimeFor(mapid, difficulty, t); // schedule the global reset/warning uint8 type; - for (type = 1; type < 5; ++type) - if (now + ResetTimeDelay[type - 1] < t) + for (type = 1; type < 4; ++type) + if (t - ResetTimeDelay[type - 1] > now) break; - ScheduleReset(t - ResetTimeDelay[type - 1], InstResetEvent(type, mapid, difficulty)); + ScheduleReset(true, t - ResetTimeDelay[type - 1], InstResetEvent(type, mapid, difficulty, 0)); + + ResetTimeMapDiffInstancesBounds range = mapDiffResetInstances.equal_range(MAKE_PAIR32(mapid, difficulty)); + for (; range.first != range.second; ++range.first) + ScheduleReset(true, t - ResetTimeDelay[type - 1], InstResetEvent(type, mapid, difficulty, range.first->second)); } } -void InstanceSaveMgr::LoadInstanceSaves() +time_t InstanceSaveMgr::GetSubsequentResetTime(uint32 mapid, Difficulty difficulty, time_t resetTime) const { - QueryResult result = CharacterDatabase.Query("SELECT id, map, resettime, difficulty, completedEncounters, data FROM instance ORDER BY id ASC"); - if (result) + MapDifficulty const* mapDiff = GetMapDifficultyData(mapid, difficulty); + if (!mapDiff || !mapDiff->resetTime) { - do - { - Field* fields = result->Fetch(); + LOG_ERROR("misc", "InstanceSaveManager::GetSubsequentResetTime: not valid difficulty or no reset delay for map %u", mapid); + return 0; + } - uint32 instanceId = fields[0].Get(); - uint32 mapId = fields[1].Get(); - time_t resettime = time_t(fields[2].Get()); - uint8 difficulty = fields[3].Get(); - uint32 completedEncounters = fields[4].Get(); - std::string instanceData = fields[5].Get(); + time_t diff = sWorld->getIntConfig(CONFIG_INSTANCE_RESET_TIME_HOUR) * HOUR; + time_t period = uint32(((mapDiff->resetTime * sWorld->getRate(RATE_INSTANCE_RESET_TIME)) / DAY) * DAY); + if (period < DAY) + period = DAY; - // Mark instance id as being used - sMapMgr->RegisterInstanceId(instanceId); - - InstanceSave* save = AddInstanceSave(mapId, instanceId, Difficulty(difficulty), true); - if (save) - { - save->SetCompletedEncounterMask(completedEncounters); - save->SetInstanceData(instanceData); - if (resettime > 0) - save->SetResetTime(resettime); - } - } while (result->NextRow()); - } + return ((resetTime + MINUTE) / DAY * DAY) + period + diff; } -void InstanceSaveMgr::LoadCharacterBinds() +void InstanceSaveMgr::SetResetTimeFor(uint32 mapid, Difficulty d, time_t t) { - lock_instLists = true; + ResetTimeByMapDifficultyMap::iterator itr = m_resetTimeByMapDifficulty.find(MAKE_PAIR64(mapid, d)); + ASSERT(itr != m_resetTimeByMapDifficulty.end()); + itr->second = t; +} - QueryResult result = CharacterDatabase.Query("SELECT guid, instance, permanent, extended FROM character_instance"); - if (result) +void InstanceSaveMgr::ScheduleReset(bool add, time_t time, InstResetEvent event) +{ + if (!add) { - do + // find the event in the queue and remove it + ResetTimeQueue::iterator itr; + std::pair range; + range = m_resetTimeQueue.equal_range(time); + for (itr = range.first; itr != range.second; ++itr) { - Field* fields = result->Fetch(); - - ObjectGuid guid = ObjectGuid::Create(fields[0].Get()); - uint32 instanceId = fields[1].Get(); - bool perm = fields[2].Get(); - bool extended = fields[3].Get(); + if (itr->second == event) + { + m_resetTimeQueue.erase(itr); + return; + } + } - if (InstanceSave* save = GetInstanceSave(instanceId)) + // in case the reset time changed (should happen very rarely), we search the whole queue + if (itr == range.second) + { + for (itr = m_resetTimeQueue.begin(); itr != m_resetTimeQueue.end(); ++itr) { - PlayerCreateBoundInstancesMaps(guid); - InstancePlayerBind& bind = playerBindStorage[guid]->m[save->GetDifficulty()][save->GetMapId()]; - if (bind.save) // pussywizard: another bind for the same map and difficulty! may happen because of mysql thread races + if (itr->second == event) { - if (bind.perm) // already loaded perm -> delete currently checked one from db - { - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_GUID); - stmt->SetData(0, guid.GetCounter()); - stmt->SetData(1, instanceId); - CharacterDatabase.Execute(stmt); - continue; - } - else // override temp bind by newest one - { - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_GUID); - stmt->SetData(0, guid.GetCounter()); - stmt->SetData(1, bind.save->GetInstanceId()); - CharacterDatabase.Execute(stmt); - bind.save->RemovePlayer(guid, this); - } + m_resetTimeQueue.erase(itr); + return; } - bind.save = save; - bind.perm = perm; - bind.extended = extended; - save->AddPlayer(guid); - if (perm) - save->SetCanReset(false); } - } while (result->NextRow()); - } - lock_instLists = false; + if (itr == m_resetTimeQueue.end()) + LOG_ERROR("misc", "InstanceSaveManager::ScheduleReset: cannot cancel the reset, the event(%d, %d, %d) was not found!", event.type, event.mapid, event.instanceId); + } + } + else + m_resetTimeQueue.insert(std::pair(time, event)); } -void InstanceSaveMgr::ScheduleReset(time_t time, InstResetEvent event) +void InstanceSaveMgr::ForceGlobalReset(uint32 mapId, Difficulty difficulty) { - m_resetTimeQueue.insert(std::pair(time, event)); + if (!GetDownscaledMapDifficultyData(mapId, difficulty)) + return; + // remove currently scheduled reset times + ScheduleReset(false, 0, InstResetEvent(1, mapId, difficulty, 0)); + ScheduleReset(false, 0, InstResetEvent(4, mapId, difficulty, 0)); + // force global reset on the instance + _ResetOrWarnAll(mapId, difficulty, false, time(nullptr)); } void InstanceSaveMgr::Update() @@ -471,22 +491,25 @@ void InstanceSaveMgr::Update() break; InstResetEvent& event = m_resetTimeQueue.begin()->second; - if (event.type) + if (event.type == 0) + { + // for individual normal instances, max creature respawn + X hours + _ResetInstance(event.mapid, event.instanceId); + m_resetTimeQueue.erase(m_resetTimeQueue.begin()); + } + else { // global reset/warning for a certain map time_t resetTime = GetResetTimeFor(event.mapid, event.difficulty); - bool warn = event.type < 5; - _ResetOrWarnAll(event.mapid, event.difficulty, warn, resetTime); - if (warn) + _ResetOrWarnAll(event.mapid, event.difficulty, event.type != 4, resetTime); + if (event.type != 4) { // schedule the next warning/reset ++event.type; - ScheduleReset(resetTime - ResetTimeDelay[event.type - 1], event); + ScheduleReset(true, resetTime - ResetTimeDelay[event.type - 1], event); } - else - resetOccurred = true; + m_resetTimeQueue.erase(m_resetTimeQueue.begin()); } - m_resetTimeQueue.erase(m_resetTimeQueue.begin()); } // pussywizard: send updated calendar and raid info @@ -507,52 +530,76 @@ void InstanceSaveMgr::_ResetSave(InstanceSaveHashMap::iterator& itr) { lock_instLists = true; - GuidList& pList = itr->second->m_playerList; - for (GuidList::iterator iter = pList.begin(), iter2; iter != pList.end(); ) + bool shouldDelete = true; + InstanceSave::PlayerListType& pList = itr->second->m_playerList; + std::vector temp; // list of expired binds that should be unbound + + for (Player* player : pList) { - iter2 = iter++; - PlayerUnbindInstanceNotExtended(*iter2, itr->second->GetMapId(), itr->second->GetDifficulty(), ObjectAccessor::FindConnectedPlayer(*iter2)); + if (InstancePlayerBind* bind = player->GetBoundInstance(itr->second->GetMapId(), itr->second->GetDifficultyID())) + { + ASSERT(bind->save == itr->second); + if (bind->perm && bind->extendState) // permanent and not already expired + { + // actual promotion in DB already happened in caller + bind->extendState = bind->extendState == EXTEND_STATE_EXTENDED ? EXTEND_STATE_NORMAL : EXTEND_STATE_EXPIRED; + shouldDelete = false; + continue; + } + } + temp.push_back(player); } - - // delete stuff if no players left (noone extended id) - if (pList.empty()) + for (Player* player : temp) { - // delete character_instance per id, delete instance per id - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE); - stmt->SetData(0, itr->second->GetInstanceId()); - CharacterDatabase.Execute(stmt); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INSTANCE_BY_INSTANCE); - stmt->SetData(0, itr->second->GetInstanceId()); - CharacterDatabase.Execute(stmt); - DeleteInstanceSavedData(itr->second->GetInstanceId()); - - // clear respawn times if the map is already unloaded and won't do it by itself - if (!sMapMgr->FindMap(itr->second->GetMapId(), itr->second->GetInstanceId())) - Map::DeleteRespawnTimesInDB(itr->second->GetMapId(), itr->second->GetInstanceId()); + player->UnbindInstance(itr->second->GetMapId(), itr->second->GetDifficultyID(), true); + } - sScriptMgr->OnInstanceIdRemoved(itr->second->GetInstanceId()); + InstanceSave::GroupListType& gList = itr->second->m_groupList; + while (!gList.empty()) + { + Group* group = *(gList.begin()); + group->UnbindInstance(itr->second->GetMapId(), itr->second->GetDifficultyID(), true); + } + if (shouldDelete) + { delete itr->second; - m_instanceSaveById.erase(itr); + itr = m_instanceSaveById.erase(itr); } else - { - // delete character_instance per id where extended = 0, transtaction with set extended = 0, transaction is used to avoid mysql thread races - CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_NOT_EXTENDED); - stmt->SetData(0, itr->second->GetInstanceId()); - trans->Append(stmt); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_INSTANCE_SET_NOT_EXTENDED); - stmt->SetData(0, itr->second->GetInstanceId()); - trans->Append(stmt); - CharacterDatabase.CommitTransaction(trans); + ++itr; + + lock_instLists = false; +} + +void InstanceSaveMgr::_ResetInstance(uint32 mapid, uint32 instanceId) +{ + LOG_DEBUG("maps", "InstanceSaveMgr::_ResetInstance %u, %u", mapid, instanceId); + Map const* map = sMapMgr->CreateBaseMap(mapid); + if (!map->Instanceable()) + return; - // update reset time and extended reset time for instance save - itr->second->SetResetTime(GetResetTimeFor(itr->second->GetMapId(), itr->second->GetDifficulty())); - itr->second->SetExtendedResetTime(GetExtendedResetTimeFor(itr->second->GetMapId(), itr->second->GetDifficulty())); + InstanceSaveHashMap::iterator itr = m_instanceSaveById.find(instanceId); + if (itr != m_instanceSaveById.end()) + _ResetSave(itr); + + DeleteInstanceFromDB(instanceId); // even if save not loaded + + Map* iMap = ((MapInstanced*)map)->FindInstanceMap(instanceId); + + if (iMap && iMap->IsDungeon()) + ((InstanceMap*)iMap)->Reset(INSTANCE_RESET_RESPAWN_DELAY); + + if (iMap) + { + iMap->DeleteRespawnTimes(); + iMap->DeleteCorpseData(); } + else + Map::DeleteRespawnTimesInDB(mapid, instanceId); - lock_instLists = false; + // Free up the instance id and allow it to be reused + sMapMgr->FreeInstanceId(instanceId); } void InstanceSaveMgr::_ResetOrWarnAll(uint32 mapid, Difficulty difficulty, bool warn, time_t resetTime) @@ -573,33 +620,55 @@ void InstanceSaveMgr::_ResetOrWarnAll(uint32 mapid, Difficulty difficulty, bool return; } - // calculate the next reset time - uint32 diff = sWorld->getIntConfig(CONFIG_INSTANCE_RESET_TIME_HOUR) * HOUR; + time_t next_reset = GetSubsequentResetTime(mapid, difficulty, resetTime); + if (!next_reset) + return; - uint32 period = uint32(((mapDiff->resetTime * sWorld->getRate(RATE_INSTANCE_RESET_TIME)) / DAY) * DAY); - if (period < DAY) - period = DAY; + // delete/promote instance binds from the DB, even if not loaded + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_EXPIRED_CHAR_INSTANCE_BY_MAP_DIFF); + stmt->SetData(0, uint16(mapid)); + stmt->SetData(1, uint8(difficulty)); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GROUP_INSTANCE_BY_MAP_DIFF); + stmt->SetData(0, uint16(mapid)); + stmt->SetData(1, uint8(difficulty)); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_EXPIRED_INSTANCE_BY_MAP_DIFF); + stmt->SetData(0, uint16(mapid)); + stmt->SetData(1, uint8(difficulty)); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_EXPIRE_CHAR_INSTANCE_BY_MAP_DIFF); + stmt->SetData(0, uint16(mapid)); + stmt->SetData(1, uint8(difficulty)); + trans->Append(stmt); + + CharacterDatabase.CommitTransaction(trans); + + // promote loaded binds to instances of the given map + for (InstanceSaveHashMap::iterator itr = m_instanceSaveById.begin(); itr != m_instanceSaveById.end();) + { + if (itr->second->GetMapId() == mapid && itr->second->GetDifficultyID() == difficulty) + _ResetSave(itr); + else + ++itr; + } - uint32 next_reset = uint32(((resetTime + MINUTE) / DAY * DAY) + period + diff); SetResetTimeFor(mapid, difficulty, next_reset); - SetExtendedResetTimeFor(mapid, difficulty, next_reset + period); - ScheduleReset(time_t(next_reset - 3600), InstResetEvent(1, mapid, difficulty)); + ScheduleReset(true, time_t(next_reset - 3600), InstResetEvent(1, mapid, difficulty, 0)); - // update it in the DB - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GLOBAL_INSTANCE_RESETTIME); - stmt->SetData(0, next_reset); + // Update it in the DB + stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GLOBAL_INSTANCE_RESETTIME); + + stmt->SetData(0, uint64(next_reset)); stmt->SetData(1, uint16(mapid)); stmt->SetData(2, uint8(difficulty)); - CharacterDatabase.Execute(stmt); - // remove all binds to instances of the given map and delete from db (delete per instance id, no mass deletion!) - // do this after new reset time is calculated - for (InstanceSaveHashMap::iterator itr = m_instanceSaveById.begin(), itr2; itr != m_instanceSaveById.end(); ) - { - itr2 = itr++; - if (itr2->second->GetMapId() == mapid && itr2->second->GetDifficulty() == difficulty) - _ResetSave(itr2); - } + CharacterDatabase.Execute(stmt); } // now loop all existing maps to warn / reset @@ -625,216 +694,25 @@ void InstanceSaveMgr::_ResetOrWarnAll(uint32 mapid, Difficulty difficulty, bool } else { - InstanceSave* save = GetInstanceSave(map2->GetInstanceId()); - map2->ToInstanceMap()->Reset(INSTANCE_RESET_GLOBAL, (save ? & (save->m_playerList) : nullptr)); + map2->ToInstanceMap()->Reset(INSTANCE_RESET_GLOBAL); } } } -InstancePlayerBind* InstanceSaveMgr::PlayerBindToInstance(ObjectGuid guid, InstanceSave* save, bool permanent, Player* player /*= nullptr*/) +uint32 InstanceSaveMgr::GetNumBoundPlayersTotal() const { - InstancePlayerBind& bind = playerBindStorage[guid]->m[save->GetDifficulty()][save->GetMapId()]; - ASSERT(!bind.perm || permanent); // ensure there's no changing permanent to temporary, this can be done only by unbinding - - if (bind.save) - { - if (save != bind.save || permanent != bind.perm) - { - bind.extended = false; - - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_INSTANCE); - stmt->SetData(0, save->GetInstanceId()); - stmt->SetData(1, permanent); - stmt->SetData(2, guid.GetCounter()); - stmt->SetData(3, bind.save->GetInstanceId()); - CharacterDatabase.Execute(stmt); - } - } - else - { - // pussywizard: protect against mysql thread races! - // pussywizard: CHANGED MY MIND! DON'T SLOW DOWN THIS QUERY! HANDLE ONLY DURING LOADING FROM DB! - // example: enter instance -> bind -> update old id to new id -> exit -> delete new id - // if delete by new id is executed before update, then we end up with in db - /*CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - // ensure any for that map+difficulty is deleted! - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_GUID_MAP_DIFF); // DELETE ci FROM character_instance ci JOIN instance i ON ci.instance = i.id WHERE ci.guid = ? AND i.map = ? AND i.difficulty = ? - stmt->SetData(0, guidLow); - stmt->SetData(1, uint16(save->GetMapId())); - stmt->SetData(2, uint8(save->GetDifficulty())); - trans->Append(stmt); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_INSTANCE); - stmt->SetData(0, guidLow); - stmt->SetData(1, save->GetInstanceId()); - stmt->SetData(2, permanent); - trans->Append(stmt); - CharacterDatabase.CommitTransaction(trans);*/ - - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_INSTANCE); - stmt->SetData(0, guid.GetCounter()); - stmt->SetData(1, save->GetInstanceId()); - stmt->SetData(2, permanent); - CharacterDatabase.Execute(stmt); - - if (player) - player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_RAID, 1); - } - - if (bind.save != save) - { - if (bind.save) - bind.save->RemovePlayer(guid, this); - save->AddPlayer(guid); - } - - if (permanent) - { - save->SetCanReset(false); - if (!bind.perm && player) // temporary changing to permanent - player->GetSession()->SendCalendarRaidLockout(save, true); - } + uint32 ret = 0; + for (InstanceSaveHashMap::const_iterator itr = m_instanceSaveById.begin(); itr != m_instanceSaveById.end(); ++itr) + ret += itr->second->GetPlayerCount(); - bind.save = save; - bind.perm = permanent; - - if (player) - sScriptMgr->OnPlayerBindToInstance(player, save->GetDifficulty(), save->GetMapId(), permanent); - - return &bind; -} - -void InstanceSaveMgr::PlayerUnbindInstance(ObjectGuid guid, uint32 mapid, Difficulty difficulty, bool deleteFromDB, Player* player /*= nullptr*/) -{ - BoundInstancesMapWrapper* w = playerBindStorage[guid]; - BoundInstancesMap::iterator itr = w->m[difficulty].find(mapid); - if (itr != w->m[difficulty].end()) - { - if (deleteFromDB) - { - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_GUID); - stmt->SetData(0, guid.GetCounter()); - stmt->SetData(1, itr->second.save->GetInstanceId()); - CharacterDatabase.Execute(stmt); - } - - if (itr->second.perm && player) - player->GetSession()->SendCalendarRaidLockout(itr->second.save, false); - - InstanceSave* tmp = itr->second.save; - w->m[difficulty].erase(itr); - tmp->RemovePlayer(guid, this); - } -} - -void InstanceSaveMgr::PlayerUnbindInstanceNotExtended(ObjectGuid guid, uint32 mapid, Difficulty difficulty, Player* player /*= nullptr*/) -{ - BoundInstancesMapWrapper* w = playerBindStorage[guid]; - BoundInstancesMap::iterator itr = w->m[difficulty].find(mapid); - if (itr != w->m[difficulty].end()) - { - if (itr->second.extended) - itr->second.extended = false; - else - { - if (itr->second.perm && player) - player->GetSession()->SendCalendarRaidLockout(itr->second.save, false); - - InstanceSave* tmp = itr->second.save; - w->m[difficulty].erase(itr); - tmp->RemovePlayer(guid, this); - } - } + return ret; } -InstancePlayerBind* InstanceSaveMgr::PlayerGetBoundInstance(ObjectGuid guid, uint32 mapid, Difficulty difficulty) +uint32 InstanceSaveMgr::GetNumBoundGroupsTotal() const { - Difficulty difficulty_fixed = ( IsSharedDifficultyMap(mapid) ? Difficulty(difficulty % 2) : difficulty); - - MapDifficulty const* mapDiff = GetDownscaledMapDifficultyData(mapid, difficulty_fixed); - if (!mapDiff) - return nullptr; - - BoundInstancesMapWrapper* w = nullptr; - PlayerBindStorage::const_iterator itr = playerBindStorage.find(guid); - if (itr != playerBindStorage.end()) - w = itr->second; - else - return nullptr; - - BoundInstancesMap::iterator itr2 = w->m[difficulty_fixed].find(mapid); - if (itr2 != w->m[difficulty_fixed].end()) - return &itr2->second; - else - return nullptr; -} + uint32 ret = 0; + for (InstanceSaveHashMap::const_iterator itr = m_instanceSaveById.begin(); itr != m_instanceSaveById.end(); ++itr) + ret += itr->second->GetGroupCount(); -bool InstanceSaveMgr::PlayerIsPermBoundToInstance(ObjectGuid guid, uint32 mapid, Difficulty difficulty) -{ - if (InstancePlayerBind* bind = PlayerGetBoundInstance(guid, mapid, difficulty)) - if (bind->perm) - return true; - return false; -} - -BoundInstancesMap const& InstanceSaveMgr::PlayerGetBoundInstances(ObjectGuid guid, Difficulty difficulty) -{ - PlayerBindStorage::iterator itr = playerBindStorage.find(guid); - if (itr != playerBindStorage.end()) - return itr->second->m[difficulty]; - return emptyBoundInstancesMap; -} - -void InstanceSaveMgr::PlayerCreateBoundInstancesMaps(ObjectGuid guid) -{ - if (playerBindStorage.find(guid) == playerBindStorage.end()) - playerBindStorage[guid] = new BoundInstancesMapWrapper; -} - -InstanceSave* InstanceSaveMgr::PlayerGetInstanceSave(ObjectGuid guid, uint32 mapid, Difficulty difficulty) -{ - InstancePlayerBind* pBind = PlayerGetBoundInstance(guid, mapid, difficulty); - return (pBind ? pBind->save : nullptr); -} - -uint32 InstanceSaveMgr::PlayerGetDestinationInstanceId(Player* player, uint32 mapid, Difficulty difficulty) -{ - // returning 0 means a new instance will be created - // non-zero implicates that InstanceSave exists - - InstancePlayerBind* ipb = PlayerGetBoundInstance(player->GetGUID(), mapid, difficulty); - if (ipb && ipb->perm) // 1. self perm - return ipb->save->GetInstanceId(); - if (Group* g = player->GetGroup()) - { - if (InstancePlayerBind* ilb = PlayerGetBoundInstance(g->GetLeaderGUID(), mapid, difficulty)) // 2. leader temp/perm - return ilb->save->GetInstanceId(); - return 0; // 3. in group, no leader bind - } - return ipb ? ipb->save->GetInstanceId() : 0; // 4. self temp -} - -void InstanceSaveMgr::CopyBinds(ObjectGuid from, ObjectGuid to, Player* toPlr) -{ - if (from == to) - return; - - for (uint8 d = 0; d < MAX_DIFFICULTY; ++d) - { - BoundInstancesMap const& bi = PlayerGetBoundInstances(from, Difficulty(d)); - for (BoundInstancesMap::const_iterator itr = bi.begin(); itr != bi.end(); ++itr) - if (!PlayerGetBoundInstance(to, itr->first, Difficulty(d))) - PlayerBindToInstance(to, itr->second.save, false, toPlr); - } -} - -void InstanceSaveMgr::UnbindAllFor(InstanceSave* save) -{ - uint32 mapId = save->GetMapId(); - Difficulty difficulty = save->GetDifficulty(); - GuidList players = save->m_playerList; - - for (ObjectGuid const& guid : players) - { - PlayerUnbindInstance(guid, mapId, difficulty, true, ObjectAccessor::FindConnectedPlayer(guid)); - } + return ret; } diff --git a/src/server/game/Instances/InstanceSaveMgr.h b/src/server/game/Instances/InstanceSaveMgr.h index d76b438aca790b..e9c0c7b9f64713 100644 --- a/src/server/game/Instances/InstanceSaveMgr.h +++ b/src/server/game/Instances/InstanceSaveMgr.h @@ -35,67 +35,104 @@ class Group; class InstanceSaveMgr; class InstanceSave; -struct InstancePlayerBind -{ - InstanceSave* save{nullptr}; - bool perm : 1; - bool extended : 1; - InstancePlayerBind() : perm(false), extended(false) {} -}; - -typedef std::unordered_map BoundInstancesMap; - -struct BoundInstancesMapWrapper -{ - BoundInstancesMap m[MAX_DIFFICULTY]; -}; - -typedef std::unordered_map PlayerBindStorage; - class InstanceSave { friend class InstanceSaveMgr; public: - InstanceSave(uint16 MapId, uint32 InstanceId, Difficulty difficulty, time_t resetTime, time_t extendedResetTime); + InstanceSave(uint16 MapId, uint32 InstanceId, Difficulty difficulty, uint32 entranceId, time_t resetTime, bool canReset); ~InstanceSave(); - [[nodiscard]] uint32 GetInstanceId() const { return m_instanceid; } - [[nodiscard]] uint32 GetMapId() const { return m_mapid; } - [[nodiscard]] Difficulty GetDifficulty() const { return m_difficulty; } - /* Saved when the instance is generated for the first time */ - void InsertToDB(); - // pussywizard: deleting is done internally when there are no binds left + uint32 GetPlayerCount() const { return uint32(m_playerList.size()); } + uint32 GetGroupCount() const { return uint32(m_groupList.size()); } - [[nodiscard]] std::string GetInstanceData() const { return m_instanceData; } - void SetInstanceData(std::string str) { m_instanceData = str; } - [[nodiscard]] uint32 GetCompletedEncounterMask() const { return m_completedEncounterMask; } - void SetCompletedEncounterMask(uint32 mask) { m_completedEncounterMask = mask; } + /* A map corresponding to the InstanceId/MapId does not always exist. + InstanceSave objects may be created on player logon but the maps are + created and loaded only when a player actually enters the instance. */ + uint32 GetInstanceId() const { return m_instanceid; } + uint32 GetMapId() const { return m_mapid; } - // pussywizard: for normal instances this corresponds to 0, for raid/heroic instances this caches the global reset time for the map - [[nodiscard]] time_t GetResetTime() const { return m_resetTime; } - [[nodiscard]] time_t GetExtendedResetTime() const { return m_extendedResetTime; } - time_t GetResetTimeForDB(); - void SetResetTime(time_t resetTime) { m_resetTime = resetTime; } - void SetExtendedResetTime(time_t extendedResetTime) { m_extendedResetTime = extendedResetTime; } + /* Saved when the instance is generated for the first time */ + void SaveToDB(); + /* When the instance is being reset (permanently deleted) */ + void DeleteFromDB(); - [[nodiscard]] bool CanReset() const { return m_canReset; } - void SetCanReset(bool canReset) { m_canReset = canReset; } + /* for normal instances this corresponds to max(creature respawn time) + X hours + for raid/heroic instances this caches the global respawn time for the map */ + time_t GetResetTime() const { return m_resetTime; } + void SetResetTime(time_t resetTime) { m_resetTime = resetTime; } + time_t GetResetTimeForDB(); InstanceTemplate const* GetTemplate(); MapEntry const* GetMapEntry(); - void AddPlayer(ObjectGuid guid); - bool RemovePlayer(ObjectGuid guid, InstanceSaveMgr* ism); + void AddPlayer(Player* player) + { + std::lock_guard lock(_lock); + m_playerList.push_back(player); + } + + bool RemovePlayer(Player* player) + { + _lock.lock(); + m_playerList.remove(player); + bool isStillValid = UnloadIfEmpty(); + _lock.unlock(); + + //delete here if needed, after releasing the lock + if (m_toDelete) + delete this; + + return isStillValid; + } + /* all groups bound to the instance */ + void AddGroup(Group* group) { m_groupList.push_back(group); } + bool RemoveGroup(Group* group) + { + m_groupList.remove(group); + bool isStillValid = UnloadIfEmpty(); + if (m_toDelete) + delete this; + return isStillValid; + } + /* instances cannot be reset (except at the global reset time) + if there are players permanently bound to it + this is cached for the case when those players are offline */ + bool CanReset() const { return m_canReset; } + void SetCanReset(bool canReset) { m_canReset = canReset; } + + /* currently it is possible to omit this information from this structure + but that would depend on a lot of things that can easily change in future */ + Difficulty GetDifficultyID() const { return m_difficulty; } + + /* used to flag the InstanceSave as to be deleted, so the caller can delete it */ + void SetToDelete(bool toDelete) + { + m_toDelete = toDelete; + } + + [[nodiscard]] std::string GetInstanceData() const { return m_instanceData; } + void SetInstanceData(std::string str) { m_instanceData = str; } + [[nodiscard]] uint32 GetCompletedEncounterMask() const { return m_completedEncounterMask; } + void SetCompletedEncounterMask(uint32 mask) { m_completedEncounterMask = mask; } + + typedef std::list PlayerListType; + typedef std::list GroupListType; private: - GuidList m_playerList; + bool UnloadIfEmpty(); + /* the only reason the instSave-object links are kept is because + the object-instSave links need to be broken at reset time */ + /// @todo: Check if maybe it's enough to just store the number of players/groups + PlayerListType m_playerList; + GroupListType m_groupList; time_t m_resetTime; - time_t m_extendedResetTime; uint32 m_instanceid; uint32 m_mapid; Difficulty m_difficulty; uint32 m_entranceId; bool m_canReset; + bool m_toDelete; + std::string m_instanceData; uint32 m_completedEncounterMask; @@ -114,89 +151,72 @@ class InstanceSaveMgr public: static InstanceSaveMgr* instance(); - typedef std::unordered_map InstanceSaveHashMap; struct InstResetEvent { - uint8 type{0}; // 0 - unused, 1-4 warnings about pending reset, 5 - reset - Difficulty difficulty: 8; - uint16 mapid{0}; - - InstResetEvent() : difficulty(DUNGEON_DIFFICULTY_NORMAL) {} - InstResetEvent(uint8 t, uint32 _mapid, Difficulty d) - : type(t), difficulty(d), mapid(_mapid) {} + uint8 type; + Difficulty difficulty : 8; + uint32 mapid; + uint32 instanceId; + + InstResetEvent() : type(0), difficulty(Difficulty::RAID_DIFFICULTY_25MAN_NORMAL), mapid(0), instanceId(0) { } + InstResetEvent(uint8 t, uint32 _mapid, Difficulty d, uint32 _instanceid) + : type(t), difficulty(d), mapid(_mapid), instanceId(_instanceid) { } + bool operator==(InstResetEvent const& e) const { return e.instanceId == instanceId; } }; typedef std::multimap ResetTimeQueue; void LoadInstances(); - void LoadResetTimes(); - void LoadInstanceSaves(); - void LoadCharacterBinds(); - [[nodiscard]] time_t GetResetTimeFor(uint32 mapid, Difficulty d) const + void LoadResetTimes(); + time_t GetResetTimeFor(uint32 mapid, Difficulty d) const { - ResetTimeByMapDifficultyMap::const_iterator itr = m_resetTimeByMapDifficulty.find(MAKE_PAIR32(mapid, d)); + ResetTimeByMapDifficultyMap::const_iterator itr = m_resetTimeByMapDifficulty.find(MAKE_PAIR64(mapid, d)); return itr != m_resetTimeByMapDifficulty.end() ? itr->second : 0; } + time_t GetSubsequentResetTime(uint32 mapid, Difficulty difficulty, time_t resetTime) const; - [[nodiscard]] time_t GetExtendedResetTimeFor(uint32 mapid, Difficulty d) const + // Use this on startup when initializing reset times + void InitializeResetTimeFor(uint32 mapid, Difficulty d, time_t t) { - ResetTimeByMapDifficultyMap::const_iterator itr = m_resetExtendedTimeByMapDifficulty.find(MAKE_PAIR32(mapid, d)); - return itr != m_resetExtendedTimeByMapDifficulty.end() ? itr->second : 0; + m_resetTimeByMapDifficulty[MAKE_PAIR64(mapid, d)] = t; } - void SetResetTimeFor(uint32 mapid, Difficulty d, time_t t) - { - m_resetTimeByMapDifficulty[MAKE_PAIR32(mapid, d)] = t; - } - - void SetExtendedResetTimeFor(uint32 mapid, Difficulty d, time_t t) - { - m_resetExtendedTimeByMapDifficulty[MAKE_PAIR32(mapid, d)] = t; - } + // Use this only when updating existing reset times + void SetResetTimeFor(uint32 mapid, Difficulty d, time_t t); [[nodiscard]] ResetTimeByMapDifficultyMap const& GetResetTimeMap() const { return m_resetTimeByMapDifficulty; } - - void ScheduleReset(time_t time, InstResetEvent event); + void ScheduleReset(bool add, time_t time, InstResetEvent event); + void ForceGlobalReset(uint32 mapId, Difficulty difficulty); void Update(); - InstanceSave* AddInstanceSave(uint32 mapId, uint32 instanceId, Difficulty difficulty, bool startup = false); - bool DeleteInstanceSaveIfNeeded(uint32 InstanceId, bool skipMapCheck); - bool DeleteInstanceSaveIfNeeded(InstanceSave* save, bool skipMapCheck, bool deleteSave = true); + InstanceSave* AddInstanceSave(uint32 mapId, uint32 instanceId, Difficulty difficulty, time_t resetTime, uint32 entranceId, + bool canReset, bool load = false); + void RemoveInstanceSave(uint32 InstanceId); + void UnloadInstanceSave(uint32 InstanceId); + static void DeleteInstanceFromDB(uint32 instanceid); InstanceSave* GetInstanceSave(uint32 InstanceId); - InstancePlayerBind* PlayerBindToInstance(ObjectGuid guid, InstanceSave* save, bool permanent, Player* player = nullptr); - void PlayerUnbindInstance(ObjectGuid guid, uint32 mapid, Difficulty difficulty, bool deleteFromDB, Player* player = nullptr); - void PlayerUnbindInstanceNotExtended(ObjectGuid guid, uint32 mapid, Difficulty difficulty, Player* player = nullptr); - InstancePlayerBind* PlayerGetBoundInstance(ObjectGuid guid, uint32 mapid, Difficulty difficulty); - bool PlayerIsPermBoundToInstance(ObjectGuid guid, uint32 mapid, Difficulty difficulty); - BoundInstancesMap const& PlayerGetBoundInstances(ObjectGuid guid, Difficulty difficulty); - void PlayerCreateBoundInstancesMaps(ObjectGuid guid); - InstanceSave* PlayerGetInstanceSave(ObjectGuid guid, uint32 mapid, Difficulty difficulty); - uint32 PlayerGetDestinationInstanceId(Player* player, uint32 mapid, Difficulty difficulty); - void CopyBinds(ObjectGuid from, ObjectGuid to, Player* toPlr = nullptr); - void UnbindAllFor(InstanceSave* save); - - void SanitizeInstanceSavedData(); - void DeleteInstanceSavedData(uint32 instanceId); + /* statistics */ + uint32 GetNumInstanceSaves() const { return uint32(m_instanceSaveById.size()); } + uint32 GetNumBoundPlayersTotal() const; + uint32 GetNumBoundGroupsTotal() const; protected: static uint16 ResetTimeDelay[]; - static PlayerBindStorage playerBindStorage; - static BoundInstancesMap emptyBoundInstancesMap; private: void _ResetOrWarnAll(uint32 mapid, Difficulty difficulty, bool warn, time_t resetTime); + void _ResetInstance(uint32 mapid, uint32 instanceId); void _ResetSave(InstanceSaveHashMap::iterator& itr); bool lock_instLists{false}; InstanceSaveHashMap m_instanceSaveById; ResetTimeByMapDifficultyMap m_resetTimeByMapDifficulty; - ResetTimeByMapDifficultyMap m_resetExtendedTimeByMapDifficulty; ResetTimeQueue m_resetTimeQueue; }; diff --git a/src/server/game/Instances/InstanceScript.cpp b/src/server/game/Instances/InstanceScript.cpp index bd622572f38cdd..ee8b9bf39d4c3b 100644 --- a/src/server/game/Instances/InstanceScript.cpp +++ b/src/server/game/Instances/InstanceScript.cpp @@ -947,7 +947,7 @@ void InstanceScript::StartChallengeMode(Player* player, KeyInfo* key, uint8 leve visitor.Visit(instance->GetObjectsStore()); // Tp back all players to begin - if (WorldSafeLocsEntry const* entranceSafeLocEntry = sObjectMgr->GetWorldSafeLoc(GetEntranceLocation())) + if (WorldSafeLocsEntry const* entranceSafeLocEntry = sObjectMgr->GetWorldSafeLoc(instance->GetId(), GetEntranceLocation())) _challengeEntranceLoc.Relocate(entranceSafeLocEntry->Loc); else if (AreaTriggerTeleport const* areaTrigger = sObjectMgr->GetMapEntranceTrigger(instance->GetId())) _challengeEntranceLoc.Relocate(areaTrigger->target_X, areaTrigger->target_Y, areaTrigger->target_Z, areaTrigger->target_Orientation); diff --git a/src/server/game/Instances/InstanceScript.h b/src/server/game/Instances/InstanceScript.h index ffb98fdcaf93f2..ab6e37adf215aa 100644 --- a/src/server/game/Instances/InstanceScript.h +++ b/src/server/game/Instances/InstanceScript.h @@ -150,7 +150,11 @@ class InstanceScript : public ZoneScript { public: InstanceScript(Map* map) : instance(map), completedEncounters(0), _entranceId(0), _temporaryEntranceId(0), - _challengeModeStarted(false), _challengeModeLevel(0), _challengeModeStartTime(0), _challengeModeDeathCount(0) {} + _challengeModeStarted(false), _challengeModeLevel(0), _challengeModeStartTime(0), _challengeModeDeathCount(0) { + + if (WorldSafeLocsEntry const* entranceSafeLocEntry = sObjectMgr->GetWorldSafeLoc(instance->GetId(), GetEntranceLocation())) + _challengeEntranceLoc.Relocate(entranceSafeLocEntry->Loc); + } ~InstanceScript() override {} diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index 783a7bf124508a..91fc02a4fb00cb 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -2918,63 +2918,24 @@ template void Map::RemoveFromMap(DynamicObject*, bool); /* ******* Dungeon Instance Maps ******* */ -InstanceMap::InstanceMap(uint32 id, uint32 InstanceId, uint8 SpawnMode, Map* _parent) +InstanceMap::InstanceMap(uint32 id, uint32 InstanceId, Difficulty SpawnMode, Map* _parent) : Map(id, InstanceId, SpawnMode, _parent), - m_resetAfterUnload(false), m_unloadWhenEmpty(false), - instance_data(nullptr), i_script_id(0) + m_resetAfterUnload(false), m_unloadWhenEmpty(false), + instance_data(nullptr), i_script_id(0) { //lets initialize visibility distance for dungeons InstanceMap::InitVisibilityDistance(); - - // the timer is started by default, and stopped when the first player joins - // this make sure it gets unloaded if for some reason no player joins - m_unloadTimer = std::max(sWorld->getIntConfig(CONFIG_INSTANCE_UNLOAD_DELAY), (uint32)MIN_UNLOAD_DELAY); - - // pussywizard: - if (IsRaid()) - if (time_t resetTime = sInstanceSaveMgr->GetResetTimeFor(id, Difficulty(SpawnMode))) - if (time_t extendedResetTime = sInstanceSaveMgr->GetExtendedResetTimeFor(id, Difficulty(SpawnMode))) - _instanceResetPeriod = extendedResetTime - resetTime; } InstanceMap::~InstanceMap() { delete instance_data; - instance_data = nullptr; - sInstanceSaveMgr->DeleteInstanceSaveIfNeeded(GetInstanceId(), true); } void InstanceMap::InitVisibilityDistance() { //init visibility distance for instances m_VisibleDistance = World::GetMaxVisibleDistanceInInstances(); - - // pussywizard: this CAN NOT exceed MAX_VISIBILITY_DISTANCE - switch (GetId()) - { - case 429: // Dire Maul - case 550: // The Eye - case 578: // The Nexus: The Oculus - m_VisibleDistance = 175.0f; - break; - case 649: // Trial of the Crusader - case 650: // Trial of the Champion - case 595: // Culling of Startholme - case 658: // Pit of Saron - m_VisibleDistance = 150.0f; - break; - case 615: // Obsidian Sanctum - case 616: // Eye of Eternity - case 603: // Ulduar - case 668: // Halls of Reflection - case 631: // Icecrown Citadel - case 724: // Ruby Sanctum - m_VisibleDistance = 200.0f; - break; - case 531: // Ahn'Qiraj Temple - m_VisibleDistance = 300.0f; - break; - } } /* @@ -3020,29 +2981,15 @@ Map::EnterState InstanceMap::CannotEnter(Player* player, bool loginCheck) return CANNOT_ENTER_UNSPECIFIED_REASON; } - // cannot enter if instance is in use by another party/soloer that have a permanent save in the same instance id - PlayerList const& playerList = GetPlayers(); - if (!playerList.IsEmpty()) - for (PlayerList::const_iterator i = playerList.begin(); i != playerList.end(); ++i) - if (Player* iPlayer = i->GetSource()) - { - if (iPlayer == player) // login case, player already added to map - continue; - if (iPlayer->IsGameMaster()) // bypass GMs - continue; - if (!player->GetGroup()) // player has not group and there is someone inside, deny entry - { - player->SendTransferAborted(GetId(), TRANSFER_ABORT_MAX_PLAYERS); - return CANNOT_ENTER_INSTANCE_BIND_MISMATCH; - } - // player inside instance has no group or his groups is different to entering player's one, deny entry - if (!iPlayer->GetGroup() || iPlayer->GetGroup() != player->GetGroup()) - { - player->SendTransferAborted(GetId(), TRANSFER_ABORT_MAX_PLAYERS); - return CANNOT_ENTER_INSTANCE_BIND_MISMATCH; - } - break; - } + // cannot enter while an encounter is in progress (unless this is a relog, in which case it is permitted) + if (!player->isBeingLoaded() && 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(), GetDifficulty())) + if (playerBind->perm && playerBind->save) + if (playerBind->save->GetInstanceId() != GetInstanceId()) + return CANNOT_ENTER_INSTANCE_BIND_MISMATCH; return Map::CannotEnter(player, loginCheck); } @@ -3059,23 +3006,29 @@ bool InstanceMap::AddPlayerToMap(Player* player) { Group* group = player->GetGroup(); + // increase current instances (hourly limit) + if (!group || !group->isLFGGroup()) + player->AddInstanceEnterTime(GetInstanceId(), time(nullptr)); + // get an instance save for the map InstanceSave* mapSave = sInstanceSaveMgr->GetInstanceSave(GetInstanceId()); if (!mapSave) { - LOG_ERROR("maps", "InstanceMap::Add: InstanceSave does not exist for map {} spawnmode {} with instance id {}", GetId(), GetSpawnMode(), GetInstanceId()); - return false; + LOG_DEBUG("maps", "InstanceMap::Add: InstanceSave does not exist for map {} spawnmode {} with instance id {}", GetId(), GetSpawnMode(), GetInstanceId()); + mapSave = sInstanceSaveMgr->AddInstanceSave(GetId(), GetInstanceId(), GetDifficulty(), 0, 0, true); } + ASSERT(mapSave); + // check for existing instance binds - InstancePlayerBind* playerBind = sInstanceSaveMgr->PlayerGetBoundInstance(player->GetGUID(), GetId(), Difficulty(GetSpawnMode())); + InstancePlayerBind* playerBind = player->GetBoundInstance(GetId(), GetDifficulty()); if (playerBind && playerBind->perm) { if (playerBind->save != mapSave) { LOG_ERROR("maps", "InstanceMap::Add: player {} ({}) is permanently bound to instance {}, {}, {}, {} but he is being put into instance {}, {}, {}, {}", - player->GetName(), player->GetGUID().ToString(), playerBind->save->GetMapId(), playerBind->save->GetInstanceId(), playerBind->save->GetDifficulty(), - playerBind->save->CanReset(), mapSave->GetMapId(), mapSave->GetInstanceId(), mapSave->GetDifficulty(), mapSave->CanReset()); + player->GetName(), player->GetGUID().ToString(), playerBind->save->GetMapId(), playerBind->save->GetInstanceId(), playerBind->save->GetDifficultyID(), + playerBind->save->CanReset(), mapSave->GetMapId(), mapSave->GetInstanceId(), mapSave->GetDifficultyID(), mapSave->CanReset()); return false; } } @@ -3086,33 +3039,62 @@ bool InstanceMap::AddPlayerToMap(Player* player) } else { - playerBind = sInstanceSaveMgr->PlayerBindToInstance(player->GetGUID(), mapSave, false, player); - // pussywizard: bind lider also if not yet bound - if (Group* g = player->GetGroup()) - if (g->GetLeaderGUID() != player->GetGUID()) - if (!sInstanceSaveMgr->PlayerGetBoundInstance(g->GetLeaderGUID(), mapSave->GetMapId(), mapSave->GetDifficulty())) + if (group) + { + // solo saves should have been reset when the map was loaded + InstanceGroupBind* groupBind = group->GetBoundInstance(this); + if (playerBind && playerBind->save != mapSave) + { + LOG_ERROR("maps", "InstanceMap::Add: player {}({}) is being put into instance {} {}, {}, {}, {}, {}, {} but he is in group {} and is bound to instance {}, {}, {}, {}, {}, {}!", player->GetName().c_str(), player->GetGUID().ToString().c_str(), GetMapName(), mapSave->GetMapId(), mapSave->GetInstanceId(), mapSave->GetDifficultyID(), mapSave->GetPlayerCount(), mapSave->GetGroupCount(), mapSave->CanReset(), group->GetLeaderGUID().ToString().c_str(), playerBind->save->GetMapId(), playerBind->save->GetInstanceId(), playerBind->save->GetDifficultyID(), playerBind->save->GetPlayerCount(), playerBind->save->GetGroupCount(), playerBind->save->CanReset()); + if (groupBind) + LOG_ERROR("maps", "InstanceMap::Add: the group is bound to the instance {} {}, {}, {}, {}, {}, {}", GetMapName(), groupBind->save->GetMapId(), groupBind->save->GetInstanceId(), groupBind->save->GetDifficultyID(), groupBind->save->GetPlayerCount(), groupBind->save->GetGroupCount(), groupBind->save->CanReset()); + //ABORT(); + return false; + } + // 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) { - sInstanceSaveMgr->PlayerCreateBoundInstancesMaps(g->GetLeaderGUID()); - sInstanceSaveMgr->PlayerBindToInstance(g->GetLeaderGUID(), mapSave, false, ObjectAccessor::FindConnectedPlayer(g->GetLeaderGUID())); + LOG_ERROR("maps", "InstanceMap::Add: player {}({}) is being put into instance {}, {}, {} but he is in group {} which is bound to instance {}, {}, {}!", player->GetName().c_str(), player->GetGUID().ToString().c_str(), mapSave->GetMapId(), mapSave->GetInstanceId(), mapSave->GetDifficultyID(), group->GetLeaderGUID().ToString().c_str(), groupBind->save->GetMapId(), groupBind->save->GetInstanceId(), groupBind->save->GetDifficultyID()); + LOG_ERROR("maps", "MapSave players: {}, group count: {}", mapSave->GetPlayerCount(), mapSave->GetGroupCount()); + if (groupBind->save) + LOG_ERROR("maps", "GroupBind save players: {}, group count: {}", groupBind->save->GetPlayerCount(), groupBind->save->GetGroupCount()); + else + LOG_ERROR("maps", "GroupBind save NULL"); + return false; } - } - - // increase current instances (hourly limit) - // xinef: specific instances are still limited - if (!group || !group->isLFGGroup() || !group->IsLfgRandomInstance()) - player->AddInstanceEnterTime(GetInstanceId(), GameTime::GetGameTime().count()); - - if (!playerBind->perm && !mapSave->CanReset() && group && !group->isLFGGroup() && !group->IsLfgRandomInstance()) - { - WorldPacket data(SMSG_INSTANCE_LOCK_WARNING_QUERY, 9); - data << uint32(60000); - data << uint32(instance_data ? instance_data->GetCompletedEncounterMask() : 0); - data << uint8(0); - player->GetSession()->SendPacket(&data); - player->SetPendingBind(mapSave->GetInstanceId(), 60000); + // if the group/leader is permanently bound to the instance + // players also become permanently bound when they enter + if (groupBind->perm) + { + WorldPacket data(SMSG_INSTANCE_LOCK_WARNING_QUERY, 9); + data << uint32(60000); + data << uint32(instance_data ? instance_data->GetCompletedEncounterMask() : 0); + data << uint8(0); + player->GetSession()->SendPacket(&data); + 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); + } } } + // for normal instances cancel the reset schedule when the + // first player enters (no players yet) + SetResetSchedule(false); + // initialize unload state m_unloadTimer = 0; m_resetAfterUnload = false; @@ -3140,6 +3122,10 @@ void InstanceMap::Update(const uint32 t_diff, const uint32 s_diff, bool /*thread void InstanceMap::RemovePlayerFromMap(Player* player, bool remove) { + //if last player set unload timer + if (!m_unloadTimer && m_mapRefMgr.getSize() == 1) + m_unloadTimer = m_unloadWhenEmpty ? MIN_UNLOAD_DELAY : std::max(sWorld->getIntConfig(CONFIG_INSTANCE_UNLOAD_DELAY), (uint32)MIN_UNLOAD_DELAY); + if (instance_data) instance_data->OnPlayerExit(player); @@ -3150,33 +3136,21 @@ void InstanceMap::RemovePlayerFromMap(Player* player, bool remove) instance_data->OnPlayerExit(player); Map::RemovePlayerFromMap(player, remove); - // If remove == true - player already deleted. - if (!remove) - player->SetPendingBind(0, 0); + // for normal instances schedule the reset after all players have left + SetResetSchedule(true); + sInstanceSaveMgr->UnloadInstanceSave(GetInstanceId()); } -void InstanceMap::AfterPlayerUnlinkFromMap() +void InstanceMap::CreateInstanceData(bool load) { - if (!m_unloadTimer && !HavePlayers()) - m_unloadTimer = m_unloadWhenEmpty ? MIN_UNLOAD_DELAY : std::max(sWorld->getIntConfig(CONFIG_INSTANCE_UNLOAD_DELAY), (uint32)MIN_UNLOAD_DELAY); - Map::AfterPlayerUnlinkFromMap(); -} - -void InstanceMap::CreateInstanceScript(bool load, std::string data, uint32 completedEncounterMask) -{ - if (instance_data) - { + if (instance_data != nullptr) return; - } bool isOtherAI = false; - sScriptMgr->OnBeforeCreateInstanceScript(this, instance_data, load, data, completedEncounterMask); - if (instance_data) isOtherAI = true; - // if Eluna AI was fetched succesfully we should not call CreateInstanceData nor set the unused scriptID if (!isOtherAI) { InstanceTemplate const* mInstance = sObjectMgr->GetInstanceTemplate(GetId()); @@ -3200,9 +3174,24 @@ void InstanceMap::CreateInstanceScript(bool load, std::string data, uint32 compl if (load) { - instance_data->SetCompletedEncountersMask(completedEncounterMask, false); - if (data != "") - instance_data->Load(data.c_str()); + /// @todo make a global storage for this + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_INSTANCE); + stmt->SetData(0, uint16(GetId())); + stmt->SetData(1, i_InstanceId); + PreparedQueryResult result = CharacterDatabase.Query(stmt); + + if (result) + { + Field* fields = result->Fetch(); + std::string data = fields[0].Get(); + instance_data->SetCompletedEncountersMask(fields[1].Get(), false); + instance_data->SetEntranceLocation(fields[2].Get()); + if (!data.empty()) + { + LOG_DEBUG("maps", "Loading instance data for `{}` with id {}", sObjectMgr->GetScriptName(i_script_id).c_str(), i_InstanceId); + instance_data->Load(data.c_str()); + } + } } } @@ -3274,23 +3263,28 @@ void InstanceMap::PermBindAllPlayers() for (MapRefMgr::iterator itr = m_mapRefMgr.begin(); itr != m_mapRefMgr.end(); ++itr) { player = itr->GetSource(); + if (player->IsGameMaster()) + continue; + group = player->GetGroup(); // players inside an instance cannot be bound to other instances // some players may already be permanently bound, in this case nothing happens - InstancePlayerBind* bind = sInstanceSaveMgr->PlayerGetBoundInstance(player->GetGUID(), save->GetMapId(), save->GetDifficulty()); - + InstancePlayerBind* bind = player->GetBoundInstance(save->GetMapId(), save->GetDifficultyID()); if (!bind || !bind->perm) { + player->BindToInstance(save, true); WorldPacket data(SMSG_INSTANCE_SAVE_CREATED, 4); data << uint32(0); player->GetSession()->SendPacket(&data); - sInstanceSaveMgr->PlayerBindToInstance(player->GetGUID(), save, true, player); - } + player->GetSession()->SendCalendarRaidLockout(save, true); + - // Xinef: Difficulty change prevention - if (group) - group->SetDifficultyChangePrevention(DIFFICULTY_PREVENTION_CHANGE_BOSS_KILLED); + // if group leader is in instance, group also gets bound + if (Group* group = player->GetGroup()) + if (group->GetLeaderGUID() == player->GetGUID()) + group->BindToInstance(save, true); + } } } @@ -3313,11 +3307,34 @@ void InstanceMap::SendResetWarnings(uint32 timeLeft) const itr->GetSource()->SendInstanceResetWarning(GetId(), itr->GetSource()->GetDifficulty(IsRaid()), timeLeft, false); } +void InstanceMap::SetResetSchedule(bool on) +{ + // only for normal instances + // the reset time is only scheduled when there are no payers inside + // it is assumed that the reset time will rarely (if ever) change while the reset is scheduled + if (IsDungeon() && !HavePlayers() && !IsRaidOrHeroicDungeon()) + { + if (InstanceSave* save = sInstanceSaveMgr->GetInstanceSave(GetInstanceId())) + sInstanceSaveMgr->ScheduleReset(on, save->GetResetTime(), InstanceSaveMgr::InstResetEvent(0, GetId(), GetDifficulty(), GetInstanceId())); + else + LOG_ERROR("maps", "InstanceMap::SetResetSchedule: cannot turn schedule {}, there is no save information for instance (map [id: {}, name: {}], instance id: {}, difficulty: {})", + on ? "on" : "off", GetId(), GetMapName(), GetInstanceId(), GetDifficulty()); + } +} + MapDifficulty const* Map::GetMapDifficulty() const { return GetMapDifficultyData(GetId(), GetDifficulty()); } +bool InstanceMap::HasPermBoundPlayers() const +{ + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PERM_BIND_BY_INSTANCE); + stmt->SetData(0, GetInstanceId()); + return !!CharacterDatabase.Query(stmt); +} + + uint32 InstanceMap::GetMaxPlayers() const { MapDifficulty const* mapDiff = GetMapDifficulty(); diff --git a/src/server/game/Maps/Map.h b/src/server/game/Maps/Map.h index e6897f89ab2062..c9bb92f93e30f1 100644 --- a/src/server/game/Maps/Map.h +++ b/src/server/game/Maps/Map.h @@ -441,7 +441,7 @@ class Map : public GridRefMgr CANNOT_ENTER_UNSPECIFIED_REASON }; - virtual EnterState CannotEnter(Player* /*player*/, bool /*loginCheck = false*/) { return CAN_ENTER; } + virtual EnterState CannotEnter(Player* /*player*/, bool loginCheck = false) { return CAN_ENTER; } [[nodiscard]] const char* GetMapName() const; @@ -833,20 +833,20 @@ enum InstanceResetMethod INSTANCE_RESET_ALL, // reset all option under portrait, resets only normal 5-mans INSTANCE_RESET_CHANGE_DIFFICULTY, // on changing difficulty INSTANCE_RESET_GLOBAL, // global id reset - INSTANCE_RESET_GROUP_JOIN, // on joining group - INSTANCE_RESET_GROUP_LEAVE // on leaving group + INSTANCE_RESET_GROUP_DISBAND, + INSTANCE_RESET_GROUP_JOIN, // on joining group + INSTANCE_RESET_RESPAWN_DELAY }; class InstanceMap : public Map { public: - InstanceMap(uint32 id, uint32 InstanceId, uint8 SpawnMode, Map* _parent); - ~InstanceMap() override; + InstanceMap(uint32 id, uint32 InstanceId, Difficulty SpawnMode, Map* _parent); + ~InstanceMap(); bool AddPlayerToMap(Player*) override; void RemovePlayerFromMap(Player*, bool) override; - void AfterPlayerUnlinkFromMap() override; void Update(const uint32, const uint32, bool thread = true) override; - void CreateInstanceScript(bool load, std::string data, uint32 completedEncounterMask); + void CreateInstanceData(bool load); bool Reset(uint8 method, GuidList* globalSkipList = nullptr); [[nodiscard]] uint32 GetScriptId() const { return i_script_id; } [[nodiscard]] std::string const& GetScriptName() const; @@ -856,9 +856,11 @@ class InstanceMap : public Map void UnloadAll() override; EnterState CannotEnter(Player* player, bool loginCheck = false) override; void SendResetWarnings(uint32 timeLeft) const; + void SetResetSchedule(bool on); - [[nodiscard]] uint32 GetMaxPlayers() const; - [[nodiscard]] uint32 GetMaxResetDelay() const; + bool HasPermBoundPlayers() const; + uint32 GetMaxPlayers() const; + uint32 GetMaxResetDelay() const; void InitVisibilityDistance() override; diff --git a/src/server/game/Maps/MapInstanced.cpp b/src/server/game/Maps/MapInstanced.cpp index b112dfad6802f7..6fda60e0c9cabf 100644 --- a/src/server/game/Maps/MapInstanced.cpp +++ b/src/server/game/Maps/MapInstanced.cpp @@ -108,12 +108,13 @@ void MapInstanced::UnloadAll() - create the instance if it's not created already - the player is not actually added to the instance (only in InstanceMap::Add) */ -Map* MapInstanced::CreateInstanceForPlayer(const uint32 mapId, Player* player) +Map* MapInstanced::CreateInstanceForPlayer(const uint32 mapId, Player* player, uint32 loginInstanceId /*= 0*/) { if (GetId() != mapId || !player) return nullptr; Map* map = nullptr; + uint32 newInstanceId = 0; if (IsBattlegroundOrArena()) { @@ -138,40 +139,49 @@ Map* MapInstanced::CreateInstanceForPlayer(const uint32 mapId, Player* player) } else { - Difficulty realdiff = player->GetDifficulty(IsRaid()); - uint32 destInstId = sInstanceSaveMgr->PlayerGetDestinationInstanceId(player, GetId(), realdiff); + InstancePlayerBind* pBind = player->GetBoundInstance(GetId(), player->GetDifficulty(GetEntry())); + InstanceSave* pSave = pBind ? pBind->save : nullptr; - if (destInstId) + if (!pBind || !pBind->perm) { - InstanceSave* pSave = sInstanceSaveMgr->GetInstanceSave(destInstId); - ASSERT(pSave); // pussywizard: must exist + if (loginInstanceId) // if the player has a saved instance id on login, we either use this instance or relocate him out (return null) + { + map = FindInstanceMap(loginInstanceId); + return (map && map->GetId() == GetId()) ? map : nullptr; // is this check necessary? or does MapInstanced only find instances of itself? + } - map = FindInstanceMap(destInstId); - if (!map) - map = CreateInstance(destInstId, pSave, realdiff); - else if (IsSharedDifficultyMap(mapId) && !map->HavePlayers() && map->GetDifficulty() != realdiff) + 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) { - if (player->isBeingLoaded()) // pussywizard: crashfix (assert(passengers.empty) fail in ~transport), could be added to a transport during loading from db - return nullptr; - - if (!map->AllTransportsEmpty()) - map->AllTransportsRemovePassengers(); // pussywizard: gameobjects / summons (assert(passengers.empty) fail in ~transport) - - for (InstancedMaps::iterator i = m_InstancedMaps.begin(); i != m_InstancedMaps.end(); ++i) - if (i->first == destInstId) - { - DestroyInstance(i); - map = CreateInstance(destInstId, pSave, realdiff); - break; - } + groupBind = group->GetBoundInstance(this); + if (groupBind) + { + // solo saves should be reset when entering a group's instance + player->UnbindInstance(GetId(), player->GetDifficulty(GetEntry())); + pSave = groupBind->save; + } } } + if (pSave) + { + // solo/perm/group + newInstanceId = pSave->GetInstanceId(); + map = FindInstanceMap(newInstanceId); + // it is possible that the save exists but the map doesn't + if (!map) + map = CreateInstance(newInstanceId, pSave, pSave->GetDifficultyID()); + } else { uint32 newInstanceId = sMapMgr->GenerateInstanceId(); - ASSERT(!FindInstanceMap(newInstanceId)); // pussywizard: instance with new id can't exist - Difficulty diff = player->GetGroup() ? player->GetGroup()->GetDifficulty(IsRaid()) : player->GetDifficulty(IsRaid()); - map = CreateInstance(newInstanceId, nullptr, diff); + Difficulty diff = player->GetGroup() ? player->GetGroup()->GetDifficultyID(GetEntry()) : player->GetDifficulty(GetEntry()); + //Seems it is now possible, but I do not know if it should be allowed + //ASSERT(!FindInstanceMap(NewInstanceId)); + map = FindInstanceMap(newInstanceId); + if (!map) + map = CreateInstance(newInstanceId, nullptr, diff); } } @@ -208,13 +218,8 @@ InstanceMap* MapInstanced::CreateInstance(uint32 InstanceId, InstanceSave* save, map->LoadRespawnTimes(); map->LoadCorpseData(); - if (save) - map->CreateInstanceScript(true, save->GetInstanceData(), save->GetCompletedEncounterMask()); - else - map->CreateInstanceScript(false, "", 0); - - if (!save) // this is for sure a dungeon (assert above), no need to check here - sInstanceSaveMgr->AddInstanceSave(GetId(), InstanceId, difficulty); + bool load_data = save != nullptr; + map->CreateInstanceData(load_data); m_InstancedMaps[InstanceId] = map; return map; diff --git a/src/server/game/Maps/MapInstanced.h b/src/server/game/Maps/MapInstanced.h index f08c5687acbd4e..4bdf6a74ecb0ac 100644 --- a/src/server/game/Maps/MapInstanced.h +++ b/src/server/game/Maps/MapInstanced.h @@ -38,7 +38,7 @@ class MapInstanced : public Map void UnloadAll() override; EnterState CannotEnter(Player* player, bool loginCheck = false) override; - Map* CreateInstanceForPlayer(const uint32 mapId, Player* player); + Map* CreateInstanceForPlayer(const uint32 mapId, Player* player, uint32 loginInstanceId); Map* FindInstanceMap(uint32 instanceId) const { InstancedMaps::const_iterator i = m_InstancedMaps.find(instanceId); diff --git a/src/server/game/Maps/MapMgr.cpp b/src/server/game/Maps/MapMgr.cpp index 9e89d64e219daa..f65163624bfa35 100644 --- a/src/server/game/Maps/MapMgr.cpp +++ b/src/server/game/Maps/MapMgr.cpp @@ -105,12 +105,12 @@ Map* MapMgr::FindBaseNonInstanceMap(uint32 mapId) const return map; } -Map* MapMgr::CreateMap(uint32 id, Player* player) +Map* MapMgr::CreateMap(uint32 id, Player* player, uint32 loginInstanceId) { Map* m = CreateBaseMap(id); if (m && m->Instanceable()) - m = ((MapInstanced*)m)->CreateInstanceForPlayer(id, player); + m = ((MapInstanced*)m)->CreateInstanceForPlayer(id, player, loginInstanceId); return m; } @@ -213,12 +213,12 @@ Map::EnterState MapMgr::PlayerCannotEnter(uint32 mapid, Player* player, bool log } // if map exists - check for being full, etc. - if (!loginCheck) // for login this is done by the calling function + if (!loginCheck && group) // for login this is done by the calling function { - uint32 destInstId = sInstanceSaveMgr->PlayerGetDestinationInstanceId(player, mapid, targetDifficulty); - if (destInstId) - if (Map* boundMap = sMapMgr->FindMap(mapid, destInstId)) - if (Map::EnterState denyReason = boundMap->CannotEnter(player, loginCheck)) + InstanceGroupBind* boundInstance = group->GetBoundInstance(entry); + if (boundInstance && boundInstance->save) + if (Map* boundMap = sMapMgr->FindMap(mapid, boundInstance->save->GetInstanceId())) + if (Map::EnterState denyReason = boundMap->CannotEnter(player)) return denyReason; } @@ -226,7 +226,7 @@ Map::EnterState MapMgr::PlayerCannotEnter(uint32 mapid, Player* player, bool log if (entry->IsDungeon() && (!group || !group->isLFGGroup() || !group->IsLfgRandomInstance())) { uint32 instaceIdToCheck = 0; - if (InstanceSave* save = sInstanceSaveMgr->PlayerGetInstanceSave(player->GetGUID(), mapid, player->GetDifficulty(entry->IsRaid()))) + if (InstanceSave* save = player->GetInstanceSave(mapid)) instaceIdToCheck = save->GetInstanceId(); // instaceIdToCheck can be 0 if save not found - means no bind so the instance is new @@ -382,37 +382,55 @@ void MapMgr::InitInstanceIds() { _nextInstanceId = 1; - QueryResult result = CharacterDatabase.Query("SELECT MAX(id) FROM instance"); - if (result) - { - uint32 maxId = (*result)[0].Get(); - _instanceIds.resize(maxId + 1); - } + if (QueryResult result = CharacterDatabase.Query("SELECT IFNULL(MAX(id), 0) FROM instance")) + _freeInstanceIds.resize((*result)[0].Get() + 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); + + // never allow 0 id + _freeInstanceIds[0] = false; } void MapMgr::RegisterInstanceId(uint32 instanceId) { - // Allocation was done in InitInstanceIds() - _instanceIds[instanceId] = true; + // Allocation and sizing was done in InitInstanceIds() + _freeInstanceIds[instanceId] = false; - // Instances are pulled in ascending order from db and _nextInstanceId is initialized with 1, - // so if the instance id is used, increment + // Instances are pulled in ascending order from db and nextInstanceId is initialized with 1, + // so if the instance id is used, increment until we find the first unused one for a potential new instance if (_nextInstanceId == instanceId) ++_nextInstanceId; } uint32 MapMgr::GenerateInstanceId() { - uint32 newInstanceId = _nextInstanceId; - - // find the lowest available id starting from the current _nextInstanceId - while (_nextInstanceId < 0xFFFFFFFF && ++_nextInstanceId < _instanceIds.size() && _instanceIds[_nextInstanceId]); - if (_nextInstanceId == 0xFFFFFFFF) { - LOG_ERROR("server.worldserver", "Instance ID overflow!! Can't continue, shutting down server. "); + LOG_ERROR("maps", "Instance ID overflow!! Can't continue, shutting down server. "); World::StopNow(ERROR_EXIT_CODE); + return _nextInstanceId; + } + + uint32 newInstanceId = _nextInstanceId; + ASSERT(newInstanceId < _freeInstanceIds.size()); + _freeInstanceIds[newInstanceId] = false; + + // Find the lowest available id starting from the current NextInstanceId (which should be the lowest according to the logic in FreeInstanceId() + size_t nextFreedId = _freeInstanceIds.find_next(_nextInstanceId++); + if (nextFreedId == InstanceIds::npos) + { + _nextInstanceId = uint32(_freeInstanceIds.size()); + _freeInstanceIds.push_back(true); } + else + _nextInstanceId = uint32(nextFreedId); return newInstanceId; } + +void MapMgr::FreeInstanceId(uint32 instanceId) +{ + // If freed instance id is lower than the next id available for new instances, use the freed one instead + _nextInstanceId = std::min(instanceId, _nextInstanceId); + _freeInstanceIds[instanceId] = true; +} diff --git a/src/server/game/Maps/MapMgr.h b/src/server/game/Maps/MapMgr.h index d4720c3a2d5f3f..c76d991f634ade 100644 --- a/src/server/game/Maps/MapMgr.h +++ b/src/server/game/Maps/MapMgr.h @@ -25,6 +25,7 @@ #include "MapUpdater.h" #include "Object.h" +#include #include class Transport; @@ -39,7 +40,7 @@ class MapMgr Map* CreateBaseMap(uint32 mapId); Map* FindBaseNonInstanceMap(uint32 mapId) const; - Map* CreateMap(uint32 mapId, Player* player); + Map* CreateMap(uint32 id, Player* player, uint32 loginInstanceId = 0); Map* FindMap(uint32 mapId, uint32 instanceId) const; Map* FindBaseMap(uint32 mapId) const // pussywizard: need this public for movemaps (mmaps) @@ -128,15 +129,6 @@ class MapMgr return std::fmod(o, 2.0f * static_cast(M_PI)); } - /** - * @name GetInstanceIDs - * @return vector of instance IDs - */ - std::vector GetInstanceIDs() - { - return _instanceIds; - } - void DoDelayedMovesAndRemoves(); Map::EnterState PlayerCannotEnter(uint32 mapid, Player* player, bool loginCheck = false); @@ -148,8 +140,9 @@ class MapMgr // Instance ID management void InitInstanceIds(); - void RegisterInstanceId(uint32 instanceId); uint32 GenerateInstanceId(); + void RegisterInstanceId(uint32 instanceId); + void FreeInstanceId(uint32 instanceId); MapUpdater* GetMapUpdater() { return &m_updater; } @@ -161,7 +154,7 @@ class MapMgr typedef std::unordered_map MapMapType; private: - typedef std::vector InstanceIds; + typedef boost::dynamic_bitset InstanceIds; MapMgr(); ~MapMgr(); @@ -174,7 +167,7 @@ class MapMgr IntervalTimer i_timer[4]; // continents, bgs/arenas, instances, total from the beginning uint8 mapUpdateStep; - InstanceIds _instanceIds; + InstanceIds _freeInstanceIds; uint32 _nextInstanceId; MapUpdater m_updater; }; diff --git a/src/server/game/Scripting/ScriptDefines/PlayerScript.cpp b/src/server/game/Scripting/ScriptDefines/PlayerScript.cpp index 602710c2eb196d..8ce18ab5740670 100644 --- a/src/server/game/Scripting/ScriptDefines/PlayerScript.cpp +++ b/src/server/game/Scripting/ScriptDefines/PlayerScript.cpp @@ -409,11 +409,11 @@ void ScriptMgr::OnPlayerFailedDelete(ObjectGuid guid, uint32 accountId) }); } -void ScriptMgr::OnPlayerBindToInstance(Player* player, Difficulty difficulty, uint32 mapid, bool permanent) +void ScriptMgr::OnPlayerBindToInstance(Player* player, Difficulty difficulty, uint32 mapid, bool permanent, uint8 extendState) { ExecuteScript([&](PlayerScript* script) { - script->OnBindToInstance(player, difficulty, mapid, permanent); + script->OnBindToInstance(player, difficulty, mapid, permanent, extendState); }); } diff --git a/src/server/game/Scripting/ScriptDefines/PlayerScript.h b/src/server/game/Scripting/ScriptDefines/PlayerScript.h index f41573788650d8..8b4ac6577ece9f 100644 --- a/src/server/game/Scripting/ScriptDefines/PlayerScript.h +++ b/src/server/game/Scripting/ScriptDefines/PlayerScript.h @@ -149,7 +149,7 @@ class PlayerScript : public ScriptObject virtual void OnSave(Player* /*player*/) { } // Called when a player is bound to an instance - virtual void OnBindToInstance(Player* /*player*/, Difficulty /*difficulty*/, uint32 /*mapId*/, bool /*permanent*/) { } + virtual void OnBindToInstance(Player* /*player*/, Difficulty /*difficulty*/, uint32 /*mapId*/, bool /*permanent*/, uint8) { } // Called when a player switches to a new zone virtual void OnUpdateZone(Player* /*player*/, uint32 /*newZone*/, uint32 /*newArea*/) { } diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index 55d653d51579d4..4ef32d45fac26f 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -336,7 +336,7 @@ class ScriptMgr void OnPlayerSave(Player* player); void OnPlayerDelete(ObjectGuid guid, uint32 accountId); void OnPlayerFailedDelete(ObjectGuid guid, uint32 accountId); - void OnPlayerBindToInstance(Player* player, Difficulty difficulty, uint32 mapid, bool permanent); + void OnPlayerBindToInstance(Player* player, Difficulty difficulty, uint32 mapid, bool permanent, uint8 extendState); void OnPlayerUpdateZone(Player* player, uint32 newZone, uint32 newArea); void OnPlayerUpdateArea(Player* player, uint32 oldArea, uint32 newArea); bool OnBeforePlayerTeleport(Player* player, uint32 mapid, float x, float y, float z, float orientation, uint32 options, Unit* target); diff --git a/src/server/game/Spells/Auras/SpellAuras.cpp b/src/server/game/Spells/Auras/SpellAuras.cpp index bf4ac611328c72..903701a3a850d1 100644 --- a/src/server/game/Spells/Auras/SpellAuras.cpp +++ b/src/server/game/Spells/Auras/SpellAuras.cpp @@ -2027,24 +2027,6 @@ void Aura::HandleAuraSpecificMods(AuraApplication const* aurApp, Unit* caster, b } break; } - - if (apply) - { - Unit::AuraEffectList aurEffects; - - if (caster == target) - aurEffects = caster->GetAuraEffectsByType(SPELL_AURA_MOD_TRIGGER_SPELL_ON_STACKS_ON_SELF); - else - aurEffects = caster->GetAuraEffectsByType(SPELL_AURA_MOD_TRIGGER_SPELL_ON_STACKS_ON_TARGET); - - for (auto aurEff : aurEffects) - { - auto eff = aurEff->GetSpellInfo()->GetEffect(SpellEffIndex(aurEff->GetEffIndex())); - - if (aurApp->GetBase()->GetId() == eff.MiscValue) - ProcessTriggerSpellOnStacks(aurApp->GetBase(), eff.MiscValueB, eff.TriggerSpell, eff.TargetA.GetTarget(), eff.Amplitude, caster, aurEff->GetBase()->GetEffect(aurEff->GetEffIndex())); - } - } } void Aura::ProcessTriggerSpellOnStacks(Aura* aurApp, int32 stackCount, int32 triggerSpell, Targets triggerSpellTarget, uint32 amplitude, Unit* caster, AuraEffect* const triggeringEffect) diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 651e30efcc6de9..7d5dbcb45d9b5a 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1663,9 +1663,6 @@ void World::SetInitialWorldSettings() LOG_INFO("server.loading", "Loading Instance Template..."); sObjectMgr->LoadInstanceTemplate(); - LOG_INFO("server.loading", "Loading Instance Saved Gameobject State Data..."); - sObjectMgr->LoadInstanceSavedGameobjectStateData(); - LOG_INFO("server.loading", "Loading Character Cache..."); sCharacterCache->LoadCharacterCacheStorage(); @@ -1913,6 +1910,9 @@ void World::SetInitialWorldSettings() LOG_INFO("server.loading", "Loading Pet Name Parts..."); sObjectMgr->LoadPetNames(); + LOG_INFO("server.loading", "Loading world safe locations..."); + sObjectMgr->LoadWorldSafeLocs(); + LOG_INFO("server.loading", "Loading Jump Charge Params..."); sObjectMgr->LoadJumpChargeParams(); diff --git a/src/server/scripts/Commands/cs_instance.cpp b/src/server/scripts/Commands/cs_instance.cpp index f1dca661ce10a8..924bebc23d08be 100644 --- a/src/server/scripts/Commands/cs_instance.cpp +++ b/src/server/scripts/Commands/cs_instance.cpp @@ -70,19 +70,39 @@ class instance_commandscript : public CommandScript for (uint8 i = 0; i < MAX_DIFFICULTY; ++i) { - for (auto const& [mapId, bind] : sInstanceSaveMgr->PlayerGetBoundInstances(player->GetGUID(), Difficulty(i))) + auto binds = player->GetBoundInstances(Difficulty(i)); + if (binds != player->m_boundInstances.end()) { - InstanceSave const* save = bind.save; - uint32 resetTime = bind.extended ? save->GetExtendedResetTime() : save->GetResetTime(); - uint32 ttr = (resetTime >= GameTime::GetGameTime().count() ? resetTime - GameTime::GetGameTime().count() : 0); - std::string timeleft = secsToTimeString(ttr); - handler->PSendSysMessage("map: %d, inst: %d, perm: %s, diff: %d, canReset: %s, TTR: %s%s", - mapId, save->GetInstanceId(), bind.perm ? "yes" : "no", save->GetDifficulty(), save->CanReset() ? "yes" : "no", timeleft.c_str(), (bind.extended ? " (extended)" : "")); - counter++; + for (auto itr = binds->second.begin(); itr != binds->second.end(); ++itr) + { + InstanceSave* save = itr->second.save; + std::string timeleft = secsToTimeString(save->GetResetTime() - time(nullptr)); + handler->PSendSysMessage(LANG_COMMAND_LIST_BIND_INFO, itr->first, save->GetInstanceId(), itr->second.perm ? "yes" : "no", itr->second.extendState == EXTEND_STATE_EXPIRED ? "expired" : itr->second.extendState == EXTEND_STATE_EXTENDED ? "yes" : "no", save->GetDifficultyID(), save->CanReset() ? "yes" : "no", timeleft.c_str()); + counter++; + } } } + handler->PSendSysMessage(LANG_COMMAND_LIST_BIND_PLAYER_BINDS, counter); - handler->PSendSysMessage("player binds: %d", counter); + counter = 0; + if (Group* group = player->GetGroup()) + { + for (uint8 i = 0; i < MAX_DIFFICULTY; ++i) + { + auto binds = group->GetBoundInstances(Difficulty(i)); + if (binds != group->GetBoundInstanceEnd()) + { + for (auto itr = binds->second.begin(); itr != binds->second.end(); ++itr) + { + InstanceSave* save = itr->second.save; + std::string timeleft = secsToTimeString(save->GetResetTime() - time(nullptr)); + handler->PSendSysMessage(LANG_COMMAND_LIST_BIND_INFO, itr->first, save->GetInstanceId(), itr->second.perm ? "yes" : "no", "-", save->GetDifficultyID(), save->CanReset() ? "yes" : "no", timeleft.c_str()); + counter++; + } + } + } + } + handler->PSendSysMessage(LANG_COMMAND_LIST_BIND_GROUP_BINDS, counter); return true; } @@ -105,22 +125,22 @@ class instance_commandscript : public CommandScript for (uint8 i = 0; i < MAX_DIFFICULTY; ++i) { - BoundInstancesMap const& m_boundInstances = sInstanceSaveMgr->PlayerGetBoundInstances(player->GetGUID(), Difficulty(i)); - for (BoundInstancesMap::const_iterator itr = m_boundInstances.begin(); itr != m_boundInstances.end();) + auto binds = player->GetBoundInstances(Difficulty(i)); + if (binds != player->m_boundInstances.end()) { - InstanceSave const* save = itr->second.save; - if (itr->first != player->GetMapId() && (!mapId || mapId == itr->first) && (!difficultyArg || difficultyArg == save->GetDifficulty())) + for (auto itr = binds->second.begin(); itr != binds->second.end();) { - uint32 resetTime = itr->second.extended ? save->GetExtendedResetTime() : save->GetResetTime(); - uint32 ttr = (resetTime >= GameTime::GetGameTime().count() ? resetTime - GameTime::GetGameTime().count() : 0); - std::string timeleft = secsToTimeString(ttr); - handler->PSendSysMessage("unbinding map: %d, inst: %d, perm: %s, diff: %d, canReset: %s, TTR: %s%s", itr->first, save->GetInstanceId(), itr->second.perm ? "yes" : "no", save->GetDifficulty(), save->CanReset() ? "yes" : "no", timeleft.c_str(), (itr->second.extended ? " (extended)" : "")); - sInstanceSaveMgr->PlayerUnbindInstance(player->GetGUID(), itr->first, Difficulty(i), true, player); - itr = m_boundInstances.begin(); - counter++; + InstanceSave const* save = itr->second.save; + if (itr->first != player->GetMapId() && (!mapId || mapId == itr->first) && (!difficultyArg || difficultyArg == save->GetDifficultyID())) + { + std::string timeleft = secsToTimeString(save->GetResetTime() - time(nullptr)); + handler->PSendSysMessage(LANG_COMMAND_INST_UNBIND_UNBINDING, itr->first, save->GetInstanceId(), itr->second.perm ? "yes" : "no", save->GetDifficultyID(), save->CanReset() ? "yes" : "no", timeleft.c_str()); + player->UnbindInstance(itr, binds); + counter++; + } + else + ++itr; } - else - ++itr; } } diff --git a/src/server/scripts/Commands/cs_misc.cpp b/src/server/scripts/Commands/cs_misc.cpp index b3243292f7d7e7..71b210016e327b 100644 --- a/src/server/scripts/Commands/cs_misc.cpp +++ b/src/server/scripts/Commands/cs_misc.cpp @@ -761,13 +761,15 @@ class misc_commandscript : public CommandScript } // if the GM is bound to another instance, he will not be bound to another one - InstancePlayerBind* bind = sInstanceSaveMgr->PlayerGetBoundInstance(_player->GetGUID(), targetPlayer->GetMapId(), targetPlayer->GetDifficulty(map->IsRaid())); + InstancePlayerBind* bind = _player->GetBoundInstance(targetPlayer->GetMapId(), targetPlayer->GetDifficulty(map->IsRaid())); if (!bind) { - if (InstanceSave* save = sInstanceSaveMgr->GetInstanceSave(target->GetConnectedPlayer()->GetInstanceId())) - { - sInstanceSaveMgr->PlayerBindToInstance(_player->GetGUID(), save, !save->CanReset(), _player); - } + Group* group = _player->GetGroup(); + // if no bind exists, create a solo bind + InstanceGroupBind* gBind = group ? group->GetBoundInstance(_player) : nullptr; // if no bind exists, create a solo bind + if (!gBind) + if (InstanceSave* save = sInstanceSaveMgr->GetInstanceSave(_player->GetInstanceId())) + _player->BindToInstance(save, !save->CanReset()); } if (map->IsRaid()) @@ -898,7 +900,7 @@ class misc_commandscript : public CommandScript if (destMap->Instanceable() && destMap->GetInstanceId() != map->GetInstanceId()) { - sInstanceSaveMgr->PlayerUnbindInstance(target->GetGUID(), map->GetInstanceId(), targetPlayer->GetDungeonDifficulty(), true, targetPlayer); + targetPlayer->UnbindInstance(map->GetInstanceId(), targetPlayer->GetDungeonDifficulty(), true); } // we are in an instance, and can only summon players in our group with us as leader diff --git a/src/server/scripts/EasternKingdoms/Deadmines/instance_deadmines.cpp b/src/server/scripts/EasternKingdoms/Deadmines/instance_deadmines.cpp index 3f099e4994a86c..dd6b0956a29ea5 100644 --- a/src/server/scripts/EasternKingdoms/Deadmines/instance_deadmines.cpp +++ b/src/server/scripts/EasternKingdoms/Deadmines/instance_deadmines.cpp @@ -36,34 +36,6 @@ class instance_deadmines : public InstanceMapScript memset(&_encounters, 0, sizeof(_encounters)); } - void OnGameObjectCreate(GameObject* gameobject) override - { - switch (gameobject->GetEntry()) - { - case GO_HEAVY_DOOR_1: - case GO_HEAVY_DOOR_2: - case GO_DOOR_LEVER_1: - case GO_DOOR_LEVER_2: - case GO_DOOR_LEVER_3: - case GO_CANNON: - gameobject->UpdateSaveToDb(true); - break; - case GO_FACTORY_DOOR: - gameobject->UpdateSaveToDb(true); - // GoState (Door opened) is restored during GO creation, but we need to set LootState to prevent Lever from closing it again - if (_encounters[TYPE_RHAHK_ZOR] == DONE) - gameobject->SetLootState(GO_ACTIVATED); - break; - case GO_IRON_CLAD_DOOR: - gameobject->UpdateSaveToDb(true); - if (gameobject->GetStateSavedOnInstance() == GO_STATE_ACTIVE) - { - gameobject->DespawnOrUnsummon(); - } - break; - } - } - void SetData(uint32 type, uint32 data) override { switch (type) diff --git a/src/server/scripts/EasternKingdoms/Gnomeregan/instance_gnomeregan.cpp b/src/server/scripts/EasternKingdoms/Gnomeregan/instance_gnomeregan.cpp index 6302f44b5384c9..348790628e1783 100644 --- a/src/server/scripts/EasternKingdoms/Gnomeregan/instance_gnomeregan.cpp +++ b/src/server/scripts/EasternKingdoms/Gnomeregan/instance_gnomeregan.cpp @@ -54,19 +54,6 @@ class instance_gnomeregan : public InstanceMapScript } } - void OnGameObjectCreate(GameObject* gameobject) override - { - switch (gameobject->GetEntry()) - { - case GO_CAVE_IN_1: - case GO_CAVE_IN_2: - case GO_WORKSHOP_DOOR: - case GO_FINAL_CHAMBER_DOOR: - gameobject->UpdateSaveToDb(true); - break; - } - } - void SetData(uint32 type, uint32 data) override { switch (type) diff --git a/src/server/scripts/EasternKingdoms/Scholomance/instance_scholomance.cpp b/src/server/scripts/EasternKingdoms/Scholomance/instance_scholomance.cpp index de3dc2ea83064a..9473c64bbc0519 100644 --- a/src/server/scripts/EasternKingdoms/Scholomance/instance_scholomance.cpp +++ b/src/server/scripts/EasternKingdoms/Scholomance/instance_scholomance.cpp @@ -64,9 +64,6 @@ class instance_scholomance : public InstanceMapScript case GO_GATE_KIRTONOS: GateKirtonosGUID = go->GetGUID(); break; - case GO_DOOR_OPENED_WITH_KEY: - go->UpdateSaveToDb(true); - break; case GO_GATE_GANDLING_DOWN_NORTH: GandlingGatesGUID[0] = go->GetGUID(); break; diff --git a/src/server/scripts/EasternKingdoms/Stratholme/instance_stratholme.cpp b/src/server/scripts/EasternKingdoms/Stratholme/instance_stratholme.cpp index 910e648d300a48..25aff14b7180d0 100644 --- a/src/server/scripts/EasternKingdoms/Stratholme/instance_stratholme.cpp +++ b/src/server/scripts/EasternKingdoms/Stratholme/instance_stratholme.cpp @@ -169,83 +169,6 @@ class instance_stratholme : public InstanceMapScript } } - void OnGameObjectCreate(GameObject* go) override - { - switch (go->GetEntry()) - { - case GO_CRUSADER_SQUARE_DOOR: - case GO_HOARD_DOOR: - case GO_HALL_OF_HIGH_COMMAND: - case GO_GAUNTLET_DOOR_1: - case GO_GAUNTLET_DOOR_2: - go->UpdateSaveToDb(true); - break; - case GO_ZIGGURAT_DOORS1: - go->UpdateSaveToDb(true); - _zigguratDoorsGUID1 = go->GetGUID(); - if (GetData(TYPE_ZIGGURAT1) >= 1) - go->SetGoState(GO_STATE_ACTIVE); - break; - case GO_ZIGGURAT_DOORS2: - go->UpdateSaveToDb(true); - _zigguratDoorsGUID2 = go->GetGUID(); - if (GetData(TYPE_ZIGGURAT2) >= 1) - go->SetGoState(GO_STATE_ACTIVE); - break; - case GO_ZIGGURAT_DOORS3: - go->UpdateSaveToDb(true); - _zigguratDoorsGUID3 = go->GetGUID(); - if (GetData(TYPE_ZIGGURAT3) >= 1) - go->SetGoState(GO_STATE_ACTIVE); - break; - case GO_GAUNTLET_GATE: - go->UpdateSaveToDb(true); - _gauntletGateGUID = go->GetGUID(); - if (_zigguratState1 == 2 && _zigguratState2 == 2 && _zigguratState3 == 2) - go->SetGoState(GO_STATE_ACTIVE); - break; - case GO_SLAUGTHER_GATE: - go->UpdateSaveToDb(true); - _slaughterGateGUID = go->GetGUID(); - if (_zigguratState1 == 2 && _zigguratState2 == 2 && _zigguratState3 == 2) - go->SetGoState(GO_STATE_ACTIVE); - break; - case GO_ZIGGURAT_DOORS4: - go->UpdateSaveToDb(true); - _zigguratDoorsGUID4 = go->GetGUID(); - if (_slaughterProgress == 4) - go->SetGoState(GO_STATE_ACTIVE); - break; - case GO_ZIGGURAT_DOORS5: - go->UpdateSaveToDb(true); - _zigguratDoorsGUID5 = go->GetGUID(); - if (_slaughterProgress == 4) - go->SetGoState(GO_STATE_ACTIVE); - break; - case GO_SLAUGHTER_GATE_SIDE: - go->UpdateSaveToDb(true); - if (_slaughterProgress >= 2) - go->SetGoState(GO_STATE_ACTIVE); - break; - case GO_PORT_TRAP_GATE_1: - go->UpdateSaveToDb(true); - _trapGatesGUIDs[0] = go->GetGUID(); - break; - case GO_PORT_TRAP_GATE_2: - go->UpdateSaveToDb(true); - _trapGatesGUIDs[1] = go->GetGUID(); - break; - case GO_PORT_TRAP_GATE_3: - go->UpdateSaveToDb(true); - _trapGatesGUIDs[2] = go->GetGUID(); - break; - case GO_PORT_TRAP_GATE_4: - go->UpdateSaveToDb(true); - _trapGatesGUIDs[3] = go->GetGUID(); - break; - } - } - void CheckZiggurats() { if (_zigguratState1 == 2 && _zigguratState2 == 2 && _zigguratState3 == 2)