diff --git a/sql/mangos.sql b/sql/mangos.sql index 20a912ef5..107301a90 100644 --- a/sql/mangos.sql +++ b/sql/mangos.sql @@ -24,7 +24,7 @@ CREATE TABLE `db_version` ( `version` varchar(120) default NULL, `creature_ai_version` varchar(120) default NULL, `cache_id` int(10) default '0', - `required_11985_01_mangos_gameobject_template_scripts` bit(1) default NULL + `required_11994_01_mangos_creature_linking` bit(1) default NULL ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED COMMENT='Used DB version notes'; -- @@ -966,6 +966,28 @@ LOCK TABLES `creature_involvedrelation` WRITE; /*!40000 ALTER TABLE `creature_involvedrelation` ENABLE KEYS */; UNLOCK TABLES; +-- +-- Table structure for table `creature_linking` +-- + +DROP TABLE IF EXISTS creature_linking; +CREATE TABLE `creature_linking` ( + `guid` int(10) UNSIGNED NOT NULL COMMENT 'creature.guid of the slave mob that is linked', + `master_guid` int(10) UNSIGNED NOT NULL COMMENT 'master to trigger events', + `flag` mediumint(8) UNSIGNED NOT NULL COMMENT 'flag - describing what should happen when', + PRIMARY KEY (`guid`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED COMMENT='Creature Linking System'; + + +-- +-- Dumping data for table `creature_linking` +-- + +LOCK TABLES `creature_linking` WRITE; +/*!40000 ALTER TABLE `creature_linking` DISABLE KEYS */; +/*!40000 ALTER TABLE `creature_linking` ENABLE KEYS */; +UNLOCK TABLES; + -- -- Table structure for table `creature_linking_template` -- diff --git a/sql/updates/11994_01_mangos_creature_linking.sql b/sql/updates/11994_01_mangos_creature_linking.sql new file mode 100644 index 000000000..7f10a293b --- /dev/null +++ b/sql/updates/11994_01_mangos_creature_linking.sql @@ -0,0 +1,13 @@ +ALTER TABLE db_version CHANGE COLUMN required_11985_01_mangos_gameobject_template_scripts required_11994_01_mangos_creature_linking bit; + +-- +-- Table structure for table `creature_linking` +-- + +DROP TABLE IF EXISTS creature_linking; +CREATE TABLE `creature_linking` ( + `guid` int(10) UNSIGNED NOT NULL COMMENT 'creature.guid of the slave mob that is linked', + `master_guid` int(10) UNSIGNED NOT NULL COMMENT 'master to trigger events', + `flag` mediumint(8) UNSIGNED NOT NULL COMMENT 'flag - describing what should happen when', + PRIMARY KEY (`guid`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED COMMENT='Creature Linking System'; diff --git a/src/game/CreatureLinkingMgr.cpp b/src/game/CreatureLinkingMgr.cpp index 304798496..093321c22 100644 --- a/src/game/CreatureLinkingMgr.cpp +++ b/src/game/CreatureLinkingMgr.cpp @@ -43,15 +43,20 @@ INSTANTIATE_SINGLETON_1(CreatureLinkingMgr); +#define INVALID_MAP_ID 0xFFFFFFFF + /* ********************************************************* * Method to Load From DB - * DB Format: entry, map, master_entry, flag - * 0 1 2 3 + * DB Format: entry, map, master_entry, flag, search_radius for `creature_linking_template` (by entry) + * 0 1 2 3 4 + * DB Format: guid, master_guid, flag for `creature_linking` (by guid) + * 0 1 2 * ************************************** - * entry: creature_template.entry - * map: Map on which the NPC has to be - * master_entry creature_template.entry of the npc, that shall trigger the actions - * flag: flag value, of type CreatureLinkingFlags + * entry/guid: creature_template.entry/guid + * map: Map on which the NPC has to be + * master_entry/master_guid creature_template.entry of the npc, that shall trigger the actions + * flag: flag value, of type CreatureLinkingFlags + * search_radius: radius, in which master and slave must be spawned so that they are linked together * * ***************************************************** */ @@ -59,7 +64,9 @@ void CreatureLinkingMgr::LoadFromDB() { // Clear maps m_creatureLinkingMap.clear(); + m_creatureLinkingGuidMap.clear(); m_eventTriggers.clear(); // master + m_eventGuidTriggers.clear(); QueryResult* result = WorldDatabase.Query("SELECT entry, map, master_entry, flag, search_range FROM creature_linking_template"); @@ -77,7 +84,6 @@ void CreatureLinkingMgr::LoadFromDB() } BarGoLink bar((int)result->GetRowCount()); - do { bar.step(); @@ -92,7 +98,7 @@ void CreatureLinkingMgr::LoadFromDB() tmp.searchRange = fields[4].GetUInt16(); tmp.masterDBGuid = 0; // Will be initialized for unique mobs later (only for spawning dependend) - if (!IsLinkingEntryValid(entry, &tmp)) + if (!IsLinkingEntryValid(entry, &tmp, true)) continue; // Store db-guid for master of whom pTmp is spawn dependend (only non-local bosses) @@ -117,55 +123,136 @@ void CreatureLinkingMgr::LoadFromDB() while (result->NextRow()); sLog.outString(); - sLog.outString(">> Loaded creature linking for %u creature-IDs", count); + sLog.outString(">> Loaded creature linking for %u creature-entries", count); + + delete result; + + result = WorldDatabase.Query("SELECT guid, master_guid, flag FROM creature_linking"); + + count = 0; + + if (!result) + { + BarGoLink bar(1); + bar.step(); + + sLog.outString(">> Table creature_linking is empty."); + sLog.outString(); + + return; + } + + BarGoLink guidBar((int)result->GetRowCount()); + do + { + guidBar.step(); + + Field* fields = result->Fetch(); + CreatureLinkingInfo tmp; + + uint32 guid = fields[0].GetUInt32(); + tmp.mapId = INVALID_MAP_ID; // some invalid value, this marks the guid-linking + tmp.masterId = fields[1].GetUInt32(); + tmp.linkingFlag = fields[2].GetUInt16(); + tmp.masterDBGuid = tmp.masterId; + tmp.searchRange = 0; + + if (!IsLinkingEntryValid(guid, &tmp, false)) + continue; + + ++count; + + // Add it to the map + m_creatureLinkingGuidMap.insert(CreatureLinkingMap::value_type(guid, tmp)); + + // Store master_guid + m_eventGuidTriggers.insert(tmp.masterId); + } + while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded creature linking for %u creature-Guids", count); + delete result; } // This function is used to check if a DB-Entry is valid -bool CreatureLinkingMgr::IsLinkingEntryValid(uint32 slaveEntry, CreatureLinkingInfo* pTmp) +bool CreatureLinkingMgr::IsLinkingEntryValid(uint32 slaveEntry, CreatureLinkingInfo* pTmp, bool byEntry) { // Basic checks first - CreatureInfo const* pInfo = ObjectMgr::GetCreatureTemplate(slaveEntry); - if (!pInfo) + if (byEntry) // Entry given { - sLog.outErrorDb("`creature_linking_template` has a non existing slave_entry (ID: %u), skipped.", slaveEntry); - return false; - } + CreatureInfo const* pInfo = ObjectMgr::GetCreatureTemplate(slaveEntry); + CreatureInfo const* pMasterInfo = ObjectMgr::GetCreatureTemplate(pTmp->masterId); - pInfo = ObjectMgr::GetCreatureTemplate(pTmp->masterId); - if (!pInfo) + if (!pInfo) + { + sLog.outErrorDb("`creature_linking_template` has a non existing slave_entry (slave: %u, master %u), skipped.", slaveEntry, pTmp->masterId); + return false; + } + if (!pMasterInfo) + { + sLog.outErrorDb("`creature_linking_template` has a non existing master_entry (slave: %u, master %u), skipped", slaveEntry, pTmp->masterId); + return false; + } + if (pTmp->mapId && !sMapStore.LookupEntry(pTmp->mapId)) + { + sLog.outErrorDb("`creature_linking_template` has a non existing map %u (slave %u, master %u), skipped", pTmp->mapId, slaveEntry, pTmp->masterId); + return false; + } + } + else // guid given { - sLog.outErrorDb("`creature_linking_template` has a non existing master_entry (ID: %u), skipped", pTmp->masterId); - return false; + CreatureData const* slaveData = sObjectMgr.GetCreatureData(slaveEntry); + CreatureData const* masterData = sObjectMgr.GetCreatureData(pTmp->masterId); + + if (!slaveData) + { + sLog.outErrorDb("`creature_linking` has a non existing slave (guid: %u, master_guid %u), skipped", slaveEntry, pTmp->masterId); + return false; + } + if (!masterData) + { + sLog.outErrorDb("`creature_linking` has a non existing master (guid: %u,, master_guid: %u), skipped", slaveEntry, pTmp->masterId); + return false; + } + if (slaveData->mapid != masterData->mapid) + { + sLog.outErrorDb("`creature_linking` has a slave and master on different maps (guid: %u, master_guid: %u), skipped", slaveEntry, pTmp->masterId); + return false; + } } if (pTmp->linkingFlag & ~(LINKING_FLAG_INVALID - 1) || pTmp->linkingFlag == 0) { - sLog.outErrorDb("`creature_linking_template` has invalid flag, (entry: %u, map: %u, flags: %u), skipped", slaveEntry, pTmp->mapId, pTmp->linkingFlag); + sLog.outErrorDb("`creature_linking%s` has invalid flag, (entry: %u, map: %u, flags: %u), skipped", byEntry ? "_template" : "", slaveEntry, pTmp->mapId, pTmp->linkingFlag); return false; } // Additional checks, depending on flags if (pTmp->linkingFlag & FLAG_DESPAWN_ON_RESPAWN && slaveEntry == pTmp->masterId) { - sLog.outErrorDb("`creature_linking_template` has pointless FLAG_DESPAWN_ON_RESPAWN for self, (entry: %u, map: %u), skipped", slaveEntry, pTmp->mapId); + sLog.outErrorDb("`creature_linking%s` has pointless FLAG_DESPAWN_ON_RESPAWN for self, (entry: %u, map: %u), skipped", byEntry ? "_template" : "", slaveEntry, pTmp->mapId); return false; } - // Check for uniqueness of mob whom is followed, on whom spawning is dependend - if (pTmp->searchRange == 0 && pTmp->linkingFlag & (FLAG_FOLLOW | FLAG_CANT_SPAWN_IF_BOSS_DEAD | FLAG_CANT_SPAWN_IF_BOSS_ALIVE)) + if (byEntry) { - // Painfully slow, needs better idea - QueryResult *result = WorldDatabase.PQuery("SELECT COUNT(guid) FROM creature WHERE id=%u AND map=%u", pTmp->masterId, pTmp->mapId); - if (result) + // Check for uniqueness of mob whom is followed, on whom spawning is dependend + if (pTmp->searchRange == 0 && pTmp->linkingFlag & (FLAG_FOLLOW | FLAG_CANT_SPAWN_IF_BOSS_DEAD | FLAG_CANT_SPAWN_IF_BOSS_ALIVE)) { - if ((*result)[0].GetUInt32() > 1) - sLog.outErrorDb("`creature_linking_template` has FLAG_FOLLOW, but non unique master, (entry: %u, map: %u, master: %u)", slaveEntry, pTmp->mapId, pTmp->masterId); - delete result; + // Painfully slow, needs better idea + QueryResult *result = WorldDatabase.PQuery("SELECT COUNT(guid) FROM creature WHERE id=%u AND map=%u", pTmp->masterId, pTmp->mapId); + if (result) + { + if ((*result)[0].GetUInt32() > 1) + sLog.outErrorDb("`creature_linking_template` has FLAG_FOLLOW, but non unique master, (entry: %u, map: %u, master: %u)", slaveEntry, pTmp->mapId, pTmp->masterId); + delete result; + } } } - // All checks are passed, entry is valid + // All checks are passed, entry/guid is valid return true; } @@ -182,11 +269,14 @@ enum EventMask // This functions checks if the NPC has linked NPCs for dynamic action bool CreatureLinkingMgr::IsLinkedEventTrigger(Creature* pCreature) { - // TODO could actually be improved to also check for the map - // Depends if we want to cache this bool into Creature or not + // Entry case if (m_eventTriggers.find(pCreature->GetEntry()) != m_eventTriggers.end()) return true; + // Guid case + if (m_eventGuidTriggers.find(pCreature->GetGUIDLow()) != m_eventGuidTriggers.end()) + return true; + // Also return true for npcs that trigger reverse actions, or for followers(needed in respawn) if (CreatureLinkingInfo const* pInfo = GetLinkedTriggerInformation(pCreature)) return pInfo->linkingFlag & EVENT_MASK_TRIGGER_TO; @@ -195,6 +285,7 @@ bool CreatureLinkingMgr::IsLinkedEventTrigger(Creature* pCreature) } // This function check if the NPC is a master to other NPCs +// return true only for masters stored by entry - this prevents adding them to master-holder maps bool CreatureLinkingMgr::IsLinkedMaster(Creature* pCreature) { return m_eventTriggers.find(pCreature->GetEntry()) != m_eventTriggers.end(); @@ -212,7 +303,13 @@ bool CreatureLinkingMgr::IsSpawnedByLinkedMob(Creature* pCreature) // Depends of the map CreatureLinkingInfo const* CreatureLinkingMgr::GetLinkedTriggerInformation(Creature* pCreature) { - CreatureLinkingMapBounds bounds = m_creatureLinkingMap.equal_range(pCreature->GetEntry()); + // guid case + CreatureLinkingMapBounds bounds = m_creatureLinkingGuidMap.equal_range(pCreature->GetGUIDLow()); + for (CreatureLinkingMap::const_iterator iter = bounds.first; iter != bounds.second; ++iter) + return &(iter->second); + + // entry case + bounds = m_creatureLinkingMap.equal_range(pCreature->GetEntry()); for (CreatureLinkingMap::const_iterator iter = bounds.first; iter != bounds.second; ++iter) { if (iter->second.mapId == pCreature->GetMapId()) @@ -229,6 +326,31 @@ void CreatureLinkingHolder::AddSlaveToHolder(Creature* pCreature) if (!pInfo) return; + if (pInfo->mapId == INVALID_MAP_ID) // Guid case, store master->slaves for fast access + { + HolderMapBounds bounds = m_holderGuidMap.equal_range(pInfo->masterId); + for (HolderMap::iterator itr = bounds.first; itr != bounds.second; ++itr) + { + if (itr->second.linkingFlag == pInfo->linkingFlag) + { + itr->second.linkedGuids.push_back(pCreature->GetObjectGuid()); + pCreature = NULL; // Store that is was handled + break; + } + } + + // If this is a new flag, insert new entry + if (pCreature) + { + InfoAndGuids tmp; + tmp.linkedGuids.push_back(pCreature->GetObjectGuid()); + tmp.linkingFlag = pInfo->linkingFlag; + tmp.searchRange = 0; + m_holderGuidMap.insert(HolderMap::value_type(pInfo->masterId, tmp)); + } + return; + } + // First try to find holder with same flag HolderMapBounds bounds = m_holderMap.equal_range(pInfo->masterId); for (HolderMap::iterator itr = bounds.first; itr != bounds.second; ++itr) @@ -258,7 +380,7 @@ void CreatureLinkingHolder::AddMasterToHolder(Creature* pCreature) if (pCreature->IsPet()) return; - // Only add master NPCs + // Only add master NPCs (by entry) if (!sCreatureLinkingMgr.IsLinkedMaster(pCreature)) return; @@ -290,15 +412,19 @@ void CreatureLinkingHolder::DoCreatureLinkingEvent(CreatureLinkingEvent eventTyp switch (eventType) { - case LINKING_EVENT_AGGRO: eventFlagFilter = EVENT_MASK_ON_AGGRO; reverseEventFlagFilter = FLAG_TO_AGGRO_ON_AGGRO; break; - case LINKING_EVENT_EVADE: eventFlagFilter = EVENT_MASK_ON_EVADE; reverseEventFlagFilter = FLAG_TO_RESPAWN_ON_EVADE; break; - case LINKING_EVENT_DIE: eventFlagFilter = EVENT_MASK_ON_DIE; reverseEventFlagFilter = 0; break; + case LINKING_EVENT_AGGRO: eventFlagFilter = EVENT_MASK_ON_AGGRO; reverseEventFlagFilter = FLAG_TO_AGGRO_ON_AGGRO; break; + case LINKING_EVENT_EVADE: eventFlagFilter = EVENT_MASK_ON_EVADE; reverseEventFlagFilter = FLAG_TO_RESPAWN_ON_EVADE; break; + case LINKING_EVENT_DIE: eventFlagFilter = EVENT_MASK_ON_DIE; reverseEventFlagFilter = 0; break; case LINKING_EVENT_RESPAWN: eventFlagFilter = EVENT_MASK_ON_RESPAWN; reverseEventFlagFilter = FLAG_FOLLOW; break; } - // Process Slaves + // Process Slaves (by entry) HolderMapBounds bounds = m_holderMap.equal_range(pSource->GetEntry()); - // Get all holders for this boss + for (HolderMap::iterator itr = bounds.first; itr != bounds.second; ++itr) + ProcessSlaveGuidList(eventType, pSource, itr->second.linkingFlag & eventFlagFilter, itr->second.searchRange, itr->second.linkedGuids, pEnemy); + + // Process Slaves (by guid) + bounds = m_holderGuidMap.equal_range(pSource->GetGUIDLow()); for (HolderMap::iterator itr = bounds.first; itr != bounds.second; ++itr) ProcessSlaveGuidList(eventType, pSource, itr->second.linkingFlag & eventFlagFilter, itr->second.searchRange, itr->second.linkedGuids, pEnemy); @@ -307,31 +433,44 @@ void CreatureLinkingHolder::DoCreatureLinkingEvent(CreatureLinkingEvent eventTyp { if (pInfo->linkingFlag & reverseEventFlagFilter) { - BossGuidMapBounds finds = m_masterGuid.equal_range(pInfo->masterId); - for (BossGuidMap::iterator itr = finds.first; itr != finds.second; ++itr) + Creature* pMaster = NULL; + if (pInfo->mapId != INVALID_MAP_ID) // entry case { - Creature* pMaster = pSource->GetMap()->GetCreature(itr->second); - if (pMaster && IsSlaveInRangeOfBoss(pSource, pMaster, pInfo->searchRange)) + BossGuidMapBounds finds = m_masterGuid.equal_range(pInfo->masterId); + for (BossGuidMap::iterator itr = finds.first; itr != finds.second; ++itr) { - switch (eventType) - { - case LINKING_EVENT_AGGRO: - if (pMaster->IsControlledByPlayer()) - return; + pMaster = pSource->GetMap()->GetCreature(itr->second); + if (pMaster && IsSlaveInRangeOfBoss(pSource, pMaster, pInfo->searchRange)) + break; + } + } + else // guid case + { + CreatureData const* masterData = sObjectMgr.GetCreatureData(pInfo->masterDBGuid); + CreatureInfo const* cInfo = ObjectMgr::GetCreatureTemplate(masterData->id); + pMaster = pSource->GetMap()->GetCreature(ObjectGuid(cInfo->GetHighGuid(), cInfo->Entry, pInfo->masterDBGuid)); + } - if (pMaster->isInCombat()) - pMaster->SetInCombatWith(pEnemy); - else - pMaster->AI()->AttackStart(pEnemy); - break; - case LINKING_EVENT_EVADE: - if (!pMaster->isAlive()) - pMaster->Respawn(); - break; - case LINKING_EVENT_RESPAWN: - if (pMaster->isAlive()) - SetFollowing(pSource, pMaster); - } + if (pMaster) + { + switch (eventType) + { + case LINKING_EVENT_AGGRO: + if (pMaster->IsControlledByPlayer()) + return; + + if (pMaster->isInCombat()) + pMaster->SetInCombatWith(pEnemy); + else + pMaster->AI()->AttackStart(pEnemy); + break; + case LINKING_EVENT_EVADE: + if (!pMaster->isAlive()) + pMaster->Respawn(); + break; + case LINKING_EVENT_RESPAWN: + if (pMaster->isAlive()) + SetFollowing(pSource, pMaster); } } } diff --git a/src/game/CreatureLinkingMgr.h b/src/game/CreatureLinkingMgr.h index 160825896..7c55f91d1 100644 --- a/src/game/CreatureLinkingMgr.h +++ b/src/game/CreatureLinkingMgr.h @@ -85,8 +85,8 @@ struct CreatureLinkingInfo uint32 mapId; uint32 masterId; uint32 masterDBGuid; - uint16 linkingFlag; - uint16 searchRange; + uint16 linkingFlag:16; + uint16 searchRange:16; }; /** @@ -121,12 +121,15 @@ class CreatureLinkingMgr // Storage of Data: npc_entry_slave, (map, npc_entry_master, flag, master_db_guid[If Unique], search_range) CreatureLinkingMap m_creatureLinkingMap; + // Storage of Data: npc_guid_slave, (map, npc_guid_master, flag, master_db_guid, search_range) + CreatureLinkingMap m_creatureLinkingGuidMap; // Lookup Storage for fast access: - UNORDERED_SET m_eventTriggers; // master + UNORDERED_SET m_eventTriggers; // master by entry + UNORDERED_SET m_eventGuidTriggers; // master by guid // Check-routine - bool IsLinkingEntryValid(uint32 slaveEntry, CreatureLinkingInfo* pInfo); + bool IsLinkingEntryValid(uint32 slaveEntry, CreatureLinkingInfo* pInfo, bool byEntry); }; /** @@ -158,15 +161,21 @@ class CreatureLinkingHolder private: typedef std::list GuidList; - // Structure associated to a master + // Structure associated to a master (entry case) struct InfoAndGuids { - uint16 linkingFlag; - uint16 searchRange; + uint16 linkingFlag:16; + uint16 searchRange:16; GuidList linkedGuids; }; + // Structure associated to a master (guid case) + struct InfoAndGuid + { + uint16 linkingFlag; + ObjectGuid linkedGuid; + }; - typedef std::multimap HolderMap; + typedef std::multimap HolderMap; typedef std::pair HolderMapBounds; typedef std::multimap BossGuidMap; typedef std::pair BossGuidMapBounds; @@ -180,8 +189,10 @@ class CreatureLinkingHolder // Helper function to return if a slave is in range of a boss bool IsSlaveInRangeOfBoss(Creature* pSlave, Creature* pBoss, uint16 searchRange); - // Storage of Data (boss, flag) GuidList for action triggering + // Storage of Data (boss, flag, searchRange, GuidList) for action triggering HolderMap m_holderMap; + // Storage of Data (boss, flag, slave-guid) + HolderMap m_holderGuidMap; // boss_entry, guid for reverse action triggering and check alive BossGuidMap m_masterGuid; }; diff --git a/src/shared/revision_nr.h b/src/shared/revision_nr.h index ff48859ba..b18aaad48 100644 --- a/src/shared/revision_nr.h +++ b/src/shared/revision_nr.h @@ -1,4 +1,4 @@ #ifndef __REVISION_NR_H__ #define __REVISION_NR_H__ - #define REVISION_NR "11993" + #define REVISION_NR "11994" #endif // __REVISION_NR_H__ diff --git a/src/shared/revision_sql.h b/src/shared/revision_sql.h index 0937cb464..901a7be8a 100644 --- a/src/shared/revision_sql.h +++ b/src/shared/revision_sql.h @@ -1,6 +1,6 @@ #ifndef __REVISION_SQL_H__ #define __REVISION_SQL_H__ #define REVISION_DB_CHARACTERS "required_11785_02_characters_instance" - #define REVISION_DB_MANGOS "required_11985_01_mangos_gameobject_template_scripts" + #define REVISION_DB_MANGOS "required_11994_01_mangos_creature_linking" #define REVISION_DB_REALMD "required_10008_01_realmd_realmd_db_version" #endif // __REVISION_SQL_H__