[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.
This commit is contained in:
VladimirMangos 2011-06-02 09:17:08 +04:00
parent c2199e7030
commit 108a167c46
13 changed files with 179 additions and 29 deletions

View file

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

View file

@ -140,6 +140,7 @@ DBCStorage <ScalingStatValuesEntry> sScalingStatValuesStore(ScalingStatValuesfmt
DBCStorage <SkillLineEntry> sSkillLineStore(SkillLinefmt);
DBCStorage <SkillLineAbilityEntry> sSkillLineAbilityStore(SkillLineAbilityfmt);
DBCStorage <SkillRaceClassInfoEntry> sSkillRaceClassInfoStore(SkillRaceClassInfofmt);
DBCStorage <SoundEntriesEntry> 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)

View file

@ -141,6 +141,7 @@ extern DBCStorage <ScalingStatDistributionEntry> sScalingStatDistributionStore;
extern DBCStorage <ScalingStatValuesEntry> sScalingStatValuesStore;
extern DBCStorage <SkillLineEntry> sSkillLineStore;
extern DBCStorage <SkillLineAbilityEntry> sSkillLineAbilityStore;
extern DBCStorage <SkillRaceClassInfoEntry> sSkillRaceClassInfoStore;
extern DBCStorage <SoundEntriesEntry> sSoundEntriesStore;
extern DBCStorage <SpellCastTimesEntry> sSpellCastTimesStore;
extern DBCStorage <SpellDifficultyEntry> sSpellDifficultyStore;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -802,6 +802,9 @@ typedef std::pair<SpellLearnSpellMap::const_iterator,SpellLearnSpellMap::const_i
typedef std::multimap<uint32, SkillLineAbilityEntry const*> SkillLineAbilityMap;
typedef std::pair<SkillLineAbilityMap::const_iterator,SkillLineAbilityMap::const_iterator> SkillLineAbilityMapBounds;
typedef std::multimap<uint32, SkillRaceClassInfoEntry const*> SkillRaceClassInfoMap;
typedef std::pair<SkillRaceClassInfoMap::const_iterator,SkillRaceClassInfoMap::const_iterator> SkillRaceClassInfoMapBounds;
typedef std::multimap<uint32, uint32> PetLevelupSpellSet;
typedef std::map<uint32, PetLevelupSpellSet> 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

View file

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

View file

@ -1,4 +1,4 @@
#ifndef __REVISION_NR_H__
#define __REVISION_NR_H__
#define REVISION_NR "11586"
#define REVISION_NR "11587"
#endif // __REVISION_NR_H__