Core/Players: re-implement talents

*todo: class restriction and talent tier checks and remove the now useless learn all talents command
This commit is contained in:
Ovahlord
2023-11-20 20:06:54 +01:00
parent e08019ee56
commit 1195d7c190
13 changed files with 241 additions and 325 deletions

View File

@@ -0,0 +1,2 @@
ALTER TABLE `character_talent`
ADD COLUMN `talentRank` TINYINT UNSIGNED DEFAULT 0 NOT NULL AFTER `talentId`;

View File

@@ -150,7 +150,7 @@ void CharacterDatabaseConnection::DoPrepareStatements()
"appearance17, appearance18, mainHandEnchant, offHandEnchant FROM character_transmog_outfits WHERE guid = ? ORDER BY setindex", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_BGDATA, "SELECT instanceId, team, joinX, joinY, joinZ, joinO, joinMapId, taxiStart, taxiEnd, mountSpell, queueId FROM character_battleground_data WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_GLYPHS, "SELECT talentGroup, glyphId FROM character_glyphs WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_TALENTS, "SELECT talentId, talentGroup FROM character_talent WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_TALENTS, "SELECT talentId, talentRank, talentGroup FROM character_talent WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_SKILLS, "SELECT skill, value, max, professionSlot FROM character_skills WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_RANDOMBG, "SELECT guid FROM character_battleground_random WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_BANNED, "SELECT guid FROM character_banned WHERE guid = ? AND active = 1", CONNECTION_ASYNC);
@@ -658,7 +658,7 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_DEL_PETITION_BY_OWNER, "DELETE FROM petition WHERE ownerguid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_PETITION_SIGNATURE_BY_OWNER, "DELETE FROM petition_sign WHERE ownerguid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_GLYPHS, "INSERT INTO character_glyphs VALUES(?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_TALENT, "INSERT INTO character_talent (guid, talentId, talentGroup) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_TALENT, "INSERT INTO character_talent (guid, talentId, talentRank, talentGroup) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_LIST_SLOT, "UPDATE characters SET slot = ? WHERE guid = ? AND account = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_FISHINGSTEPS, "INSERT INTO character_fishingsteps (guid, fishingSteps) VALUES (?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_FISHINGSTEPS, "DELETE FROM character_fishingsteps WHERE guid = ?", CONNECTION_ASYNC);

View File

@@ -2129,6 +2129,7 @@ DEFINE_ENUM_FLAG(SummonPropertiesFlags);
#define MAX_TALENT_TIERS 11
#define MAX_TALENT_COLUMNS 4
#define MAX_TALENT_RANK 9
enum class TaxiNodeFlags : int32
{

View File

@@ -2414,8 +2414,9 @@ bool Player::IsMaxLevel() const
void Player::InitTalentForLevel()
{
uint8 level = GetLevel();
int32 talentPoints = CalculateTalentsPoints();
if (level < MIN_SPECIALIZATION_LEVEL)
uint32 talentPoints = CalculateTalentsPoints();
uint32 spentTalentPoints = GetSpentTalentPointsCount();
if (spentTalentPoints && level < MIN_SPECIALIZATION_LEVEL)
{
// Remove all talent points
ResetTalents(true);
@@ -2424,7 +2425,8 @@ void Player::InitTalentForLevel()
if (GetSpentTalentPointsCount() > talentPoints)
ResetTalents(true);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::MaxTalentTiers), talentPoints);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::MaxTalentTiers), static_cast<int32>(talentPoints));
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::CharacterPoints), static_cast<int32>(talentPoints - spentTalentPoints));
if (!GetSession()->PlayerLoading())
SendTalentsInfoData(); // update at client
@@ -2709,33 +2711,56 @@ void DeleteSpellFromAllPlayers(uint32 spellId)
CharacterDatabase.Execute(stmt);
}
bool Player::AddTalent(TalentEntry const* talent, uint8 spec, bool learning)
bool Player::AddTalent(TalentEntry const* talent, uint16 rank, uint8 talentGroupId, bool learning)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellID, DIFFICULTY_NONE);
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellRank[rank], DIFFICULTY_NONE);
if (!spellInfo)
{
TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) does not exist.", talent->SpellID);
// do character spell book cleanup (all characters)
if (!IsInWorld() && !learning) // spell load case
{
TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) does not exist. Deleting for all characters in `character_spell` and `character_talent`.", talent->SpellRank[rank]);
DeleteSpellFromAllPlayers(talent->SpellRank[rank]);
}
else
TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) does not exist", talent->SpellRank[rank]);
return false;
}
if (!SpellMgr::IsSpellValid(spellInfo, this, false))
{
TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) is invalid", talent->SpellID);
// do character spell book cleanup (all characters)
if (!IsInWorld() && !learning) // spell load case
{
TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) is invalid. Deleting for all characters in `character_spell` and `character_talent`.", talent->SpellRank[rank]);
DeleteSpellFromAllPlayers(talent->SpellRank[rank]);
}
else
TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) is invalid", talent->SpellRank[rank]);
return false;
}
PlayerTalentMap::iterator itr = GetTalentMap(spec)->find(talent->ID);
if (itr != GetTalentMap(spec)->end())
itr->second = PLAYERSPELL_UNCHANGED;
else
(*GetTalentMap(spec))[talent->ID] = learning ? PLAYERSPELL_NEW : PLAYERSPELL_UNCHANGED;
if (spec == GetActiveTalentGroup())
PlayerTalentMap& talentMap = GetPlayerTalentMap(talentGroupId);
auto itr = talentMap.find(talent->ID);
if (itr != talentMap.end())
{
LearnSpell(talent->SpellID, true);
if (talent->OverridesSpellID)
AddOverrideSpell(talent->OverridesSpellID, talent->SpellID);
itr->second.State = PLAYERSPELL_UNCHANGED;
itr->second.Rank = static_cast<uint8>(rank);
}
else
talentMap[talent->ID] = { PLAYERSPELL_UNCHANGED, static_cast<uint8>(rank) };
// Inactive talent groups will only be initialized
if (GetActiveTalentGroup() != talentGroupId)
return true;
LearnSpell(spellInfo->Id, true);
if (talent->OverridesSpellID)
AddOverrideSpell(talent->OverridesSpellID, talent->SpellID);
if (learning)
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::ChangeTalent);
@@ -2745,11 +2770,20 @@ bool Player::AddTalent(TalentEntry const* talent, uint8 spec, bool learning)
void Player::RemoveTalent(TalentEntry const* talent)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellID, DIFFICULTY_NONE);
PlayerTalentMap& talentMap = GetPlayerTalentMap(GetActiveTalentGroup());
auto itr = talentMap.find(talent->ID);
if (itr == talentMap.end())
return;
uint32 spellId = talent->SpellRank[itr->second.Rank];
if (!spellId)
return;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE);
if (!spellInfo)
return;
RemoveSpell(talent->SpellID, true);
RemoveSpell(spellId, true);
// search for spells that the talent teaches and unlearn them
for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
@@ -2759,10 +2793,9 @@ void Player::RemoveTalent(TalentEntry const* talent)
if (talent->OverridesSpellID)
RemoveOverrideSpell(talent->OverridesSpellID, talent->SpellID);
// if this talent rank can be found in the PlayerTalentMap, mark the talent as removed so it gets deleted
PlayerTalentMap::iterator plrTalent = GetTalentMap(GetActiveTalentGroup())->find(talent->ID);
if (plrTalent != GetTalentMap(GetActiveTalentGroup())->end())
plrTalent->second = PLAYERSPELL_REMOVED;
// Mark the talent as deleted to it will be deleted upon the next save cycle
itr->second.State = PLAYERSPELL_REMOVED;
itr->second.Rank = 0;
}
void Player::AddStoredAuraTeleportLocation(uint32 spellId)
@@ -3573,23 +3606,25 @@ bool Player::ResetTalents(bool noCost)
RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT, true);
for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId)
PlayerTalentMap const& talentMap = GetPlayerTalentMap(GetActiveTalentGroup());
for (auto const& pair : talentMap)
{
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
if (!talentInfo)
TalentEntry const* talentEntry = sTalentStore.LookupEntry(pair.first);
if (!talentEntry)
continue;
/*
TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TabID);
if (!talentTabInfo)
continue;
// unlearn only talents for character class
// some spell learned by one class as normal spells or know at creation but another class learn it as talent,
// to prevent unexpected lost normal learned spell skip another class talents
if (talentInfo->ClassID != GetClass())
if ((GetClassMask() & talentTabInfo->ClassMask) == 0)
continue;
*/
// skip non-existent talent ranks
if (talentInfo->SpellID == 0)
continue;
RemoveTalent(talentInfo);
RemoveTalent(talentEntry);
}
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
@@ -3615,6 +3650,8 @@ bool Player::ResetTalents(bool noCost)
}
*/
InitTalentForLevel();
return true;
}
@@ -3812,12 +3849,6 @@ bool Player::HasSpell(uint32 spell) const
!itr->second.disabled);
}
bool Player::HasTalent(uint32 talentId, uint8 group) const
{
PlayerTalentMap::const_iterator itr = GetTalentMap(group)->find(talentId);
return (itr != GetTalentMap(group)->end() && itr->second != PLAYERSPELL_REMOVED);
}
bool Player::HasActiveSpell(uint32 spell) const
{
PlayerSpellMap::const_iterator itr = m_spells.find(spell);
@@ -26086,94 +26117,98 @@ bool Player::ModifierTreeSatisfied(uint32 modifierTreeId) const
return m_achievementMgr->ModifierTreeSatisfied(modifierTreeId);
}
TalentLearnResult Player::LearnTalent(uint32 talentId, int32* spellOnCooldown)
static constexpr uint8 NEEDED_TALENT_POINT_PER_TIER = 5;
bool Player::LearnTalent(uint32 talentId, uint16 requestedRank)
{
if (IsInCombat())
return TALENT_FAILED_AFFECTING_COMBAT;
// No talent points left to spend, skip learn request
if (!m_activePlayerData->CharacterPoints)
return false;
if (isDead())
return TALENT_FAILED_CANT_DO_THAT_RIGHT_NOW;
if (GetPrimarySpecialization() == ChrSpecialization::None)
return TALENT_FAILED_NO_PRIMARY_TREE_SELECTED;
if (requestedRank >= MAX_TALENT_RANK)
return false;
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
if (!talentInfo)
return TALENT_FAILED_UNKNOWN;
return false;
if (talentInfo->SpecID && ChrSpecialization(talentInfo->SpecID) != GetPrimarySpecialization())
return TALENT_FAILED_UNKNOWN;
/*
TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TabID);
if (!talentTabInfo)
return;
// prevent learn talent for different class (cheating)
if (talentInfo->ClassID != GetClass())
return TALENT_FAILED_UNKNOWN;
if (!(GetClassMask() & talentTabInfo->ClassMask))
return;
*/
// check if we have enough talent points
if (talentInfo->TierID >= m_activePlayerData->MaxTalentTiers)
return TALENT_FAILED_UNKNOWN;
// Check for existing talents
// TODO: prevent changing talents that are on cooldown
// Check if there is a different talent for us to learn in selected slot
// Example situation:
// Warrior talent row 2 slot 0
// Talent.dbc has an entry for each specialization
// but only 2 out of 3 have SpecID != 0
// We need to make sure that if player is in one of these defined specs he will not learn the other choice
TalentEntry const* bestSlotMatch = nullptr;
for (TalentEntry const* talent : sDB2Manager.GetTalentsByPosition(GetClass(), talentInfo->TierID, talentInfo->ColumnIndex))
uint32 neededTalentPoints = 0;
PlayerTalentMap const& talentMap = GetPlayerTalentMap(GetActiveTalentGroup());
auto itr = talentMap.find(talentId);
if (itr != talentMap.end())
{
if (!talent->SpecID)
bestSlotMatch = talent;
else if (ChrSpecialization(talent->SpecID) == GetPrimarySpecialization())
{
bestSlotMatch = talent;
break;
}
// We already know this or a higher rank of this talent
if (itr->second.Rank >= requestedRank)
return false;
neededTalentPoints = (itr->second.Rank - requestedRank) + 1;
}
else
neededTalentPoints = requestedRank + 1;
// Not enough talent points to learn the talent at this rank
if (neededTalentPoints > static_cast<uint32>(m_activePlayerData->CharacterPoints))
return false;
// Check talent dependencies
for (size_t i = 0; i < talentInfo->PrereqRank.size(); ++i)
{
if (!talentInfo->PrereqTalent[i])
continue;
itr = talentMap.find(talentInfo->PrereqTalent[i]);
if (itr == talentMap.end() || itr->second.Rank < talentInfo->PrereqRank[i])
return false;
}
if (talentInfo != bestSlotMatch)
return TALENT_FAILED_UNKNOWN;
// Find out how many points we have in this field
/*
uint32 spentPoints = 0;
uint32 tTab = talentInfo->TabID;
if (talentInfo->TierID > 0)
for (TalentEntry const* tmpTalent : sTalentStore) // the way talents are tracked
if (tmpTalent->TabID == tTab)
for (uint8 rank = 0; rank < MAX_TALENT_RANK; rank++)
if (tmpTalent->SpellRank[rank] != 0)
if (HasSpell(tmpTalent->SpellRank[rank]))
spentPoints += (rank + 1);
// Check if player doesn't have any talent in current tier
for (uint32 c = 0; c < MAX_TALENT_COLUMNS; ++c)
{
for (TalentEntry const* talent : sDB2Manager.GetTalentsByPosition(GetClass(), talentInfo->TierID, c))
{
if (!HasTalent(talent->ID, GetActiveTalentGroup()))
continue;
if (!HasPlayerFlag(PLAYER_FLAGS_RESTING) && !HasUnitFlag2(UNIT_FLAG2_ALLOW_CHANGING_TALENTS))
return TALENT_FAILED_REST_AREA;
if (GetSpellHistory()->HasCooldown(talent->SpellID))
{
*spellOnCooldown = talent->SpellID;
return TALENT_FAILED_CANT_REMOVE_TALENT;
}
RemoveTalent(talent);
}
}
// not have required min points spent in talent tree
if (spentPoints < (talentInfo->TierID * NEEDED_TALENT_POINT_PER_TIER))
return;
>*/
// spell not set in talent.dbc
uint32 spellid = talentInfo->SpellID;
if (!spellid)
uint32 spellId = talentInfo->SpellRank[requestedRank];
if (spellId == 0)
{
TC_LOG_ERROR("entities.player", "Player::LearnTalent: Talent.dbc has no spellInfo for talent: {} (spell id = 0)", talentId);
return TALENT_FAILED_UNKNOWN;
return false;
}
// already known
if (HasTalent(talentId, GetActiveTalentGroup()) || HasSpell(spellid))
return TALENT_FAILED_UNKNOWN;
if (HasSpell(spellId))
return false;
if (!AddTalent(talentInfo, GetActiveTalentGroup(), true))
return TALENT_FAILED_UNKNOWN;
AddTalent(talentInfo, requestedRank, GetActiveTalentGroup(), true);
TC_LOG_DEBUG("misc", "Player::LearnTalent: TalentID: {} Spell: {} Group: {}\n", talentId, spellid, GetActiveTalentGroup());
TC_LOG_DEBUG("misc", "Player::LearnTalent: TalentID: {} Spell: {} Group: {}\n", talentId, spellId, uint32(GetActiveTalentGroup()));
return TALENT_LEARN_OK;
// update free talent points
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::CharacterPoints), static_cast<int32>(CalculateTalentsPoints() - GetSpentTalentPointsCount()));
return true;
}
void Player::EnablePvpRules(bool dueToCombat /*= false*/)
@@ -26367,39 +26402,37 @@ void Player::SendTalentsInfoData()
uint8 activeGroup = GetActiveTalentGroup();
packet.ActiveGroup = activeGroup;
packet.UnspentTalentPoints = CalculateTalentsPoints() - GetSpentTalentPointsCount();
packet.UnspentTalentPoints = m_activePlayerData->CharacterPoints;
for (uint8 i = 0; i < (1 + GetBonusTalentGroupCount()); ++i)
{
WorldPackets::Talent::TalentGroupInfo& groupInfo = packet.TalentGroupInfos.emplace_back();
groupInfo.SpecID = MAX_SPECIALIZATIONS;
if (PlayerTalentMap const* talentMap = GetTalentMap(i))
PlayerTalentMap const& talentMap = GetPlayerTalentMap(i);
groupInfo.Talents.reserve(talentMap.size());
for (auto const& pair : talentMap)
{
groupInfo.Talents.reserve(talentMap->size());
if (pair.second.State == PLAYERSPELL_REMOVED)
continue;
for (auto const& pair : *talentMap)
TalentEntry const* talentInfo = sTalentStore.LookupEntry(pair.first);
if (!talentInfo)
{
if (pair.second == PLAYERSPELL_REMOVED)
continue;
TalentEntry const* talentInfo = sTalentStore.LookupEntry(pair.first);
if (!talentInfo)
{
TC_LOG_ERROR("entities.player", "Player::SendTalentsInfoData: Player '{}' ({}) has unknown talent id: {}",
GetName(), GetGUID().ToString(), pair.first);
continue;
}
SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(talentInfo->SpellID, DIFFICULTY_NONE);
if (!spellEntry)
{
TC_LOG_ERROR("entities.player", "Player::SendTalentsInfoData: Player '{}' ({}) has unknown talent spell: {}",
GetName(), GetGUID().ToString(), talentInfo->SpellID);
continue;
}
groupInfo.Talents.push_back(uint16(pair.first));
TC_LOG_ERROR("entities.player", "Player::SendTalentsInfoData: Player '{}' ({}) has unknown talent id: {}",
GetName(), GetGUID().ToString(), pair.first);
continue;
}
SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(talentInfo->SpellRank[pair.second.Rank], DIFFICULTY_NONE);
if (!spellEntry)
{
TC_LOG_ERROR("entities.player", "Player::SendTalentsInfoData: Player '{}' ({}) has unknown talent spell: {}",
GetName(), GetGUID().ToString(), talentInfo->SpellID);
continue;
}
groupInfo.Talents.push_back({ pair.first, pair.second.Rank });
}
std::vector<uint32> glyphs = GetGlyphs(activeGroup);
@@ -26676,12 +26709,12 @@ void Player::_SaveGlyphs(CharacterDatabaseTransaction trans) const
void Player::_LoadTalents(PreparedQueryResult result)
{
// "SELECT talentId, talentGroup FROM character_talent WHERE guid = ?"
// "SELECT talentId, talentRank, talentGroup FROM character_talent WHERE guid = ?"
if (result)
{
do
if (TalentEntry const* talent = sTalentStore.LookupEntry((*result)[0].GetUInt32()))
AddTalent(talent, (*result)[1].GetUInt8(), false);
AddTalent(talent, (*result)[1].GetUInt8(), (*result)[2].GetUInt8(), false);
while (result->NextRow());
}
}
@@ -26834,19 +26867,20 @@ void Player::_SaveTalents(CharacterDatabaseTransaction trans)
for (uint8 group = 0; group < MAX_SPECIALIZATIONS; ++group)
{
PlayerTalentMap* talents = GetTalentMap(group);
for (auto itr = talents->begin(); itr != talents->end();)
PlayerTalentMap& talents = _specializationInfo.Talents[group];
for (auto itr = talents.begin(); itr != talents.end();)
{
if (itr->second == PLAYERSPELL_REMOVED)
if (itr->second.State == PLAYERSPELL_REMOVED)
{
itr = talents->erase(itr);
itr = talents.erase(itr);
continue;
}
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_TALENT);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, itr->first);
stmt->setUInt8(2, group);
stmt->setUInt8(2, itr->second.Rank);
stmt->setUInt8(3, group);
trans->Append(stmt);
++itr;
}
@@ -26989,37 +27023,31 @@ void Player::ActivateTalentGroup(uint8 talentGroup)
// Let client clear his current Actions
SendActionButtons(2);
// m_actionButtons.clear() is called in the next _LoadActionButtons
for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId)
PlayerTalentMap const& talentMap = GetPlayerTalentMap(GetActiveTalentGroup());
for (auto const& pair : talentMap)
{
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
if (!talentInfo)
TalentEntry const* talentEntry = sTalentStore.LookupEntry(pair.first);
if (!talentEntry)
continue;
// unlearn only talents for character class
// some spell learned by one class as normal spells or know at creation but another class learn it as talent,
// to prevent unexpected lost normal learned spell skip another class talents
if (talentInfo->ClassID != GetClass())
continue;
if (talentInfo->SpellID == 0)
continue;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talentInfo->SpellID, DIFFICULTY_NONE);
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talentEntry->SpellRank[pair.second.Rank], DIFFICULTY_NONE);
if (!spellInfo)
continue;
RemoveSpell(talentInfo->SpellID, true);
RemoveSpell(spellInfo->Id, true);
// search for spells that the talent teaches and unlearn them
for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
if (spellEffectInfo.IsEffect(SPELL_EFFECT_LEARN_SPELL) && spellEffectInfo.TriggerSpell > 0)
RemoveSpell(spellEffectInfo.TriggerSpell, true);
if (talentInfo->OverridesSpellID)
RemoveOverrideSpell(talentInfo->OverridesSpellID, talentInfo->SpellID);
if (talentEntry->OverridesSpellID)
RemoveOverrideSpell(talentEntry->OverridesSpellID, talentEntry->SpellID);
}
// m_actionButtons.clear() is called in the next _LoadActionButtons
ApplyTraitConfig(m_activePlayerData->ActiveCombatTraitConfigID, false);
for (uint32 glyphId : GetGlyphs(GetActiveTalentGroup()))
@@ -27028,26 +27056,21 @@ void Player::ActivateTalentGroup(uint8 talentGroup)
SetActiveTalentGroup(talentGroup);
SetPrimarySpecialization(0);
for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId)
// if the talent can be found in the newly activated PlayerTalentMap
PlayerTalentMap const& activatedTalentMap = GetPlayerTalentMap(GetActiveTalentGroup());
for (auto const& pair : activatedTalentMap)
{
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
if (!talentInfo)
TalentEntry const* talentEntry = sTalentStore.LookupEntry(pair.first);
if (!talentEntry)
continue;
// learn only talents for character class
if (talentInfo->ClassID != GetClass())
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talentEntry->SpellRank[pair.second.Rank], DIFFICULTY_NONE);
if (!spellInfo)
continue;
if (!talentInfo->SpellID)
continue;
// if the talent can be found in the newly activated PlayerTalentMap
if (HasTalent(talentInfo->ID, GetActiveTalentGroup()))
{
LearnSpell(talentInfo->SpellID, true); // add the talent to the PlayerSpellMap
if (talentInfo->OverridesSpellID)
AddOverrideSpell(talentInfo->OverridesSpellID, talentInfo->SpellID);
}
LearnSpell(spellInfo->Id, true); // add the talent to the PlayerSpellMap
if (talentEntry->OverridesSpellID)
AddOverrideSpell(talentEntry->OverridesSpellID, talentEntry->SpellID);
}
InitTalentForLevel();
@@ -28732,8 +28755,13 @@ void Player::SetBonusTalentGroupCount(uint8 amount)
uint32 Player::GetSpentTalentPointsCount() const
{
PlayerTalentMap const& talentMap = _specializationInfo.Talents[GetActiveTalentGroup()];
return std::count_if(talentMap.begin(), talentMap.end(), [](auto const& pair) { return (pair.second != PLAYERSPELL_REMOVED); });
PlayerTalentMap const& talentMap = GetPlayerTalentMap(GetActiveTalentGroup());
uint32 count = 0;
for (auto const& pair : talentMap)
if (pair.second.State != PLAYERSPELL_REMOVED)
count += pair.second.Rank + 1;
return count;
}
ChrSpecializationEntry const* Player::GetPrimarySpecializationEntry() const

View File

@@ -264,7 +264,13 @@ struct PlayerCurrency
CurrencyDbFlags Flags;
};
typedef std::unordered_map<uint32, PlayerSpellState> PlayerTalentMap;
struct PlayerTalentState
{
PlayerSpellState State = PLAYERSPELL_UNCHANGED;
uint8 Rank = 0;
};
typedef std::unordered_map<uint32, PlayerTalentState> PlayerTalentMap;
typedef std::unordered_map<uint32, PlayerSpell> PlayerSpellMap;
typedef std::unordered_set<SpellModifier*> SpellModContainer;
typedef std::unordered_map<uint32, PlayerCurrency> PlayerCurrenciesMap;
@@ -1047,24 +1053,8 @@ struct GroupUpdateCounter
int32 UpdateSequenceNumber;
};
enum TalentLearnResult : int32
{
TALENT_LEARN_OK = 0,
TALENT_FAILED_UNKNOWN = 1,
TALENT_FAILED_NOT_ENOUGH_TALENTS_IN_PRIMARY_TREE = 2,
TALENT_FAILED_NO_PRIMARY_TREE_SELECTED = 3,
TALENT_FAILED_CANT_DO_THAT_RIGHT_NOW = 4,
TALENT_FAILED_AFFECTING_COMBAT = 5,
TALENT_FAILED_CANT_REMOVE_TALENT = 6,
TALENT_FAILED_CANT_DO_THAT_CHALLENGE_MODE_ACTIVE = 7,
TALENT_FAILED_REST_AREA = 8,
TALENT_FAILED_UNSPENT_TALENT_POINTS = 9,
TALENT_FAILED_IN_PVP_MATCH = 10
};
struct TC_GAME_API SpecializationInfo
{
SpecializationInfo() : ResetTalentsCost(0), ResetTalentsTime(0), ActiveGroup(0), BonusGroups(0) { }
PlayerTalentMap Talents[MAX_SPECIALIZATIONS];
@@ -1818,6 +1808,9 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
void SetTalentResetCost(uint32 cost) { _specializationInfo.ResetTalentsCost = cost; }
time_t GetTalentResetTime() const { return _specializationInfo.ResetTalentsTime; }
void SetTalentResetTime(time_t time_) { _specializationInfo.ResetTalentsTime = time_; }
PlayerTalentMap const& GetPlayerTalentMap(uint8 talentGroupId) const { return _specializationInfo.Talents[talentGroupId]; }
PlayerTalentMap& GetPlayerTalentMap(uint8 talentGroupId) { return _specializationInfo.Talents[talentGroupId]; }
ChrSpecialization GetPrimarySpecialization() const { return ChrSpecialization(*m_playerData->CurrentSpecID); }
void SetPrimarySpecialization(uint32 spec) { SetUpdateFieldValue(m_values.ModifyValue(&Player::m_playerData).ModifyValue(&UF::PlayerData::CurrentSpecID), spec); }
uint8 GetActiveTalentGroup() const { return _specializationInfo.ActiveGroup; }
@@ -1833,9 +1826,8 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
void InitTalentForLevel();
void SendTalentsInfoData();
uint32 CalculateTalentsPoints() const;
TalentLearnResult LearnTalent(uint32 talentId, int32* spellOnCooldown);
bool AddTalent(TalentEntry const* talent, uint8 spec, bool learning);
bool HasTalent(uint32 spell_id, uint8 spec) const;
bool LearnTalent(uint32 talentId, uint16 requestedRank);
bool AddTalent(TalentEntry const* talent, uint16 rank, uint8 talentGroupId, bool learning);
void RemoveTalent(TalentEntry const* talent);
void EnablePvpRules(bool dueToCombat = false);
@@ -1847,8 +1839,6 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
// Dual Spec
void ActivateTalentGroup(uint8 talentGroup);
PlayerTalentMap const* GetTalentMap(uint8 spec) const { return &_specializationInfo.Talents[spec]; }
PlayerTalentMap* GetTalentMap(uint8 spec) { return &_specializationInfo.Talents[spec]; }
std::vector<uint32> const& GetGlyphs(uint8 spec) const { return _specializationInfo.Glyphs[spec]; }
std::vector<uint32>& GetGlyphs(uint8 spec) { return _specializationInfo.Glyphs[spec]; }
ActionButtonList const& GetActionButtons() const { return m_actionButtons; }

View File

@@ -47,10 +47,12 @@ void WorldSession::HandleInspectOpcode(WorldPackets::Inspect::Inspect& inspect)
if (GetPlayer()->CanBeGameMaster() || sWorld->getIntConfig(CONFIG_TALENTS_INSPECTING) + (GetPlayer()->GetEffectiveTeam() == player->GetEffectiveTeam()) > 1)
{
/*
PlayerTalentMap const* talents = player->GetTalentMap(player->GetActiveTalentGroup());
for (PlayerTalentMap::value_type const& v : *talents)
if (v.second != PLAYERSPELL_REMOVED)
inspectResult.Talents.push_back(v.first);
*/
inspectResult.TalentTraits.Level = player->GetLevel();
inspectResult.TalentTraits.ChrSpecializationID = AsUnderlyingType(player->GetPrimarySpecialization());

View File

@@ -28,25 +28,11 @@
void WorldSession::HandleLearnTalentsOpcode(WorldPackets::Talent::LearnTalents& packet)
{
WorldPackets::Talent::LearnTalentFailed learnTalentFailed;
bool anythingLearned = false;
for (uint32 talentId : packet.Talents)
{
if (TalentLearnResult result = _player->LearnTalent(talentId, &learnTalentFailed.SpellID))
{
if (!learnTalentFailed.Reason)
learnTalentFailed.Reason = result;
}
learnTalentFailed.Talents.push_back(talentId);
}
else
anythingLearned = true;
}
if (learnTalentFailed.Reason)
SendPacket(learnTalentFailed.Write());
if (anythingLearned)
void WorldSession::HandleLearnTalentOpcode(WorldPackets::Talent::LearnTalent& packet)
{
if (_player->LearnTalent(packet.TalentID, packet.RequestedRank))
_player->SendTalentsInfoData();
}

View File

@@ -25,120 +25,6 @@
void WorldSession::HandleTraitsCommitConfig(WorldPackets::Traits::TraitsCommitConfig const& traitsCommitConfig)
{
int32 configId = traitsCommitConfig.Config.ID;
UF::TraitConfig const* existingConfig = _player->GetTraitConfig(configId);
if (!existingConfig)
{
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write());
return;
}
if (_player->IsInCombat())
{
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_AFFECTING_COMBAT).Write());
return;
}
if (_player->GetBattleground() && _player->GetBattleground()->GetStatus() == STATUS_IN_PROGRESS)
{
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_IN_PVP_MATCH).Write());
return;
}
auto findEntry = [](WorldPackets::Traits::TraitConfig& config, int32 traitNodeId, int32 traitNodeEntryId) -> WorldPackets::Traits::TraitEntry*
{
auto entryItr = std::find_if(config.Entries.begin(), config.Entries.end(), [=](WorldPackets::Traits::TraitEntry const& traitEntry)
{
return traitEntry.TraitNodeID == traitNodeId && traitEntry.TraitNodeEntryID == traitNodeEntryId;
});
return entryItr != config.Entries.end() ? &*entryItr : nullptr;
};
bool hasRemovedEntries = false;
WorldPackets::Traits::TraitConfig newConfigState(*existingConfig);
for (WorldPackets::Traits::TraitEntry const& newEntry : traitsCommitConfig.Config.Entries)
{
WorldPackets::Traits::TraitEntry* traitEntry = findEntry(newConfigState, newEntry.TraitNodeID, newEntry.TraitNodeEntryID);
if (traitEntry && traitEntry->Rank > newEntry.Rank)
{
TraitNodeEntry const* traitNode = sTraitNodeStore.LookupEntry(newEntry.TraitNodeID);
if (!traitNode)
{
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write());
return;
}
TraitTreeEntry const* traitTree = sTraitTreeStore.LookupEntry(traitNode->TraitTreeID);
if (!traitTree)
{
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write());
return;
}
if (traitTree->GetFlags().HasFlag(TraitTreeFlag::CannotRefund))
{
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_CANT_REMOVE_TALENT).Write());
return;
}
TraitNodeEntryEntry const* traitNodeEntry = sTraitNodeEntryStore.LookupEntry(newEntry.TraitNodeEntryID);
if (!traitNodeEntry)
{
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write());
return;
}
TraitDefinitionEntry const* traitDefinition = sTraitDefinitionStore.LookupEntry(traitNodeEntry->TraitDefinitionID);
if (!traitDefinition)
{
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write());
return;
}
if (traitDefinition->SpellID && _player->GetSpellHistory()->HasCooldown(traitDefinition->SpellID))
{
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, traitDefinition->SpellID, TALENT_FAILED_CANT_REMOVE_TALENT).Write());
return;
}
if (traitDefinition->VisibleSpellID && _player->GetSpellHistory()->HasCooldown(traitDefinition->VisibleSpellID))
{
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, traitDefinition->VisibleSpellID, TALENT_FAILED_CANT_REMOVE_TALENT).Write());
return;
}
hasRemovedEntries = true;
}
if (traitEntry)
{
if (newEntry.Rank)
traitEntry->Rank = newEntry.Rank;
else
newConfigState.Entries.erase(std::remove_if(newConfigState.Entries.begin(), newConfigState.Entries.end(), [&newEntry](WorldPackets::Traits::TraitEntry const& traitEntry)
{
return traitEntry.TraitNodeID == newEntry.TraitNodeID && traitEntry.TraitNodeEntryID == newEntry.TraitNodeEntryID;
}), newConfigState.Entries.end());
}
else
newConfigState.Entries.emplace_back() = newEntry;
}
TraitMgr::LearnResult validationResult = TraitMgr::ValidateConfig(newConfigState, _player, true);
if (validationResult != TraitMgr::LearnResult::Ok)
{
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, AsUnderlyingType(validationResult)).Write());
return;
}
bool needsCastTime = newConfigState.Type == TraitConfigType::Combat && hasRemovedEntries;
if (traitsCommitConfig.SavedLocalIdentifier)
newConfigState.LocalIdentifier = traitsCommitConfig.SavedLocalIdentifier;
else if (UF::TraitConfig const* savedConfig = _player->GetTraitConfig(traitsCommitConfig.SavedLocalIdentifier))
newConfigState.LocalIdentifier = savedConfig->LocalIdentifier;
_player->UpdateTraitConfig(std::move(newConfigState), traitsCommitConfig.SavedConfigID, needsCastTime);
}
void WorldSession::HandleClassTalentsRequestNewConfig(WorldPackets::Traits::ClassTalentsRequestNewConfig& classTalentsRequestNewConfig)

View File

@@ -78,6 +78,12 @@ void WorldPackets::Talent::LearnTalents::Read()
_worldPacket >> Talents[i];
}
void WorldPackets::Talent::LearnTalent::Read()
{
_worldPacket >> TalentID;
_worldPacket >> RequestedRank;
}
WorldPacket const* WorldPackets::Talent::RespecWipeConfirm::Write()
{
_worldPacket << int8(RespecType);

View File

@@ -42,7 +42,7 @@ namespace WorldPackets
struct TalentGroupInfo
{
int32 SpecID = 0;
std::vector<uint16> Talents;
std::vector<TalentInfo> Talents;
std::vector<uint16> GlyphIDs;
};
@@ -143,6 +143,17 @@ namespace WorldPackets
int32 SpellID = 0;
std::vector<PvPTalent> Talents;
};
class LearnTalent final : public ClientPacket
{
public:
LearnTalent(WorldPacket&& packet) : ClientPacket(CMSG_LEARN_TALENT, std::move(packet)) { }
void Read() override;
int32 TalentID = 0;
uint16 RequestedRank = 0;
};
}
}

View File

@@ -557,7 +557,7 @@ void OpcodeTable::Initialize()
DEFINE_HANDLER(CMSG_LEARN_TALENTS, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleLearnTalentsOpcode);
DEFINE_HANDLER(CMSG_LEARN_PREVIEW_TALENTS, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
DEFINE_HANDLER(CMSG_LEARN_PREVIEW_TALENTS_PET, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
DEFINE_HANDLER(CMSG_LEARN_TALENT, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
DEFINE_HANDLER(CMSG_LEARN_TALENT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLearnTalentOpcode);
DEFINE_HANDLER(CMSG_LEAVE_GROUP, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLeaveGroupOpcode);
DEFINE_HANDLER(CMSG_LEAVE_PET_BATTLE_QUEUE, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL);
DEFINE_HANDLER(CMSG_LFG_LIST_APPLY_TO_GROUP, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);

View File

@@ -733,6 +733,7 @@ namespace WorldPackets
namespace Talent
{
class LearnTalent;
class LearnTalents;
class LearnPvpTalents;
class ConfirmRespecWipe;
@@ -1514,6 +1515,7 @@ class TC_GAME_API WorldSession
void HandleLearnPvpTalentsOpcode(WorldPackets::Talent::LearnPvpTalents& packet);
void HandleLearnTalentsOpcode(WorldPackets::Talent::LearnTalents& packet);
void HandleLearnTalentOpcode(WorldPackets::Talent::LearnTalent& packet);
void HandleConfirmRespecWipeOpcode(WorldPackets::Talent::ConfirmRespecWipe& confirmRespecWipe);
void HandleUnlearnSkillOpcode(WorldPackets::Spells::UnlearnSkill& packet);
void HandleTradeSkillSetFavorite(WorldPackets::Spells::TradeSkillSetFavorite const& tradeSkillSetFavorite);

View File

@@ -183,6 +183,7 @@ public:
static bool HandleLearnAllTalentsCommand(ChatHandler* handler)
{
/*
Player* player = handler->GetSession()->GetPlayer();
uint32 playerClass = player->GetClass();
@@ -209,6 +210,7 @@ public:
player->SendTalentsInfoData();
handler->SendSysMessage(LANG_COMMAND_LEARN_CLASS_TALENTS);
*/
return true;
}