From 108a167c46a9223d4b7e1b88b7c20cdd812864c5 Mon Sep 17 00:00:00 2001 From: VladimirMangos Date: Thu, 2 Jun 2011 09:17:08 +0400 Subject: [PATCH] [11587] Use SkillRaceClassInfo.dbc data for check spell training. * Show race/class specific min level in trainer list (for weapon master for example) and use it for check spell traing possibility. * Hide for specific race/class some spells marked by flag ABILITY_SKILL_NONTRAINABLE This possible in cases when spell available for race/class but learned from talent/etc and then don't must be show for race/calss pair in trainer list. Single case in 3.3.5a. * Add to code tables for profession ranks for seelct correct rank related reqlevel and check DB side data for it. Note: DB reqlevel values redundant for profession rank spells now. * Use for reqlevel selection (if DB value not provided) learned spell insteed cast-spell spellLevel field. This let in more cases select training reqlevel without provided DB value. For LogFilter_DbStrictedCheck = 0 mode output data about like redundant reqlevel values in traner tables. --- src/game/DBCEnums.h | 5 ++++ src/game/DBCStores.cpp | 4 ++- src/game/DBCStores.h | 1 + src/game/DBCStructure.h | 10 +++---- src/game/DBCfmt.h | 1 + src/game/NPCHandler.cpp | 14 +++++---- src/game/ObjectMgr.cpp | 47 +++++++++++++++++++++++------- src/game/Player.cpp | 43 ++++++++++++++++++++++++--- src/game/Player.h | 2 +- src/game/SpellMgr.cpp | 63 ++++++++++++++++++++++++++++++++++++++++ src/game/SpellMgr.h | 11 +++++++ src/game/World.cpp | 5 +++- src/shared/revision_nr.h | 2 +- 13 files changed, 179 insertions(+), 29 deletions(-) diff --git a/src/game/DBCEnums.h b/src/game/DBCEnums.h index fc42bb5ec..1cd1ecad1 100644 --- a/src/game/DBCEnums.h +++ b/src/game/DBCEnums.h @@ -313,6 +313,11 @@ enum AbilytyLearnType ABILITY_LEARNED_ON_GET_RACE_OR_CLASS_SKILL = 2 }; +enum AbilitySkillFlags +{ + ABILITY_SKILL_NONTRAINABLE = 0x100 +}; + enum ItemEnchantmentType { ITEM_ENCHANTMENT_TYPE_NONE = 0, diff --git a/src/game/DBCStores.cpp b/src/game/DBCStores.cpp index 8bcb89975..7dd470b45 100644 --- a/src/game/DBCStores.cpp +++ b/src/game/DBCStores.cpp @@ -140,6 +140,7 @@ DBCStorage sScalingStatValuesStore(ScalingStatValuesfmt DBCStorage sSkillLineStore(SkillLinefmt); DBCStorage sSkillLineAbilityStore(SkillLineAbilityfmt); +DBCStorage sSkillRaceClassInfoStore(SkillRaceClassInfofmt); DBCStorage sSoundEntriesStore(SoundEntriesfmt); @@ -361,7 +362,7 @@ void LoadDBCStores(const std::string& dataPath) exit(1); } - const uint32 DBCFilesCount = 91; + const uint32 DBCFilesCount = 92; barGoLink bar( (int)DBCFilesCount ); @@ -477,6 +478,7 @@ void LoadDBCStores(const std::string& dataPath) LoadDBC(availableDbcLocales,bar,bad_dbc_files,sScalingStatValuesStore, dbcPath,"ScalingStatValues.dbc"); LoadDBC(availableDbcLocales,bar,bad_dbc_files,sSkillLineStore, dbcPath,"SkillLine.dbc"); LoadDBC(availableDbcLocales,bar,bad_dbc_files,sSkillLineAbilityStore, dbcPath,"SkillLineAbility.dbc"); + LoadDBC(availableDbcLocales,bar,bad_dbc_files,sSkillRaceClassInfoStore, dbcPath,"SkillRaceClassInfo.dbc"); LoadDBC(availableDbcLocales,bar,bad_dbc_files,sSoundEntriesStore, dbcPath,"SoundEntries.dbc"); LoadDBC(availableDbcLocales,bar,bad_dbc_files,sSpellStore, dbcPath,"Spell.dbc"); for(uint32 i = 1; i < sSpellStore.GetNumRows(); ++i) diff --git a/src/game/DBCStores.h b/src/game/DBCStores.h index acffa014d..485ae875c 100644 --- a/src/game/DBCStores.h +++ b/src/game/DBCStores.h @@ -141,6 +141,7 @@ extern DBCStorage sScalingStatDistributionStore; extern DBCStorage sScalingStatValuesStore; extern DBCStorage sSkillLineStore; extern DBCStorage sSkillLineAbilityStore; +extern DBCStorage sSkillRaceClassInfoStore; extern DBCStorage sSoundEntriesStore; extern DBCStorage sSpellCastTimesStore; extern DBCStorage sSpellDifficultyStore; diff --git a/src/game/DBCStructure.h b/src/game/DBCStructure.h index f42ea3d67..2df965e2f 100644 --- a/src/game/DBCStructure.h +++ b/src/game/DBCStructure.h @@ -1434,17 +1434,17 @@ struct ScalingStatValuesEntry uint32 displayOrder; // 19 m_sortIndex };*/ -/*struct SkillRaceClassInfoEntry +struct SkillRaceClassInfoEntry { - uint32 id; // 0 m_ID + //uint32 id; // 0 m_ID uint32 skillId; // 1 m_skillID uint32 raceMask; // 2 m_raceMask uint32 classMask; // 3 m_classMask uint32 flags; // 4 m_flags uint32 reqLevel; // 5 m_minLevel - uint32 skillTierId; // 6 m_skillTierID - uint32 skillCostID; // 7 m_skillCostIndex -};*/ + //uint32 skillTierId; // 6 m_skillTierID + //uint32 skillCostID; // 7 m_skillCostIndex +}; /*struct SkillTiersEntry{ uint32 id; // 0 m_ID diff --git a/src/game/DBCfmt.h b/src/game/DBCfmt.h index 559c48a0a..ee84fdd59 100644 --- a/src/game/DBCfmt.h +++ b/src/game/DBCfmt.h @@ -87,6 +87,7 @@ const char ScalingStatDistributionfmt[]="niiiiiiiiiiiiiiiiiiiii"; const char ScalingStatValuesfmt[]="iniiiiiiiiiiiiiiiiixiiii"; const char SkillLinefmt[]="nixssssssssssssssssxxxxxxxxxxxxxxxxxxixxxxxxxxxxxxxxxxxi"; const char SkillLineAbilityfmt[]="niiiixxiiiiixx"; +const char SkillRaceClassInfofmt[]="diiiiixx"; const char SoundEntriesfmt[]="nxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; const char SpellCastTimefmt[]="nixx"; const char SpellDurationfmt[]="niii"; diff --git a/src/game/NPCHandler.cpp b/src/game/NPCHandler.cpp index 9a7b26082..db7b03504 100644 --- a/src/game/NPCHandler.cpp +++ b/src/game/NPCHandler.cpp @@ -119,7 +119,7 @@ void WorldSession::SendTrainerList(ObjectGuid guid) } -static void SendTrainerSpellHelper(WorldPacket& data, TrainerSpell const* tSpell, TrainerSpellState state, float fDiscountMod, bool can_learn_primary_prof) +static void SendTrainerSpellHelper(WorldPacket& data, TrainerSpell const* tSpell, TrainerSpellState state, float fDiscountMod, bool can_learn_primary_prof, uint32 skillReqLevel) { bool primary_prof_first_rank = sSpellMgr.IsPrimaryProfessionFirstRankSpell(tSpell->learnedSpell); SpellChainNode const* chain_node = sSpellMgr.GetSpellChainNode(tSpell->learnedSpell); @@ -131,7 +131,7 @@ static void SendTrainerSpellHelper(WorldPacket& data, TrainerSpell const* tSpell data << uint32(primary_prof_first_rank && can_learn_primary_prof ? 1 : 0); // primary prof. learn confirmation dialog data << uint32(primary_prof_first_rank ? 1 : 0); // must be equal prev. field to have learn button in enabled state - data << uint8(tSpell->reqLevel); + data << uint8(skillReqLevel ? skillReqLevel : tSpell->reqLevel); data << uint32(tSpell->reqSkill); data << uint32(tSpell->reqSkillValue); data << uint32(!tSpell->IsCastable() && chain_node ? (chain_node->prev ? chain_node->prev : chain_node->req) : 0); @@ -193,12 +193,13 @@ void WorldSession::SendTrainerList(ObjectGuid guid, const std::string& strTitle) { TrainerSpell const* tSpell = &itr->second; - if(!_player->IsSpellFitByClassAndRace(tSpell->learnedSpell)) + uint32 skillReqLevel = 0; + if(!_player->IsSpellFitByClassAndRace(tSpell->learnedSpell, &skillReqLevel)) continue; TrainerSpellState state = _player->GetTrainerSpellState(tSpell); - SendTrainerSpellHelper(data, tSpell, state, fDiscountMod, can_learn_primary_prof); + SendTrainerSpellHelper(data, tSpell, state, fDiscountMod, can_learn_primary_prof, skillReqLevel); ++count; } @@ -210,12 +211,13 @@ void WorldSession::SendTrainerList(ObjectGuid guid, const std::string& strTitle) { TrainerSpell const* tSpell = &itr->second; - if(!_player->IsSpellFitByClassAndRace(tSpell->learnedSpell)) + uint32 skillReqLevel = 0; + if(!_player->IsSpellFitByClassAndRace(tSpell->learnedSpell, &skillReqLevel)) continue; TrainerSpellState state = _player->GetTrainerSpellState(tSpell); - SendTrainerSpellHelper(data, tSpell, state, fDiscountMod, can_learn_primary_prof); + SendTrainerSpellHelper(data, tSpell, state, fDiscountMod, can_learn_primary_prof, skillReqLevel); ++count; } diff --git a/src/game/ObjectMgr.cpp b/src/game/ObjectMgr.cpp index 32c73e83e..412db2061 100644 --- a/src/game/ObjectMgr.cpp +++ b/src/game/ObjectMgr.cpp @@ -8041,13 +8041,13 @@ void ObjectMgr::LoadTrainers(char const* tableName, bool isTemplates) SpellEntry const *spellinfo = sSpellStore.LookupEntry(spell); if (!spellinfo) { - sLog.outErrorDb("Table `%s` for trainer (Entry: %u ) has non existing spell %u, ignore", tableName, entry, spell); + sLog.outErrorDb("Table `%s` (Entry: %u ) has non existing spell %u, ignore", tableName, entry, spell); continue; } if (!SpellMgr::IsSpellValid(spellinfo)) { - sLog.outErrorDb("Table `%s` for trainer (Entry: %u) has broken learning spell %u, ignore", tableName, entry, spell); + sLog.outErrorDb("Table `%s` (Entry: %u) has broken learning spell %u, ignore", tableName, entry, spell); continue; } @@ -8085,7 +8085,7 @@ void ObjectMgr::LoadTrainers(char const* tableName, bool isTemplates) { if (tSpells->spellList.find(spell) != tSpells->spellList.end()) { - sLog.outErrorDb("Table `%s` for trainer (Entry: %u) has spell %u listed in trainer template %u, ignore", tableName, entry, spell, cInfo->trainerId); + sLog.outErrorDb("Table `%s` (Entry: %u) has spell %u listed in trainer template %u, ignore", tableName, entry, spell, cInfo->trainerId); continue; } } @@ -8100,25 +8100,52 @@ void ObjectMgr::LoadTrainers(char const* tableName, bool isTemplates) trainerSpell.reqSkillValue = fields[4].GetUInt32(); trainerSpell.reqLevel = fields[5].GetUInt32(); - if(!trainerSpell.reqLevel) - trainerSpell.reqLevel = spellinfo->spellLevel; - // calculate learned spell for profession case when stored cast-spell trainerSpell.learnedSpell = spell; for(int i = 0; i < MAX_EFFECT_INDEX; ++i) { - if (spellinfo->Effect[i] != SPELL_EFFECT_LEARN_SPELL) - continue; - if (SpellMgr::IsProfessionOrRidingSpell(spellinfo->EffectTriggerSpell[i])) + if (spellinfo->Effect[i] == SPELL_EFFECT_LEARN_SPELL && + SpellMgr::IsProfessionOrRidingSpell(spellinfo->EffectTriggerSpell[i])) { - trainerSpell.learnedSpell = spellinfo->EffectTriggerSpell[i]; + // prof spells sometime only additions to main spell learn that have level data + for(int j = 0; j < MAX_EFFECT_INDEX; ++j) + { + if (spellinfo->Effect[j] == SPELL_EFFECT_LEARN_SPELL) + { + trainerSpell.learnedSpell = spellinfo->EffectTriggerSpell[j]; + break; + } + } break; } } + // already checked as valid spell so exist. + SpellEntry const *learnSpellinfo = sSpellStore.LookupEntry(trainerSpell.learnedSpell); + if (trainerSpell.reqLevel) + { + if (trainerSpell.reqLevel == learnSpellinfo->spellLevel) + ERROR_DB_STRICT_LOG("Table `%s` (Entry: %u) has redundant reqlevel %u (=spell level) for spell %u", tableName, entry, trainerSpell.reqLevel, spell); + } + else + trainerSpell.reqLevel = learnSpellinfo->spellLevel; + if (SpellMgr::IsProfessionSpell(trainerSpell.learnedSpell)) + { data.trainerType = 2; + uint32 minLevel = sSpellMgr.GetProfessionSpellMinLevel(trainerSpell.learnedSpell); + if (trainerSpell.reqLevel) + { + if (minLevel == trainerSpell.reqLevel) + ERROR_DB_STRICT_LOG("Table `%s` (Entry: %u) has redundant reqlevel %u (=prof reqlevel) for spell %u", tableName, entry, trainerSpell.reqLevel, spell); + else + sLog.outErrorDb("Table `%s` (Entry: %u) has wrong redundant reqlevel %u (<>prof reqlevel %u) for spell %u", tableName, entry, trainerSpell.reqLevel, minLevel, spell); + } + else + trainerSpell.reqLevel = minLevel; + } + ++count; } while (result->NextRow()); diff --git a/src/game/Player.cpp b/src/game/Player.cpp index 049c23b1a..4e75e33db 100644 --- a/src/game/Player.cpp +++ b/src/game/Player.cpp @@ -20556,7 +20556,16 @@ float Player::GetReputationPriceDiscount( Creature const* pCreature ) const return 1.0f - 0.05f* (rank - REP_NEUTRAL); } -bool Player::IsSpellFitByClassAndRace( uint32 spell_id ) const +/** + * Check spell availability for training base at SkillLineAbility/SkillRaceClassInfo data. + * Checked allowed race/class and dependent from race/class allowed min level + * + * @param spell_id checked spell id + * @param pReqlevel if arg provided then function work in view mode (level check not applied but detected minlevel returned to var by arg pointer. + if arg not provided then considered train action mode and level checked + * @return true if spell available for show in trainer list (with skip level check) or training. + */ +bool Player::IsSpellFitByClassAndRace(uint32 spell_id, uint32* pReqlevel /*= NULL*/) const { uint32 racemask = getRaceMask(); uint32 classmask = getClassMask(); @@ -20565,16 +20574,42 @@ bool Player::IsSpellFitByClassAndRace( uint32 spell_id ) const if (bounds.first==bounds.second) return true; - for(SkillLineAbilityMap::const_iterator _spell_idx = bounds.first; _spell_idx != bounds.second; ++_spell_idx) + for (SkillLineAbilityMap::const_iterator _spell_idx = bounds.first; _spell_idx != bounds.second; ++_spell_idx) { + SkillLineAbilityEntry const* abilityEntry = _spell_idx->second; // skip wrong race skills - if (_spell_idx->second->racemask && (_spell_idx->second->racemask & racemask) == 0) + if (abilityEntry->racemask && (abilityEntry->racemask & racemask) == 0) continue; // skip wrong class skills - if (_spell_idx->second->classmask && (_spell_idx->second->classmask & classmask) == 0) + if (abilityEntry->classmask && (abilityEntry->classmask & classmask) == 0) continue; + SkillRaceClassInfoMapBounds bounds = sSpellMgr.GetSkillRaceClassInfoMapBounds(abilityEntry->skillId); + for (SkillRaceClassInfoMap::const_iterator itr = bounds.first; itr != bounds.second; ++itr) + { + SkillRaceClassInfoEntry const* skillRCEntry = itr->second; + if ((skillRCEntry->raceMask & racemask) && (skillRCEntry->classMask & classmask)) + { + if (skillRCEntry->flags & ABILITY_SKILL_NONTRAINABLE) + return false; + + if (pReqlevel) // show trainers list case + { + if (skillRCEntry->reqLevel) + { + *pReqlevel = skillRCEntry->reqLevel; + return true; + } + } + else // check availble case at train + { + if (skillRCEntry->reqLevel && getLevel() < skillRCEntry->reqLevel) + return false; + } + } + } + return true; } diff --git a/src/game/Player.h b/src/game/Player.h index 36d7d5c66..67410618b 100644 --- a/src/game/Player.h +++ b/src/game/Player.h @@ -1576,7 +1576,7 @@ class MANGOS_DLL_SPEC Player : public Unit bool HasSpell(uint32 spell) const; bool HasActiveSpell(uint32 spell) const; // show in spellbook TrainerSpellState GetTrainerSpellState(TrainerSpell const* trainer_spell) const; - bool IsSpellFitByClassAndRace( uint32 spell_id ) const; + bool IsSpellFitByClassAndRace(uint32 spell_id, uint32* pReqlevel = NULL) const; bool IsNeedCastPassiveLikeSpellAtLearn(SpellEntry const* spellInfo) const; bool IsImmuneToSpellEffect(SpellEntry const* spellInfo, SpellEffectIndex index) const; diff --git a/src/game/SpellMgr.cpp b/src/game/SpellMgr.cpp index e81893551..8ab145d1d 100644 --- a/src/game/SpellMgr.cpp +++ b/src/game/SpellMgr.cpp @@ -2426,6 +2426,42 @@ bool SpellMgr::IsPrimaryProfessionSpell(uint32 spellId) return IsPrimaryProfessionSkill(skill); } +uint32 SpellMgr::GetProfessionSpellMinLevel(uint32 spellId) +{ + uint32 s2l[8][3] = + { // 0 - gather 1 - non-gather 2 - fish + /*0*/ { 0, 5, 5 }, + /*1*/ { 0, 5, 5 }, + /*2*/ { 0, 10, 10 }, + /*3*/ { 10, 20, 10 }, + /*4*/ { 25, 35, 10 }, + /*5*/ { 40, 50, 10 }, + /*6*/ { 55, 65, 10 }, + /*7*/ { 75, 75, 10 }, + }; + + uint32 rank = GetSpellRank(spellId); + if (rank >= 8) + return 0; + + SkillLineAbilityMapBounds bounds = GetSkillLineAbilityMapBounds(spellId); + if (bounds.first == bounds.second) + return 0; + + switch (bounds.first->second->skillId) + { + case SKILL_FISHING: + return s2l[rank][2]; + case SKILL_HERBALISM: + case SKILL_MINING: + case SKILL_SKINNING: + return s2l[rank][0]; + default: + return s2l[rank][1]; + } +} + + bool SpellMgr::IsPrimaryProfessionFirstRankSpell(uint32 spellId) const { return IsPrimaryProfessionSpell(spellId) && GetSpellRank(spellId)==1; @@ -3931,6 +3967,33 @@ void SpellMgr::LoadSkillLineAbilityMap() sLog.outString(">> Loaded %u SkillLineAbility MultiMap Data", count); } +void SpellMgr::LoadSkillRaceClassInfoMap() +{ + mSkillRaceClassInfoMap.clear(); + + barGoLink bar( (int)sSkillRaceClassInfoStore.GetNumRows() ); + uint32 count = 0; + + for (uint32 i = 0; i < sSkillRaceClassInfoStore.GetNumRows(); ++i) + { + bar.step(); + SkillRaceClassInfoEntry const *skillRCInfo = sSkillRaceClassInfoStore.LookupEntry(i); + if (!skillRCInfo) + continue; + + // not all skills really listed in ability skills list + if (!sSkillLineStore.LookupEntry(skillRCInfo->skillId)) + continue; + + mSkillRaceClassInfoMap.insert(SkillRaceClassInfoMap::value_type(skillRCInfo->skillId,skillRCInfo)); + + ++count; + } + + sLog.outString(); + sLog.outString(">> Loaded %u SkillRaceClassInfo MultiMap Data", count); +} + void SpellMgr::CheckUsedSpells(char const* table) { uint32 countSpells = 0; diff --git a/src/game/SpellMgr.h b/src/game/SpellMgr.h index 2b9020162..1b130b89f 100644 --- a/src/game/SpellMgr.h +++ b/src/game/SpellMgr.h @@ -802,6 +802,9 @@ typedef std::pair SkillLineAbilityMap; typedef std::pair SkillLineAbilityMapBounds; +typedef std::multimap SkillRaceClassInfoMap; +typedef std::pair SkillRaceClassInfoMapBounds; + typedef std::multimap PetLevelupSpellSet; typedef std::map PetLevelupSpellMap; @@ -1047,6 +1050,7 @@ class SpellMgr static bool IsProfessionSpell(uint32 spellId); static bool IsPrimaryProfessionSpell(uint32 spellId); bool IsPrimaryProfessionFirstRankSpell(uint32 spellId) const; + uint32 GetProfessionSpellMinLevel(uint32 spellId); bool IsSkillBonusSpell(uint32 spellId) const; @@ -1065,6 +1069,11 @@ class SpellMgr return mSkillLineAbilityMap.equal_range(spell_id); } + SkillRaceClassInfoMapBounds GetSkillRaceClassInfoMapBounds(uint32 skill_id) const + { + return mSkillRaceClassInfoMap.equal_range(skill_id); + } + PetAura const* GetPetAura(uint32 spell_id, SpellEffectIndex eff) { SpellPetAuraMap::const_iterator itr = mSpellPetAuraMap.find((spell_id<<8) + eff); @@ -1140,6 +1149,7 @@ class SpellMgr void LoadSpellTargetPositions(); void LoadSpellThreats(); void LoadSkillLineAbilityMap(); + void LoadSkillRaceClassInfoMap(); void LoadSpellPetAuras(); void LoadPetLevelupSpellMap(); void LoadPetDefaultSpells(); @@ -1160,6 +1170,7 @@ class SpellMgr SpellProcItemEnchantMap mSpellProcItemEnchantMap; SpellBonusMap mSpellBonusMap; SkillLineAbilityMap mSkillLineAbilityMap; + SkillRaceClassInfoMap mSkillRaceClassInfoMap; SpellPetAuraMap mSpellPetAuraMap; PetLevelupSpellMap mPetLevelupSpellMap; PetDefaultSpellsMap mPetDefaultSpellsMap; // only spells not listed in related mPetLevelupSpellMap entry diff --git a/src/game/World.cpp b/src/game/World.cpp index 1a68fb522..d7c1ae172 100644 --- a/src/game/World.cpp +++ b/src/game/World.cpp @@ -949,9 +949,12 @@ void World::SetInitialWorldSettings() sLog.outString( "Loading InstanceTemplate..." ); sObjectMgr.LoadInstanceTemplate(); - sLog.outString( "Loading SkillLineAbilityMultiMap Data..." ); + sLog.outString("Loading SkillLineAbilityMultiMap Data..."); sSpellMgr.LoadSkillLineAbilityMap(); + sLog.outString("Loading SkillRaceClassInfoMultiMap Data..."); + sSpellMgr.LoadSkillRaceClassInfoMap(); + ///- Clean up and pack instances sLog.outString( "Cleaning up instances..." ); sMapPersistentStateMgr.CleanupInstances(); // must be called before `creature_respawn`/`gameobject_respawn` tables diff --git a/src/shared/revision_nr.h b/src/shared/revision_nr.h index ca4473cdd..4ed355b53 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 "11586" + #define REVISION_NR "11587" #endif // __REVISION_NR_H__