Core/Skills: ported skill refactors from master branch

* implement automatic skill learning from dbc data which
This commit is contained in:
Ovahlord committed 2025-06-21 01:23:27 +02:00
1 parent d4b18aad8f
commit a9579cb33b
17 files changed
+284 -210

No files matched your search

@@ -0,0 +1 @@
DROP TABLE IF EXISTS `playercreateinfo_skills`;
+25 -5
View File
@@ -70,6 +70,13 @@ namespace Trinity
namespace Containers namespace Containers
{ {
template <typename M>
concept Map = requires (M)
{
typename M::key_type;
typename M::mapped_type;
};
// replace with std::size in C++17 // replace with std::size in C++17
template<class C> template<class C>
constexpr inline std::size_t Size(C const& container) constexpr inline std::size_t Size(C const& container)
@@ -253,15 +260,28 @@ namespace Trinity
/** /**
* Returns a pointer to mapped value (or the value itself if map stores pointers) * Returns a pointer to mapped value (or the value itself if map stores pointers)
*/ */
template<class M> template <Map M>
inline auto MapGetValuePtr(M& map, typename M::key_type const& key) -> decltype(AddressOrSelf(map.find(key)->second)) inline auto MapGetValuePtr(M& map, typename M::key_type const& key)
{ {
using mapped_type = typename M::mapped_type;
auto itr = map.find(key); auto itr = map.find(key);
return itr != map.end() ? AddressOrSelf(itr->second) : nullptr; if constexpr (std::is_pointer_v<mapped_type>)
return itr != map.end() ? itr->second : nullptr; // raw pointer
else if constexpr (requires(mapped_type const& p) { p.operator->(); })
{
// smart pointers
if constexpr (std::is_copy_constructible_v<mapped_type>)
return itr != map.end() ? itr->second : nullptr; // copyable (like shared_ptr)
else
return itr != map.end() ? std::to_address(itr->second) : nullptr; // non-copyable unique_ptr like, unwrap it to raw pointer
}
else
return itr != map.end() ? std::addressof(itr->second) : nullptr; // value
} }
template<class K, class V, template<class, class, class...> class M, class... Rest> template <Map M>
inline void MultimapErasePair(M<K, V, Rest...>& multimap, K const& key, V const& value) inline void MultimapErasePair(M& multimap, typename M::key_type const& key, typename M::mapped_type const& value)
{ {
auto range = multimap.equal_range(key); auto range = multimap.equal_range(key);
for (auto itr = range.first; itr != range.second; ) for (auto itr = range.first; itr != range.second; )
+6 -3
View File
@@ -532,10 +532,13 @@ enum class MapFlags : uint32
DEFINE_ENUM_FLAG(MapFlags); DEFINE_ENUM_FLAG(MapFlags);
enum AbilytyLearnType enum class SkillLineAbilityAcquireMethod : int32
{ {
SKILL_LINE_ABILITY_LEARNED_ON_SKILL_VALUE = 1, // Spell state will update depending on skill value Learned = 0,
SKILL_LINE_ABILITY_LEARNED_ON_SKILL_LEARN = 2 // Spell will be learned/removed together with entire skill AutomaticSkillRank = 1, // Spell state will update depending on skill value
AutomaticCharLevel = 2, // Spell will be learned/removed together with entire skill
NeverLearned = 3,
LearnedOrAutomaticCharLevel = 4,
}; };
enum ItemEnchantmentType enum ItemEnchantmentType
+1 -1
View File
@@ -648,7 +648,7 @@ void DBCManager::LoadStores(const std::string& dataPath, uint32 defaultLocale)
if (skillLine->SkillLine != cFamily->SkillLine[0] && skillLine->SkillLine != cFamily->SkillLine[1]) if (skillLine->SkillLine != cFamily->SkillLine[0] && skillLine->SkillLine != cFamily->SkillLine[1])
continue; continue;
if (skillLine->AcquireMethod != SKILL_LINE_ABILITY_LEARNED_ON_SKILL_LEARN) if (skillLine->GetAcquireMethod() != SkillLineAbilityAcquireMethod::AutomaticCharLevel)
continue; continue;
sPetFamilySpellsStore[cFamily->ID].insert(spellInfo->ID); sPetFamilySpellsStore[cFamily->ID].insert(spellInfo->ID);
+1
View File
@@ -152,6 +152,7 @@ TC_GAME_API extern DBCStorage <ResearchProjectEntry> sResearchProjectSto
TC_GAME_API extern DBCStorage <ResearchSiteEntry> sResearchSiteStore; TC_GAME_API extern DBCStorage <ResearchSiteEntry> sResearchSiteStore;
TC_GAME_API extern DBCStorage <ScalingStatDistributionEntry> sScalingStatDistributionStore; TC_GAME_API extern DBCStorage <ScalingStatDistributionEntry> sScalingStatDistributionStore;
TC_GAME_API extern DBCStorage <ScalingStatValuesEntry> sScalingStatValuesStore; TC_GAME_API extern DBCStorage <ScalingStatValuesEntry> sScalingStatValuesStore;
TC_GAME_API extern DBCStorage <SkillRaceClassInfoEntry> sSkillRaceClassInfoStore;
TC_GAME_API extern DBCStorage <SkillLineEntry> sSkillLineStore; TC_GAME_API extern DBCStorage <SkillLineEntry> sSkillLineStore;
TC_GAME_API extern DBCStorage <SkillLineAbilityEntry> sSkillLineAbilityStore; TC_GAME_API extern DBCStorage <SkillLineAbilityEntry> sSkillLineAbilityStore;
TC_GAME_API extern DBCStorage <SkillTiersEntry> sSkillTiersStore; TC_GAME_API extern DBCStorage <SkillTiersEntry> sSkillTiersStore;
+14 -1
View File
@@ -1521,6 +1521,8 @@ struct SkillLineAbilityEntry
uint32 TrivialSkillLineRankLow; // 11 uint32 TrivialSkillLineRankLow; // 11
uint32 NumSkillUps; // 12 uint32 NumSkillUps; // 12
uint32 UniqueBit; // 13 uint32 UniqueBit; // 13
SkillLineAbilityAcquireMethod GetAcquireMethod() const { return static_cast<SkillLineAbilityAcquireMethod>(AcquireMethod); }
}; };
struct SkillRaceClassInfoEntry struct SkillRaceClassInfoEntry
@@ -1531,7 +1533,7 @@ struct SkillRaceClassInfoEntry
uint32 ClassMask; // 3 uint32 ClassMask; // 3
uint32 Flags; // 4 uint32 Flags; // 4
uint32 Availability; // 5 uint32 Availability; // 5
//uint32 MinLevel; // 6 uint32 MinLevel; // 6
uint32 SkillTierID; // 7 uint32 SkillTierID; // 7
//uint32 SkillCostIndex; // 8 //uint32 SkillCostIndex; // 8
}; };
@@ -1543,6 +1545,17 @@ struct SkillTiersEntry
uint32 ID; // 0 uint32 ID; // 0
//uint32 Cost[MAX_SKILL_STEP]; // 1-16 //uint32 Cost[MAX_SKILL_STEP]; // 1-16
uint32 Value[MAX_SKILL_STEP]; // 17-32 uint32 Value[MAX_SKILL_STEP]; // 17-32
uint32 GetValueForTierIndex(uint32 tierIndex) const
{
if (tierIndex >= MAX_SKILL_STEP)
tierIndex = MAX_SKILL_STEP - 1;
while (Value[tierIndex] == 0 && tierIndex > 0)
--tierIndex;
return Value[tierIndex];
}
}; };
struct SoundEntriesEntry struct SoundEntriesEntry
+1 -1
View File
@@ -138,7 +138,7 @@ char const ScalingStatDistributionfmt[] = "niiiiiiiiiiiiiiiiiiiiii";
char const ScalingStatValuesfmt[] = "iniiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii"; char const ScalingStatValuesfmt[] = "iniiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii";
char const SkillLinefmt[] = "nisxixi"; char const SkillLinefmt[] = "nisxixi";
char const SkillLineAbilityfmt[] = "niiiixxiiiiiii"; char const SkillLineAbilityfmt[] = "niiiixxiiiiiii";
char const SkillRaceClassInfofmt[] = "diiiiixix"; char const SkillRaceClassInfofmt[] = "diiiiiiix";
char const SkillTiersfmt[] = "nxxxxxxxxxxxxxxxxiiiiiiiiiiiiiiii"; char const SkillTiersfmt[] = "nxxxxxxxxxxxxxxxxiiiiiiiiiiiiiiii";
char const SoundEntriesfmt[] = "nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; char const SoundEntriesfmt[] = "nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
char const SpellCastTimefmt[] = "nixx"; char const SpellCastTimefmt[] = "nixx";
+143 -64
View File
@@ -2952,13 +2952,44 @@ bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent
// add dependent skills if this spell is not learned from adding skill already // add dependent skills if this spell is not learned from adding skill already
if (spellLearnSkill->skill != fromSkill) if (spellLearnSkill->skill != fromSkill)
{ {
uint32 skill_value = GetPureSkillValue(spellLearnSkill->skill); uint16 skill_value = GetPureSkillValue(spellLearnSkill->skill);
uint32 skill_max_value = GetPureMaxSkillValue(spellLearnSkill->skill); uint16 skill_max_value = GetPureMaxSkillValue(spellLearnSkill->skill);
if (skill_value < spellLearnSkill->value) if (skill_value < spellLearnSkill->value)
skill_value = spellLearnSkill->value; skill_value = spellLearnSkill->value;
uint32 new_skill_max_value = spellLearnSkill->maxvalue == 0 ? GetMaxSkillValueForLevel() : spellLearnSkill->maxvalue; uint16 new_skill_max_value = spellLearnSkill->maxvalue;
if (new_skill_max_value == 0)
{
if (SkillRaceClassInfoEntry const* rcInfo = sDBCManager.GetSkillRaceClassInfo(spellLearnSkill->skill, getRace(), getClass()))
{
switch (GetSkillRangeType(rcInfo))
{
case SKILL_RANGE_LANGUAGE:
skill_value = 300;
new_skill_max_value = 300;
break;
case SKILL_RANGE_LEVEL:
new_skill_max_value = GetMaxSkillValueForLevel();
break;
case SKILL_RANGE_MONO:
new_skill_max_value = 1;
break;
case SKILL_RANGE_RANK:
{
SkillTiersEntry const* tier = sSkillTiersStore.AssertEntry(rcInfo->SkillTierID);
new_skill_max_value = tier->GetValueForTierIndex(spellLearnSkill->step - 1);
break;
}
default:
break;
}
if (rcInfo->Flags & SKILL_FLAG_ALWAYS_MAX_VALUE)
skill_value = new_skill_max_value;
}
}
if (skill_max_value < new_skill_max_value) if (skill_max_value < new_skill_max_value)
skill_max_value = new_skill_max_value; skill_max_value = new_skill_max_value;
@@ -2978,10 +3009,10 @@ bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent
if (pSkill->ID == fromSkill) if (pSkill->ID == fromSkill)
continue; continue;
///@todo: confirm if rogues start with lockpicking skill at level 1 but only receive the spell to use it at level 16 // Runeforging special case
// Also added for runeforging. It's already confirmed this happens upon learning for Death Knights, not from character creation. if ((_spell_idx->second->GetAcquireMethod() == SkillLineAbilityAcquireMethod::AutomaticCharLevel && !HasSkill(_spell_idx->second->SkillLine)) || ((_spell_idx->second->SkillLine == SKILL_RUNEFORGING) && _spell_idx->second->TrivialSkillLineRankHigh == 0))
if ((_spell_idx->second->AcquireMethod == SKILL_LINE_ABILITY_LEARNED_ON_SKILL_LEARN && !HasSkill(pSkill->ID)) || ((pSkill->ID == SKILL_LOCKPICKING || pSkill->ID == SKILL_RUNEFORGING) && _spell_idx->second->TrivialSkillLineRankHigh == 0)) if (SkillRaceClassInfoEntry const* rcInfo = sDBCManager.GetSkillRaceClassInfo(_spell_idx->second->SkillLine, getRace(), getClass()))
LearnDefaultSkill(pSkill->ID, 0); LearnDefaultSkill(rcInfo);
} }
} }
@@ -3213,17 +3244,50 @@ void Player::RemoveSpell(uint32 spell_id, bool disabled, bool learn_low_rank)
SetSkill(spellLearnSkill->skill, 0, 0, 0); SetSkill(spellLearnSkill->skill, 0, 0, 0);
else // set to prev. skill setting values else // set to prev. skill setting values
{ {
uint32 skill_value = GetPureSkillValue(prevSkill->skill); uint16 skill_value = GetPureSkillValue(prevSkill->skill);
uint32 skill_max_value = GetPureMaxSkillValue(prevSkill->skill); uint16 skill_max_value = GetPureMaxSkillValue(prevSkill->skill);
if (skill_value > prevSkill->value) uint16 new_skill_max_value = prevSkill->maxvalue;
if (new_skill_max_value == 0)
{
if (SkillRaceClassInfoEntry const* rcInfo = sDBCManager.GetSkillRaceClassInfo(prevSkill->skill, getRace(), getClass()))
{
switch (GetSkillRangeType(rcInfo))
{
case SKILL_RANGE_LANGUAGE:
skill_value = 300;
new_skill_max_value = 300;
break;
case SKILL_RANGE_LEVEL:
new_skill_max_value = GetMaxSkillValueForLevel();
break;
case SKILL_RANGE_MONO:
new_skill_max_value = 1;
break;
case SKILL_RANGE_RANK:
{
SkillTiersEntry const* tier = sSkillTiersStore.AssertEntry(rcInfo->SkillTierID);
new_skill_max_value = tier->GetValueForTierIndex(prevSkill->step - 1);
break;
}
default:
break;
}
if (rcInfo->Flags & SKILL_FLAG_ALWAYS_MAX_VALUE)
skill_value = new_skill_max_value;
}
}
else if (skill_value > prevSkill->value)
skill_value = prevSkill->value; skill_value = prevSkill->value;
uint32 new_skill_max_value = prevSkill->maxvalue == 0 ? GetMaxSkillValueForLevel() : prevSkill->maxvalue;
if (skill_max_value > new_skill_max_value) if (skill_max_value > new_skill_max_value)
skill_max_value = new_skill_max_value; skill_max_value = new_skill_max_value;
if (skill_value > new_skill_max_value)
skill_value = new_skill_max_value;
SetSkill(prevSkill->skill, prevSkill->step, skill_value, skill_max_value); SetSkill(prevSkill->skill, prevSkill->step, skill_value, skill_max_value);
} }
} }
@@ -5401,12 +5465,30 @@ void Player::InitializeSkillFields()
// To "remove" a skill line, set it's values to zero // To "remove" a skill line, set it's values to zero
void Player::SetSkill(uint16 id, uint16 step, uint16 newVal, uint16 maxVal) void Player::SetSkill(uint16 id, uint16 step, uint16 newVal, uint16 maxVal)
{ {
if (!id) SkillLineEntry const* skillEntry = sSkillLineStore.LookupEntry(id);
if (!skillEntry)
{
TC_LOG_ERROR("misc", "Player::SetSkill: Skill (SkillID: %u) not found in SkillLineStore for player '%s' (%s)",
id, GetName().c_str(), GetGUID().ToString().c_str());
return; return;
}
uint16 currVal; uint16 currVal;
SkillStatusMap::iterator itr = mSkillStatus.find(id); SkillStatusMap::iterator itr = mSkillStatus.find(id);
auto refreshSkillBonusAuras = [&]
{
// Temporary bonuses
for (AuraEffect* effect : GetAuraEffectsByType(SPELL_AURA_MOD_SKILL))
if (effect->GetMiscValue() == int32(id))
effect->HandleEffect(this, AURA_EFFECT_HANDLE_SKILL, true);
// Permanent bonuses
for (AuraEffect* effect : GetAuraEffectsByType(SPELL_AURA_MOD_SKILL_TALENT))
if (effect->GetMiscValue() == int32(id))
effect->HandleEffect(this, AURA_EFFECT_HANDLE_SKILL, true);
};
// Handle already stored skills // Handle already stored skills
if (itr != mSkillStatus.end()) if (itr != mSkillStatus.end())
{ {
@@ -5441,7 +5523,21 @@ void Player::SetSkill(uint16 id, uint16 step, uint16 newVal, uint16 maxVal)
if (itr->second.uState == SKILL_UNCHANGED) if (itr->second.uState == SKILL_UNCHANGED)
{ {
if (currVal == 0) // activated skill, mark as new to save into database if (currVal == 0) // activated skill, mark as new to save into database
itr->second.uState = SKILL_NEW; {
itr->second.uState = itr->second.uState != SKILL_DELETED
? SKILL_NEW
: SKILL_CHANGED; // skills marked as SKILL_DELETED already exist in database, mark as changed instead of new
if (skillEntry->CategoryID == SKILL_CATEGORY_PROFESSION)
{
if (!GetUInt32Value(PLAYER_PROFESSION_SKILL_LINE_1))
SetUInt32Value(PLAYER_PROFESSION_SKILL_LINE_1, id);
else if (!GetUInt32Value(PLAYER_PROFESSION_SKILL_LINE_1 + 1))
SetUInt32Value(PLAYER_PROFESSION_SKILL_LINE_1 + 1, id);
}
refreshSkillBonusAuras();
}
else // updated skill, mark as changed to save into database else // updated skill, mark as changed to save into database
itr->second.uState = SKILL_CHANGED; itr->second.uState = SKILL_CHANGED;
} }
@@ -5467,10 +5563,9 @@ void Player::SetSkill(uint16 id, uint16 step, uint16 newVal, uint16 maxVal)
SetSkillPermBonus(itr->second.pos, 0); SetSkillPermBonus(itr->second.pos, 0);
// mark as deleted so the next save will delete the data from the database // mark as deleted so the next save will delete the data from the database
if (itr->second.uState != SKILL_NEW) itr->second.uState = itr->second.uState != SKILL_NEW
itr->second.uState = SKILL_DELETED; ? SKILL_DELETED
else : SKILL_UNCHANGED; // skills marked as SKILL_NEW don't exist in database (this distinction is not neccessary for deletion but for re-learning the same skill before save to db happens)
itr->second.uState = SKILL_UNCHANGED;
// remove all spells that related to this skill // remove all spells that related to this skill
if (std::vector<SkillLineAbilityEntry const*> const* skillLineAbilities = sDBCManager.GetSkillLineAbilitiesBySkill(id)) if (std::vector<SkillLineAbilityEntry const*> const* skillLineAbilities = sDBCManager.GetSkillLineAbilitiesBySkill(id))
@@ -5509,13 +5604,6 @@ void Player::SetSkill(uint16 id, uint16 step, uint16 newVal, uint16 maxVal)
return; return;
} }
SkillLineEntry const* skillEntry = sSkillLineStore.LookupEntry(id);
if (!skillEntry)
{
TC_LOG_ERROR("misc", "Skill not found in SkillLineStore: skill #%u", id);
return;
}
if (skillEntry->CategoryID == SKILL_CATEGORY_PROFESSION) if (skillEntry->CategoryID == SKILL_CATEGORY_PROFESSION)
{ {
if (!GetUInt32Value(PLAYER_PROFESSION_SKILL_LINE_1)) if (!GetUInt32Value(PLAYER_PROFESSION_SKILL_LINE_1))
@@ -5544,15 +5632,7 @@ void Player::SetSkill(uint16 id, uint16 step, uint16 newVal, uint16 maxVal)
UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL, id); UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL, id);
UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILL_LEVEL, id); UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILL_LEVEL, id);
// temporary bonuses refreshSkillBonusAuras();
for (AuraEffect* effect : GetAuraEffectsByType(SPELL_AURA_MOD_SKILL))
if (effect->GetMiscValue() == int32(id))
effect->HandleEffect(this, AURA_EFFECT_HANDLE_SKILL, true);
// permanent bonuses
for (AuraEffect* effect : GetAuraEffectsByType(SPELL_AURA_MOD_SKILL_TALENT))
if (effect->GetMiscValue() == int32(id))
effect->HandleEffect(this, AURA_EFFECT_HANDLE_SKILL, true);
// archaeology skill learned // archaeology skill learned
if (id == SKILL_ARCHAEOLOGY) if (id == SKILL_ARCHAEOLOGY)
@@ -23128,21 +23208,20 @@ void Player::LearnDefaultSkills()
ASSERT(info); ASSERT(info);
for (PlayerCreateInfoSkills::const_iterator itr = info->skills.begin(); itr != info->skills.end(); ++itr) for (PlayerCreateInfoSkills::const_iterator itr = info->skills.begin(); itr != info->skills.end(); ++itr)
{ {
uint32 skillId = itr->SkillId; SkillRaceClassInfoEntry const* rcInfo = *itr;
if (HasSkill(skillId)) if (HasSkill(rcInfo->SkillID))
continue; continue;
LearnDefaultSkill(skillId, itr->Rank); if (rcInfo->MinLevel > getLevel())
continue;
LearnDefaultSkill(rcInfo);
} }
} }
void Player::LearnDefaultSkill(uint32 skillId, uint16 rank) void Player::LearnDefaultSkill(SkillRaceClassInfoEntry const* rcInfo)
{ {
SkillRaceClassInfoEntry const* rcInfo = sDBCManager.GetSkillRaceClassInfo(skillId, getRace(), getClass()); uint16 skillId = rcInfo->SkillID;
if (!rcInfo)
return;
TC_LOG_DEBUG("entities.player.loading", "PLAYER (Class: %u Race: %u): Adding initial skill, id = %u", uint32(getClass()), uint32(getRace()), skillId);
switch (GetSkillRangeType(rcInfo)) switch (GetSkillRangeType(rcInfo))
{ {
case SKILL_RANGE_LANGUAGE: case SKILL_RANGE_LANGUAGE:
@@ -23169,16 +23248,13 @@ void Player::LearnDefaultSkill(uint32 skillId, uint16 rank)
break; break;
case SKILL_RANGE_RANK: case SKILL_RANGE_RANK:
{ {
if (!rank)
break;
SkillTiersEntry const* tier = sSkillTiersStore.LookupEntry(rcInfo->SkillTierID); SkillTiersEntry const* tier = sSkillTiersStore.LookupEntry(rcInfo->SkillTierID);
uint16 maxValue = tier->Value[std::max<int32>(rank - 1, 0)]; uint16 maxValue = tier->GetValueForTierIndex(0);
uint16 skillValue = 1; uint16 skillValue = 1;
if (rcInfo->Flags & SKILL_FLAG_ALWAYS_MAX_VALUE) if (rcInfo->Flags & SKILL_FLAG_ALWAYS_MAX_VALUE)
skillValue = maxValue; skillValue = maxValue;
else if (getClass() == CLASS_DEATH_KNIGHT) else if (getClass() == CLASS_DEATH_KNIGHT)
skillValue = std::min(std::max<uint16>({ uint16(1), uint16((getLevel() - 1) * 5) }), maxValue); skillValue = std::min(std::max(uint16(1), uint16((getLevel() - 1) * 5)), maxValue);
SetSkill(skillId, 1, skillValue, maxValue); SetSkill(skillId, 1, skillValue, maxValue);
break; break;
@@ -23226,13 +23302,19 @@ void Player::LearnQuestRewardedSpells(Quest const* quest)
uint32 learned_0 = spellInfo->Effects[0].TriggerSpell; uint32 learned_0 = spellInfo->Effects[0].TriggerSpell;
if (!HasSpell(learned_0)) if (!HasSpell(learned_0))
{ {
SpellInfo const* learnedInfo = sSpellMgr->GetSpellInfo(learned_0); found = false;
if (!learnedInfo) SkillLineAbilityMapBounds skills = sSpellMgr->GetSkillLineAbilityMapBounds(learned_0);
return; for (auto skillItr = skills.first; skillItr != skills.second; ++skillItr)
{
if (skillItr->second->GetAcquireMethod() == SkillLineAbilityAcquireMethod::LearnedOrAutomaticCharLevel)
{
found = true;
break;
}
}
// profession specialization can be re-learned from npc if (!found)
if (learnedInfo->Effects[0].Effect == SPELL_EFFECT_TRADE_SKILL && learnedInfo->Effects[1].Effect == 0 && !learnedInfo->SpellLevel) return;
return;
} }
CastSpell(this, spell_id, true); CastSpell(this, spell_id, true);
@@ -23265,19 +23347,15 @@ void Player::LearnSkillRewardedSpells(uint32 skillId, uint32 skillValue)
if (!spellInfo) if (!spellInfo)
continue; continue;
switch (ability->AcquireMethod) switch (ability->GetAcquireMethod())
{ {
case SKILL_LINE_ABILITY_LEARNED_ON_SKILL_VALUE: case SkillLineAbilityAcquireMethod::AutomaticSkillRank:
case SKILL_LINE_ABILITY_LEARNED_ON_SKILL_LEARN: case SkillLineAbilityAcquireMethod::AutomaticCharLevel:
break; break;
default: default:
continue; continue;
} }
// AcquireMethod == 2 && NumSkillUps == 1 --> automatically learn riding skill spell, else we skip it (client shows riding in spellbook as trainable).
if (skillId == SKILL_RIDING && (ability->AcquireMethod != SKILL_LINE_ABILITY_LEARNED_ON_SKILL_LEARN || ability->NumSkillUps != 1))
continue;
// Check race if set // Check race if set
if (ability->RaceMask && !(ability->RaceMask & raceMask)) if (ability->RaceMask && !(ability->RaceMask & raceMask))
continue; continue;
@@ -23286,12 +23364,13 @@ void Player::LearnSkillRewardedSpells(uint32 skillId, uint32 skillValue)
if (ability->ClassMask && !(ability->ClassMask & classMask)) if (ability->ClassMask && !(ability->ClassMask & classMask))
continue; continue;
// check level, skip class spells if not high enough // Check level, skip class spells if not high enough
if (getLevel() < spellInfo->SpellLevel) uint32 requiredLevel = std::max(spellInfo->SpellLevel, spellInfo->BaseLevel);
if (requiredLevel > getLevel())
continue; continue;
// need unlearn spell // need unlearn spell
if (skillValue < ability->MinSkillLineRank && ability->AcquireMethod == SKILL_LINE_ABILITY_LEARNED_ON_SKILL_VALUE) if (int32(skillValue) < ability->MinSkillLineRank && ability->GetAcquireMethod() == SkillLineAbilityAcquireMethod::AutomaticSkillRank)
RemoveSpell(ability->Spell); RemoveSpell(ability->Spell);
// need learn // need learn
+2 -1
View File
@@ -52,6 +52,7 @@ struct PlayerTalent;
struct PlayerTalentInfo; struct PlayerTalentInfo;
struct ScalingStatDistributionEntry; struct ScalingStatDistributionEntry;
struct ScalingStatValuesEntry; struct ScalingStatValuesEntry;
struct SkillRaceClassInfoEntry;
struct TrainerSpell; struct TrainerSpell;
struct VendorItem; struct VendorItem;
struct GuildMemberProfessionData; struct GuildMemberProfessionData;
@@ -1549,7 +1550,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
void ResetSpells(bool myClassOnly = false); void ResetSpells(bool myClassOnly = false);
void LearnCustomSpells(); void LearnCustomSpells();
void LearnDefaultSkills(); void LearnDefaultSkills();
void LearnDefaultSkill(uint32 skillId, uint16 rank); void LearnDefaultSkill(SkillRaceClassInfoEntry const* rcInfo);
void LearnQuestRewardedSpells(); void LearnQuestRewardedSpells();
void LearnQuestRewardedSpells(Quest const* quest); void LearnQuestRewardedSpells(Quest const* quest);
void LearnSpellHighestRank(uint32 spellid); void LearnSpellHighestRank(uint32 spellid);
+31 -104
View File
@@ -247,9 +247,6 @@ ObjectMgr::ObjectMgr():
_gameObjectSpawnId(1), _gameObjectSpawnId(1),
DBCLocaleIndex(LOCALE_enUS) DBCLocaleIndex(LOCALE_enUS)
{ {
for (uint8 i = 0; i < MAX_CLASSES; ++i)
for (uint8 j = 0; j < MAX_RACES; ++j)
_playerInfo[j][i] = nullptr;
} }
ObjectMgr* ObjectMgr::instance() ObjectMgr* ObjectMgr::instance()
@@ -263,16 +260,6 @@ ObjectMgr::~ObjectMgr()
for (PetLevelInfoContainer::iterator i = _petInfoStore.begin(); i != _petInfoStore.end(); ++i) for (PetLevelInfoContainer::iterator i = _petInfoStore.begin(); i != _petInfoStore.end(); ++i)
delete[] i->second; delete[] i->second;
for (int race = 0; race < MAX_RACES; ++race)
{
for (int class_ = 0; class_ < MAX_CLASSES; ++class_)
{
if (_playerInfo[race][class_])
delete[] _playerInfo[race][class_]->levelInfo;
delete _playerInfo[race][class_];
}
}
for (CacheVendorItemContainer::iterator itr = _cacheVendorItemStore.begin(); itr != _cacheVendorItemStore.end(); ++itr) for (CacheVendorItemContainer::iterator itr = _cacheVendorItemStore.begin(); itr != _cacheVendorItemStore.end(); ++itr)
itr->second.Clear(); itr->second.Clear();
@@ -3321,11 +3308,12 @@ PetLevelInfo const* ObjectMgr::GetPetLevelInfo(uint32 creature_id, uint8 level)
void ObjectMgr::PlayerCreateInfoAddItemHelper(uint32 race_, uint32 class_, uint32 itemId, int32 count) void ObjectMgr::PlayerCreateInfoAddItemHelper(uint32 race_, uint32 class_, uint32 itemId, int32 count)
{ {
if (!_playerInfo[race_][class_]) PlayerInfo* playerInfo = Trinity::Containers::MapGetValuePtr(_playerInfo, { Races(race_), Classes(class_) });
if (!playerInfo)
return; return;
if (count > 0) if (count > 0)
_playerInfo[race_][class_]->item.push_back(PlayerCreateInfoItem(itemId, count)); playerInfo->item.emplace_back(itemId, count);
else else
{ {
if (count < -1) if (count < -1)
@@ -3421,7 +3409,7 @@ void ObjectMgr::LoadPlayerInfo()
continue; continue;
} }
PlayerInfo* info = new PlayerInfo(); std::unique_ptr<PlayerInfo> info = std::make_unique<PlayerInfo>();
info->mapId = mapId; info->mapId = mapId;
info->areaId = areaId; info->areaId = areaId;
info->positionX = positionX; info->positionX = positionX;
@@ -3430,7 +3418,7 @@ void ObjectMgr::LoadPlayerInfo()
info->orientation = orientation; info->orientation = orientation;
info->displayId_m = rEntry->MaleDisplayID; info->displayId_m = rEntry->MaleDisplayID;
info->displayId_f = rEntry->FemaleDisplayID; info->displayId_f = rEntry->FemaleDisplayID;
_playerInfo[current_race][current_class] = info; _playerInfo[{ Races(current_race), Classes(current_class) }] = std::move(info);
++count; ++count;
} }
@@ -3516,77 +3504,19 @@ void ObjectMgr::LoadPlayerInfo()
{ {
uint32 oldMSTime = getMSTime(); uint32 oldMSTime = getMSTime();
QueryResult result = WorldDatabase.PQuery("SELECT raceMask, classMask, skill, `rank` FROM playercreateinfo_skills"); for (SkillRaceClassInfoEntry const* rcInfo : sSkillRaceClassInfoStore)
if (rcInfo->Availability == 1)
if (!result)
{
TC_LOG_ERROR("server.loading", ">> Loaded 0 player create skills. DB table `playercreateinfo_skills` is empty.");
}
else
{
uint32 count = 0;
do
{
Field* fields = result->Fetch();
uint32 raceMask = fields[0].GetUInt32();
uint32 classMask = fields[1].GetUInt32();
PlayerCreateInfoSkill skill;
skill.SkillId = fields[2].GetUInt16();
skill.Rank = fields[3].GetUInt16();
if (skill.Rank >= MAX_SKILL_STEP)
{
TC_LOG_ERROR("sql.sql", "Skill rank value %hu set for skill %hu raceMask %u classMask %u is too high, max allowed value is %d", skill.Rank, skill.SkillId, raceMask, classMask, MAX_SKILL_STEP);
continue;
}
if (raceMask != 0 && !(raceMask & RACEMASK_ALL_PLAYABLE))
{
TC_LOG_ERROR("sql.sql", "Wrong race mask %u in `playercreateinfo_skills` table, ignoring.", raceMask);
continue;
}
if (classMask != 0 && !(classMask & CLASSMASK_ALL_PLAYABLE))
{
TC_LOG_ERROR("sql.sql", "Wrong class mask %u in `playercreateinfo_skills` table, ignoring.", classMask);
continue;
}
if (!sSkillLineStore.LookupEntry(skill.SkillId))
{
TC_LOG_ERROR("sql.sql", "Wrong skill id %u in `playercreateinfo_skills` table, ignoring.", skill.SkillId);
continue;
}
for (uint32 raceIndex = RACE_HUMAN; raceIndex < MAX_RACES; ++raceIndex) for (uint32 raceIndex = RACE_HUMAN; raceIndex < MAX_RACES; ++raceIndex)
{ if (rcInfo->RaceMask == 0 || (rcInfo->RaceMask & (1 << (raceIndex - 1)) != 0))
if (raceMask == 0 || ((1 << (raceIndex - 1)) & raceMask))
{
for (uint32 classIndex = CLASS_WARRIOR; classIndex < MAX_CLASSES; ++classIndex) for (uint32 classIndex = CLASS_WARRIOR; classIndex < MAX_CLASSES; ++classIndex)
{ if (rcInfo->ClassMask == -1 || rcInfo->ClassMask == 0 || ((1 << (classIndex - 1)) & rcInfo->ClassMask))
if (classMask == 0 || ((1 << (classIndex - 1)) & classMask)) if (auto const& playerInfo = Trinity::Containers::MapGetValuePtr(_playerInfo, { Races(raceIndex), Classes(classIndex) }))
{ playerInfo->skills.push_back(rcInfo);
if (!sDBCManager.GetSkillRaceClassInfo(skill.SkillId, raceIndex, classIndex))
continue;
if (PlayerInfo* info = _playerInfo[raceIndex][classIndex]) TC_LOG_INFO("server.loading", ">> Loaded player create skills in %u ms", GetMSTimeDiffToNow(oldMSTime));
{
info->skills.push_back(skill);
++count;
}
}
}
}
}
} while (result->NextRow());
TC_LOG_INFO("server.loading", ">> Loaded %u player create skills in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
}
} }
// Load playercreate spells // Load playercreate spells
TC_LOG_INFO("server.loading", "Loading Player Create Spell Data..."); TC_LOG_INFO("server.loading", "Loading Player Create Custom Spell Data...");
{ {
uint32 oldMSTime = getMSTime(); uint32 oldMSTime = getMSTime();
@@ -3627,9 +3557,9 @@ void ObjectMgr::LoadPlayerInfo()
{ {
if (classMask == 0 || ((1 << (classIndex - 1)) & classMask)) if (classMask == 0 || ((1 << (classIndex - 1)) & classMask))
{ {
if (PlayerInfo* info = _playerInfo[raceIndex][classIndex]) if (auto const& playerInfo = Trinity::Containers::MapGetValuePtr(_playerInfo, { Races(raceIndex), Classes(classIndex) }))
{ {
info->customSpells.push_back(spellId); playerInfo->customSpells.push_back(spellId);
++count; ++count;
} }
// We need something better here, the check is not accounting for spells used by multiple races/classes but not all of them. // We need something better here, the check is not accounting for spells used by multiple races/classes but not all of them.
@@ -3687,9 +3617,9 @@ void ObjectMgr::LoadPlayerInfo()
{ {
if (classMask == 0 || ((1 << (classIndex - 1)) & classMask)) if (classMask == 0 || ((1 << (classIndex - 1)) & classMask))
{ {
if (PlayerInfo* info = _playerInfo[raceIndex][classIndex]) if (auto const& playerInfo = Trinity::Containers::MapGetValuePtr(_playerInfo, { Races(raceIndex), Classes(classIndex) }))
{ {
info->castSpells.push_back(spellId); playerInfo->castSpells.push_back(spellId);
++count; ++count;
} }
} }
@@ -3736,8 +3666,8 @@ void ObjectMgr::LoadPlayerInfo()
continue; continue;
} }
if (PlayerInfo* info = _playerInfo[current_race][current_class]) if (auto const& playerInfo = Trinity::Containers::MapGetValuePtr(_playerInfo, { Races(current_race), Classes(current_class) }))
info->action.push_back(PlayerCreateInfoAction(fields[2].GetUInt16(), fields[3].GetUInt32(), fields[4].GetUInt16())); playerInfo->action.push_back(PlayerCreateInfoAction(fields[2].GetUInt16(), fields[3].GetUInt32(), fields[4].GetUInt16()));
++count; ++count;
} }
@@ -3822,12 +3752,12 @@ void ObjectMgr::LoadPlayerInfo()
for (std::size_t race = 0; race < raceStatModifiers.size(); ++race) for (std::size_t race = 0; race < raceStatModifiers.size(); ++race)
{ {
if (PlayerInfo* info = _playerInfo[race][current_class]) if (auto const& playerInfo = Trinity::Containers::MapGetValuePtr(_playerInfo, { Races(race), Classes(current_class) }))
{ {
if (!info->levelInfo) if (!playerInfo->levelInfo)
info->levelInfo = new PlayerLevelInfo[sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)]; playerInfo->levelInfo = std::make_unique<PlayerLevelInfo[]>(sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL));
PlayerLevelInfo& levelInfo = info->levelInfo[current_level - 1]; PlayerLevelInfo& levelInfo = playerInfo->levelInfo[current_level - 1];
for (uint8 i = 0; i < MAX_STATS; i++) for (uint8 i = 0; i < MAX_STATS; i++)
levelInfo.stats[i] = fields[i + 2].GetUInt16() + raceStatModifiers[race].StatModifier[i]; levelInfo.stats[i] = fields[i + 2].GetUInt16() + raceStatModifiers[race].StatModifier[i];
} }
@@ -3850,8 +3780,8 @@ void ObjectMgr::LoadPlayerInfo()
if (!sChrClassesStore.LookupEntry(class_)) if (!sChrClassesStore.LookupEntry(class_))
continue; continue;
PlayerInfo* info = _playerInfo[race][class_]; auto const& playerInfo = Trinity::Containers::MapGetValuePtr(_playerInfo, { Races(race), Classes(class_) });
if (!info) if (!playerInfo)
continue; continue;
// skip expansion races if not playing with expansion // skip expansion races if not playing with expansion
@@ -3867,7 +3797,7 @@ void ObjectMgr::LoadPlayerInfo()
continue; continue;
// fatal error if no level 1 data // fatal error if no level 1 data
if (!info->levelInfo || info->levelInfo[0].stats[0] == 0) if (!playerInfo->levelInfo || playerInfo->levelInfo[0].stats[0] == 0)
{ {
TC_LOG_ERROR("sql.sql", "Race %i Class %i Level 1 does not have stats data!", race, class_); TC_LOG_ERROR("sql.sql", "Race %i Class %i Level 1 does not have stats data!", race, class_);
exit(1); exit(1);
@@ -3876,10 +3806,10 @@ void ObjectMgr::LoadPlayerInfo()
// fill level gaps // fill level gaps
for (uint8 level = 1; level < sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); ++level) for (uint8 level = 1; level < sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); ++level)
{ {
if (info->levelInfo[level].stats[0] == 0) if (playerInfo->levelInfo[level].stats[0] == 0)
{ {
TC_LOG_ERROR("sql.sql", "Race %i Class %i Level %i does not have stats data. Using stats data of level %i.", race, class_, level + 1, level); TC_LOG_ERROR("sql.sql", "Race %i Class %i Level %i does not have stats data. Using stats data of level %i.", race, class_, level + 1, level);
info->levelInfo[level] = info->levelInfo[level - 1]; playerInfo->levelInfo[level] = playerInfo->levelInfo[level - 1];
} }
} }
} }
@@ -3972,7 +3902,7 @@ void ObjectMgr::GetPlayerLevelInfo(uint32 race, uint32 class_, uint8 level, Play
if (level < 1 || race >= MAX_RACES || class_ >= MAX_CLASSES) if (level < 1 || race >= MAX_RACES || class_ >= MAX_CLASSES)
return; return;
PlayerInfo const* pInfo = _playerInfo[race][class_]; PlayerInfo const* pInfo = Trinity::Containers::MapGetValuePtr(_playerInfo, {Races(race), Classes(class_)});
if (!pInfo) if (!pInfo)
return; return;
@@ -3985,7 +3915,7 @@ void ObjectMgr::GetPlayerLevelInfo(uint32 race, uint32 class_, uint8 level, Play
void ObjectMgr::BuildPlayerLevelInfo(uint8 race, uint8 _class, uint8 level, PlayerLevelInfo* info) const void ObjectMgr::BuildPlayerLevelInfo(uint8 race, uint8 _class, uint8 level, PlayerLevelInfo* info) const
{ {
// base data (last known level) // base data (last known level)
*info = _playerInfo[race][_class]->levelInfo[sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)-1]; *info = ASSERT_NOTNULL(Trinity::Containers::MapGetValuePtr(_playerInfo, { Races(race), Classes(_class) }))->levelInfo[sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) - 1];
// if conversion from uint32 to uint8 causes unexpected behaviour, change lvl to uint32 // if conversion from uint32 to uint8 causes unexpected behaviour, change lvl to uint32
for (uint8 lvl = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)-1; lvl < level; ++lvl) for (uint8 lvl = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)-1; lvl < level; ++lvl)
@@ -10111,10 +10041,7 @@ PlayerInfo const* ObjectMgr::GetPlayerInfo(uint32 race, uint32 class_) const
return nullptr; return nullptr;
if (class_ >= MAX_CLASSES) if (class_ >= MAX_CLASSES)
return nullptr; return nullptr;
PlayerInfo const* info = _playerInfo[race][class_]; return Trinity::Containers::MapGetValuePtr(_playerInfo, { Races(race), Classes(class_) });
if (!info)
return nullptr;
return info;
} }
CreatureStaticFlagsOverride const* ObjectMgr::GetCreatureStaticFlagsOverride(ObjectGuid::LowType spawnId, Difficulty difficultyId) const CreatureStaticFlagsOverride const* ObjectMgr::GetCreatureStaticFlagsOverride(ObjectGuid::LowType spawnId, Difficulty difficultyId) const
+3 -11
View File
@@ -688,15 +688,7 @@ struct PlayerCreateInfoAction
typedef std::vector<PlayerCreateInfoAction> PlayerCreateInfoActions; typedef std::vector<PlayerCreateInfoAction> PlayerCreateInfoActions;
struct PlayerCreateInfoSkill typedef std::vector<SkillRaceClassInfoEntry const*> PlayerCreateInfoSkills;
{
PlayerCreateInfoSkill() : SkillId(0), Rank(0) { }
uint16 SkillId;
uint16 Rank;
};
typedef std::vector<PlayerCreateInfoSkill> PlayerCreateInfoSkills;
// existence checked by displayId != 0 // existence checked by displayId != 0
struct PlayerInfo struct PlayerInfo
@@ -716,7 +708,7 @@ struct PlayerInfo
PlayerCreateInfoSkills skills; PlayerCreateInfoSkills skills;
//[level-1] 0..MaxPlayerLevel-1 //[level-1] 0..MaxPlayerLevel-1
PlayerLevelInfo* levelInfo = nullptr; std::unique_ptr<PlayerLevelInfo[]> levelInfo;
}; };
@@ -1734,7 +1726,7 @@ class TC_GAME_API ObjectMgr
void BuildPlayerLevelInfo(uint8 race, uint8 class_, uint8 level, PlayerLevelInfo* plinfo) const; void BuildPlayerLevelInfo(uint8 race, uint8 class_, uint8 level, PlayerLevelInfo* plinfo) const;
PlayerInfo* _playerInfo[MAX_RACES][MAX_CLASSES]; std::unordered_map<std::pair<Races, Classes>, std::unique_ptr<PlayerInfo>> _playerInfo;
typedef std::vector<uint32> PlayerXPperLevel; // [level] typedef std::vector<uint32> PlayerXPperLevel; // [level]
PlayerXPperLevel _playerXPperLevel; PlayerXPperLevel _playerXPperLevel;
+1 -1
View File
@@ -592,7 +592,7 @@ void GuildMgr::LoadGuildProfessionData()
GuildProfession prof; GuildProfession prof;
prof.SpellId = entry->Spell; prof.SpellId = entry->Spell;
prof.UniqueBits = entry->UniqueBit; prof.UniqueBits = entry->UniqueBit;
prof.AutoLearn = entry->AcquireMethod == SKILL_LINE_ABILITY_LEARNED_ON_SKILL_VALUE; prof.AutoLearn = entry->GetAcquireMethod() == SkillLineAbilityAcquireMethod::AutomaticSkillRank;
GuildProfessionStore[skill->ID].push_back(prof); GuildProfessionStore[skill->ID].push_back(prof);
} }
} }
@@ -693,7 +693,7 @@ enum SpellAttr9
SPELL_ATTR9_UNK3 = 0x00000008, // 3 SPELL_ATTR9_UNK3 = 0x00000008, // 3
SPELL_ATTR9_SPECIAL_DELAY_CALCULATION = 0x00000010, // 4 SPELL_ATTR9_SPECIAL_DELAY_CALCULATION = 0x00000010, // 4
SPELL_ATTR9_SUMMON_PLAYER_TOTEM = 0x00000020, // 5 SPELL_ATTR9_SUMMON_PLAYER_TOTEM = 0x00000020, // 5
SPELL_ATTR9_UNK6 = 0x00000040, // 6 SPELL_ATTR9_ITEM_CAST_GRANTS_SKILL_GAIN = 0x00000040, // 6 Item Cast Grants Skill Gain
SPELL_ATTR9_UNK7 = 0x00000080, // 7 SPELL_ATTR9_UNK7 = 0x00000080, // 7
SPELL_ATTR9_AIMED_SHOT = 0x00000100, // 8 SPELL_ATTR9_AIMED_SHOT = 0x00000100, // 8
SPELL_ATTR9_NOT_USABLE_IN_ARENA = 0x00000200, // 9 Cannot be used in arenas SPELL_ATTR9_NOT_USABLE_IN_ARENA = 0x00000200, // 9 Cannot be used in arenas
+5
View File
@@ -3732,6 +3732,11 @@ void Spell::_cast(bool skipCheck)
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL, m_spellInfo->Id); player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL, m_spellInfo->Id);
} }
// Spells that don't create items can have this attribute - handle here
if (m_CastItem && m_spellInfo->HasAttribute(SPELL_ATTR9_ITEM_CAST_GRANTS_SKILL_GAIN))
if (Player* playerCaster = m_caster->ToPlayer())
playerCaster->UpdateCraftSkill(m_spellInfo);
if (!(_triggeredCastFlags & TRIGGERED_IGNORE_POWER_AND_REAGENT_COST)) if (!(_triggeredCastFlags & TRIGGERED_IGNORE_POWER_AND_REAGENT_COST))
{ {
// Powers have to be taken before SendSpellGo // Powers have to be taken before SendSpellGo
+45 -10
View File
@@ -1401,7 +1401,8 @@ void Spell::DoCreateItem(uint32 /*i*/, uint32 itemtype)
// we succeeded in creating at least one item, so a levelup is possible // we succeeded in creating at least one item, so a levelup is possible
if (bgType == 0) if (bgType == 0)
player->UpdateCraftSkill(m_spellInfo); if (!m_CastItem)
player->UpdateCraftSkill(m_spellInfo);
} }
/* /*
@@ -1437,7 +1438,8 @@ void Spell::EffectCreateItem2(SpellEffIndex effIndex)
if (m_spellInfo->IsLootCrafting()) if (m_spellInfo->IsLootCrafting())
{ {
player->AutoStoreLoot(m_spellInfo->Id, LootTemplates_Spell, false); player->AutoStoreLoot(m_spellInfo->Id, LootTemplates_Spell, false);
player->UpdateCraftSkill(m_spellInfo); if (!m_CastItem)
player->UpdateCraftSkill(m_spellInfo);
} }
else // If there's no random loot entries for this spell, pick the item associated with this spell else // If there's no random loot entries for this spell, pick the item associated with this spell
{ {
@@ -2274,14 +2276,15 @@ void Spell::EffectLearnSkill(SpellEffIndex effIndex)
if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET) if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET)
return; return;
if (unitTarget->GetTypeId() != TYPEID_PLAYER) Player* playerTarget = Object::ToPlayer(unitTarget);
if (!playerTarget)
return; return;
if (damage < 0) if (damage < 0)
return; return;
uint32 skillid = m_spellInfo->Effects[effIndex].MiscValue; uint32 skillid = m_spellInfo->Effects[effIndex].MiscValue;
SkillRaceClassInfoEntry const* rcEntry = sDBCManager.GetSkillRaceClassInfo(skillid, unitTarget->getRace(), unitTarget->getClass()); SkillRaceClassInfoEntry const* rcEntry = sDBCManager.GetSkillRaceClassInfo(skillid, playerTarget->getRace(), playerTarget->getClass());
if (!rcEntry) if (!rcEntry)
return; return;
@@ -2289,8 +2292,13 @@ void Spell::EffectLearnSkill(SpellEffIndex effIndex)
if (!tier) if (!tier)
return; return;
uint16 skillval = unitTarget->ToPlayer()->GetPureSkillValue(skillid); uint16 skillval = std::max<uint16>(1, playerTarget->GetPureSkillValue(skillid));
unitTarget->ToPlayer()->SetSkill(skillid, m_spellInfo->Effects[effIndex].CalcValue(), std::max<uint16>(skillval, 1), tier->Value[damage - 1]); uint16 maxSkillVal = tier->Value[damage - 1];
if (rcEntry->Flags & SKILL_FLAG_ALWAYS_MAX_VALUE)
skillval = maxSkillVal;
playerTarget->SetSkill(skillid, damage, skillval, maxSkillVal);
} }
void Spell::EffectPlayMovie(SpellEffIndex effIndex) void Spell::EffectPlayMovie(SpellEffIndex effIndex)
@@ -2347,7 +2355,7 @@ void Spell::EffectEnchantItemPerm(SpellEffIndex effIndex)
else else
{ {
// do not increase skill if vellum used // do not increase skill if vellum used
if (!(m_CastItem && m_CastItem->GetTemplate()->GetFlags() & ITEM_FLAG_NO_REAGENT_COST)) if (!m_CastItem)
player->UpdateCraftSkill(m_spellInfo); player->UpdateCraftSkill(m_spellInfo);
uint32 enchant_id = m_spellInfo->Effects[effIndex].MiscValue; uint32 enchant_id = m_spellInfo->Effects[effIndex].MiscValue;
@@ -3932,7 +3940,9 @@ void Spell::EffectDisEnchant(SpellEffIndex /*effIndex*/)
if (Player* caster = m_caster->ToPlayer()) if (Player* caster = m_caster->ToPlayer())
{ {
caster->UpdateCraftSkill(m_spellInfo); if (!m_CastItem)
caster->UpdateCraftSkill(m_spellInfo);
caster->SendLoot(itemTarget->GetGUID(), LOOT_DISENCHANTING); caster->SendLoot(itemTarget->GetGUID(), LOOT_DISENCHANTING);
} }
@@ -4988,12 +4998,37 @@ void Spell::EffectMilling(SpellEffIndex /*effIndex*/)
player->SendLoot(itemTarget->GetGUID(), LOOT_MILLING); player->SendLoot(itemTarget->GetGUID(), LOOT_MILLING);
} }
void Spell::EffectSkill(SpellEffIndex /*effIndex*/) void Spell::EffectSkill(SpellEffIndex effIndex)
{ {
if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT) if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT)
return; return;
TC_LOG_DEBUG("spells", "WORLD: SkillEFFECT"); Player* playerTarget = Object::ToPlayer(GetCaster());
if (!playerTarget)
return;
if (damage < 1)
return;
uint32 skillid = m_spellInfo->Effects[effIndex].MiscValue;
if (playerTarget->GetSkillStep(skillid) >= damage)
return;
SkillRaceClassInfoEntry const* rcEntry = sDBCManager.GetSkillRaceClassInfo(skillid, playerTarget->getRace(), playerTarget->getClass());
if (!rcEntry)
return;
SkillTiersEntry const* tier =sSkillTiersStore.AssertEntry(rcEntry->SkillTierID);
if (!tier)
return;
uint16 skillval = std::max<uint16>(1, playerTarget->GetPureSkillValue(skillid));
uint16 maxSkillVal = tier->Value[damage - 1];
if (rcEntry->Flags & SKILL_FLAG_ALWAYS_MAX_VALUE)
skillval = maxSkillVal;
playerTarget->SetSkill(skillid, damage, skillval, maxSkillVal);
} }
/* There is currently no need for this effect. We handle it in Battleground.cpp /* There is currently no need for this effect. We handle it in Battleground.cpp
+1 -1
View File
@@ -1264,7 +1264,7 @@ bool SpellInfo::IsAbilityLearnedWithProfession() const
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* pAbility = _spell_idx->second; SkillLineAbilityEntry const* pAbility = _spell_idx->second;
if (!pAbility || pAbility->AcquireMethod != SKILL_LINE_ABILITY_LEARNED_ON_SKILL_VALUE) if (!pAbility || pAbility->GetAcquireMethod() != SkillLineAbilityAcquireMethod::AutomaticSkillRank)
continue; continue;
if (pAbility->MinSkillLineRank > 0) if (pAbility->MinSkillLineRank > 0)
+3 -6
View File
@@ -1024,11 +1024,8 @@ void SpellMgr::LoadSpellLearnSkills()
case SPELL_EFFECT_SKILL: case SPELL_EFFECT_SKILL:
dbc_node.skill = entry->Effects[i].MiscValue; dbc_node.skill = entry->Effects[i].MiscValue;
dbc_node.step = entry->Effects[i].CalcValue(); dbc_node.step = entry->Effects[i].CalcValue();
if (dbc_node.skill != SKILL_RIDING) dbc_node.value = 0;
dbc_node.value = 1; dbc_node.maxvalue = 0;
else
dbc_node.value = dbc_node.step * 75;
dbc_node.maxvalue = dbc_node.step * 75;
break; break;
case SPELL_EFFECT_DUAL_WIELD: case SPELL_EFFECT_DUAL_WIELD:
dbc_node.skill = SKILL_DUAL_WIELD; dbc_node.skill = SKILL_DUAL_WIELD;
@@ -2164,7 +2161,7 @@ void SpellMgr::LoadPetLevelupSpellMap()
if (skillLine->SkillLine != creatureFamily->SkillLine[j]) if (skillLine->SkillLine != creatureFamily->SkillLine[j])
continue; continue;
if (skillLine->AcquireMethod != SKILL_LINE_ABILITY_LEARNED_ON_SKILL_LEARN) if (skillLine->GetAcquireMethod() != SkillLineAbilityAcquireMethod::AutomaticCharLevel)
continue; continue;
SpellInfo const* spell = GetSpellInfo(skillLine->Spell); SpellInfo const* spell = GetSpellInfo(skillLine->Spell);