diff --git a/data/sql/updates/pending_db_world/Item.sql b/data/sql/updates/pending_db_world/Item.sql new file mode 100644 index 000000000..de62faf07 --- /dev/null +++ b/data/sql/updates/pending_db_world/Item.sql @@ -0,0 +1,15 @@ +-- add item_dbc table +DROP TABLE IF EXISTS `item_dbc`; +CREATE TABLE `item_dbc` ( `ID` INT NOT NULL DEFAULT '0', `ClassID` INT NOT NULL DEFAULT '0', `SubclassID` INT NOT NULL DEFAULT '0', `Sound_Override_Subclassid` INT NOT NULL DEFAULT '0', `Material` INT NOT NULL DEFAULT '0', `DisplayInfoID` INT NOT NULL DEFAULT '0', `InventoryType` INT NOT NULL DEFAULT '0', `SheatheType` INT NOT NULL DEFAULT '0', PRIMARY KEY (`ID`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- Corrects subclass error messages +UPDATE `item_template` SET `subclass`=4 WHERE `entry`=17; +UPDATE `item_template` SET `subclass`=6 WHERE `entry`=2556; +UPDATE `item_template` SET `subclass`=0 WHERE `entry`=20221; +UPDATE `item_template` SET `subclass`=13 WHERE `entry`=31802; +UPDATE `item_template` SET `subclass`=3 WHERE `entry`=33080; +UPDATE `item_template` SET `subclass`=3 WHERE `entry`=33604; +UPDATE `item_template` SET `subclass`=8 WHERE `entry`=37445; +UPDATE `item_template` SET `subclass`=12 WHERE `entry`=37677; +UPDATE `item_template` SET `subclass`=7 WHERE `entry`=41749; +UPDATE `item_template` SET `subclass`=1 WHERE `entry`=53048; diff --git a/src/server/apps/worldserver/worldserver.conf.dist b/src/server/apps/worldserver/worldserver.conf.dist index 99dfc9cc0..ac3935a12 100644 --- a/src/server/apps/worldserver/worldserver.conf.dist +++ b/src/server/apps/worldserver/worldserver.conf.dist @@ -1309,6 +1309,14 @@ DeletedCharacterTicketTrace = 0 DungeonFinder.OptionsMask = 5 +# +# DBC.EnforceItemAttributes +# Disallow overriding item attributes stored in DBC files with values from the database +# Default: 0 - Off, Use DB values +# 1 - On, Enforce DBC Values (default) + +DBC.EnforceItemAttributes = 1 + # # AccountInstancesPerHour # Description: Controls the max amount of different instances player can enter within hour diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.h b/src/server/game/AI/SmartScripts/SmartScriptMgr.h index 70308be04..88d82027e 100644 --- a/src/server/game/AI/SmartScripts/SmartScriptMgr.h +++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.h @@ -2007,7 +2007,7 @@ private: bool IsItemValid(SmartScriptHolder const& e, uint32 entry) { - if (!sObjectMgr->GetItemTemplate(entry)) + if (!sItemStore.LookupEntry(entry)) { LOG_ERROR("sql.sql", "SmartAIMgr: Entry {} SourceType {} Event {} Action {} uses non-existent Item entry {}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), entry); return false; diff --git a/src/server/game/DataStores/DBCStores.cpp b/src/server/game/DataStores/DBCStores.cpp index 400b294f8..fbd483b87 100644 --- a/src/server/game/DataStores/DBCStores.cpp +++ b/src/server/game/DataStores/DBCStores.cpp @@ -98,6 +98,7 @@ DBCStorage sGtRegenMPPerSptStore(GtRegenMPPerSptf DBCStorage sHolidaysStore(Holidaysfmt); +DBCStorage sItemStore(Itemfmt); DBCStorage sItemBagFamilyStore(ItemBagFamilyfmt); //DBCStorage sItemCondExtCostsStore(ItemCondExtCostsEntryfmt); DBCStorage sItemDisplayInfoStore(ItemDisplayTemplateEntryfmt); @@ -317,6 +318,7 @@ void LoadDBCStores(const std::string& dataPath) LOAD_DBC(sGtRegenHPPerSptStore, "gtRegenHPPerSpt.dbc", "gtregenhpperspt_dbc"); LOAD_DBC(sGtRegenMPPerSptStore, "gtRegenMPPerSpt.dbc", "gtregenmpperspt_dbc"); LOAD_DBC(sHolidaysStore, "Holidays.dbc", "holidays_dbc"); + LOAD_DBC(sItemStore, "Item.dbc", "item_dbc"); LOAD_DBC(sItemBagFamilyStore, "ItemBagFamily.dbc", "itembagfamily_dbc"); LOAD_DBC(sItemDisplayInfoStore, "ItemDisplayInfo.dbc", "itemdisplayinfo_dbc"); //LOAD_DBC(sItemCondExtCostsStore, "ItemCondExtCosts.dbc", "itemcondextcosts_dbc"); @@ -631,9 +633,10 @@ void LoadDBCStores(const std::string& dataPath) } // Check loaded DBC files proper version - if (!sAreaTableStore.LookupEntry(4987) || // last area added in 3.3.5a + if (!sAreaTableStore.LookupEntry(4987) || // last area added in 3.3.5a !sCharTitlesStore.LookupEntry(177) || // last char title added in 3.3.5a !sGemPropertiesStore.LookupEntry(1629) || // last added spell in 3.3.5a + !sItemStore.LookupEntry(56806) || // last client known item added in 3.3.5a !sItemExtendedCostStore.LookupEntry(2997) || // last item extended cost added in 3.3.5a !sMapStore.LookupEntry(724) || // last map added in 3.3.5a !sSpellStore.LookupEntry(80864) ) // last client known item added in 3.3.5a diff --git a/src/server/game/DataStores/DBCStores.h b/src/server/game/DataStores/DBCStores.h index 07bf72a63..9c1a9670d 100644 --- a/src/server/game/DataStores/DBCStores.h +++ b/src/server/game/DataStores/DBCStores.h @@ -124,6 +124,7 @@ extern DBCStorage sGtRegenHPPerSptStore; extern DBCStorage sGtRegenMPPerSptStore; extern DBCStorage sHolidaysStore; extern DBCStorage sItemBagFamilyStore; +extern DBCStorage sItemStore; extern DBCStorage sItemDisplayInfoStore; extern DBCStorage sItemExtendedCostStore; extern DBCStorage sItemLimitCategoryStore; diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index e3176547d..c40591cfd 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -1595,9 +1595,9 @@ void ObjectMgr::LoadEquipmentTemplates() if (!equipmentInfo.ItemEntry[i]) continue; - ItemTemplate const* item = GetItemTemplate(equipmentInfo.ItemEntry[i]); + ItemEntry const* dbcItem = sItemStore.LookupEntry(equipmentInfo.ItemEntry[i]); - if (!item) + if (!dbcItem) { LOG_ERROR("sql.sql", "Unknown item (ID={}) in creature_equip_template.ItemID{} for CreatureID = {} and ID = {}, forced to 0.", equipmentInfo.ItemEntry[i], i + 1, entry, id); @@ -1605,15 +1605,15 @@ void ObjectMgr::LoadEquipmentTemplates() continue; } - if (item->InventoryType != INVTYPE_WEAPON && - item->InventoryType != INVTYPE_SHIELD && - item->InventoryType != INVTYPE_RANGED && - item->InventoryType != INVTYPE_2HWEAPON && - item->InventoryType != INVTYPE_WEAPONMAINHAND && - item->InventoryType != INVTYPE_WEAPONOFFHAND && - item->InventoryType != INVTYPE_HOLDABLE && - item->InventoryType != INVTYPE_THROWN && - item->InventoryType != INVTYPE_RANGEDRIGHT) + if (dbcItem->InventoryType != INVTYPE_WEAPON && + dbcItem->InventoryType != INVTYPE_SHIELD && + dbcItem->InventoryType != INVTYPE_RANGED && + dbcItem->InventoryType != INVTYPE_2HWEAPON && + dbcItem->InventoryType != INVTYPE_WEAPONMAINHAND && + dbcItem->InventoryType != INVTYPE_WEAPONOFFHAND && + dbcItem->InventoryType != INVTYPE_HOLDABLE && + dbcItem->InventoryType != INVTYPE_THROWN && + dbcItem->InventoryType != INVTYPE_RANGEDRIGHT) { LOG_ERROR("sql.sql", "Item (ID={}) in creature_equip_template.ItemID{} for CreatureID = {} and ID = {} is not equipable in a hand, forced to 0.", equipmentInfo.ItemEntry[i], i + 1, entry, id); @@ -2741,8 +2741,10 @@ void ObjectMgr::LoadItemTemplates() return; } - _itemTemplateStore.rehash(result->GetRowCount()); + _itemTemplateStore.reserve(result->GetRowCount()); uint32 count = 0; + // original inspiration https://github.com/TrinityCore/TrinityCore/commit/0c44bd33ee7b42c924859139a9f4b04cf2b91261 + bool enforceDBCAttributes = sWorld->getBoolConfig(CONFIG_DBC_ENFORCE_ITEM_ATTRIBUTES); do { @@ -2859,17 +2861,55 @@ void ObjectMgr::LoadItemTemplates() itemTemplate.FlagsCu = fields[137].Get(); // Checks - if (itemTemplate.Class >= MAX_ITEM_CLASS) - { - LOG_ERROR("sql.sql", "Item (Entry: {}) has wrong Class value ({})", entry, itemTemplate.Class); - itemTemplate.Class = ITEM_CLASS_MISC; - } + ItemEntry const* dbcitem = sItemStore.LookupEntry(entry); - if (itemTemplate.SubClass >= MaxItemSubclassValues[itemTemplate.Class]) + if (dbcitem) { - LOG_ERROR("sql.sql", "Item (Entry: {}) has wrong Subclass value ({}) for class {}", entry, itemTemplate.SubClass, itemTemplate.Class); - itemTemplate.SubClass = 0;// exist for all item classes + if (itemTemplate.Class != dbcitem->ClassID) + { + LOG_ERROR("sql.sql", "Item (Entry: {}) has wrong Class value ({}), must be ({}).", entry, itemTemplate.Class, dbcitem->ClassID); + if (enforceDBCAttributes) + itemTemplate.Class = dbcitem->ClassID; + } + if (itemTemplate.SubClass != dbcitem->SubclassID) + { + LOG_ERROR("sql.sql", "Item (Entry: {}) has wrong Subclass value ({}) for class {}, must be ({}).", entry, itemTemplate.SubClass, itemTemplate.Class, dbcitem->SubclassID); + if (enforceDBCAttributes) + itemTemplate.SubClass = dbcitem->SubclassID; + } + if (itemTemplate.SoundOverrideSubclass != dbcitem->SoundOverrideSubclassID) + { + LOG_ERROR("sql.sql", "Item (Entry: {}) does not have a correct SoundOverrideSubclass ({}), must be {}.", entry, itemTemplate.SoundOverrideSubclass); + if (enforceDBCAttributes) + itemTemplate.SoundOverrideSubclass = dbcitem->SoundOverrideSubclassID; + } + if (itemTemplate.Material != dbcitem->Material) + { + LOG_ERROR("sql.sql", "Item (Entry: {%u}}) does not have a correct material ({}), must be {}.", entry, itemTemplate.Material, dbcitem->Material); + if (enforceDBCAttributes) + itemTemplate.Material = dbcitem->Material; + } + if (itemTemplate.InventoryType != dbcitem->InventoryType) + { + LOG_ERROR("sql.sql", "Item (Entry: {}) has wrong InventoryType value ({}), must be {}.", entry, itemTemplate.InventoryType, dbcitem->InventoryType); + if (enforceDBCAttributes) + itemTemplate.InventoryType = dbcitem->InventoryType; + } + if (itemTemplate.DisplayInfoID != dbcitem->DisplayInfoID) + { + LOG_ERROR("sql.sql", "Item (Entry: {%u}}) does not have a correct display id ({}), must be {}.", entry, itemTemplate.DisplayInfoID, dbcitem->DisplayInfoID); + if (enforceDBCAttributes) + itemTemplate.DisplayInfoID = dbcitem->DisplayInfoID; + } + if (itemTemplate.Sheath != dbcitem->SheatheType) + { + LOG_ERROR("sql.sql", "Item (Entry: {}) has wrong Sheath ({}), must be {}.", entry, itemTemplate.Sheath, dbcitem->SheatheType); + if (enforceDBCAttributes) + itemTemplate.Sheath = dbcitem->SheatheType; + } } + else + LOG_ERROR("sql.sql", "Item (Entry: {}) does not exist in item.dbc! (not correct id?).", entry); if (itemTemplate.Quality >= MAX_ITEM_QUALITY) { @@ -2902,12 +2942,6 @@ void ObjectMgr::LoadItemTemplates() itemTemplate.BuyCount = 1; } - if (itemTemplate.InventoryType >= MAX_INVTYPE) - { - LOG_ERROR("sql.sql", "Item (Entry: {}) has wrong InventoryType value ({})", entry, itemTemplate.InventoryType); - itemTemplate.InventoryType = INVTYPE_NON_EQUIP; - } - if (itemTemplate.RequiredSkill >= MAX_SKILL_TYPE) { LOG_ERROR("sql.sql", "Item (Entry: {}) has wrong RequiredSkill value ({})", entry, itemTemplate.RequiredSkill); @@ -3118,12 +3152,6 @@ void ObjectMgr::LoadItemTemplates() if (itemTemplate.LockID && !sLockStore.LookupEntry(itemTemplate.LockID)) LOG_ERROR("sql.sql", "Item (Entry: {}) has wrong LockID ({})", entry, itemTemplate.LockID); - if (itemTemplate.Sheath >= MAX_SHEATHETYPE) - { - LOG_ERROR("sql.sql", "Item (Entry: {}) has wrong Sheath ({})", entry, itemTemplate.Sheath); - itemTemplate.Sheath = SHEATHETYPE_NONE; - } - if (itemTemplate.RandomProperty) { // To be implemented later @@ -9995,8 +10023,8 @@ void ObjectMgr::LoadGameObjectQuestItems() { uint32 oldMSTime = getMSTime(); - // 0 1 - QueryResult result = WorldDatabase.Query("SELECT GameObjectEntry, ItemId FROM gameobject_questitem ORDER BY Idx ASC"); + // 0 1 2 + QueryResult result = WorldDatabase.Query("SELECT GameObjectEntry, ItemId, Idx FROM gameobject_questitem ORDER BY Idx ASC"); if (!result) { @@ -10011,6 +10039,21 @@ void ObjectMgr::LoadGameObjectQuestItems() uint32 entry = fields[0].Get(); uint32 item = fields[1].Get(); + uint32 idx = fields[2].Get(); + + GameObjectTemplate const* goInfo = GetGameObjectTemplate(entry); + if (!goInfo) + { + LOG_ERROR("sql.sql", "Table `gameobject_questitem` has data for nonexistent gameobject (entry: {}, idx: {}), skipped", entry, idx); + continue; + }; + + ItemEntry const* dbcData = sItemStore.LookupEntry(item); + if (!dbcData) + { + LOG_ERROR("sql.sql", "Table `gameobject_questitem` has nonexistent item (ID: {}) in gameobject (entry: {}, idx: {}), skipped", item, entry, idx); + continue; + }; _gameObjectQuestItemStore[entry].push_back(item); @@ -10025,8 +10068,8 @@ void ObjectMgr::LoadCreatureQuestItems() { uint32 oldMSTime = getMSTime(); - // 0 1 - QueryResult result = WorldDatabase.Query("SELECT CreatureEntry, ItemId FROM creature_questitem ORDER BY Idx ASC"); + // 0 1 2 + QueryResult result = WorldDatabase.Query("SELECT CreatureEntry, ItemId, Idx FROM creature_questitem ORDER BY Idx ASC"); if (!result) { @@ -10041,6 +10084,21 @@ void ObjectMgr::LoadCreatureQuestItems() uint32 entry = fields[0].Get(); uint32 item = fields[1].Get(); + uint32 idx = fields[2].Get(); + + CreatureTemplate const* creatureInfo = GetCreatureTemplate(entry); + if (!creatureInfo) + { + LOG_ERROR("sql.sql", "Table `creature_questitem` has data for nonexistent creature (entry: {}, idx: {}), skipped", entry, idx); + continue; + }; + + ItemEntry const* dbcData = sItemStore.LookupEntry(item); + if (!dbcData) + { + LOG_ERROR("sql.sql", "Table `creature_questitem` has nonexistent item (ID: {}) in creature (entry: {}, idx: {}), skipped", item, entry, idx); + continue; + }; _creatureQuestItemStore[entry].push_back(item); diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index d1bb58fca..06fa659d2 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -4928,11 +4928,11 @@ void Spell::WriteAmmoToPacket(WorldPacket* data) { if (uint32 item_id = m_caster->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + i)) { - if (ItemTemplate const* itemEntry = sObjectMgr->GetItemTemplate(item_id)) + if (ItemEntry const* itemEntry = sItemStore.LookupEntry(item_id)) { - if (itemEntry->Class == ITEM_CLASS_WEAPON) + if (itemEntry->ClassID == ITEM_CLASS_WEAPON) { - switch (itemEntry->SubClass) + switch (itemEntry->SubclassID) { case ITEM_SUBCLASS_WEAPON_THROWN: ammoDisplayID = itemEntry->DisplayInfoID; diff --git a/src/server/game/World/IWorld.h b/src/server/game/World/IWorld.h index 6e340c515..c5fff797b 100644 --- a/src/server/game/World/IWorld.h +++ b/src/server/game/World/IWorld.h @@ -138,6 +138,7 @@ enum WorldBoolConfigs CONFIG_AUTOBROADCAST, CONFIG_ALLOW_TICKETS, CONFIG_DELETE_CHARACTER_TICKET_TRACE, + CONFIG_DBC_ENFORCE_ITEM_ATTRIBUTES, CONFIG_PRESERVE_CUSTOM_CHANNELS, CONFIG_PDUMP_NO_PATHS, CONFIG_PDUMP_NO_OVERWRITE, diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 61fe44ddb..b53515b1e 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1356,6 +1356,9 @@ void World::LoadConfigSettings(bool reload) // Dungeon finder _int_configs[CONFIG_LFG_OPTIONSMASK] = sConfigMgr->GetOption("DungeonFinder.OptionsMask", 5); + // DBC_ItemAttributes + _bool_configs[CONFIG_DBC_ENFORCE_ITEM_ATTRIBUTES] = sConfigMgr->GetOption("DBC.EnforceItemAttributes", true); + // Max instances per hour _int_configs[CONFIG_MAX_INSTANCES_PER_HOUR] = sConfigMgr->GetOption("AccountInstancesPerHour", 5); diff --git a/src/server/shared/DataStores/DBCStructure.h b/src/server/shared/DataStores/DBCStructure.h index 07a9f8552..07aec5209 100644 --- a/src/server/shared/DataStores/DBCStructure.h +++ b/src/server/shared/DataStores/DBCStructure.h @@ -1127,6 +1127,18 @@ struct HolidaysEntry //uint32 flags; // 54 m_flags (0 = Darkmoon Faire, Fishing Contest and Wotlk Launch, rest is 1) }; +struct ItemEntry +{ + uint32 ID; // 0 + uint32 ClassID; // 1 + uint32 SubclassID; // 2 + int32 SoundOverrideSubclassID; // 3 + int32 Material; // 4 + uint32 DisplayInfoID; // 5 + uint32 InventoryType; // 6 + uint32 SheatheType; // 7 +}; + struct ItemBagFamilyEntry { uint32 ID; // 0 diff --git a/src/server/shared/DataStores/DBCfmt.h b/src/server/shared/DataStores/DBCfmt.h index dc8fefb35..c672332be 100644 --- a/src/server/shared/DataStores/DBCfmt.h +++ b/src/server/shared/DataStores/DBCfmt.h @@ -68,6 +68,7 @@ char constexpr GtOCTRegenHPfmt[] = "df"; char constexpr GtRegenHPPerSptfmt[] = "df"; char constexpr GtRegenMPPerSptfmt[] = "df"; char constexpr Holidaysfmt[] = "niiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiixxsiix"; +char constexpr Itemfmt[] = "niiiiiii"; char constexpr ItemBagFamilyfmt[] = "nxxxxxxxxxxxxxxxxx"; char constexpr ItemDisplayTemplateEntryfmt[] = "nxxxxsxxxxxxxxxxxxxxxxxxx"; //char constexpr ItemCondExtCostsEntryfmt[] = "xiii";