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 <dowlandtop@yandex.com>

* 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 <dowlandtop@yandex.com>
Co-authored-by: Skjalf <47818697+Nyeriah@users.noreply.github.com>
This commit is contained in:
Hanabi
2022-05-24 14:33:45 +01:00
committed by GitHub
parent 305a2689e9
commit a6a2ca8ef7
22 changed files with 502 additions and 6 deletions

View File

@@ -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;

View File

@@ -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');

View File

@@ -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)

View File

@@ -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
};

View File

@@ -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(&param));
}
}
}
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)

View File

@@ -1010,6 +1010,25 @@ public:
static std::unordered_map<int, goEventFlag> 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

View File

@@ -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<InstanceSave*>::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<InstanceSave*>::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<InstanceSave*>::const_iterator itr = toUnbind.begin(); itr != toUnbind.end(); ++itr)
sInstanceSaveMgr->PlayerUnbindInstance(p->GetGUID(), (*itr)->GetMapId(), (*itr)->GetDifficulty(), true, p);

View File

@@ -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<uint32>(), fields[1].Get<uint32>(), fields[2].Get<unsigned short>() });
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)

View File

@@ -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<uint32> _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<GameobjectInstanceSavedState> GameobjectInstanceSavedStateList;
};
#define sObjectMgr ObjectMgr::instance()

View File

@@ -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<InstanceSave*>::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<InstanceSave*>::const_iterator itr = toUnbind.begin(); itr != toUnbind.end(); ++itr)
sInstanceSaveMgr->UnbindAllFor(*itr);

View File

@@ -314,9 +314,10 @@ public:
uint32 GetDifficultyChangePreventionTime() const;
DifficultyPreventionChangeType GetDifficultyChangePreventionReason() const { return _difficultyChangePreventionType; }
void SetDifficultyChangePrevention(DifficultyPreventionChangeType type);
void DoForAllMembers(std::function<void(Player*)> const& worker);
// Reset Instance Gameobjects
void ResetInstanceSavedGameobjects(uint32 instanceId);
protected:
void _homebindIfInstance(Player* player);
void _cancelHomebindIfInstance(Player* player);

View File

@@ -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", " ");
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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 <class AI, class T>

View File

@@ -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;
}
}

View File

@@ -28,4 +28,23 @@ inline AI* GetGnomereganAI(T* obj)
return GetInstanceAI<AI>(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

View File

@@ -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];
};
};

View File

@@ -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;

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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