diff options
| -rw-r--r-- | sql/base/characters_database.sql | 3 | ||||
| -rw-r--r-- | sql/updates/characters/2016_01_24_instanceextend.sql | 2 | ||||
| -rw-r--r-- | sql/updates/world/2016_01_25_instanceextend.sql | 6 | ||||
| -rw-r--r-- | src/server/database/Database/Implementation/CharacterDatabase.cpp | 12 | ||||
| -rw-r--r-- | src/server/database/Database/Implementation/CharacterDatabase.h | 6 | ||||
| -rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 57 | ||||
| -rw-r--r-- | src/server/game/Entities/Player/Player.h | 25 | ||||
| -rw-r--r-- | src/server/game/Groups/Group.cpp | 3 | ||||
| -rw-r--r-- | src/server/game/Handlers/CalendarHandler.cpp | 15 | ||||
| -rw-r--r-- | src/server/game/Instances/InstanceSaveMgr.cpp | 102 | ||||
| -rw-r--r-- | src/server/game/Instances/InstanceSaveMgr.h | 2 | ||||
| -rw-r--r-- | src/server/game/Maps/Map.cpp | 34 | ||||
| -rw-r--r-- | src/server/game/Maps/Map.h | 3 | ||||
| -rw-r--r-- | src/server/game/Scripting/ScriptMgr.cpp | 4 | ||||
| -rw-r--r-- | src/server/game/Scripting/ScriptMgr.h | 4 | ||||
| -rw-r--r-- | src/server/scripts/Commands/cs_debug.cpp | 29 | ||||
| -rw-r--r-- | src/server/scripts/Commands/cs_instance.cpp | 4 | 
17 files changed, 233 insertions, 78 deletions
diff --git a/sql/base/characters_database.sql b/sql/base/characters_database.sql index 69c6c990e1e..19898fa39e1 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 00000000000..c93c1fd15cd --- /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 00000000000..5feb4f44163 --- /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 140f3bf31c9..ab68aca2a8c 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 c0ff9cbbfbe..19d4a609e77 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 9ae9f21c967..c7f674ec72c 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 9b64023698c..2388cf9d0c7 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<Player>          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 43159828a3c..99c5d610e64 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 8bd7086fc1b..540eeba0752 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 9a64335e6f0..3538262ed35 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_t, InstResetEvent>(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<Player*> 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 e3d8175cbc4..d2b3237b3cf 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 1560d4bdd08..ab5d3f69f58 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 13d48db0f9d..c39af9e7a46 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 3fa16cf4517..37917f64de6 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 5dfc0be688a..6706c4f9c14 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 9902b83ff63..b937fc4e0a4 100644 --- a/src/server/scripts/Commands/cs_debug.cpp +++ b/src/server/scripts/Commands/cs_debug.cpp @@ -93,7 +93,8 @@ public:              { "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<ChatCommand> commandTable =          { @@ -1442,6 +1443,32 @@ public:          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 a53d1f00b54..d0325a317db 100644 --- a/src/server/scripts/Commands/cs_instance.cpp +++ b/src/server/scripts/Commands/cs_instance.cpp @@ -82,7 +82,7 @@ public:              {                  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 @@ public:                  {                      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++;                  }              }  | 
