From 6102a7937ddb0cf145c72fe056fc5ac4b4c3b1b5 Mon Sep 17 00:00:00 2001 From: treeston Date: Mon, 25 Jan 2016 01:03:56 +0100 Subject: [PATCH] Maps/Instances: Implement handling of CMSG_SET_SAVED_INSTANCE_EXTEND --- sql/base/characters_database.sql | 3 +- .../characters/2016_01_24_instanceextend.sql | 2 + .../world/2016_01_25_instanceextend.sql | 6 ++ .../Implementation/CharacterDatabase.cpp | 12 ++- .../Implementation/CharacterDatabase.h | 6 +- src/server/game/Entities/Player/Player.cpp | 57 ++++++---- src/server/game/Entities/Player/Player.h | 25 +++-- src/server/game/Groups/Group.cpp | 3 +- src/server/game/Handlers/CalendarHandler.cpp | 15 +++ src/server/game/Instances/InstanceSaveMgr.cpp | 102 +++++++++++++----- src/server/game/Instances/InstanceSaveMgr.h | 2 + src/server/game/Maps/Map.cpp | 34 ++++-- src/server/game/Maps/Map.h | 3 + src/server/game/Scripting/ScriptMgr.cpp | 4 +- src/server/game/Scripting/ScriptMgr.h | 4 +- src/server/scripts/Commands/cs_debug.cpp | 29 ++++- src/server/scripts/Commands/cs_instance.cpp | 4 +- 17 files changed, 233 insertions(+), 78 deletions(-) create mode 100644 sql/updates/characters/2016_01_24_instanceextend.sql create mode 100644 sql/updates/world/2016_01_25_instanceextend.sql diff --git a/sql/base/characters_database.sql b/sql/base/characters_database.sql index 69c6c990e1e6e..19898fa39e18f 100644 --- a/sql/base/characters_database.sql +++ b/sql/base/characters_database.sql @@ -783,6 +783,7 @@ CREATE TABLE `character_instance` ( `guid` int(10) unsigned NOT NULL DEFAULT '0', `instance` int(10) unsigned NOT NULL DEFAULT '0', `permanent` tinyint(3) unsigned NOT NULL DEFAULT '0', + `extendState` TINYINT(2) UNSIGNED NOT NULL DEFAULT '1', PRIMARY KEY (`guid`,`instance`), KEY `instance` (`instance`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -2539,7 +2540,7 @@ CREATE TABLE `updates` ( LOCK TABLES `updates` WRITE; /*!40000 ALTER TABLE `updates` DISABLE KEYS */; -INSERT INTO `updates` VALUES ('2015_03_20_00_characters.sql','B761760804EA73BD297F296C5C1919687DF7191C','ARCHIVED','2015-03-21 21:44:15',0),('2015_03_20_01_characters.sql','894F08B70449A5481FFAF394EE5571D7FC4D8A3A','ARCHIVED','2015-03-21 21:44:15',0),('2015_03_20_02_characters.sql','97D7BE0CAADC79F3F11B9FD296B8C6CD40FE593B','ARCHIVED','2015-03-21 21:44:51',0),('2015_06_26_00_characters_335.sql','C2CC6E50AFA1ACCBEBF77CC519AAEB09F3BBAEBC','ARCHIVED','2015-07-13 23:49:22',0),('2015_09_28_00_characters_335.sql','F8682A431D50E54BDC4AC0E7DBED21AE8AAB6AD4','ARCHIVED','2015-09-28 21:00:00',0),('2015_08_26_00_characters_335.sql','C7D6A3A00FECA3EBFF1E71744CA40D3076582374','ARCHIVED','2015-08-26 21:00:00',0),('2015_10_06_00_characters.sql','16842FDD7E8547F2260D3312F53EFF8761EFAB35','ARCHIVED','2015-10-06 16:06:38',0),('2015_10_07_00_characters.sql','E15AB463CEBE321001D7BFDEA4B662FF618728FD','ARCHIVED','2015-10-07 23:32:00',0),('2015_10_12_00_characters.sql','D6F9927BDED72AD0A81D6EC2C6500CBC34A39FA2','ARCHIVED','2015-10-12 15:35:47',0),('2015_10_28_00_characters.sql','622A9CA8FCE690429EBE23BA071A37C7A007BF8B','ARCHIVED','2015-10-19 14:32:22',0),('2015_10_29_00_characters_335.sql','4555A7F35C107E54C13D74D20F141039ED42943E','ARCHIVED','2015-10-29 17:05:43',0),('2015_11_03_00_characters.sql','CC045717B8FDD9733351E52A5302560CD08AAD57','ARCHIVED','2015-10-12 15:23:33',0),('2015_11_07_00_characters.sql','BAF9F6B8F97A30D04BDBBA8127A62A1720F9B904','RELEASED','2015-11-07 15:40:47',0); +INSERT INTO `updates` VALUES ('2015_03_20_00_characters.sql','B761760804EA73BD297F296C5C1919687DF7191C','ARCHIVED','2015-03-21 21:44:15',0),('2015_03_20_01_characters.sql','894F08B70449A5481FFAF394EE5571D7FC4D8A3A','ARCHIVED','2015-03-21 21:44:15',0),('2015_03_20_02_characters.sql','97D7BE0CAADC79F3F11B9FD296B8C6CD40FE593B','ARCHIVED','2015-03-21 21:44:51',0),('2015_06_26_00_characters_335.sql','C2CC6E50AFA1ACCBEBF77CC519AAEB09F3BBAEBC','ARCHIVED','2015-07-13 23:49:22',0),('2015_09_28_00_characters_335.sql','F8682A431D50E54BDC4AC0E7DBED21AE8AAB6AD4','ARCHIVED','2015-09-28 21:00:00',0),('2015_08_26_00_characters_335.sql','C7D6A3A00FECA3EBFF1E71744CA40D3076582374','ARCHIVED','2015-08-26 21:00:00',0),('2015_10_06_00_characters.sql','16842FDD7E8547F2260D3312F53EFF8761EFAB35','ARCHIVED','2015-10-06 16:06:38',0),('2015_10_07_00_characters.sql','E15AB463CEBE321001D7BFDEA4B662FF618728FD','ARCHIVED','2015-10-07 23:32:00',0),('2015_10_12_00_characters.sql','D6F9927BDED72AD0A81D6EC2C6500CBC34A39FA2','ARCHIVED','2015-10-12 15:35:47',0),('2015_10_28_00_characters.sql','622A9CA8FCE690429EBE23BA071A37C7A007BF8B','ARCHIVED','2015-10-19 14:32:22',0),('2015_10_29_00_characters_335.sql','4555A7F35C107E54C13D74D20F141039ED42943E','ARCHIVED','2015-10-29 17:05:43',0),('2015_11_03_00_characters.sql','CC045717B8FDD9733351E52A5302560CD08AAD57','ARCHIVED','2015-10-12 15:23:33',0),('2015_11_07_00_characters.sql','BAF9F6B8F97A30D04BDBBA8127A62A1720F9B904','RELEASED','2015-11-07 15:40:47',0),('2016_01_24_instanceextend.sql','418F9639F558346E0F7A33512059906E94E30E49','RELEASED','2016-01-24 00:00:00',0); /*!40000 ALTER TABLE `updates` ENABLE KEYS */; UNLOCK TABLES; diff --git a/sql/updates/characters/2016_01_24_instanceextend.sql b/sql/updates/characters/2016_01_24_instanceextend.sql new file mode 100644 index 0000000000000..c93c1fd15cd15 --- /dev/null +++ b/sql/updates/characters/2016_01_24_instanceextend.sql @@ -0,0 +1,2 @@ +-- extend=0 is normal, extend=1 is extended, extend=2 is expired +ALTER TABLE `character_instance` ADD COLUMN `extendState` TINYINT(2) UNSIGNED NOT NULL DEFAULT '1'; diff --git a/sql/updates/world/2016_01_25_instanceextend.sql b/sql/updates/world/2016_01_25_instanceextend.sql new file mode 100644 index 0000000000000..5feb4f4416324 --- /dev/null +++ b/sql/updates/world/2016_01_25_instanceextend.sql @@ -0,0 +1,6 @@ +-- +DELETE FROM `command` WHERE `name`="debug raidreset"; +INSERT INTO `command` (`name`,`permission`,`help`) VALUES ("debug raidreset",414,"Syntax: .debug raidreset mapid [difficulty] +Forces a global reset of the specified map on all difficulties (or only the specific difficulty if specified). Effectively the same as setting the specified map's reset timer to now."); + +UPDATE `trinity_string` SET `content_default`="Map: %d | ID: %d | perm: %s | extended: %s | diff: %d | canReset: %s | TTR: %s" WHERE `entry`=5045; diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 140f3bf31c9dc..ab68aca2a8c19 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -72,7 +72,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() "FROM characters WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_GROUP_MEMBER, "SELECT guid FROM group_member WHERE memberGuid = ?", CONNECTION_BOTH); - PrepareStatement(CHAR_SEL_CHARACTER_INSTANCE, "SELECT id, permanent, map, difficulty, resettime FROM character_instance LEFT JOIN instance ON instance = id WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_SEL_CHARACTER_INSTANCE, "SELECT id, permanent, map, difficulty, extendState, resettime FROM character_instance LEFT JOIN instance ON instance = id WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_AURAS, "SELECT casterGuid, spell, effectMask, recalculateMask, stackCount, amount0, amount1, amount2, " "base_amount0, base_amount1, base_amount2, maxDuration, remainTime, remainCharges FROM character_aura WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_SPELL, "SELECT spell, active, disabled FROM character_spell WHERE guid = ?", CONNECTION_ASYNC); @@ -405,8 +405,8 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_UPD_WORLDSTATE, "UPDATE worldstates SET value = ? WHERE entry = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_WORLDSTATE, "INSERT INTO worldstates (entry, value) VALUES (?, ?)", 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 = ? WHERE guid = ? AND instance = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_INS_CHAR_INSTANCE, "INSERT INTO character_instance (guid, instance, permanent) VALUES (?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_CHAR_INSTANCE, "UPDATE character_instance SET instance = ?, permanent = ?, extendState = ? WHERE guid = ? AND instance = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_CHAR_INSTANCE, "INSERT INTO character_instance (guid, instance, permanent, extendState) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_GENDER_PLAYERBYTES, "UPDATE characters SET gender = ?, playerBytes = ?, playerBytes2 = ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHARACTER_SKILL, "DELETE FROM character_skills WHERE guid = ? AND skill = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_ADD_CHARACTER_SOCIAL_FLAGS, "UPDATE character_social SET flags = flags | ? WHERE guid = ? AND friend = ?", CONNECTION_ASYNC); @@ -434,6 +434,7 @@ 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_AT_LOGIN_TITLES, "SELECT at_login, knownTitles FROM characters WHERE guid = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_INSTANCE, "SELECT data, completedEncounters 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_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); PrepareStatement(CHAR_SEL_CHAR_OLD_CHARS, "SELECT guid, deleteInfos_Account FROM characters WHERE deleteDate IS NOT NULL AND deleteDate < ?", CONNECTION_SYNCH); @@ -470,9 +471,10 @@ void CharacterDatabaseConnection::DoPrepareStatements() 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_CHAR_INSTANCE_BY_MAP_DIFF, "DELETE FROM character_instance USING character_instance LEFT JOIN instance ON character_instance.instance = id WHERE map = ? and difficulty = ?", 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 (extendState = 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 map = ? and difficulty = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_DEL_INSTANCE_BY_MAP_DIFF, "DELETE FROM instance WHERE map = ? and 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 extendState != 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 extendState = extendState-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); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index c0ff9cbbfbeda..19d4a609e7791 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -365,6 +365,7 @@ enum CharacterDatabaseStatements CHAR_SEL_CHAR_CLASS_LVL_AT_LOGIN, CHAR_SEL_CHAR_AT_LOGIN_TITLES, CHAR_SEL_INSTANCE, + CHAR_SEL_PERM_BIND_BY_INSTANCE, CHAR_SEL_CHAR_COD_ITEM_MAIL, CHAR_SEL_CHAR_SOCIAL, CHAR_SEL_CHAR_OLD_CHARS, @@ -396,9 +397,10 @@ enum CharacterDatabaseStatements CHAR_INS_CHAR_GIFT, CHAR_DEL_INSTANCE_BY_INSTANCE, CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE, - CHAR_DEL_CHAR_INSTANCE_BY_MAP_DIFF, + CHAR_DEL_EXPIRED_CHAR_INSTANCE_BY_MAP_DIFF, CHAR_DEL_GROUP_INSTANCE_BY_MAP_DIFF, - CHAR_DEL_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, diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 9ae9f21c967d3..c7f674ec72cbf 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -18375,8 +18375,9 @@ void Player::_LoadBoundInstances(PreparedQueryResult result) uint32 mapId = fields[2].GetUInt16(); uint32 instanceId = fields[0].GetUInt32(); uint8 difficulty = fields[3].GetUInt8(); + BindExtensionState extendState = BindExtensionState(fields[4].GetUInt8()); - time_t resetTime = time_t(fields[4].GetUInt32()); + time_t resetTime = time_t(fields[5].GetUInt32()); // 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 @@ -18425,13 +18426,13 @@ void Player::_LoadBoundInstances(PreparedQueryResult result) // since non permanent binds are always solo bind, they can always be reset if (InstanceSave* save = sInstanceSaveMgr->AddInstanceSave(mapId, instanceId, Difficulty(difficulty), resetTime, !perm, true)) - BindToInstance(save, perm, true); + BindToInstance(save, perm, extendState, true); } while (result->NextRow()); } } -InstancePlayerBind* Player::GetBoundInstance(uint32 mapid, Difficulty difficulty) +InstancePlayerBind* Player::GetBoundInstance(uint32 mapid, Difficulty difficulty, bool withExpired) { // some instances only have one difficulty MapDifficulty const* mapDiff = GetDownscaledMapDifficultyData(mapid, difficulty); @@ -18440,9 +18441,9 @@ InstancePlayerBind* Player::GetBoundInstance(uint32 mapid, Difficulty difficulty BoundInstancesMap::iterator itr = m_boundInstances[difficulty].find(mapid); if (itr != m_boundInstances[difficulty].end()) - return &itr->second; - else - return NULL; + if (itr->second.extendState || withExpired) + return &itr->second; + return nullptr; } InstanceSave* Player::GetInstanceSave(uint32 mapid, bool raid) @@ -18485,24 +18486,32 @@ void Player::UnbindInstance(BoundInstancesMap::iterator &itr, Difficulty difficu } } -InstancePlayerBind* Player::BindToInstance(InstanceSave* save, bool permanent, bool load) +InstancePlayerBind* Player::BindToInstance(InstanceSave* save, bool permanent, BindExtensionState extendState, bool load) { if (save) { InstancePlayerBind& bind = m_boundInstances[save->GetDifficulty()][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) + if (permanent != bind.perm || save != bind.save || extendState != bind.extendState) { PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_INSTANCE); stmt->setUInt32(0, save->GetInstanceId()); stmt->setBool(1, permanent); - stmt->setUInt32(2, GetGUID().GetCounter()); - stmt->setUInt32(3, bind.save->GetInstanceId()); + stmt->setUInt8(2, extendState); + stmt->setUInt32(3, GetGUID().GetCounter()); + stmt->setUInt32(4, bind.save->GetInstanceId()); CharacterDatabase.Execute(stmt); } @@ -18514,6 +18523,7 @@ InstancePlayerBind* Player::BindToInstance(InstanceSave* save, bool permanent, b stmt->setUInt32(0, GetGUID().GetCounter()); stmt->setUInt32(1, save->GetInstanceId()); stmt->setBool(2, permanent); + stmt->setUInt8(3, extendState); CharacterDatabase.Execute(stmt); } @@ -18531,9 +18541,10 @@ InstancePlayerBind* Player::BindToInstance(InstanceSave* save, bool permanent, b bind.save = save; bind.perm = permanent; + bind.extendState = extendState; if (!load) TC_LOG_DEBUG("maps", "Player::BindToInstance: %s(%d) is now bound to map %d, instance %d, difficulty %d", GetName().c_str(), GetGUID().GetCounter(), save->GetMapId(), save->GetInstanceId(), save->GetDifficulty()); - sScriptMgr->OnPlayerBindToInstance(this, save->GetDifficulty(), save->GetMapId(), permanent); + sScriptMgr->OnPlayerBindToInstance(this, save->GetDifficulty(), save->GetMapId(), permanent, uint8(extendState)); return &bind; } @@ -18551,7 +18562,7 @@ void Player::BindToInstance() GetSession()->SendPacket(&data); if (!IsGameMaster()) { - BindToInstance(mapSave, true); + BindToInstance(mapSave, true, EXTEND_STATE_KEEP); GetSession()->SendCalendarRaidLockout(mapSave, true); } } @@ -18577,15 +18588,19 @@ void Player::SendRaidInfo() { for (BoundInstancesMap::iterator itr = m_boundInstances[i].begin(); itr != m_boundInstances[i].end(); ++itr) { - if (itr->second.perm) - { - InstanceSave* save = itr->second.save; - data << uint32(save->GetMapId()); // map id - data << uint32(save->GetDifficulty()); // difficulty - data << uint64(save->GetInstanceId()); // instance id - data << uint8(1); // expired = 0 - data << uint8(0); // extended = 1 - data << uint32(save->GetResetTime() - now); // reset time + InstancePlayerBind const& bind = itr->second; + if (bind.perm) + { + InstanceSave* save = bind.save; + data << uint32(save->GetMapId()); // map id + data << uint32(save->GetDifficulty()); // difficulty + data << uint64(save->GetInstanceId()); // instance id + data << uint8(bind.extendState != EXTEND_STATE_EXPIRED); // expired = 0 + data << uint8(bind.extendState == EXTEND_STATE_EXTENDED); // extended = 1 + time_t nextReset = save->GetResetTime(); + if (bind.extendState == EXTEND_STATE_EXTENDED) + nextReset = sInstanceSaveMgr->GetSubsequentResetTime(save->GetMapId(), save->GetDifficulty(), save->GetResetTime()); + data << uint32(nextReset - now); // reset time ++counter; } } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 9b64023698c40..2388cf9d0c7a7 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -834,14 +834,27 @@ enum PlayerDelayedOperations // Maximum money amount : 2^31 - 1 extern uint32 const MAX_MONEY_AMOUNT; +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; - bool perm; /* 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. */ - InstancePlayerBind() : save(NULL), perm(false) { } + 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(NULL), perm(false), extendState(EXTEND_STATE_NORMAL) { } }; struct AccessRequirement @@ -2115,12 +2128,12 @@ class Player : public Unit, public GridObject bool m_InstanceValid; // permanent binds and solo binds by difficulty BoundInstancesMap m_boundInstances[MAX_DIFFICULTY]; - InstancePlayerBind* GetBoundInstance(uint32 mapid, Difficulty difficulty); + InstancePlayerBind* GetBoundInstance(uint32 mapid, Difficulty difficulty, bool withExpired = false); BoundInstancesMap& GetBoundInstances(Difficulty difficulty) { return m_boundInstances[difficulty]; } InstanceSave* GetInstanceSave(uint32 mapid, bool raid); void UnbindInstance(uint32 mapid, Difficulty difficulty, bool unload = false); void UnbindInstance(BoundInstancesMap::iterator &itr, Difficulty difficulty, bool unload = false); - InstancePlayerBind* BindToInstance(InstanceSave* save, bool permanent, bool load = false); + InstancePlayerBind* BindToInstance(InstanceSave* save, bool permanent, BindExtensionState extendState = EXTEND_STATE_NORMAL, bool load = false); void BindToInstance(); void SetPendingBind(uint32 instanceId, uint32 bindTimer); bool HasPendingBind() const { return _pendingBindId > 0; } diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp index 43159828a3c89..99c5d610e6498 100644 --- a/src/server/game/Groups/Group.cpp +++ b/src/server/game/Groups/Group.cpp @@ -687,7 +687,8 @@ void Group::ConvertLeaderInstancesToGroup(Player* player, Group* group, bool swi for (Player::BoundInstancesMap::iterator itr = player->m_boundInstances[i].begin(); itr != player->m_boundInstances[i].end();) { if (!switchLeader || !group->GetBoundInstance(itr->second.save->GetDifficulty(), itr->first)) - group->BindToInstance(itr->second.save, itr->second.perm, false); + 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) diff --git a/src/server/game/Handlers/CalendarHandler.cpp b/src/server/game/Handlers/CalendarHandler.cpp index 8bd7086fc1b1b..540eeba0752bc 100644 --- a/src/server/game/Handlers/CalendarHandler.cpp +++ b/src/server/game/Handlers/CalendarHandler.cpp @@ -695,6 +695,21 @@ void WorldSession::HandleSetSavedInstanceExtend(WorldPacket& recvData) recvData >> mapId >> difficulty>> toggleExtend; TC_LOG_DEBUG("network", "CMSG_SET_SAVED_INSTANCE_EXTEND - MapId: %u, Difficulty: %u, ToggleExtend: %s", mapId, difficulty, toggleExtend ? "On" : "Off"); + if (Player* player = GetPlayer()) + { + InstancePlayerBind* instanceBind = player->GetBoundInstance(mapId, Difficulty(difficulty), toggleExtend == 1); // include expired instances if we are toggling extend on + if (!instanceBind || !instanceBind->save || !instanceBind->perm) + return; + + BindExtensionState newState; + if (!toggleExtend || instanceBind->extendState == EXTEND_STATE_EXPIRED) + newState = EXTEND_STATE_NORMAL; + else + newState = EXTEND_STATE_EXTENDED; + + player->BindToInstance(instanceBind->save, true, newState, false); + } + /* InstancePlayerBind* instanceBind = _player->GetBoundInstance(mapId, Difficulty(difficulty)); if (!instanceBind || !instanceBind->save) diff --git a/src/server/game/Instances/InstanceSaveMgr.cpp b/src/server/game/Instances/InstanceSaveMgr.cpp index 9a64335e6f04a..3538262ed3502 100644 --- a/src/server/game/Instances/InstanceSaveMgr.cpp +++ b/src/server/game/Instances/InstanceSaveMgr.cpp @@ -439,6 +439,23 @@ void InstanceSaveManager::LoadResetTimes() } } +time_t InstanceSaveManager::GetSubsequentResetTime(uint32 mapid, Difficulty difficulty, time_t resetTime) const +{ + MapDifficulty const* mapDiff = GetMapDifficultyData(mapid, difficulty); + if (!mapDiff || !mapDiff->resetTime) + { + TC_LOG_ERROR("misc", "InstanceSaveManager::GetSubsequentResetTime: not valid difficulty or no reset delay for map %u", mapid); + return 0; + } + + 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; + + return ((resetTime + MINUTE) / DAY * DAY) + period + diff; +} + void InstanceSaveManager::ScheduleReset(bool add, time_t time, InstResetEvent event) { if (!add) @@ -476,6 +493,17 @@ void InstanceSaveManager::ScheduleReset(bool add, time_t time, InstResetEvent ev m_resetTimeQueue.insert(std::pair(time, event)); } +void InstanceSaveManager::ForceGlobalReset(uint32 mapId, Difficulty difficulty) +{ + 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 InstanceSaveManager::Update() { time_t now = time(NULL); @@ -516,10 +544,26 @@ void InstanceSaveManager::_ResetSave(InstanceSaveHashMap::iterator &itr) // do not allow UnbindInstance to automatically unload the InstanceSaves lock_instLists = true; + bool shouldDelete = true; InstanceSave::PlayerListType &pList = itr->second->m_playerList; - while (!pList.empty()) + std::vector temp; // list of expired binds that should be unbound + for (Player* player : pList) + { + if (InstancePlayerBind* bind = player->GetBoundInstance(itr->second->GetMapId(), itr->second->GetDifficulty())) + { + 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); + } + for (Player* player : temp) { - Player* player = *(pList.begin()); player->UnbindInstance(itr->second->GetMapId(), itr->second->GetDifficulty(), true); } @@ -530,8 +574,13 @@ void InstanceSaveManager::_ResetSave(InstanceSaveHashMap::iterator &itr) group->UnbindInstance(itr->second->GetMapId(), itr->second->GetDifficulty(), true); } - delete itr->second; - m_instanceSaveById.erase(itr++); + if (shouldDelete) + { + delete itr->second; + itr = m_instanceSaveById.erase(itr); + } + else + ++itr; lock_instLists = false; } @@ -572,31 +621,21 @@ void InstanceSaveManager::_ResetOrWarnAll(uint32 mapid, Difficulty difficulty, b MapEntry const* mapEntry = sMapStore.LookupEntry(mapid); if (!mapEntry->Instanceable()) return; + TC_LOG_DEBUG("misc", "InstanceSaveManager::ResetOrWarnAll: Processing map %s (%u) on difficulty %u (warn? %u)", mapEntry->name[0], mapid, uint8(difficulty), warn); time_t now = time(NULL); if (!warn) { - MapDifficulty const* mapDiff = GetMapDifficultyData(mapid, difficulty); - if (!mapDiff || !mapDiff->resetTime) - { - TC_LOG_ERROR("misc", "InstanceSaveManager::ResetOrWarnAll: not valid difficulty or no reset delay for map %d", mapid); + // calculate the next reset time + time_t next_reset = GetSubsequentResetTime(mapid, difficulty, resetTime); + if (!next_reset) return; - } - // remove all 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->GetDifficulty() == difficulty) - _ResetSave(itr); - else - ++itr; - } - - // delete them from the DB, even if not loaded + // delete/promote instance binds from the DB, even if not loaded SQLTransaction trans = CharacterDatabase.BeginTransaction(); - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_MAP_DIFF); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_EXPIRED_CHAR_INSTANCE_BY_MAP_DIFF); stmt->setUInt16(0, uint16(mapid)); stmt->setUInt8(1, uint8(difficulty)); trans->Append(stmt); @@ -606,21 +645,26 @@ void InstanceSaveManager::_ResetOrWarnAll(uint32 mapid, Difficulty difficulty, b stmt->setUInt8(1, uint8(difficulty)); trans->Append(stmt); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INSTANCE_BY_MAP_DIFF); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_EXPIRED_INSTANCE_BY_MAP_DIFF); stmt->setUInt16(0, uint16(mapid)); stmt->setUInt8(1, uint8(difficulty)); trans->Append(stmt); - CharacterDatabase.CommitTransaction(trans); - - // calculate the next reset time - uint32 diff = sWorld->getIntConfig(CONFIG_INSTANCE_RESET_TIME_HOUR) * HOUR; + stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_EXPIRE_CHAR_INSTANCE_BY_MAP_DIFF); + stmt->setUInt16(0, uint16(mapid)); + stmt->setUInt8(1, uint8(difficulty)); + trans->Append(stmt); - uint32 period = uint32(((mapDiff->resetTime * sWorld->getRate(RATE_INSTANCE_RESET_TIME))/DAY) * DAY); - if (period < DAY) - period = DAY; + CharacterDatabase.CommitTransaction(trans); - uint32 next_reset = uint32(((resetTime + MINUTE) / DAY * DAY) + period + diff); + // 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->GetDifficulty() == difficulty) + _ResetSave(itr); + else + ++itr; + } SetResetTimeFor(mapid, difficulty, next_reset); ScheduleReset(true, time_t(next_reset-3600), InstResetEvent(1, mapid, difficulty, 0)); diff --git a/src/server/game/Instances/InstanceSaveMgr.h b/src/server/game/Instances/InstanceSaveMgr.h index e3d8175cbc4c1..d2b3237b3cff9 100644 --- a/src/server/game/Instances/InstanceSaveMgr.h +++ b/src/server/game/Instances/InstanceSaveMgr.h @@ -190,6 +190,7 @@ class InstanceSaveManager ResetTimeByMapDifficultyMap::const_iterator itr = m_resetTimeByMapDifficulty.find(MAKE_PAIR32(mapid, d)); return itr != m_resetTimeByMapDifficulty.end() ? itr->second : 0; } + time_t GetSubsequentResetTime(uint32 mapid, Difficulty difficulty, time_t resetTime) const; // Use this on startup when initializing reset times void InitializeResetTimeFor(uint32 mapid, Difficulty d, time_t t) @@ -210,6 +211,7 @@ class InstanceSaveManager return m_resetTimeByMapDifficulty; } void ScheduleReset(bool add, time_t time, InstResetEvent event); + void ForceGlobalReset(uint32 mapId, Difficulty difficulty); void Update(); diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index 1560d4bdd083d..ab5d3f69f5846 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -3209,22 +3209,37 @@ bool InstanceMap::Reset(uint8 method) } else { + bool doUnload = true; if (method == INSTANCE_RESET_GLOBAL) + { // set the homebind timer for players inside (1 minute) for (MapRefManager::iterator itr = m_mapRefManager.begin(); itr != m_mapRefManager.end(); ++itr) - itr->GetSource()->m_InstanceValid = false; + { + InstancePlayerBind* bind = itr->GetSource()->GetBoundInstance(GetId(), GetDifficulty()); + if (bind && bind->extendState && bind->save->GetInstanceId() == GetInstanceId()) + doUnload = false; + else + itr->GetSource()->m_InstanceValid = false; + } + + if (doUnload && HasPermBoundPlayers()) // check if any unloaded players have a nonexpired save to this + doUnload = false; + } - // the unload timer is not started - // instead the map will unload immediately after the players have left - m_unloadWhenEmpty = true; - m_resetAfterUnload = true; + if (doUnload) + { + // the unload timer is not started + // instead the map will unload immediately after the players have left + m_unloadWhenEmpty = true; + m_resetAfterUnload = true; + } } } else { // unloaded at next update m_unloadTimer = MIN_UNLOAD_DELAY; - m_resetAfterUnload = true; + m_resetAfterUnload = !(method == INSTANCE_RESET_GLOBAL && HasPermBoundPlayers()); } return m_mapRefManager.isEmpty(); @@ -3305,6 +3320,13 @@ MapDifficulty const* Map::GetMapDifficulty() const return GetMapDifficultyData(GetId(), GetDifficulty()); } +bool InstanceMap::HasPermBoundPlayers() const +{ + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PERM_BIND_BY_INSTANCE); + stmt->setUInt16(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 13d48db0f9d3d..c39af9e7a46d7 100644 --- a/src/server/game/Maps/Map.h +++ b/src/server/game/Maps/Map.h @@ -771,6 +771,9 @@ class InstanceMap : public Map void SendResetWarnings(uint32 timeLeft) const; void SetResetSchedule(bool on); + /* this checks if any players have a permanent bind (included reactivatable expired binds) to the instance ID + it needs a DB query, so use sparingly */ + bool HasPermBoundPlayers() const; uint32 GetMaxPlayers() const; uint32 GetMaxResetDelay() const; diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 3fa16cf4517ba..37917f64de659 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -1368,9 +1368,9 @@ void ScriptMgr::OnPlayerSave(Player* player) FOREACH_SCRIPT(PlayerScript)->OnSave(player); } -void ScriptMgr::OnPlayerBindToInstance(Player* player, Difficulty difficulty, uint32 mapid, bool permanent) +void ScriptMgr::OnPlayerBindToInstance(Player* player, Difficulty difficulty, uint32 mapid, bool permanent, uint8 extendState) { - FOREACH_SCRIPT(PlayerScript)->OnBindToInstance(player, difficulty, mapid, permanent); + FOREACH_SCRIPT(PlayerScript)->OnBindToInstance(player, difficulty, mapid, permanent, extendState); } void ScriptMgr::OnPlayerUpdateZone(Player* player, uint32 newZone, uint32 newArea) diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index 5dfc0be688ae8..6706c4f9c145a 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -734,7 +734,7 @@ class PlayerScript : public UnitScript 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 /*extendState*/) { } // Called when a player switches to a new zone virtual void OnUpdateZone(Player* /*player*/, uint32 /*newZone*/, uint32 /*newArea*/) { } @@ -1054,7 +1054,7 @@ class ScriptMgr void OnPlayerDelete(ObjectGuid guid, uint32 accountId); void OnPlayerFailedDelete(ObjectGuid guid, uint32 accountId); void OnPlayerSave(Player* player); - 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 OnQuestStatusChange(Player* player, uint32 questId, QuestStatus status); diff --git a/src/server/scripts/Commands/cs_debug.cpp b/src/server/scripts/Commands/cs_debug.cpp index 9902b83ff6377..b937fc4e0a4a5 100644 --- a/src/server/scripts/Commands/cs_debug.cpp +++ b/src/server/scripts/Commands/cs_debug.cpp @@ -93,7 +93,8 @@ class debug_commandscript : public CommandScript { "moveflags", rbac::RBAC_PERM_COMMAND_DEBUG_MOVEFLAGS, false, &HandleDebugMoveflagsCommand, "" }, { "transport", rbac::RBAC_PERM_COMMAND_DEBUG_TRANSPORT, false, &HandleDebugTransportCommand, "" }, { "loadcells", rbac::RBAC_PERM_COMMAND_DEBUG_LOADCELLS, false, &HandleDebugLoadCellsCommand, "" }, - { "boundary", rbac::RBAC_PERM_COMMAND_DEBUG_BOUNDARY, false, &HandleDebugBoundaryCommand, "" } + { "boundary", rbac::RBAC_PERM_COMMAND_DEBUG_BOUNDARY, false, &HandleDebugBoundaryCommand, "" }, + { "raidreset", rbac::RBAC_PERM_COMMAND_INSTANCE_UNBIND, false, &HandleDebugRaidResetCommand, "" } }; static std::vector commandTable = { @@ -1442,6 +1443,32 @@ class debug_commandscript : public CommandScript return true; } + + static bool HandleDebugRaidResetCommand(ChatHandler* /*handler*/, char const* args) + { + char* map_str = args ? strtok((char*)args, " ") : nullptr; + char* difficulty_str = args ? strtok(nullptr, " ") : nullptr; + + int32 map = map_str ? atoi(map_str) : -1; + if (map <= 0) + return false; + MapEntry const* mEntry = sMapStore.LookupEntry(map); + if (!mEntry || !mEntry->IsRaid()) + return false; + int32 difficulty = difficulty_str ? atoi(difficulty_str) : -1; + if (difficulty >= MAX_RAID_DIFFICULTY || difficulty < -1) + return false; + + if (difficulty == -1) + for (uint8 diff = 0; diff < MAX_RAID_DIFFICULTY; ++diff) + { + if (GetMapDifficultyData(mEntry->MapID, Difficulty(diff))) + sInstanceSaveMgr->ForceGlobalReset(mEntry->MapID, Difficulty(diff)); + } + else + sInstanceSaveMgr->ForceGlobalReset(mEntry->MapID, Difficulty(difficulty)); + return true; + } }; void AddSC_debug_commandscript() diff --git a/src/server/scripts/Commands/cs_instance.cpp b/src/server/scripts/Commands/cs_instance.cpp index a53d1f00b5465..d0325a317dbba 100644 --- a/src/server/scripts/Commands/cs_instance.cpp +++ b/src/server/scripts/Commands/cs_instance.cpp @@ -82,7 +82,7 @@ class instance_commandscript : public CommandScript { InstanceSave* save = itr->second.save; std::string timeleft = GetTimeString(save->GetResetTime() - time(NULL)); - handler->PSendSysMessage(LANG_COMMAND_LIST_BIND_INFO, itr->first, save->GetInstanceId(), itr->second.perm ? "yes" : "no", save->GetDifficulty(), save->CanReset() ? "yes" : "no", timeleft.c_str()); + 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->GetDifficulty(), save->CanReset() ? "yes" : "no", timeleft.c_str()); counter++; } } @@ -98,7 +98,7 @@ class instance_commandscript : public CommandScript { InstanceSave* save = itr->second.save; std::string timeleft = GetTimeString(save->GetResetTime() - time(NULL)); - handler->PSendSysMessage(LANG_COMMAND_LIST_BIND_INFO, itr->first, save->GetInstanceId(), itr->second.perm ? "yes" : "no", save->GetDifficulty(), save->CanReset() ? "yes" : "no", timeleft.c_str()); + handler->PSendSysMessage(LANG_COMMAND_LIST_BIND_INFO, itr->first, save->GetInstanceId(), itr->second.perm ? "yes" : "no", "-", save->GetDifficulty(), save->CanReset() ? "yes" : "no", timeleft.c_str()); counter++; } }