diff options
| author | Shauren <shauren.trinity@gmail.com> | 2022-12-16 22:44:55 +0100 |
|---|---|---|
| committer | Shauren <shauren.trinity@gmail.com> | 2022-12-16 22:44:55 +0100 |
| commit | 0cc5ab8372f19dad7412038d52dcd39db5e0e171 (patch) | |
| tree | 99e621862e90fc9f073897445d4a8bdd4fbfa557 /src/server/game/Entities | |
| parent | 9be60f240960f6538329b5e017f435c6237a89ea (diff) | |
Core/Players: Implemented new talent system
Diffstat (limited to 'src/server/game/Entities')
| -rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 791 | ||||
| -rw-r--r-- | src/server/game/Entities/Player/Player.h | 37 |
2 files changed, 789 insertions, 39 deletions
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index ad5e1d2a632..178a053c5b3 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -118,6 +118,8 @@ #include "TerrainMgr.h" #include "ToyPackets.h" #include "TradeData.h" +#include "TraitMgr.h" +#include "TraitPacketsCommon.h" #include "Transport.h" #include "UpdateData.h" #include "Util.h" @@ -2802,7 +2804,7 @@ WorldLocation const* Player::GetStoredAuraTeleportLocation(uint32 spellId) const return nullptr; } -bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent, bool disabled, bool loading /*= false*/, int32 fromSkill /*= 0*/) +bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent, bool disabled, bool loading /*= false*/, int32 fromSkill /*= 0*/, Optional<int32> traitDefinitionId /*= {}*/) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE); if (!spellInfo) @@ -2882,6 +2884,15 @@ bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent dependent_set = true; } + if (itr->second.TraitDefinitionId != traitDefinitionId) + { + if (itr->second.TraitDefinitionId) + if (TraitDefinitionEntry const* traitDefinition = sTraitDefinitionStore.LookupEntry(*itr->second.TraitDefinitionId)) + RemoveOverrideSpell(traitDefinition->OverridesSpellID, spellId); + + itr->second.TraitDefinitionId = traitDefinitionId; + } + // update active state for known spell if (itr->second.active != active && itr->second.state != PLAYERSPELL_REMOVED && !itr->second.disabled) { @@ -2963,6 +2974,8 @@ bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent newspell.active = active; newspell.dependent = dependent; newspell.disabled = disabled; + if (traitDefinitionId) + newspell.TraitDefinitionId = *traitDefinitionId; // replace spells in action bars and spellbook to bigger rank if only one spell rank must be accessible if (newspell.active && !newspell.disabled && spellInfo->IsRanked()) @@ -3011,26 +3024,66 @@ bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent return false; } + bool castSpell = false; + // cast talents with SPELL_EFFECT_LEARN_SPELL (other dependent spells will learned later as not auto-learned) // note: all spells with SPELL_EFFECT_LEARN_SPELL isn't passive if (!loading && spellInfo->HasAttribute(SPELL_ATTR0_CU_IS_TALENT) && spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL)) - { // ignore stance requirement for talent learn spell (stance set for spell only for client spell description show) - CastSpell(this, spellId, true); - } + castSpell = true; // also cast passive spells (including all talents without SPELL_EFFECT_LEARN_SPELL) with additional checks else if (spellInfo->IsPassive()) - { - if (HandlePassiveSpellLearn(spellInfo)) - CastSpell(this, spellId, true); - } + castSpell = HandlePassiveSpellLearn(spellInfo); else if (spellInfo->HasEffect(SPELL_EFFECT_SKILL_STEP)) + castSpell = true; + else if (spellInfo->HasAttribute(SPELL_ATTR1_CAST_WHEN_LEARNED)) + castSpell = true; + + if (castSpell) { - CastSpell(this, spellId, true); - return false; + CastSpellExtraArgs args; + args.SetTriggerFlags(TRIGGERED_FULL_MASK); + + if (traitDefinitionId) + { + if (UF::TraitConfig const* traitConfig = GetTraitConfig(m_activePlayerData->ActiveCombatTraitConfigID)) + { + int32 traitEntryIndex = traitConfig->Entries.FindIndexIf([traitDefinitionId](UF::TraitEntry const& traitEntry) + { + return sTraitNodeEntryStore.AssertEntry(traitEntry.TraitNodeEntryID)->TraitDefinitionID == traitDefinitionId; + }); + int32 rank = 0; + if (traitEntryIndex >= 0) + rank = traitConfig->Entries[traitEntryIndex].Rank + traitConfig->Entries[traitEntryIndex].GrantedRanks; + + if (rank > 0) + { + if (std::vector<TraitDefinitionEffectPointsEntry const*> const* traitDefinitionEffectPoints = TraitMgr::GetTraitDefinitionEffectPointModifiers(*traitDefinitionId)) + { + for (TraitDefinitionEffectPointsEntry const* traitDefinitionEffectPoint : *traitDefinitionEffectPoints) + { + if (traitDefinitionEffectPoint->EffectIndex >= int32(spellInfo->GetEffects().size())) + continue; + + float basePoints = sDB2Manager.GetCurveValueAt(traitDefinitionEffectPoint->CurveID, rank); + if (traitDefinitionEffectPoint->GetOperationType() == TraitPointsOperationType::Multiply) + basePoints *= spellInfo->GetEffect(SpellEffIndex(traitDefinitionEffectPoint->EffectIndex)).CalcBaseValue(this, nullptr, 0, -1); + + args.AddSpellMod(SpellValueMod(SPELLVALUE_BASE_POINT0 + traitDefinitionEffectPoint->EffectIndex), basePoints); + } + } + } + } + } + + CastSpell(this, spellId, args); + if (spellInfo->HasEffect(SPELL_EFFECT_SKILL_STEP)) + return false; } - else if (spellInfo->HasAttribute(SPELL_ATTR1_CAST_WHEN_LEARNED)) - CastSpell(this, spellId, true); + + if (traitDefinitionId) + if (TraitDefinitionEntry const* traitDefinition = sTraitDefinitionStore.LookupEntry(*traitDefinitionId)) + AddOverrideSpell(traitDefinition->OverridesSpellID, spellId); // update free primary prof.points (if any, can be none in case GM .learn prof. learning) if (uint32 freeProfs = GetFreePrimaryProfessionPoints()) @@ -3168,14 +3221,14 @@ bool Player::HandlePassiveSpellLearn(SpellInfo const* spellInfo) return need_cast && (!spellInfo->CasterAuraState || HasAuraState(AuraStateType(spellInfo->CasterAuraState))); } -void Player::LearnSpell(uint32 spell_id, bool dependent, int32 fromSkill /*= 0*/, bool suppressMessaging /*= false*/) +void Player::LearnSpell(uint32 spell_id, bool dependent, int32 fromSkill /*= 0*/, bool suppressMessaging /*= false*/, Optional<int32> traitDefinitionId /*= {}*/) { PlayerSpellMap::iterator itr = m_spells.find(spell_id); bool disabled = (itr != m_spells.end()) ? itr->second.disabled : false; bool active = disabled ? itr->second.active : true; - bool learning = AddSpell(spell_id, active, true, dependent, false, false, fromSkill); + bool learning = AddSpell(spell_id, active, true, dependent, false, false, fromSkill, traitDefinitionId); // prevent duplicated entires in spell book, also not send if not in world (loading) if (learning && IsInWorld()) @@ -3183,6 +3236,7 @@ void Player::LearnSpell(uint32 spell_id, bool dependent, int32 fromSkill /*= 0*/ WorldPackets::Spells::LearnedSpells learnedSpells; WorldPackets::Spells::LearnedSpellInfo& learnedSpellInfo = learnedSpells.ClientLearnedSpellData.emplace_back(); learnedSpellInfo.SpellID = spell_id; + learnedSpellInfo.TraitDefinitionID = traitDefinitionId; learnedSpells.SuppressMessaging = suppressMessaging; SendDirectMessage(learnedSpells.Write()); } @@ -4169,6 +4223,14 @@ void Player::DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRe Garrison::DeleteFromDB(guid, trans); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TRAIT_ENTRIES_BY_CHAR); + stmt->setUInt64(0, guid); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TRAIT_CONFIGS_BY_CHAR); + stmt->setUInt64(0, guid); + trans->Append(stmt); + sCharacterCache->DeleteCharacterCacheEntry(playerguid, name); break; } @@ -17589,6 +17651,9 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol LearnDefaultSkills(); LearnCustomSpells(); + _LoadTraits(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_TRAIT_CONFIGS), + holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_TRAIT_ENTRIES)); // must be after loading spells + // must be before inventory (some items required reputation check) m_reputationMgr->LoadFromDB(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_REPUTATION)); @@ -17606,7 +17671,7 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol // update items with duration and realtime UpdateItemDuration(time_diff, true); - _LoadActions(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_ACTIONS)); + StartLoadingActionButtons(); // unread mails and next delivery time, actual mails not loaded _LoadMail(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_MAILS), @@ -19559,6 +19624,7 @@ void Player::SaveToDB(LoginDatabaseTransaction loginTransaction, CharacterDataba _SaveMonthlyQuestStatus(trans); _SaveGlyphs(trans); _SaveTalents(trans); + _SaveTraits(trans); _SaveSpells(trans); GetSpellHistory()->SaveToDB<Player>(trans); _SaveActions(trans); @@ -19661,6 +19727,26 @@ void Player::_SaveCustomizations(CharacterDatabaseTransaction trans) void Player::_SaveActions(CharacterDatabaseTransaction trans) { + int32 traitConfigId = [&]() -> int32 + { + UF::TraitConfig const* traitConfig = GetTraitConfig(m_activePlayerData->ActiveCombatTraitConfigID); + if (!traitConfig) + return 0; + + int32 usedSavedTraitConfigIndex = m_activePlayerData->TraitConfigs.FindIndexIf([localIdent = *traitConfig->LocalIdentifier](UF::TraitConfig const& savedConfig) + { + return static_cast<TraitConfigType>(*savedConfig.Type) == TraitConfigType::Combat + && (static_cast<TraitCombatConfigFlags>(*savedConfig.CombatConfigFlags) & TraitCombatConfigFlags::ActiveForSpec) == TraitCombatConfigFlags::None + && (static_cast<TraitCombatConfigFlags>(*savedConfig.CombatConfigFlags) & TraitCombatConfigFlags::SharedActionBars) == TraitCombatConfigFlags::None + && savedConfig.LocalIdentifier == localIdent; + }); + + if (usedSavedTraitConfigIndex >= 0) + return m_activePlayerData->TraitConfigs[usedSavedTraitConfigIndex].ID; + + return 0; + }(); + CharacterDatabasePreparedStatement* stmt; for (ActionButtonList::iterator itr = m_actionButtons.begin(); itr != m_actionButtons.end();) @@ -19671,9 +19757,10 @@ void Player::_SaveActions(CharacterDatabaseTransaction trans) stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_ACTION); stmt->setUInt64(0, GetGUID().GetCounter()); stmt->setUInt8(1, GetActiveTalentGroup()); - stmt->setUInt8(2, itr->first); - stmt->setUInt64(3, itr->second.GetAction()); - stmt->setUInt8(4, uint8(itr->second.GetType())); + stmt->setInt32(2, traitConfigId); + stmt->setUInt8(3, itr->first); + stmt->setUInt64(4, itr->second.GetAction()); + stmt->setUInt8(5, uint8(itr->second.GetType())); trans->Append(stmt); itr->second.uState = ACTIONBUTTON_UNCHANGED; @@ -19686,6 +19773,7 @@ void Player::_SaveActions(CharacterDatabaseTransaction trans) stmt->setUInt64(2, GetGUID().GetCounter()); stmt->setUInt8(3, itr->first); stmt->setUInt8(4, GetActiveTalentGroup()); + stmt->setInt32(5, traitConfigId); trans->Append(stmt); itr->second.uState = ACTIONBUTTON_UNCHANGED; @@ -19696,6 +19784,7 @@ void Player::_SaveActions(CharacterDatabaseTransaction trans) stmt->setUInt64(0, GetGUID().GetCounter()); stmt->setUInt8(1, itr->first); stmt->setUInt8(2, GetActiveTalentGroup()); + stmt->setInt32(3, traitConfigId); trans->Append(stmt); m_actionButtons.erase(itr++); @@ -26493,6 +26582,146 @@ void Player::_LoadPvpTalents(PreparedQueryResult result) } } +void Player::_LoadTraits(PreparedQueryResult configsResult, PreparedQueryResult entriesResult) +{ + std::unordered_multimap<int32, WorldPackets::Traits::TraitEntry> traitEntriesByConfig; + if (entriesResult) + { + // 0 1, 2 3 4 + // SELECT traitConfigId, traitNodeId, traitNodeEntryId, rank, grantedRanks FROM character_trait_entry WHERE guid = ? + do + { + Field* fields = entriesResult->Fetch(); + WorldPackets::Traits::TraitEntry traitEntry; + traitEntry.TraitNodeID = fields[1].GetInt32(); + traitEntry.TraitNodeEntryID = fields[2].GetInt32(); + traitEntry.Rank = fields[3].GetInt32(); + traitEntry.GrantedRanks = fields[4].GetInt32(); + + if (!TraitMgr::IsValidEntry(traitEntry)) + continue; + + traitEntriesByConfig.emplace(fields[0].GetInt32(), traitEntry); + + } while (entriesResult->NextRow()); + } + + if (configsResult) + { + // 0 1 2 3 4 5 6 7 + // SELECT traitConfigId, type, chrSpecializationId, combatConfigFlags, localIdentifier, skillLineId, traitSystemId, `name` FROM character_trait_config WHERE guid = ? + do + { + Field* fields = configsResult->Fetch(); + WorldPackets::Traits::TraitConfig traitConfig; + traitConfig.ID = fields[0].GetInt32(); + traitConfig.Type = static_cast<TraitConfigType>(fields[1].GetInt32()); + switch (traitConfig.Type) + { + case TraitConfigType::Combat: + traitConfig.ChrSpecializationID = fields[2].GetInt32(); + traitConfig.CombatConfigFlags = static_cast<TraitCombatConfigFlags>(fields[3].GetInt32()); + traitConfig.LocalIdentifier = fields[4].GetInt32(); + break; + case TraitConfigType::Profession: + traitConfig.SkillLineID = fields[5].GetInt32(); + break; + case TraitConfigType::Generic: + traitConfig.TraitSystemID = fields[6].GetInt32(); + break; + default: + break; + } + + traitConfig.Name = fields[7].GetString(); + + for (auto&& [_, traitEntry] : Trinity::Containers::MapEqualRange(traitEntriesByConfig, traitConfig.ID)) + traitConfig.Entries.emplace_back() = traitEntry; + + if (TraitMgr::ValidateConfig(traitConfig, this) != TALENT_LEARN_OK) + { + traitConfig.Entries.clear(); + for (UF::TraitEntry const& grantedEntry : TraitMgr::GetGrantedTraitEntriesForConfig(traitConfig, this)) + traitConfig.Entries.emplace_back(grantedEntry); + } + + AddTraitConfig(traitConfig); + + } while (configsResult->NextRow()); + } + + auto hasConfigForSpec = [&](int32 specId) + { + return m_activePlayerData->TraitConfigs.FindIndexIf([=](UF::TraitConfig const& traitConfig) + { + return traitConfig.Type == AsUnderlyingType(TraitConfigType::Combat) + && traitConfig.ChrSpecializationID == specId + && traitConfig.CombatConfigFlags & AsUnderlyingType(TraitCombatConfigFlags::ActiveForSpec); + }) >= 0; + }; + + auto findFreeLocalIdentifier = [&](int32 specId) + { + int32 index = 1; + while (m_activePlayerData->TraitConfigs.FindIndexIf([specId, index](UF::TraitConfig const& traitConfig) + { + return traitConfig.Type == AsUnderlyingType(TraitConfigType::Combat) + && traitConfig.ChrSpecializationID == specId + && traitConfig.LocalIdentifier == index; + }) >= 0) + ++index; + + return index; + }; + + for (uint32 i = 0; i < MAX_SPECIALIZATIONS - 1 /*initial spec doesnt get a config*/; ++i) + { + if (ChrSpecializationEntry const* spec = sDB2Manager.GetChrSpecializationByIndex(GetClass(), i)) + { + if (hasConfigForSpec(spec->ID)) + continue; + + WorldPackets::Traits::TraitConfig traitConfig; + traitConfig.Type = TraitConfigType::Combat; + traitConfig.ChrSpecializationID = spec->ID; + traitConfig.CombatConfigFlags = TraitCombatConfigFlags::ActiveForSpec; + traitConfig.LocalIdentifier = findFreeLocalIdentifier(spec->ID); + traitConfig.Name = spec->Name[GetSession()->GetSessionDbcLocale()]; + + CreateTraitConfig(traitConfig); + } + } + + int32 activeConfig = m_activePlayerData->TraitConfigs.FindIndexIf([&](UF::TraitConfig const& traitConfig) + { + return traitConfig.Type == AsUnderlyingType(TraitConfigType::Combat) + && traitConfig.ChrSpecializationID == int32(GetPrimarySpecialization()) + && traitConfig.CombatConfigFlags & AsUnderlyingType(TraitCombatConfigFlags::ActiveForSpec); + }); + + if (activeConfig >= 0) + SetActiveCombatTraitConfigID(m_activePlayerData->TraitConfigs[activeConfig].ID); + + for (UF::TraitConfig const& traitConfig : m_activePlayerData->TraitConfigs) + { + switch (static_cast<TraitConfigType>(*traitConfig.Type)) + { + case TraitConfigType::Combat: + if (traitConfig.ID != int32(*m_activePlayerData->ActiveCombatTraitConfigID)) + continue; + break; + case TraitConfigType::Profession: + if (!HasSkill(traitConfig.SkillLineID)) + continue; + break; + default: + break; + } + + ApplyTraitConfig(traitConfig.ID, true); + } +} + void Player::_SaveTalents(CharacterDatabaseTransaction trans) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TALENT); @@ -26537,6 +26766,97 @@ void Player::_SaveTalents(CharacterDatabaseTransaction trans) } } +void Player::_SaveTraits(CharacterDatabaseTransaction trans) +{ + CharacterDatabasePreparedStatement* stmt = nullptr; + for (auto& [traitConfigId, state] : m_traitConfigStates) + { + switch (state) + { + case PLAYERSPELL_CHANGED: + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TRAIT_ENTRIES); + stmt->setUInt64(0, GetGUID().GetCounter()); + stmt->setInt32(1, traitConfigId); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TRAIT_CONFIGS); + stmt->setUInt64(0, GetGUID().GetCounter()); + stmt->setInt32(1, traitConfigId); + trans->Append(stmt); + + if (UF::TraitConfig const* traitConfig = GetTraitConfig(traitConfigId)) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_TRAIT_CONFIGS); + stmt->setUInt64(0, GetGUID().GetCounter()); + stmt->setInt32(1, traitConfig->ID); + stmt->setInt32(2, traitConfig->Type); + switch (static_cast<TraitConfigType>(*traitConfig->Type)) + { + case TraitConfigType::Combat: + stmt->setInt32(3, traitConfig->ChrSpecializationID); + stmt->setInt32(4, traitConfig->CombatConfigFlags); + stmt->setInt32(5, traitConfig->LocalIdentifier); + stmt->setNull(6); + stmt->setNull(7); + break; + case TraitConfigType::Profession: + stmt->setNull(3); + stmt->setNull(4); + stmt->setNull(5); + stmt->setInt32(6, traitConfig->SkillLineID); + stmt->setNull(7); + break; + case TraitConfigType::Generic: + stmt->setNull(3); + stmt->setNull(4); + stmt->setNull(5); + stmt->setNull(6); + stmt->setInt32(7, traitConfig->TraitSystemID); + break; + default: + break; + } + + stmt->setString(8, traitConfig->Name); + trans->Append(stmt); + + for (UF::TraitEntry const& traitEntry : traitConfig->Entries) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_TRAIT_ENTRIES); + stmt->setUInt64(0, GetGUID().GetCounter()); + stmt->setInt32(1, traitConfig->ID); + stmt->setInt32(2, traitEntry.TraitNodeID); + stmt->setInt32(3, traitEntry.TraitNodeEntryID); + stmt->setInt32(4, traitEntry.Rank); + stmt->setInt32(5, traitEntry.GrantedRanks); + trans->Append(stmt); + } + } + break; + case PLAYERSPELL_REMOVED: + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TRAIT_ENTRIES); + stmt->setUInt64(0, GetGUID().GetCounter()); + stmt->setInt32(1, traitConfigId); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TRAIT_CONFIGS); + stmt->setUInt64(0, GetGUID().GetCounter()); + stmt->setInt32(1, traitConfigId); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACTION_BY_TRAIT_CONFIG); + stmt->setUInt64(0, GetGUID().GetCounter()); + stmt->setInt32(1, traitConfigId); + trans->Append(stmt); + break; + default: + break; + } + } + + m_traitConfigStates.clear(); +} + void Player::ActivateTalentGroup(ChrSpecializationEntry const* spec) { if (GetActiveTalentGroup() == spec->OrderIndex) @@ -26634,6 +26954,8 @@ void Player::ActivateTalentGroup(ChrSpecializationEntry const* spec) RemoveOverrideSpell(talentInfo->OverridesSpellID, talentInfo->SpellID); } + ApplyTraitConfig(m_activePlayerData->ActiveCombatTraitConfigID, false); + // Remove spec specific spells RemoveSpecializationSpells(); @@ -26642,6 +26964,16 @@ void Player::ActivateTalentGroup(ChrSpecializationEntry const* spec) SetActiveTalentGroup(spec->OrderIndex); SetPrimarySpecialization(spec->ID); + int32 specTraitConfigIndex = m_activePlayerData->TraitConfigs.FindIndexIf([spec](UF::TraitConfig const& traitConfig) + { + return static_cast<TraitConfigType>(*traitConfig.Type) == TraitConfigType::Combat + && traitConfig.ChrSpecializationID == int32(spec->ID) + && (static_cast<TraitCombatConfigFlags>(*traitConfig.CombatConfigFlags) & TraitCombatConfigFlags::ActiveForSpec) != TraitCombatConfigFlags::None; + }); + if (specTraitConfigIndex >= 0) + SetActiveCombatTraitConfigID(m_activePlayerData->TraitConfigs[specTraitConfigIndex].ID); + else + SetActiveCombatTraitConfigID(0); for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId) { @@ -26684,24 +27016,11 @@ void Player::ActivateTalentGroup(ChrSpecializationEntry const* spec) if (uint32 mastery = spec->MasterySpellID[i]) LearnSpell(mastery, true); + ApplyTraitConfig(m_activePlayerData->ActiveCombatTraitConfigID, true); + InitTalentForLevel(); - // load them asynchronously - { - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_ACTIONS_SPEC); - stmt->setUInt64(0, GetGUID().GetCounter()); - stmt->setUInt8(1, GetActiveTalentGroup()); - - WorldSession* mySess = GetSession(); - mySess->GetQueryProcessor().AddCallback(CharacterDatabase.AsyncQuery(stmt) - .WithPreparedCallback([mySess](PreparedQueryResult result) - { - // safe callback, we can't pass this pointer directly - // in case player logs out before db response (player would be deleted in that case) - if (Player* thisPlayer = mySess->GetPlayer()) - thisPlayer->LoadActions(result); - })); - } + StartLoadingActionButtons(); UpdateDisplayPower(); Powers pw = GetPowerType(); @@ -26759,6 +27078,48 @@ void Player::ActivateTalentGroup(ChrSpecializationEntry const* spec) } } +void Player::StartLoadingActionButtons(std::function<void()>&& callback /*= nullptr*/) +{ + int32 traitConfigId = [&]() -> int32 + { + UF::TraitConfig const* traitConfig = GetTraitConfig(m_activePlayerData->ActiveCombatTraitConfigID); + if (!traitConfig) + return 0; + + int32 usedSavedTraitConfigIndex = m_activePlayerData->TraitConfigs.FindIndexIf([localIdent = *traitConfig->LocalIdentifier](UF::TraitConfig const& savedConfig) + { + return static_cast<TraitConfigType>(*savedConfig.Type) == TraitConfigType::Combat + && (static_cast<TraitCombatConfigFlags>(*savedConfig.CombatConfigFlags) & TraitCombatConfigFlags::ActiveForSpec) == TraitCombatConfigFlags::None + && (static_cast<TraitCombatConfigFlags>(*savedConfig.CombatConfigFlags) & TraitCombatConfigFlags::SharedActionBars) == TraitCombatConfigFlags::None + && savedConfig.LocalIdentifier == localIdent; + }); + + if (usedSavedTraitConfigIndex >= 0) + return m_activePlayerData->TraitConfigs[usedSavedTraitConfigIndex].ID; + + return 0; + }(); + + // load them asynchronously + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_ACTIONS_SPEC); + stmt->setUInt64(0, GetGUID().GetCounter()); + stmt->setUInt8(1, GetActiveTalentGroup()); + stmt->setInt32(2, traitConfigId); + + WorldSession* mySess = GetSession(); + mySess->GetQueryProcessor().AddCallback(CharacterDatabase.AsyncQuery(stmt) + .WithPreparedCallback([mySess, myGuid = GetGUID(), callback = std::move(callback)](PreparedQueryResult result) + { + // safe callback, we can't pass this pointer directly + // in case player logs out before db response (player would be deleted in that case) + if (Player* thisPlayer = mySess->GetPlayer(); thisPlayer && thisPlayer->GetGUID() == myGuid) + thisPlayer->LoadActions(result); + + if (callback) + callback(); + })); +} + void Player::LoadActions(PreparedQueryResult result) { _LoadActions(result); @@ -26766,6 +27127,368 @@ void Player::LoadActions(PreparedQueryResult result) SendActionButtons(1); } +void Player::CreateTraitConfig(WorldPackets::Traits::TraitConfig& traitConfig) +{ + uint32 configId = TraitMgr::GenerateNewTraitConfigId(); + auto hasConfigId = [&](int32 id) + { + return m_activePlayerData->TraitConfigs.FindIndexIf([id](UF::TraitConfig const& config) { return config.ID == id; }) >= 0; + }; + + while (hasConfigId(configId)) + configId = TraitMgr::GenerateNewTraitConfigId(); + + traitConfig.ID = configId; + + int32 traitConfigIndex = m_activePlayerData->TraitConfigs.size(); + AddTraitConfig(traitConfig); + + for (UF::TraitEntry const& grantedEntry : TraitMgr::GetGrantedTraitEntriesForConfig(traitConfig, this)) + { + auto entryItr = std::find_if(traitConfig.Entries.begin(), traitConfig.Entries.end(), + [&](WorldPackets::Traits::TraitEntry const& entry) { return entry.TraitNodeID == grantedEntry.TraitNodeID && entry.TraitNodeEntryID == grantedEntry.TraitNodeEntryID; }); + + if (entryItr == traitConfig.Entries.end()) + AddDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs, traitConfigIndex) + .ModifyValue(&UF::TraitConfig::Entries)) = grantedEntry; + } + + m_traitConfigStates[configId] = PLAYERSPELL_CHANGED; +} + +void Player::AddTraitConfig(WorldPackets::Traits::TraitConfig const& traitConfig) +{ + auto setter = AddDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::TraitConfigs)); + setter.ModifyValue(&UF::TraitConfig::ID).SetValue(traitConfig.ID); + setter.ModifyValue(&UF::TraitConfig::Name).SetValue(traitConfig.Name); + setter.ModifyValue(&UF::TraitConfig::Type).SetValue(AsUnderlyingType(traitConfig.Type)); + setter.ModifyValue(&UF::TraitConfig::SkillLineID).SetValue(traitConfig.SkillLineID);; + setter.ModifyValue(&UF::TraitConfig::ChrSpecializationID).SetValue(traitConfig.ChrSpecializationID); + setter.ModifyValue(&UF::TraitConfig::CombatConfigFlags).SetValue(AsUnderlyingType(traitConfig.CombatConfigFlags)); + setter.ModifyValue(&UF::TraitConfig::LocalIdentifier).SetValue(traitConfig.LocalIdentifier); + setter.ModifyValue(&UF::TraitConfig::TraitSystemID).SetValue(traitConfig.TraitSystemID); + + for (WorldPackets::Traits::TraitEntry const& traitEntry : traitConfig.Entries) + { + UF::TraitEntry& newEntry = AddDynamicUpdateFieldValue(setter.ModifyValue(&UF::TraitConfig::Entries)); + newEntry.TraitNodeID = traitEntry.TraitNodeID; + newEntry.TraitNodeEntryID = traitEntry.TraitNodeEntryID; + newEntry.Rank = traitEntry.Rank; + newEntry.GrantedRanks = traitEntry.GrantedRanks; + } +} + +UF::TraitConfig const* Player::GetTraitConfig(int32 configId) const +{ + int32 index = m_activePlayerData->TraitConfigs.FindIndexIf([configId](UF::TraitConfig const& config) { return config.ID == configId; }); + if (index < 0) + return nullptr; + + return &m_activePlayerData->TraitConfigs[index]; +} + +void Player::UpdateTraitConfig(WorldPackets::Traits::TraitConfig&& newConfig, int32 savedConfigId, bool withCastTime) +{ + int32 index = m_activePlayerData->TraitConfigs.FindIndexIf([&](UF::TraitConfig const& config) { return config.ID == newConfig.ID; }); + if (index < 0) + return; + + if (withCastTime) + { + CastSpell(this, TraitMgr::COMMIT_COMBAT_TRAIT_CONFIG_CHANGES_SPELL_ID, CastSpellExtraArgs(SPELLVALUE_BASE_POINT0, savedConfigId).SetCustomArg(std::move(newConfig))); + return; + } + + bool isActiveConfig = true; + bool loadActionButtons = false; + switch (TraitConfigType(*m_activePlayerData->TraitConfigs[index].Type)) + { + case TraitConfigType::Combat: + isActiveConfig = newConfig.ID == int32(*m_activePlayerData->ActiveCombatTraitConfigID); + loadActionButtons = m_activePlayerData->TraitConfigs[index].LocalIdentifier != newConfig.LocalIdentifier; + break; + case TraitConfigType::Profession: + isActiveConfig = HasSkill(m_activePlayerData->TraitConfigs[index].SkillLineID); + break; + default: + break; + } + + std::function<void()> finalizeTraitConfigUpdate = [=, newConfig = std::move(newConfig)]() + { + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs, index) + .ModifyValue(&UF::TraitConfig::LocalIdentifier), newConfig.LocalIdentifier); + + ApplyTraitEntryChanges(newConfig.ID, newConfig, isActiveConfig, true); + + if (savedConfigId) + ApplyTraitEntryChanges(savedConfigId, newConfig, false, false); + + if (EnumFlag(newConfig.CombatConfigFlags).HasFlag(TraitCombatConfigFlags::StarterBuild)) + SetTraitConfigUseStarterBuild(newConfig.ID, true); + }; + + if (loadActionButtons) + { + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + _SaveActions(trans); + CharacterDatabase.CommitTransaction(trans); + + StartLoadingActionButtons(std::move(finalizeTraitConfigUpdate)); + } + else + finalizeTraitConfigUpdate(); +} + +void Player::ApplyTraitEntryChanges(int32 editedConfigId, WorldPackets::Traits::TraitConfig const& newConfig, bool applyTraits, bool consumeCurrencies) +{ + int32 editedIndex = m_activePlayerData->TraitConfigs.FindIndexIf([editedConfigId](UF::TraitConfig const& config) { return config.ID == editedConfigId; }); + if (editedIndex < 0) + return; + + auto makeTraitEntryFinder = [](int32 traitNodeId, int32 traitNodeEntryId) + { + return [=](auto const& ufEntry) { return ufEntry.TraitNodeID == traitNodeId && ufEntry.TraitNodeEntryID == traitNodeEntryId; }; + }; + + UF::TraitConfig const& editedConfig = m_activePlayerData->TraitConfigs[editedIndex]; + + // remove traits not found in new config + std::set<int32, std::greater<>> entryIndicesToRemove; + for (int32 i = 0; i < int32(editedConfig.Entries.size()); ++i) + { + UF::TraitEntry const& oldEntry = editedConfig.Entries[i]; + auto entryItr = std::find_if(newConfig.Entries.begin(), newConfig.Entries.end(), makeTraitEntryFinder(oldEntry.TraitNodeID, oldEntry.TraitNodeEntryID)); + if (entryItr != newConfig.Entries.end()) + continue; + + if (applyTraits) + ApplyTraitEntry(oldEntry.TraitNodeEntryID, 0, 0, false); + + entryIndicesToRemove.insert(i); + } + + for (int32 indexToRemove : entryIndicesToRemove) + { + RemoveDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs, editedIndex) + .ModifyValue(&UF::TraitConfig::Entries), indexToRemove); + } + + std::vector<WorldPackets::Traits::TraitEntry> costEntries; + + // apply new traits + for (std::size_t i = 0; i < newConfig.Entries.size(); ++i) + { + WorldPackets::Traits::TraitEntry const& newEntry = newConfig.Entries[i]; + int32 oldEntryIndex = editedConfig.Entries.FindIndexIf(makeTraitEntryFinder(newEntry.TraitNodeID, newEntry.TraitNodeEntryID)); + if (oldEntryIndex < 0) + { + if (consumeCurrencies) + costEntries.push_back(newEntry); + + UF::TraitEntry& newUfEntry = AddDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs, editedIndex) + .ModifyValue(&UF::TraitConfig::Entries)); + newUfEntry.TraitNodeID = newEntry.TraitNodeID; + newUfEntry.TraitNodeEntryID = newEntry.TraitNodeEntryID; + newUfEntry.Rank = newEntry.Rank; + newUfEntry.GrantedRanks = newEntry.GrantedRanks; + + if (applyTraits) + ApplyTraitEntry(newUfEntry.TraitNodeEntryID, newUfEntry.Rank, 0, true); + } + else if (newEntry.Rank != editedConfig.Entries[oldEntryIndex].Rank || newEntry.GrantedRanks != editedConfig.Entries[oldEntryIndex].GrantedRanks) + { + if (consumeCurrencies && newEntry.Rank > editedConfig.Entries[oldEntryIndex].Rank) + { + WorldPackets::Traits::TraitEntry& costEntry = costEntries.emplace_back(newEntry); + costEntry.Rank -= editedConfig.Entries[oldEntryIndex].Rank; + } + + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs, editedIndex) + .ModifyValue(&UF::TraitConfig::Entries, oldEntryIndex) + .ModifyValue(&UF::TraitEntry::Rank), newEntry.Rank); + + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs, editedIndex) + .ModifyValue(&UF::TraitConfig::Entries, oldEntryIndex) + .ModifyValue(&UF::TraitEntry::GrantedRanks), newEntry.GrantedRanks); + + if (applyTraits) + ApplyTraitEntry(newEntry.TraitNodeEntryID, newEntry.Rank, newEntry.GrantedRanks, true); + } + } + + if (consumeCurrencies) + { + std::map<int32, int32> currencies; + for (WorldPackets::Traits::TraitEntry const& costEntry : costEntries) + TraitMgr::FillSpentCurrenciesMap(costEntry, currencies); + + for (auto [traitCurrencyId, amount] : currencies) + { + TraitCurrencyEntry const* traitCurrency = sTraitCurrencyStore.LookupEntry(traitCurrencyId); + if (!traitCurrency) + continue; + + switch (traitCurrency->GetType()) + { + case TraitCurrencyType::Gold: + ModifyMoney(-amount); + break; + case TraitCurrencyType::CurrencyTypesBased: + ModifyCurrency(traitCurrency->CurrencyTypesID, -amount); + break; + default: + break; + } + } + } + + m_traitConfigStates[editedConfigId] = PLAYERSPELL_CHANGED; +} + +void Player::RenameTraitConfig(int32 editedConfigId, std::string&& newName) +{ + int32 editedIndex = m_activePlayerData->TraitConfigs.FindIndexIf([editedConfigId](UF::TraitConfig const& traitConfig) + { + return traitConfig.ID == editedConfigId + && static_cast<TraitConfigType>(*traitConfig.Type) == TraitConfigType::Combat + && (static_cast<TraitCombatConfigFlags>(*traitConfig.CombatConfigFlags) & TraitCombatConfigFlags::ActiveForSpec) == TraitCombatConfigFlags::None; + }); + if (editedIndex < 0) + return; + + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs, editedIndex) + .ModifyValue(&UF::TraitConfig::Name), std::move(newName)); + + m_traitConfigStates[editedConfigId] = PLAYERSPELL_CHANGED; +} + +void Player::DeleteTraitConfig(int32 deletedConfigId) +{ + int32 deletedIndex = m_activePlayerData->TraitConfigs.FindIndexIf([deletedConfigId](UF::TraitConfig const& traitConfig) + { + return traitConfig.ID == deletedConfigId + && static_cast<TraitConfigType>(*traitConfig.Type) == TraitConfigType::Combat + && (static_cast<TraitCombatConfigFlags>(*traitConfig.CombatConfigFlags) & TraitCombatConfigFlags::ActiveForSpec) == TraitCombatConfigFlags::None; + }); + if (deletedIndex < 0) + return; + + RemoveDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs), deletedIndex); + + m_traitConfigStates[deletedConfigId] = PLAYERSPELL_REMOVED; +} + +void Player::ApplyTraitConfig(int32 configId, bool apply) +{ + UF::TraitConfig const* traitConfig = GetTraitConfig(configId); + if (!traitConfig) + return; + + for (UF::TraitEntry const& traitEntry : traitConfig->Entries) + ApplyTraitEntry(traitEntry.TraitNodeEntryID, traitEntry.Rank, traitEntry.GrantedRanks, apply); +} + +void Player::ApplyTraitEntry(int32 traitNodeEntryId, int32 /*rank*/, int32 /*grantedRanks*/, bool apply) +{ + TraitNodeEntryEntry const* traitNodeEntry = sTraitNodeEntryStore.LookupEntry(traitNodeEntryId); + if (!traitNodeEntry) + return; + + TraitDefinitionEntry const* traitDefinition = sTraitDefinitionStore.LookupEntry(traitNodeEntry->TraitDefinitionID); + if (!traitDefinition) + return; + + if (traitDefinition->SpellID) + { + if (apply) + LearnSpell(traitDefinition->SpellID, true, 0, false, traitNodeEntry->TraitDefinitionID); + else + RemoveSpell(traitDefinition->SpellID); + } +} + +void Player::SetTraitConfigUseStarterBuild(int32 traitConfigId, bool useStarterBuild) +{ + int32 configIndex = m_activePlayerData->TraitConfigs.FindIndexIf([traitConfigId](UF::TraitConfig const& traitConfig) + { + return traitConfig.ID == traitConfigId + && static_cast<TraitConfigType>(*traitConfig.Type) == TraitConfigType::Combat + && (static_cast<TraitCombatConfigFlags>(*traitConfig.CombatConfigFlags) & TraitCombatConfigFlags::ActiveForSpec) != TraitCombatConfigFlags::None; + }); + if (configIndex < 0) + return; + + bool currentlyUsesStarterBuild = EnumFlag(static_cast<TraitCombatConfigFlags>(*m_activePlayerData->TraitConfigs[configIndex].CombatConfigFlags)).HasFlag(TraitCombatConfigFlags::StarterBuild); + if (currentlyUsesStarterBuild == useStarterBuild) + return; + + if (useStarterBuild) + SetUpdateFieldFlagValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs, configIndex) + .ModifyValue(&UF::TraitConfig::CombatConfigFlags), AsUnderlyingType(TraitCombatConfigFlags::StarterBuild)); + else + RemoveUpdateFieldFlagValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs, configIndex) + .ModifyValue(&UF::TraitConfig::CombatConfigFlags), AsUnderlyingType(TraitCombatConfigFlags::StarterBuild)); + + m_traitConfigStates[traitConfigId] = PLAYERSPELL_CHANGED; +} + +void Player::SetTraitConfigUseSharedActionBars(int32 traitConfigId, bool usesSharedActionBars, bool isLastSelectedSavedConfig) +{ + int32 configIndex = m_activePlayerData->TraitConfigs.FindIndexIf([traitConfigId](UF::TraitConfig const& traitConfig) + { + return traitConfig.ID == traitConfigId + && static_cast<TraitConfigType>(*traitConfig.Type) == TraitConfigType::Combat + && (static_cast<TraitCombatConfigFlags>(*traitConfig.CombatConfigFlags) & TraitCombatConfigFlags::ActiveForSpec) == TraitCombatConfigFlags::None; + }); + if (configIndex < 0) + return; + + bool currentlyUsesSharedActionBars = EnumFlag(static_cast<TraitCombatConfigFlags>(*m_activePlayerData->TraitConfigs[configIndex].CombatConfigFlags)).HasFlag(TraitCombatConfigFlags::SharedActionBars); + if (currentlyUsesSharedActionBars == usesSharedActionBars) + return; + + if (usesSharedActionBars) + { + SetUpdateFieldFlagValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs, configIndex) + .ModifyValue(&UF::TraitConfig::CombatConfigFlags), AsUnderlyingType(TraitCombatConfigFlags::SharedActionBars)); + + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACTION_BY_TRAIT_CONFIG); + stmt->setUInt64(0, GetGUID().GetCounter()); + stmt->setInt32(1, traitConfigId); + CharacterDatabase.Execute(stmt); + } + + if (isLastSelectedSavedConfig) + StartLoadingActionButtons(); // load action buttons that were saved in shared mode + } + else + { + RemoveUpdateFieldFlagValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs, configIndex) + .ModifyValue(&UF::TraitConfig::CombatConfigFlags), AsUnderlyingType(TraitCombatConfigFlags::SharedActionBars)); + + // trigger a save with traitConfigId + for (auto&& [_, button] : m_actionButtons) + if (button.uState != ACTIONBUTTON_DELETED) + button.uState = ACTIONBUTTON_NEW; + } + + m_traitConfigStates[traitConfigId] = PLAYERSPELL_CHANGED; +} + void Player::SetReputation(uint32 factionentry, int32 value) { GetReputationMgr().SetReputation(sFactionStore.LookupEntry(factionentry), value); diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index ff4b3b1afa9..1c108fc4cdb 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -120,6 +120,12 @@ namespace WorldPackets { enum class UpdateCollisionHeightReason : uint8; } + + namespace Traits + { + struct TraitConfig; + struct TraitEntry; + } } TC_GAME_API uint32 GetBagSize(Bag const* bag); @@ -178,10 +184,11 @@ enum PlayerSpellState : uint8 struct PlayerSpell { - PlayerSpellState state : 8; + PlayerSpellState state; bool active : 1; // show in spellbook bool dependent : 1; // learned as result another spell learn, skill grow, quest reward, etc bool disabled : 1; // first rank has been learned in result talent learn but currently talent unlearned, save max learned ranks + Optional<int32> TraitDefinitionId; }; struct StoredAuraTeleportLocation @@ -859,7 +866,6 @@ enum PlayerLoginQueryIndex PLAYER_LOGIN_QUERY_LOAD_AZERITE_MILESTONE_POWERS, PLAYER_LOGIN_QUERY_LOAD_AZERITE_UNLOCKED_ESSENCES, PLAYER_LOGIN_QUERY_LOAD_AZERITE_EMPOWERED, - PLAYER_LOGIN_QUERY_LOAD_ACTIONS, PLAYER_LOGIN_QUERY_LOAD_MAILS, PLAYER_LOGIN_QUERY_LOAD_MAIL_ITEMS, PLAYER_LOGIN_QUERY_LOAD_MAIL_ITEMS_ARTIFACT, @@ -901,6 +907,8 @@ enum PlayerLoginQueryIndex PLAYER_LOGIN_QUERY_LOAD_GARRISON_BUILDINGS, PLAYER_LOGIN_QUERY_LOAD_GARRISON_FOLLOWERS, PLAYER_LOGIN_QUERY_LOAD_GARRISON_FOLLOWER_ABILITIES, + PLAYER_LOGIN_QUERY_LOAD_TRAIT_ENTRIES, + PLAYER_LOGIN_QUERY_LOAD_TRAIT_CONFIGS, MAX_PLAYER_LOGIN_QUERY }; @@ -1059,7 +1067,7 @@ struct GroupUpdateCounter int32 UpdateSequenceNumber; }; -enum TalentLearnResult +enum TalentLearnResult : int32 { TALENT_LEARN_OK = 0, TALENT_FAILED_UNKNOWN = 1, @@ -1793,8 +1801,8 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> void SendProficiency(ItemClass itemClass, uint32 itemSubclassMask) const; void SendKnownSpells(); void SendUnlearnSpells(); - bool AddSpell(uint32 spellId, bool active, bool learning, bool dependent, bool disabled, bool loading = false, int32 fromSkill = 0); - void LearnSpell(uint32 spell_id, bool dependent, int32 fromSkill = 0, bool suppressMessaging = false); + bool AddSpell(uint32 spellId, bool active, bool learning, bool dependent, bool disabled, bool loading = false, int32 fromSkill = 0, Optional<int32> traitDefinitionId = {}); + void LearnSpell(uint32 spell_id, bool dependent, int32 fromSkill = 0, bool suppressMessaging = false, Optional<int32> traitDefinitionId = {}); void RemoveSpell(uint32 spell_id, bool disabled = false, bool learn_low_rank = true, bool suppressMessaging = false); void ResetSpells(bool myClassOnly = false); void LearnCustomSpells(); @@ -1869,8 +1877,23 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> 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; } + void StartLoadingActionButtons(std::function<void()>&& callback = nullptr); void LoadActions(PreparedQueryResult result); + // Traits + void CreateTraitConfig(WorldPackets::Traits::TraitConfig& traitConfig); + void AddTraitConfig(WorldPackets::Traits::TraitConfig const& traitConfig); + UF::TraitConfig const* GetTraitConfig(int32 configId) const; + void UpdateTraitConfig(WorldPackets::Traits::TraitConfig&& newConfig, int32 savedConfigId, bool withCastTime); + void ApplyTraitEntryChanges(int32 editedConfigId, WorldPackets::Traits::TraitConfig const& newConfig, bool applyTraits, bool consumeCurrencies); + void RenameTraitConfig(int32 editedConfigId, std::string&& newName); + void DeleteTraitConfig(int32 deletedConfigId); + void ApplyTraitConfig(int32 configId, bool apply); + void ApplyTraitEntry(int32 traitNodeEntryId, int32 rank, int32 grantedRanks, bool apply); + void SetActiveCombatTraitConfigID(int32 traitConfigId) { SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ActiveCombatTraitConfigID), traitConfigId); } + void SetTraitConfigUseStarterBuild(int32 traitConfigId, bool useStarterBuild); + void SetTraitConfigUseSharedActionBars(int32 traitConfigId, bool usesSharedActionBars, bool isLastSelectedSavedConfig); + uint32 GetFreePrimaryProfessionPoints() const { return m_activePlayerData->CharacterPoints; } void SetFreePrimaryProfessions(uint16 profs) { SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::CharacterPoints), profs); } void InitPrimaryProfessions(); @@ -2908,6 +2931,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> void _LoadGlyphs(PreparedQueryResult result); void _LoadTalents(PreparedQueryResult result); void _LoadPvpTalents(PreparedQueryResult result); + void _LoadTraits(PreparedQueryResult configsResult, PreparedQueryResult entriesResult); void _LoadInstanceTimeRestrictions(PreparedQueryResult result); void _LoadPetStable(uint32 summonedPetNumber, PreparedQueryResult result); void _LoadCurrency(PreparedQueryResult result); @@ -2935,6 +2959,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> void _SaveBGData(CharacterDatabaseTransaction trans); void _SaveGlyphs(CharacterDatabaseTransaction trans) const; void _SaveTalents(CharacterDatabaseTransaction trans); + void _SaveTraits(CharacterDatabaseTransaction trans); void _SaveStats(CharacterDatabaseTransaction trans) const; void _SaveInstanceTimeRestrictions(CharacterDatabaseTransaction trans); void _SaveCurrency(CharacterDatabaseTransaction trans); @@ -3014,6 +3039,8 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> SpecializationInfo _specializationInfo; + std::unordered_map<int32, PlayerSpellState> m_traitConfigStates; + ActionButtonList m_actionButtons; float m_auraBaseFlatMod[BASEMOD_END]; |
