From a6a2ca8ef76716f5b58b4ed2ae53d0bde424775b Mon Sep 17 00:00:00 2001 From: Hanabi <54484196+hanabi5@users.noreply.github.com> Date: Tue, 24 May 2022 14:33:45 +0100 Subject: [PATCH 01/11] feat(Core/GameObjects): Instance gameobject save data implementation (#11113) * fix(Core): Save gameobject state on instances Currently, azerothcore doesn't save gameobject states on instances. Whenever there's a re-start or crash, the instance's gameobjects and their states aren't saved, producing un-wanted behaviours and blocking instances at times. Implemented CRUD for new table `instance_saved_data` that holds the states of gameobjects. - When worldserver launches and gameobjects are loaded, this will check if this object's state exists on the DB and sets the previous state. - On instance deletion (reset) these states are also removed based on the instance ID. - Whenever a gameobject state changes inside a dungeon or raid, we save on the database the set state. * Select query to synchronous and used FindMap() * loading gameobject states on create * reseting instance saved data * missing reset methods and on create state * database structure * Update src/server/game/Entities/GameObject/GameObject.cpp Co-authored-by: Kitzunu <24550914+Kitzunu@users.noreply.github.com> * Update src/server/game/Entities/GameObject/GameObject.cpp Co-authored-by: Kitzunu <24550914+Kitzunu@users.noreply.github.com> * Update src/server/game/Entities/GameObject/GameObject.cpp Co-authored-by: Kitzunu <24550914+Kitzunu@users.noreply.github.com> * Update src/server/game/Entities/GameObject/GameObject.cpp Co-authored-by: Kitzunu <24550914+Kitzunu@users.noreply.github.com> * Update src/server/game/Entities/Player/PlayerMisc.cpp Co-authored-by: Kitzunu <24550914+Kitzunu@users.noreply.github.com> * Update src/server/game/Groups/Group.cpp Co-authored-by: Kitzunu <24550914+Kitzunu@users.noreply.github.com> * codestyle * table changes * table style * codestyle * table changes for columns * data sanitization * todo: - Finish loading db data into the containers - Using containers to find data - How to get data from ObjectMGR inside Gameobject? * loading on start up and db changes * Removing unused data structure * Uninitialised integer * Whitespace * clean-up and hooks to save states on memory * Codestyle MySQL deprecated backticks * i dont understand codefactor * build * Update data/sql/updates/pending_db_world/rev_1643395587559675400.sql Co-authored-by: Kitzunu <24550914+Kitzunu@users.noreply.github.com> * Update src/server/game/Globals/ObjectMgr.h Co-authored-by: Kargatum * review changes * unecessary removal * pushback instead of emplace * wrong database update * Update ObjectMgr.cpp * missing check * removing entry from the PR * missing removals * last delete * build * aha! Found the culprit for the sudden assert errors * type safety, save only important gameobjects * static cast to unsigned short * Update data/sql/updates/pending_db_characters/rev_1643629468629316100.sql Co-authored-by: Kitzunu <24550914+Kitzunu@users.noreply.github.com> * type changes * queries fix * fix build * enabling which gameobjects to save on the database * deadmines iron clad door * Adjustment to gameobject onj create state and instances: - Gnomeregan doors and Grubbis boss state - Deadmines missing doors - Stratholme gameobjects state saved * forgot emi blastfuse change to despawn * Leaving group logic * codestyle * fixing merge issues * prevent bad behaviour * brain meltdown * Update data/sql/updates/pending_db_characters/rev_1643629468629316100.sql * Update data/sql/updates/pending_db_world/rev_1649359139539727000.sql Co-authored-by: Claudiodfc <54484196+claudiodfc@users.noreply.github.com> Co-authored-by: Kitzunu <24550914+Kitzunu@users.noreply.github.com> Co-authored-by: Kargatum Co-authored-by: Skjalf <47818697+Nyeriah@users.noreply.github.com> --- .../rev_1643629468629316100.sql | 8 + .../rev_1649359139539727000.sql | 4 + .../Implementation/CharacterDatabase.cpp | 7 + .../Implementation/CharacterDatabase.h | 6 + .../game/Entities/GameObject/GameObject.cpp | 166 +++++++++++++++++- .../game/Entities/GameObject/GameObject.h | 21 +++ .../game/Entities/Player/PlayerMisc.cpp | 26 +++ src/server/game/Globals/ObjectMgr.cpp | 66 +++++++ src/server/game/Globals/ObjectMgr.h | 13 ++ src/server/game/Groups/Group.cpp | 33 ++++ src/server/game/Groups/Group.h | 3 +- src/server/game/Instances/InstanceSaveMgr.cpp | 9 + src/server/game/Instances/InstanceSaveMgr.h | 1 + src/server/game/World/World.cpp | 3 + .../EasternKingdoms/Deadmines/deadmines.h | 8 +- .../Deadmines/instance_deadmines.cpp | 18 +- .../EasternKingdoms/Gnomeregan/gnomeregan.h | 19 ++ .../Gnomeregan/instance_gnomeregan.cpp | 68 +++++++ .../Scholomance/instance_scholomance.cpp | 3 + .../EasternKingdoms/Scholomance/scholomance.h | 2 + .../Stratholme/instance_stratholme.cpp | 19 ++ .../EasternKingdoms/Stratholme/stratholme.h | 5 + 22 files changed, 502 insertions(+), 6 deletions(-) create mode 100644 data/sql/updates/pending_db_characters/rev_1643629468629316100.sql create mode 100644 data/sql/updates/pending_db_world/rev_1649359139539727000.sql diff --git a/data/sql/updates/pending_db_characters/rev_1643629468629316100.sql b/data/sql/updates/pending_db_characters/rev_1643629468629316100.sql new file mode 100644 index 000000000..7b4f3fba8 --- /dev/null +++ b/data/sql/updates/pending_db_characters/rev_1643629468629316100.sql @@ -0,0 +1,8 @@ + +DROP TABLE IF EXISTS `instance_saved_go_state_data`; +CREATE TABLE IF NOT EXISTS `instance_saved_go_state_data` ( + `id` INT UNSIGNED NOT NULL COMMENT 'instance.id', + `guid` INT UNSIGNED NOT NULL COMMENT 'gameobject.guid', + `state` TINYINT UNSIGNED DEFAULT '0' COMMENT 'gameobject.state', + PRIMARY KEY (`id`, `guid`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/data/sql/updates/pending_db_world/rev_1649359139539727000.sql b/data/sql/updates/pending_db_world/rev_1649359139539727000.sql new file mode 100644 index 000000000..103dc3ac0 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1649359139539727000.sql @@ -0,0 +1,4 @@ + +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 7361) AND (`source_type` = 0) AND (`id` IN (1)); +INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES +(7361, 0, 1, 0, 6, 0, 100, 0, 0, 0, 0, 0, 0, 34, 0, 3, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Grubbis - On Just Died - Set Instance Data 0 to 3'); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index c36541585..9b7aa0501 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -601,6 +601,13 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_SEL_CHAR_SETTINGS, "SELECT source, data FROM character_settings WHERE guid = ?", CONNECTION_ASYNC); 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); } CharacterDatabaseConnection::CharacterDatabaseConnection(MySQLConnectionInfo& connInfo) : MySQLConnection(connInfo) diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index 0f3d50161..939f1d863 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -515,6 +515,12 @@ 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, + MAX_CHARACTERDATABASE_STATEMENTS }; diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index e3b9216bc..ef224798d 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -329,7 +329,31 @@ 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)); - SetGoState(go_state); + + 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); + } + SetGoArtKit(artKit); SetDisplayId(goinfo->displayId); @@ -2431,6 +2455,146 @@ 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() +{ + switch (m_goInfo->type) + { + case GAMEOBJECT_TYPE_DOOR: + case GAMEOBJECT_TYPE_BUTTON: + case GAMEOBJECT_TYPE_TRAP: + case GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING: + case GAMEOBJECT_TYPE_TRAPDOOR: + return true; + default: + return false; + } +} + +uint8 GameObject::GameobjectStateToInt(GOState* state) +{ + uint8 m_state = 3; + + if (state) + { + switch (*state) + { + case GO_STATE_ACTIVE: + m_state = 0; + return m_state; + case GO_STATE_READY: + m_state = 1; + return m_state; + case GO_STATE_ACTIVE_ALTERNATIVE: + m_state = 2; + return m_state; + } + } + + // Returning any value that is not one of the specified ones + // Which will default into the invalid part of the switch + return m_state; +} + +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) diff --git a/src/server/game/Entities/GameObject/GameObject.h b/src/server/game/Entities/GameObject/GameObject.h index ad344bb9a..db8d05e08 100644 --- a/src/server/game/Entities/GameObject/GameObject.h +++ b/src/server/game/Entities/GameObject/GameObject.h @@ -1010,6 +1010,25 @@ public: 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 + * a state change occurs + */ + 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(); protected: bool AIM_Initialize(); GameObjectModel* CreateModel(); @@ -1066,5 +1085,7 @@ private: return IsInRange(obj->GetPositionX(), obj->GetPositionY(), obj->GetPositionZ(), dist2compare); } GameObjectAI* m_AI; + + bool m_saveStateOnDb = false; }; #endif diff --git a/src/server/game/Entities/Player/PlayerMisc.cpp b/src/server/game/Entities/Player/PlayerMisc.cpp index f57381d57..b7e2561b4 100644 --- a/src/server/game/Entities/Player/PlayerMisc.cpp +++ b/src/server/game/Entities/Player/PlayerMisc.cpp @@ -181,6 +181,16 @@ void Player::SendResetFailedNotify(uint32 mapid) GetSession()->SendPacket(&data); } +void DeleteInstanceSavedData(uint32 instanceId) +{ + if (instanceId) + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DELETE_INSTANCE_SAVED_DATA); + stmt->SetData(0, instanceId); + CharacterDatabase.Execute(stmt); + } +} + /// Reset all solo instances and optionally send a message on success for each void Player::ResetInstances(ObjectGuid guid, uint8 method, bool isRaid) { @@ -198,7 +208,9 @@ void Player::ResetInstances(ObjectGuid guid, uint8 method, bool isRaid) InstanceSave* instanceSave = itr->second.save; MapEntry const* entry = sMapStore.LookupEntry(itr->first); if (!entry || entry->IsRaid() || !instanceSave->CanReset()) + { continue; + } Map* map = sMapMgr->FindMap(instanceSave->GetMapId(), instanceSave->GetInstanceId()); if (!map || map->ToInstanceMap()->Reset(method)) @@ -207,10 +219,16 @@ void Player::ResetInstances(ObjectGuid guid, uint8 method, bool isRaid) toUnbind.push_back(instanceSave); } else + { p->SendResetInstanceFailed(0, instanceSave->GetMapId()); + } + + DeleteInstanceSavedData(instanceSave->GetInstanceId()); } for (std::vector::const_iterator itr = toUnbind.begin(); itr != toUnbind.end(); ++itr) + { sInstanceSaveMgr->UnbindAllFor(*itr); + } } break; case INSTANCE_RESET_CHANGE_DIFFICULTY: @@ -225,7 +243,9 @@ void Player::ResetInstances(ObjectGuid guid, uint8 method, bool isRaid) InstanceSave* instanceSave = itr->second.save; MapEntry const* entry = sMapStore.LookupEntry(itr->first); if (!entry || entry->IsRaid() != isRaid || !instanceSave->CanReset()) + { continue; + } Map* map = sMapMgr->FindMap(instanceSave->GetMapId(), instanceSave->GetInstanceId()); if (!map || map->ToInstanceMap()->Reset(method)) @@ -234,7 +254,11 @@ void Player::ResetInstances(ObjectGuid guid, uint8 method, bool isRaid) toUnbind.push_back(instanceSave); } else + { p->SendResetInstanceFailed(0, instanceSave->GetMapId()); + } + + DeleteInstanceSavedData(instanceSave->GetInstanceId()); } for (std::vector::const_iterator itr = toUnbind.begin(); itr != toUnbind.end(); ++itr) sInstanceSaveMgr->UnbindAllFor(*itr); @@ -262,6 +286,8 @@ void Player::ResetInstances(ObjectGuid guid, uint8 method, bool isRaid) } //else // p->SendResetInstanceFailed(0, instanceSave->GetMapId()); + + 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); diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index 161f1d638..32eda9fe7 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -9762,6 +9762,72 @@ 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) diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index 7463358ad..bc2cdd748 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -1410,6 +1410,11 @@ public: [[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); private: // first free id for selected id type uint32 _auctionId; // pussywizard: accessed by a single thread @@ -1579,6 +1584,14 @@ private: std::set _transportMaps; // Helper container storing map ids that are for transports only, loaded from gameobject_template QuestMoneyRewardStore _questMoneyRewards; + + struct GameobjectInstanceSavedState + { + uint32 m_instance; + uint32 m_guid; + unsigned short m_state; + }; + std::vector GameobjectInstanceSavedStateList; }; #define sObjectMgr ObjectMgr::instance() diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp index 2c010c7d2..5b08d774e 100644 --- a/src/server/game/Groups/Group.cpp +++ b/src/server/game/Groups/Group.cpp @@ -744,6 +744,8 @@ void Group::Disband(bool hideDestroy /* = false */) sScriptMgr->OnGroupDisband(this); Player* player; + uint32 instanceId = 0; + for (member_citerator citr = m_memberSlots.begin(); citr != m_memberSlots.end(); ++citr) { if (!isBGGroup() && !isBFGroup()) @@ -753,6 +755,11 @@ void Group::Disband(bool hideDestroy /* = false */) player = ObjectAccessor::FindConnectedPlayer(citr->guid); + if (player && !instanceId && !isBGGroup() && !isBFGroup()) + { + instanceId = player->GetInstanceId(); + } + _homebindIfInstance(player); if (!isBGGroup() && !isBFGroup()) Player::ResetInstances(citr->guid, INSTANCE_RESET_GROUP_LEAVE, false); @@ -821,6 +828,14 @@ void Group::Disband(bool hideDestroy /* = false */) CharacterDatabase.Execute(stmt); } + // Cleaning up instance saved data for gameobjects when a group is disbanded + if (instanceId) + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DELETE_INSTANCE_SAVED_DATA); + stmt->SetData(0, instanceId); + CharacterDatabase.Execute(stmt); + } + sGroupMgr->RemoveGroup(this); delete this; } @@ -2022,6 +2037,16 @@ void Group::SetRaidDifficulty(Difficulty difficulty) } } +void Group::ResetInstanceSavedGameobjects(uint32 instanceId) +{ + if (instanceId) + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DELETE_INSTANCE_SAVED_DATA); + stmt->SetData(0, instanceId); + CharacterDatabase.Execute(stmt); + } +} + void Group::ResetInstances(uint8 method, bool isRaid, Player* leader) { if (isBGGroup() || isBFGroup() || isLFGGroup()) @@ -2049,7 +2074,11 @@ void Group::ResetInstances(uint8 method, bool isRaid, Player* leader) toUnbind.push_back(instanceSave); } else + { leader->SendResetInstanceFailed(0, instanceSave->GetMapId()); + } + + ResetInstanceSavedGameobjects(instanceSave->GetInstanceId()); } for (std::vector::const_iterator itr = toUnbind.begin(); itr != toUnbind.end(); ++itr) sInstanceSaveMgr->UnbindAllFor(*itr); @@ -2073,7 +2102,11 @@ void Group::ResetInstances(uint8 method, bool isRaid, Player* leader) toUnbind.push_back(instanceSave); } else + { leader->SendResetInstanceFailed(0, instanceSave->GetMapId()); + } + + ResetInstanceSavedGameobjects(instanceSave->GetInstanceId()); } for (std::vector::const_iterator itr = toUnbind.begin(); itr != toUnbind.end(); ++itr) sInstanceSaveMgr->UnbindAllFor(*itr); diff --git a/src/server/game/Groups/Group.h b/src/server/game/Groups/Group.h index a1d142fb8..a1041be31 100644 --- a/src/server/game/Groups/Group.h +++ b/src/server/game/Groups/Group.h @@ -314,9 +314,10 @@ public: uint32 GetDifficultyChangePreventionTime() const; DifficultyPreventionChangeType GetDifficultyChangePreventionReason() const { return _difficultyChangePreventionType; } void SetDifficultyChangePrevention(DifficultyPreventionChangeType type); - void DoForAllMembers(std::function const& worker); + // Reset Instance Gameobjects + void ResetInstanceSavedGameobjects(uint32 instanceId); protected: void _homebindIfInstance(Player* player); void _cancelHomebindIfInstance(Player* player); diff --git a/src/server/game/Instances/InstanceSaveMgr.cpp b/src/server/game/Instances/InstanceSaveMgr.cpp index 389348a4f..152cffd45 100644 --- a/src/server/game/Instances/InstanceSaveMgr.cpp +++ b/src/server/game/Instances/InstanceSaveMgr.cpp @@ -238,6 +238,12 @@ bool InstanceSave::RemovePlayer(ObjectGuid guid, InstanceSaveMgr* ism) return deleteSave; } +void InstanceSaveMgr::SanitizeInstanceSavedData() +{ + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SANITIZE_INSTANCE_SAVED_DATA); + CharacterDatabase.Execute(stmt); +} + void InstanceSaveMgr::LoadInstances() { uint32 oldMSTime = getMSTime(); @@ -270,6 +276,9 @@ void InstanceSaveMgr::LoadInstances() LoadInstanceSaves(); LoadCharacterBinds(); + // Sanitize pending rows on Instance_saved_data for data that wasn't deleted properly + SanitizeInstanceSavedData(); + LOG_INFO("server.loading", ">> Loaded instances and binds in {} ms", GetMSTimeDiffToNow(oldMSTime)); LOG_INFO("server.loading", " "); } diff --git a/src/server/game/Instances/InstanceSaveMgr.h b/src/server/game/Instances/InstanceSaveMgr.h index b2adb2869..945b4d79d 100644 --- a/src/server/game/Instances/InstanceSaveMgr.h +++ b/src/server/game/Instances/InstanceSaveMgr.h @@ -182,6 +182,7 @@ public: void CopyBinds(ObjectGuid from, ObjectGuid to, Player* toPlr = nullptr); void UnbindAllFor(InstanceSave* save); + void SanitizeInstanceSavedData(); protected: static uint16 ResetTimeDelay[]; static PlayerBindStorage playerBindStorage; diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 7778ed610..24c35fa74 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1587,6 +1587,9 @@ 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", "Load Character Cache..."); sCharacterCache->LoadCharacterCacheStorage(); diff --git a/src/server/scripts/EasternKingdoms/Deadmines/deadmines.h b/src/server/scripts/EasternKingdoms/Deadmines/deadmines.h index 1e4cb2da8..3694c5993 100644 --- a/src/server/scripts/EasternKingdoms/Deadmines/deadmines.h +++ b/src/server/scripts/EasternKingdoms/Deadmines/deadmines.h @@ -32,7 +32,13 @@ enum DataTypes enum GameObjects { GO_FACTORY_DOOR = 13965, - GO_IRON_CLAD_DOOR = 16397 + GO_HEAVY_DOOR_1 = 17153, + GO_HEAVY_DOOR_2 = 17154, + GO_IRON_CLAD_DOOR = 16397, + GO_DOOR_LEVER_1 = 101831, + GO_DOOR_LEVER_2 = 101833, + GO_DOOR_LEVER_3 = 101834, + GO_CANNON = 16398, }; template diff --git a/src/server/scripts/EasternKingdoms/Deadmines/instance_deadmines.cpp b/src/server/scripts/EasternKingdoms/Deadmines/instance_deadmines.cpp index e09d0bd4a..430ea08db 100644 --- a/src/server/scripts/EasternKingdoms/Deadmines/instance_deadmines.cpp +++ b/src/server/scripts/EasternKingdoms/Deadmines/instance_deadmines.cpp @@ -1,4 +1,4 @@ -/* +/* * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information * * This program is free software; you can redistribute it and/or modify it @@ -39,13 +39,25 @@ public: { 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); if (_encounters[TYPE_RHAHK_ZOR] == DONE) gameobject->SetGoState(GO_STATE_ACTIVE); break; case GO_IRON_CLAD_DOOR: - if (_encounters[TYPE_CANNON] == DONE) - HandleGameObject(ObjectGuid::Empty, true, gameobject); + gameobject->UpdateSaveToDb(true); + if (gameobject->GetStateSavedOnInstance() == GO_STATE_ACTIVE) + { + gameobject->DespawnOrUnsummon(); + } break; } } diff --git a/src/server/scripts/EasternKingdoms/Gnomeregan/gnomeregan.h b/src/server/scripts/EasternKingdoms/Gnomeregan/gnomeregan.h index 9f512b6b8..02d916f58 100644 --- a/src/server/scripts/EasternKingdoms/Gnomeregan/gnomeregan.h +++ b/src/server/scripts/EasternKingdoms/Gnomeregan/gnomeregan.h @@ -28,4 +28,23 @@ inline AI* GetGnomereganAI(T* obj) return GetInstanceAI(obj, GnomereganScriptName); } +enum DataTypes +{ + TYPE_GRUBBIS = 0, + MAX_ENCOUNTERS = 1 +}; + +enum GameObjects +{ + GO_CAVE_IN_1 = 146085, + GO_CAVE_IN_2 = 146086, + GO_WORKSHOP_DOOR = 90858, + GO_FINAL_CHAMBER_DOOR = 142207, +}; + +enum NPCs +{ + NPC_EMI_SHORTFUSE = 7998 +}; + #endif diff --git a/src/server/scripts/EasternKingdoms/Gnomeregan/instance_gnomeregan.cpp b/src/server/scripts/EasternKingdoms/Gnomeregan/instance_gnomeregan.cpp index 209fbe8a1..26767b10a 100644 --- a/src/server/scripts/EasternKingdoms/Gnomeregan/instance_gnomeregan.cpp +++ b/src/server/scripts/EasternKingdoms/Gnomeregan/instance_gnomeregan.cpp @@ -37,6 +37,74 @@ public: instance_gnomeregan_InstanceMapScript(Map* map) : InstanceScript(map) { } + + void OnCreatureCreate(Creature* creature) override + { + switch (creature->GetEntry()) + { + case NPC_EMI_SHORTFUSE: + if (_encounters[TYPE_GRUBBIS] == DONE) + { + creature->DespawnOrUnsummon(); + } + break; + } + } + + 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) + { + case TYPE_GRUBBIS: + _encounters[type] = data; + break; + } + + if (data == DONE) + SaveToDB(); + } + + std::string GetSaveData() override + { + std::ostringstream saveStream; + saveStream << "D E " << _encounters[0]; + return saveStream.str(); + } + + void Load(const char* in) override + { + if (!in) + return; + + char dataHead1, dataHead2; + std::istringstream loadStream(in); + loadStream >> dataHead1 >> dataHead2; + if (dataHead1 == 'D' && dataHead2 == 'E') + { + for (uint8 i = 0; i < MAX_ENCOUNTERS; ++i) + { + loadStream >> _encounters[i]; + if (_encounters[i] == IN_PROGRESS) + _encounters[i] = NOT_STARTED; + } + } + } + + private: + uint32 _encounters[MAX_ENCOUNTERS]; }; }; diff --git a/src/server/scripts/EasternKingdoms/Scholomance/instance_scholomance.cpp b/src/server/scripts/EasternKingdoms/Scholomance/instance_scholomance.cpp index 3a7cbe24b..19f51d319 100644 --- a/src/server/scripts/EasternKingdoms/Scholomance/instance_scholomance.cpp +++ b/src/server/scripts/EasternKingdoms/Scholomance/instance_scholomance.cpp @@ -62,6 +62,9 @@ public: 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/Scholomance/scholomance.h b/src/server/scripts/EasternKingdoms/Scholomance/scholomance.h index a53f33ebf..0382d9a8f 100644 --- a/src/server/scripts/EasternKingdoms/Scholomance/scholomance.h +++ b/src/server/scripts/EasternKingdoms/Scholomance/scholomance.h @@ -52,6 +52,8 @@ enum GameobjectIds GO_BRAZIER_KIRTONOS = 175564, GO_GATE_KIRTONOS = 175570, + GO_DOOR_OPENED_WITH_KEY = 175167, + GO_GATE_GANDLING_ENTRANCE = 177374, GO_GATE_GANDLING_DOWN_NORTH = 177371, diff --git a/src/server/scripts/EasternKingdoms/Stratholme/instance_stratholme.cpp b/src/server/scripts/EasternKingdoms/Stratholme/instance_stratholme.cpp index 506663351..4194235c8 100644 --- a/src/server/scripts/EasternKingdoms/Stratholme/instance_stratholme.cpp +++ b/src/server/scripts/EasternKingdoms/Stratholme/instance_stratholme.cpp @@ -171,55 +171,74 @@ public: { 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; } diff --git a/src/server/scripts/EasternKingdoms/Stratholme/stratholme.h b/src/server/scripts/EasternKingdoms/Stratholme/stratholme.h index c51eb32ec..16e7fc766 100644 --- a/src/server/scripts/EasternKingdoms/Stratholme/stratholme.h +++ b/src/server/scripts/EasternKingdoms/Stratholme/stratholme.h @@ -57,6 +57,11 @@ enum CreatureIds enum GameobjectIds { + GO_CRUSADER_SQUARE_DOOR = 175967, + GO_HOARD_DOOR = 175968, + GO_HALL_OF_HIGH_COMMAND = 176194, + GO_GAUNTLET_DOOR_1 = 175357, + GO_GAUNTLET_DOOR_2 = 175356, GO_ZIGGURAT_DOORS1 = 175380, // baroness GO_ZIGGURAT_DOORS2 = 175379, // nerub'enkan GO_ZIGGURAT_DOORS3 = 175381, // maleki From a4af1136856604e836a4ed6d9f8a65fbca9357bc Mon Sep 17 00:00:00 2001 From: AzerothCoreBot Date: Tue, 24 May 2022 13:35:53 +0000 Subject: [PATCH 02/11] chore(DB): import pending files Referenced commit(s): a6a2ca8ef76716f5b58b4ed2ae53d0bde424775b --- .../2022_05_24_00.sql} | 1 + .../rev_1649359139539727000.sql => db_world/2022_05_24_00.sql} | 1 + 2 files changed, 2 insertions(+) rename data/sql/updates/{pending_db_characters/rev_1643629468629316100.sql => db_characters/2022_05_24_00.sql} (89%) rename data/sql/updates/{pending_db_world/rev_1649359139539727000.sql => db_world/2022_05_24_00.sql} (94%) diff --git a/data/sql/updates/pending_db_characters/rev_1643629468629316100.sql b/data/sql/updates/db_characters/2022_05_24_00.sql similarity index 89% rename from data/sql/updates/pending_db_characters/rev_1643629468629316100.sql rename to data/sql/updates/db_characters/2022_05_24_00.sql index 7b4f3fba8..b194a17a1 100644 --- a/data/sql/updates/pending_db_characters/rev_1643629468629316100.sql +++ b/data/sql/updates/db_characters/2022_05_24_00.sql @@ -1,3 +1,4 @@ +-- DB update 2022_04_28_00 -> 2022_05_24_00 DROP TABLE IF EXISTS `instance_saved_go_state_data`; CREATE TABLE IF NOT EXISTS `instance_saved_go_state_data` ( diff --git a/data/sql/updates/pending_db_world/rev_1649359139539727000.sql b/data/sql/updates/db_world/2022_05_24_00.sql similarity index 94% rename from data/sql/updates/pending_db_world/rev_1649359139539727000.sql rename to data/sql/updates/db_world/2022_05_24_00.sql index 103dc3ac0..be16a41c2 100644 --- a/data/sql/updates/pending_db_world/rev_1649359139539727000.sql +++ b/data/sql/updates/db_world/2022_05_24_00.sql @@ -1,3 +1,4 @@ +-- DB update 2022_05_23_05 -> 2022_05_24_00 DELETE FROM `smart_scripts` WHERE (`entryorguid` = 7361) AND (`source_type` = 0) AND (`id` IN (1)); INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES From 7ef53bcda1cf6df4c46987de4d2983669d15a26f Mon Sep 17 00:00:00 2001 From: Hanabi <54484196+hanabi5@users.noreply.github.com> Date: Wed, 25 May 2022 03:19:08 +0100 Subject: [PATCH 03/11] fix(DB/Loot): Bear Flank drop rates (#11530) --- .../pending_db_world/rev_1650887143894550200.sql | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1650887143894550200.sql diff --git a/data/sql/updates/pending_db_world/rev_1650887143894550200.sql b/data/sql/updates/pending_db_world/rev_1650887143894550200.sql new file mode 100644 index 000000000..4795cc3b2 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1650887143894550200.sql @@ -0,0 +1,10 @@ +-- Insert missing Bear Flank drop rate for Rabid Shardtooth +DELETE FROM `creature_loot_template` WHERE `Entry` = 7446 AND `Item` = 35562; +INSERT INTO `creature_loot_template` (`Entry`, `Item`, `Chance`, `Comment`) VALUES (7446, 35562, 65, 'Rabid Shardtooth - Bear Flank'); + +-- Update Bears from level 47 to 60 with correct Bear Flank drop rates +UPDATE `creature_loot_template` SET `Chance` = 45 WHERE `Item` = 35562 AND `Entry` = 8957; +UPDATE `creature_loot_template` SET `Chance` = 50 WHERE `Item` = 35562 AND `Entry` IN(8956, 1815, 5274); +UPDATE `creature_loot_template` SET `Chance` = 55 WHERE `Item` = 35562 AND `Entry` IN(7444, 7443); +UPDATE `creature_loot_template` SET `Chance` = 60 WHERE `Item` = 35562 AND `Entry` = 8958; +UPDATE `creature_loot_template` SET `Chance` = 65 WHERE `Item` = 35562 AND `Entry` IN(1816, 7446, 7445); From a229a53ffaa4aa6aea8002a6ed1f2ae1ef9cb743 Mon Sep 17 00:00:00 2001 From: AzerothCoreBot Date: Wed, 25 May 2022 02:21:02 +0000 Subject: [PATCH 04/11] chore(DB): import pending files Referenced commit(s): 7ef53bcda1cf6df4c46987de4d2983669d15a26f --- .../rev_1650887143894550200.sql => db_world/2022_05_25_00.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1650887143894550200.sql => db_world/2022_05_25_00.sql} (95%) diff --git a/data/sql/updates/pending_db_world/rev_1650887143894550200.sql b/data/sql/updates/db_world/2022_05_25_00.sql similarity index 95% rename from data/sql/updates/pending_db_world/rev_1650887143894550200.sql rename to data/sql/updates/db_world/2022_05_25_00.sql index 4795cc3b2..d0e1386e8 100644 --- a/data/sql/updates/pending_db_world/rev_1650887143894550200.sql +++ b/data/sql/updates/db_world/2022_05_25_00.sql @@ -1,3 +1,4 @@ +-- DB update 2022_05_24_00 -> 2022_05_25_00 -- Insert missing Bear Flank drop rate for Rabid Shardtooth DELETE FROM `creature_loot_template` WHERE `Entry` = 7446 AND `Item` = 35562; INSERT INTO `creature_loot_template` (`Entry`, `Item`, `Chance`, `Comment`) VALUES (7446, 35562, 65, 'Rabid Shardtooth - Bear Flank'); From bc101780e815de73bd0b9eeef122be2ba4033246 Mon Sep 17 00:00:00 2001 From: avarishd <46330494+avarishd@users.noreply.github.com> Date: Wed, 25 May 2022 05:29:06 +0300 Subject: [PATCH 05/11] fix(DB/SAI): Commander Felstrom Infinite Loot Farm (#11744) --- .../updates/pending_db_world/rev_1652563880706540800.sql | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1652563880706540800.sql diff --git a/data/sql/updates/pending_db_world/rev_1652563880706540800.sql b/data/sql/updates/pending_db_world/rev_1652563880706540800.sql new file mode 100644 index 000000000..1bedb68eb --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1652563880706540800.sql @@ -0,0 +1,7 @@ +-- Commander Felstrom, set unlootable if resurrection goes through and despawn body after +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 771; +DELETE FROM `smart_scripts` WHERE `entryorguid` = 771; +INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES +(771, 0, 0, 0, 2, 0, 100, 1, 0, 10, 0, 0, 0, 11, 3488, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Commander Felstrom - Between 0-10% Health - Cast \'Felstrom Resurrection\' (No Repeat)'), +(771, 0, 1, 2, 8, 0, 100, 0, 3488, 0, 0, 0, 0, 94, 128, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Commander Felstrom - On Spellhit \'Felstrom Resurrection\' - Set Dynamic Flags Tapped By Threatlist'), +(771, 0, 2, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Commander Felstrom - On Spellhit \'Felstrom Resurrection\' - Despawn In 2000 ms'); From edfd2bba8e334507b447d30c7c1405274a4e9e02 Mon Sep 17 00:00:00 2001 From: AzerothCoreBot Date: Wed, 25 May 2022 02:31:16 +0000 Subject: [PATCH 06/11] chore(DB): import pending files Referenced commit(s): bc101780e815de73bd0b9eeef122be2ba4033246 --- .../rev_1652563880706540800.sql => db_world/2022_05_25_01.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1652563880706540800.sql => db_world/2022_05_25_01.sql} (96%) diff --git a/data/sql/updates/pending_db_world/rev_1652563880706540800.sql b/data/sql/updates/db_world/2022_05_25_01.sql similarity index 96% rename from data/sql/updates/pending_db_world/rev_1652563880706540800.sql rename to data/sql/updates/db_world/2022_05_25_01.sql index 1bedb68eb..98be51357 100644 --- a/data/sql/updates/pending_db_world/rev_1652563880706540800.sql +++ b/data/sql/updates/db_world/2022_05_25_01.sql @@ -1,3 +1,4 @@ +-- DB update 2022_05_25_00 -> 2022_05_25_01 -- Commander Felstrom, set unlootable if resurrection goes through and despawn body after UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 771; DELETE FROM `smart_scripts` WHERE `entryorguid` = 771; From b66586c1d7fc6809f8a0deb5493ca6fba939d69d Mon Sep 17 00:00:00 2001 From: UltraNix <80540499+UltraNix@users.noreply.github.com> Date: Wed, 25 May 2022 09:56:35 +0200 Subject: [PATCH 07/11] =?UTF-8?q?fix(Core/Spells):=20Fixed=20players=20bei?= =?UTF-8?q?ng=20able=20to=20mount=20with=20all=20transfor=E2=80=A6=20(#117?= =?UTF-8?q?67)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …m auras. Co-authored-by: Shauren --- .../rev_1652621127582675200.sql | 26 +++++++++ src/server/game/DataStores/DBCStores.cpp | 2 + src/server/game/DataStores/DBCStores.h | 1 + src/server/game/Entities/Unit/Unit.cpp | 55 +++++++++++++++++++ src/server/game/Entities/Unit/Unit.h | 11 +--- src/server/shared/DataStores/DBCStructure.h | 39 +++++++++++-- src/server/shared/DataStores/DBCfmt.h | 7 ++- 7 files changed, 126 insertions(+), 15 deletions(-) create mode 100644 data/sql/updates/pending_db_world/rev_1652621127582675200.sql diff --git a/data/sql/updates/pending_db_world/rev_1652621127582675200.sql b/data/sql/updates/pending_db_world/rev_1652621127582675200.sql new file mode 100644 index 000000000..a95dc6d3d --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1652621127582675200.sql @@ -0,0 +1,26 @@ +-- +DROP TABLE IF EXISTS `creaturedisplayinfoextra_dbc`; +CREATE TABLE `creaturedisplayinfoextra_dbc` +( `ID` INT UNSIGNED NOT NULL DEFAULT '0', +`DisplayRaceID` INT UNSIGNED NOT NULL DEFAULT '0', +`DisplaySexID` INT UNSIGNED NOT NULL DEFAULT '0', +`SkinID` INT UNSIGNED NOT NULL DEFAULT '0', +`FaceID` INT UNSIGNED NOT NULL DEFAULT '0', +`HairStyleID` INT UNSIGNED NOT NULL DEFAULT '0', +`HairColorID` INT UNSIGNED NOT NULL DEFAULT '0', +`FacialHairID` INT UNSIGNED NOT NULL DEFAULT '0', +`NPCItemDisplay1` INT UNSIGNED NOT NULL DEFAULT '0', +`NPCItemDisplay2` INT UNSIGNED NOT NULL DEFAULT '0', +`NPCItemDisplay3` INT UNSIGNED NOT NULL DEFAULT '0', +`NPCItemDisplay4` INT UNSIGNED NOT NULL DEFAULT '0', +`NPCItemDisplay5` INT UNSIGNED NOT NULL DEFAULT '0', +`NPCItemDisplay6` INT UNSIGNED NOT NULL DEFAULT '0', +`NPCItemDisplay7` INT UNSIGNED NOT NULL DEFAULT '0', +`NPCItemDisplay8` INT UNSIGNED NOT NULL DEFAULT '0', +`NPCItemDisplay9` INT UNSIGNED NOT NULL DEFAULT '0', +`NPCItemDisplay10` INT UNSIGNED NOT NULL DEFAULT '0', +`NPCItemDisplay11` INT UNSIGNED NOT NULL DEFAULT '0', +`Flags` INT UNSIGNED NOT NULL DEFAULT '0', +`BakeName` VARCHAR(100) NOT NULL, +PRIMARY KEY (`ID`) ) +ENGINE=MYISAM CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; diff --git a/src/server/game/DataStores/DBCStores.cpp b/src/server/game/DataStores/DBCStores.cpp index b9ff31654..c396c3c1e 100644 --- a/src/server/game/DataStores/DBCStores.cpp +++ b/src/server/game/DataStores/DBCStores.cpp @@ -56,6 +56,7 @@ DBCStorage sChrRacesStore(ChrRacesEntryfmt); DBCStorage sCinematicCameraStore(CinematicCameraEntryfmt); DBCStorage sCinematicSequencesStore(CinematicSequencesEntryfmt); DBCStorage sCreatureDisplayInfoStore(CreatureDisplayInfofmt); +DBCStorage sCreatureDisplayInfoExtraStore(CreatureDisplayInfoExtrafmt); DBCStorage sCreatureFamilyStore(CreatureFamilyfmt); DBCStorage sCreatureModelDataStore(CreatureModelDatafmt); DBCStorage sCreatureSpellDataStore(CreatureSpellDatafmt); @@ -279,6 +280,7 @@ void LoadDBCStores(const std::string& dataPath) LOAD_DBC(sCinematicCameraStore, "CinematicCamera.dbc", "cinematiccamera_dbc"); LOAD_DBC(sCinematicSequencesStore, "CinematicSequences.dbc", "cinematicsequences_dbc"); LOAD_DBC(sCreatureDisplayInfoStore, "CreatureDisplayInfo.dbc", "creaturedisplayinfo_dbc"); + LOAD_DBC(sCreatureDisplayInfoExtraStore, "CreatureDisplayInfoExtra.dbc", "creaturedisplayinfoextra_dbc"); LOAD_DBC(sCreatureFamilyStore, "CreatureFamily.dbc", "creaturefamily_dbc"); LOAD_DBC(sCreatureModelDataStore, "CreatureModelData.dbc", "creaturemodeldata_dbc"); LOAD_DBC(sCreatureSpellDataStore, "CreatureSpellData.dbc", "creaturespelldata_dbc"); diff --git a/src/server/game/DataStores/DBCStores.h b/src/server/game/DataStores/DBCStores.h index 046071e06..cd122c788 100644 --- a/src/server/game/DataStores/DBCStores.h +++ b/src/server/game/DataStores/DBCStores.h @@ -90,6 +90,7 @@ extern DBCStorage sChrRacesStore; extern DBCStorage sCinematicCameraStore; extern DBCStorage sCinematicSequencesStore; extern DBCStorage sCreatureDisplayInfoStore; +extern DBCStorage sCreatureDisplayInfoExtraStore; extern DBCStorage sCreatureFamilyStore; extern DBCStorage sCreatureModelDataStore; extern DBCStorage sCreatureSpellDataStore; diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index aa6f0891a..1595ddcb4 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -20669,3 +20669,58 @@ bool Unit::CanRestoreMana(SpellInfo const* spellInfo) const return false; } + +bool Unit::IsInDisallowedMountForm() const +{ + if (SpellInfo const* transformSpellInfo = sSpellMgr->GetSpellInfo(getTransForm())) + { + if (transformSpellInfo->HasAttribute(SPELL_ATTR0_ALLOW_WHILE_MOUNTED)) + { + return false; + } + } + + if (ShapeshiftForm form = GetShapeshiftForm()) + { + SpellShapeshiftEntry const* shapeshift = sSpellShapeshiftStore.LookupEntry(form); + if (!shapeshift) + { + return true; + } + + if (!(shapeshift->flags1 & 0x1)) + { + return true; + } + } + + if (GetDisplayId() == GetNativeDisplayId()) + { + return false; + } + + CreatureDisplayInfoEntry const* display = sCreatureDisplayInfoStore.LookupEntry(GetDisplayId()); + if (!display) + { + return true; + } + + CreatureDisplayInfoExtraEntry const* displayExtra = sCreatureDisplayInfoExtraStore.LookupEntry(display->ExtendedDisplayInfoID); + if (!displayExtra) + { + return true; + } + + CreatureModelDataEntry const* model = sCreatureModelDataStore.LookupEntry(display->ModelId); + ChrRacesEntry const* race = sChrRacesStore.LookupEntry(displayExtra->DisplayRaceID); + + if (model && !(model->HasFlag(CREATURE_MODEL_DATA_FLAGS_CAN_MOUNT))) + { + if (race && !(race->HasFlag(CHRRACES_FLAGS_CAN_MOUNT))) + { + return true; + } + } + + return false; +} diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 02f2943ff..55b133428 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -2034,18 +2034,13 @@ public: SetByteValue(UNIT_FIELD_BYTES_2, 3, form); } - [[nodiscard]] inline bool IsInFeralForm() const + [[nodiscard]] bool IsInFeralForm() const { ShapeshiftForm form = GetShapeshiftForm(); return form == FORM_CAT || form == FORM_BEAR || form == FORM_DIREBEAR || form == FORM_GHOSTWOLF; // Xinef: added shamans Ghost Wolf, should behave exactly like druid forms } - [[nodiscard]] inline bool IsInDisallowedMountForm() const - { - ShapeshiftForm form = GetShapeshiftForm(); - return form != FORM_NONE && form != FORM_BATTLESTANCE && form != FORM_BERSERKERSTANCE && form != FORM_DEFENSIVESTANCE && - form != FORM_SHADOW && form != FORM_STEALTH && form != FORM_UNDEAD; - } + [[nodiscard]] bool IsInDisallowedMountForm() const; float m_modMeleeHitChance; float m_modRangedHitChance; @@ -2127,7 +2122,7 @@ public: void AddInterruptMask(uint32 mask) { m_interruptMask |= mask; } void UpdateInterruptMask(); - uint32 GetDisplayId() { return GetUInt32Value(UNIT_FIELD_DISPLAYID); } + [[nodiscard]] uint32 GetDisplayId() const { return GetUInt32Value(UNIT_FIELD_DISPLAYID); } virtual void SetDisplayId(uint32 modelId); [[nodiscard]] uint32 GetNativeDisplayId() const { return GetUInt32Value(UNIT_FIELD_NATIVEDISPLAYID); } void RestoreDisplayId(); diff --git a/src/server/shared/DataStores/DBCStructure.h b/src/server/shared/DataStores/DBCStructure.h index 63a5a1075..7ee64900e 100644 --- a/src/server/shared/DataStores/DBCStructure.h +++ b/src/server/shared/DataStores/DBCStructure.h @@ -667,10 +667,17 @@ struct ChrClassesEntry uint32 expansion; // 59 (0 - original race, 1 - tbc addon, ...) }; +enum ChrRacesFlags +{ + CHRRACES_FLAGS_NOT_PLAYABLE = 0x01, + CHRRACES_FLAGS_BARE_FEET = 0x02, + CHRRACES_FLAGS_CAN_MOUNT = 0x04 +}; + struct ChrRacesEntry { uint32 RaceID; // 0 - // 1 unused + uint32 Flags; // 1 uint32 FactionID; // 2 facton template id // 3 unused uint32 model_m; // 4 @@ -688,6 +695,8 @@ struct ChrRacesEntry // 64 string flags, unused // 65-67 unused uint32 expansion; // 68 (0 - original race, 1 - tbc addon, ...) + + inline bool HasFlag(ChrRacesFlags flag) const { return (Flags & flag) != 0; } }; struct CinematicCameraEntry @@ -712,7 +721,7 @@ struct CreatureDisplayInfoEntry uint32 Displayid; // 0 m_ID uint32 ModelId; // 1 m_modelID // 2 m_soundID - // 3 m_extendedDisplayInfoID + uint32 ExtendedDisplayInfoID; // 3 float scale; // 4 m_creatureModelScale // 5 m_creatureModelAlpha // 6-8 m_textureVariation[3] @@ -725,6 +734,21 @@ struct CreatureDisplayInfoEntry // 15 m_objectEffectPackageID }; +struct CreatureDisplayInfoExtraEntry +{ + //uint32 ID; // 0 + uint32 DisplayRaceID; // 1 + uint32 DisplaySexID; // 2 + //uint32 SkinID; // 3 + //uint32 FaceID; // 4 + //uint32 HairStyleID; // 5 + //uint32 HairColorID; // 6 + //uint32 FacialHairID; // 7 + //uint32 NPCItemDisplay[11]; // 8-18 + //uint32 Flags; // 19 + //char const* BakeName; // 20 +}; + struct CreatureFamilyEntry { uint32 ID; // 0 m_ID @@ -741,11 +765,16 @@ struct CreatureFamilyEntry // 27 m_iconFile }; +enum CreatureModelDataFlags +{ + CREATURE_MODEL_DATA_FLAGS_CAN_MOUNT = 0x00000080 +}; + struct CreatureModelDataEntry { uint32 Id; - //uint32 Flags; - //char const* ModelPath[16] + uint32 Flags; + //char const* ModelPath //uint32 Unk1; float Scale; // Used in calculation of unit collision data //int32 Unk2 @@ -761,6 +790,8 @@ struct CreatureModelDataEntry float CollisionHeight; float MountHeight; // Used in calculation of unit collision data when mounted //float Unks[11] + + inline bool HasFlag(CreatureModelDataFlags flag) const { return (Flags & flag) != 0; } }; #define MAX_CREATURE_SPELL_DATA_SLOT 4 diff --git a/src/server/shared/DataStores/DBCfmt.h b/src/server/shared/DataStores/DBCfmt.h index 0cf21d8b3..7dc5867da 100644 --- a/src/server/shared/DataStores/DBCfmt.h +++ b/src/server/shared/DataStores/DBCfmt.h @@ -32,12 +32,13 @@ char constexpr CharStartOutfitEntryfmt[] = "dbbbXiiiiiiiiiiiiiiiiiiiiiiiixxxxxxx char constexpr CharTitlesEntryfmt[] = "nxssssssssssssssssxssssssssssssssssxi"; char constexpr ChatChannelsEntryfmt[] = "nixssssssssssssssssxxxxxxxxxxxxxxxxxx"; // ChatChannelsEntryfmt, index not used (more compact store) char constexpr ChrClassesEntryfmt[] = "nxixssssssssssssssssxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxixii"; -char constexpr ChrRacesEntryfmt[] = "nxixiixixxxxixssssssssssssssssxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxi"; +char constexpr ChrRacesEntryfmt[] = "niixiixixxxxixssssssssssssssssxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxi"; char constexpr CinematicCameraEntryfmt[] = "nsiffff"; char constexpr CinematicSequencesEntryfmt[] = "nxixxxxxxx"; -char constexpr CreatureDisplayInfofmt[] = "nixxfxxxxxxxxxxx"; +char constexpr CreatureDisplayInfofmt[] = "nixifxxxxxxxxxxx"; +char constexpr CreatureDisplayInfoExtrafmt[] = "diixxxxxxxxxxxxxxxxxx"; char constexpr CreatureFamilyfmt[] = "nfifiiiiixssssssssssssssssxx"; -char constexpr CreatureModelDatafmt[] = "nxxxfxxxxxxxxxfffxxxxxxxxxxx"; +char constexpr CreatureModelDatafmt[] = "nixxfxxxxxxxxxfffxxxxxxxxxxx"; char constexpr CreatureSpellDatafmt[] = "niiiixxxx"; char constexpr CreatureTypefmt[] = "nxxxxxxxxxxxxxxxxxx"; char constexpr CurrencyTypesfmt[] = "xnxi"; From 5912f654df0c57275c4a23bc2a7f0a8e6d2880f3 Mon Sep 17 00:00:00 2001 From: AzerothCoreBot Date: Wed, 25 May 2022 07:58:38 +0000 Subject: [PATCH 08/11] chore(DB): import pending files Referenced commit(s): b66586c1d7fc6809f8a0deb5493ca6fba939d69d --- .../rev_1652621127582675200.sql => db_world/2022_05_25_02.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1652621127582675200.sql => db_world/2022_05_25_02.sql} (96%) diff --git a/data/sql/updates/pending_db_world/rev_1652621127582675200.sql b/data/sql/updates/db_world/2022_05_25_02.sql similarity index 96% rename from data/sql/updates/pending_db_world/rev_1652621127582675200.sql rename to data/sql/updates/db_world/2022_05_25_02.sql index a95dc6d3d..7fc9463c9 100644 --- a/data/sql/updates/pending_db_world/rev_1652621127582675200.sql +++ b/data/sql/updates/db_world/2022_05_25_02.sql @@ -1,3 +1,4 @@ +-- DB update 2022_05_25_01 -> 2022_05_25_02 -- DROP TABLE IF EXISTS `creaturedisplayinfoextra_dbc`; CREATE TABLE `creaturedisplayinfoextra_dbc` From 7b4e1ef7d3d638f4ed182130af286a8f99528f53 Mon Sep 17 00:00:00 2001 From: UltraNix <80540499+UltraNix@users.noreply.github.com> Date: Wed, 25 May 2022 10:00:05 +0200 Subject: [PATCH 09/11] fix(Core/Spells): Shadow Weaving should not proc off from DoTs. (#11825) --- src/server/game/Spells/SpellInfoCorrections.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/server/game/Spells/SpellInfoCorrections.cpp b/src/server/game/Spells/SpellInfoCorrections.cpp index 126f7a5e0..f2d8a9de0 100644 --- a/src/server/game/Spells/SpellInfoCorrections.cpp +++ b/src/server/game/Spells/SpellInfoCorrections.cpp @@ -1200,13 +1200,6 @@ void SpellMgr::LoadSpellInfoCorrections() spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER_AREA_PARTY); }); - // Shadow Weaving - ApplySpellFix({ 15257, 15331, 15332 }, [](SpellInfo* spellInfo) - { - spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); - spellInfo->Effects[EFFECT_0].ApplyAuraName = SPELL_AURA_PROC_TRIGGER_SPELL; - }); - // Hymn of Hope ApplySpellFix({ 64904 }, [](SpellInfo* spellInfo) { From 84ccf508efd01f418ad22dc50caefc02fe273a66 Mon Sep 17 00:00:00 2001 From: mpfans Date: Wed, 25 May 2022 19:50:49 +0800 Subject: [PATCH 10/11] fix(DB/Translation): Chinese translation of two quests (#11867) fix 2 quests Chinese Translation * update `Slaves of the Stormforged` quest Chinese translation * update `Waterlogged Recipe` quest Chinese translation --- .../sql/updates/pending_db_world/rev_1653462380744994669.sql | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1653462380744994669.sql diff --git a/data/sql/updates/pending_db_world/rev_1653462380744994669.sql b/data/sql/updates/pending_db_world/rev_1653462380744994669.sql new file mode 100644 index 000000000..3c6b8bdc9 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1653462380744994669.sql @@ -0,0 +1,5 @@ +-- update `Slaves of the Stormforged` quest Chinese translation +UPDATE `quest_template_locale` SET `ObjectiveText1` = '解救机械侏儒俘虏' WHERE `ID` = 12957 AND `locale` = 'zhCN'; + +-- update `Waterlogged Recipe` quest Chinese translation +UPDATE `quest_template_locale` SET `CompletedText` = '去达拉然找再来一杯的克莉丝蒂·斯多克顿。' WHERE `ID` = 14203 AND `locale` = 'zhCN'; From 3772582e5c62dcb8f0ce2b471f97e78567f52601 Mon Sep 17 00:00:00 2001 From: AzerothCoreBot Date: Wed, 25 May 2022 11:52:59 +0000 Subject: [PATCH 11/11] chore(DB): import pending files Referenced commit(s): 84ccf508efd01f418ad22dc50caefc02fe273a66 --- .../rev_1653462380744994669.sql => db_world/2022_05_25_03.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1653462380744994669.sql => db_world/2022_05_25_03.sql} (90%) diff --git a/data/sql/updates/pending_db_world/rev_1653462380744994669.sql b/data/sql/updates/db_world/2022_05_25_03.sql similarity index 90% rename from data/sql/updates/pending_db_world/rev_1653462380744994669.sql rename to data/sql/updates/db_world/2022_05_25_03.sql index 3c6b8bdc9..0d0a9f07f 100644 --- a/data/sql/updates/pending_db_world/rev_1653462380744994669.sql +++ b/data/sql/updates/db_world/2022_05_25_03.sql @@ -1,3 +1,4 @@ +-- DB update 2022_05_25_02 -> 2022_05_25_03 -- update `Slaves of the Stormforged` quest Chinese translation UPDATE `quest_template_locale` SET `ObjectiveText1` = '解救机械侏儒俘虏' WHERE `ID` = 12957 AND `locale` = 'zhCN';