diff --git a/sql/characters.sql b/sql/characters.sql index cd14803d8a5..683c03d593e 100644 --- a/sql/characters.sql +++ b/sql/characters.sql @@ -21,7 +21,7 @@ DROP TABLE IF EXISTS `character_db_version`; CREATE TABLE `character_db_version` ( - `required_9646_01_characters_characters` bit(1) default NULL + `required_9661_01_characters_character_talent` bit(1) default NULL ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED COMMENT='Last applied sql update to DB'; -- @@ -830,6 +830,31 @@ LOCK TABLES `character_spell_cooldown` WRITE; /*!40000 ALTER TABLE `character_spell_cooldown` ENABLE KEYS */; UNLOCK TABLES; +-- +-- Table structure for table `character_talent` +-- + +DROP TABLE IF EXISTS `character_talent`; +CREATE TABLE `character_talent` ( + `guid` int(11) unsigned NOT NULL, + `talent_id` int(11) unsigned NOT NULL, + `current_rank` tinyint(3) unsigned NOT NULL DEFAULT '0', + `spec` tinyint(3) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`guid`,`talent_id`,`spec`), + KEY guid_key (`guid`), + KEY talent_key (`talent_id`), + KEY spec_key (`spec`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Dumping data for table `character_talent` +-- + +LOCK TABLES `character_talent` WRITE; +/*!40000 ALTER TABLE `character_talent` DISABLE KEYS */; +/*!40000 ALTER TABLE `character_talent` ENABLE KEYS */; +UNLOCK TABLES; + -- -- Table structure for table `character_ticket` -- diff --git a/sql/updates/9661_01_characters_character_talent.sql b/sql/updates/9661_01_characters_character_talent.sql new file mode 100644 index 00000000000..8772134e8ac --- /dev/null +++ b/sql/updates/9661_01_characters_character_talent.sql @@ -0,0 +1,13 @@ +ALTER TABLE character_db_version CHANGE COLUMN required_9646_01_characters_characters required_9661_01_characters_character_talent bit; + +DROP TABLE IF EXISTS `character_talent`; +CREATE TABLE `character_talent` ( + `guid` int(11) unsigned NOT NULL, + `talent_id` int(11) unsigned NOT NULL, + `current_rank` tinyint(3) unsigned NOT NULL DEFAULT '0', + `spec` tinyint(3) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`guid`,`talent_id`,`spec`), + KEY guid_key (`guid`), + KEY talent_key (`talent_id`), + KEY spec_key (`spec`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/sql/updates/Makefile.am b/sql/updates/Makefile.am index b399428b953..72e61032557 100644 --- a/sql/updates/Makefile.am +++ b/sql/updates/Makefile.am @@ -100,6 +100,7 @@ pkgdata_DATA = \ 9651_01_mangos_quest_poi.sql \ 9656_01_mangos_command.sql \ 9656_02_mangos_mangos_string.sql \ + 9661_01_characters_character_talent.sql \ README ## Additional files to include when running 'make dist' @@ -180,4 +181,5 @@ EXTRA_DIST = \ 9651_01_mangos_quest_poi.sql \ 9656_01_mangos_command.sql \ 9656_02_mangos_mangos_string.sql \ + 9661_01_characters_character_talent.sql \ README diff --git a/src/game/CharacterHandler.cpp b/src/game/CharacterHandler.cpp index 13e45f6b51e..c684452f146 100644 --- a/src/game/CharacterHandler.cpp +++ b/src/game/CharacterHandler.cpp @@ -95,6 +95,7 @@ bool LoginQueryHolder::Initialize() res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADEQUIPMENTSETS, "SELECT setguid, setindex, name, iconname, item0, item1, item2, item3, item4, item5, item6, item7, item8, item9, item10, item11, item12, item13, item14, item15, item16, item17, item18 FROM character_equipmentsets WHERE guid = '%u' ORDER BY setindex", GUID_LOPART(m_guid)); res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADBGDATA, "SELECT instance_id, team, join_x, join_y, join_z, join_o, join_map, taxi_start, taxi_end, mount_spell FROM character_battleground_data WHERE guid = '%u'", GUID_LOPART(m_guid)); res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADACCOUNTDATA, "SELECT type, time, data FROM character_account_data WHERE guid='%u'", GUID_LOPART(m_guid)); + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADTALENTS, "SELECT talent_id, current_rank, spec FROM character_talent WHERE guid = '%u'", GUID_LOPART(m_guid)); res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADSKILLS, "SELECT skill, value, max FROM character_skills WHERE guid = '%u'", GUID_LOPART(m_guid)); res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADGLYPHS, "SELECT spec, slot, glyph FROM character_glyphs WHERE guid='%u'", GUID_LOPART(m_guid)); res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADMAILS, "SELECT id,messageType,sender,receiver,subject,itemTextId,has_items,expire_time,deliver_time,money,cod,checked,stationery,mailTemplateId FROM mail WHERE receiver = '%u' ORDER BY id DESC", GUID_LOPART(m_guid)); diff --git a/src/game/Player.cpp b/src/game/Player.cpp index 841496f4cdb..5915123a13b 100644 --- a/src/game/Player.cpp +++ b/src/game/Player.cpp @@ -3095,6 +3095,28 @@ bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependen if (talentPos) { + // update talent map + PlayerTalentMap::iterator iter = m_talents[m_activeSpec].find(talentPos->talent_id); + if (iter != m_talents[m_activeSpec].end()) + { + // check if ranks different or removed + if ((*iter).second.state == PLAYERSPELL_REMOVED || talentPos->rank != (*iter).second.currentRank) + { + (*iter).second.currentRank = talentPos->rank; + + if ((*iter).second.state != PLAYERSPELL_NEW) + (*iter).second.state = PLAYERSPELL_CHANGED; + } + } + else + { + PlayerTalent talent; + talent.currentRank = talentPos->rank; + talent.m_talentEntry = sTalentStore.LookupEntry(talentPos->talent_id); + talent.state = IsInWorld() ? PLAYERSPELL_NEW : PLAYERSPELL_UNCHANGED; + m_talents[m_activeSpec][talentPos->talent_id] = talent; + } + // update used talent points count m_usedTalentCount += GetTalentSpellCost(talentPos); UpdateFreeTalentPoints(false); @@ -3299,6 +3321,18 @@ void Player::removeSpell(uint32 spell_id, bool disabled, bool learn_low_rank, bo TalentSpellPos const* talentPos = GetTalentSpellPos(spell_id); if (talentPos) { + // update talent map + PlayerTalentMap::iterator iter = m_talents[m_activeSpec].find(talentPos->talent_id); + if (iter != m_talents[m_activeSpec].end()) + { + if ((*iter).second.state != PLAYERSPELL_NEW) + (*iter).second.state = PLAYERSPELL_REMOVED; + else + m_talents[m_activeSpec].erase(iter); + } + else + sLog.outError("removeSpell: Player (GUID: %u) has talent spell (id: %u) but doesn't have talent",GetGUIDLow(), spell_id ); + // free talent points uint32 talentCosts = GetTalentSpellCost(talentPos); @@ -3641,26 +3675,43 @@ bool Player::resetTalents(bool no_cost) } } - for (unsigned int i = 0; i < sTalentStore.GetNumRows(); ++i) + for (PlayerTalentMap::iterator iter = m_talents[m_activeSpec].begin(); iter != m_talents[m_activeSpec].end();) { - TalentEntry const *talentInfo = sTalentStore.LookupEntry(i); + if (iter->second.state == PLAYERSPELL_REMOVED) + { + ++iter; + continue; + } - if (!talentInfo) continue; + TalentEntry const *talentInfo = (*iter).second.m_talentEntry; + if (!talentInfo) + { + iter = m_talents[m_activeSpec].erase(iter); + continue; + } TalentTabEntry const *talentTabInfo = sTalentTabStore.LookupEntry( talentInfo->TalentTab ); if (!talentTabInfo) + { + iter = m_talents[m_activeSpec].erase(iter); 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 ((getClassMask() & talentTabInfo->ClassMask) == 0) + { + ++iter; continue; + } for (int j = 0; j < MAX_TALENT_RANK; ++j) if (talentInfo->RankID[j]) removeSpell(talentInfo->RankID[j],!IsPassiveSpell(talentInfo->RankID[j]),false); + + iter = m_talents[m_activeSpec].begin(); } UpdateFreeTalentPoints(false); @@ -4091,6 +4142,7 @@ void Player::DeleteFromDB(uint64 playerguid, uint32 accountId, bool updateRealmC CharacterDatabase.PExecute("DELETE FROM character_skills WHERE guid = '%u'",guid); CharacterDatabase.PExecute("DELETE FROM character_spell WHERE guid = '%u'",guid); CharacterDatabase.PExecute("DELETE FROM character_spell_cooldown WHERE guid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_talent WHERE guid = '%u'",guid); CharacterDatabase.PExecute("DELETE FROM character_ticket WHERE guid = '%u'",guid); CharacterDatabase.PExecute("DELETE FROM item_instance WHERE owner_guid = '%u'",guid); CharacterDatabase.PExecute("DELETE FROM character_social WHERE guid = '%u' OR friend='%u'",guid,guid); @@ -15069,6 +15121,8 @@ bool Player::LoadFromDB( uint32 guid, SqlQueryHolder *holder ) _LoadQuestStatus(holder->GetResult(PLAYER_LOGIN_QUERY_LOADQUESTSTATUS)); _LoadDailyQuestStatus(holder->GetResult(PLAYER_LOGIN_QUERY_LOADDAILYQUESTSTATUS)); + _LoadTalents(holder->GetResult(PLAYER_LOGIN_QUERY_LOADTALENTS)); + // after spell and quest load InitTalentForLevel(); learnDefaultSpells(); @@ -15863,6 +15917,14 @@ void Player::_LoadSpells(QueryResult *result) uint32 spell_id = fields[0].GetUInt32(); + // skip talents & drop unneeded data + if(GetTalentSpellPos(spell_id)) + { + sLog.outError("Player::_LoadSpells: Player (GUID: %u) has talent spell in character_spell, removing it.", GetGUIDLow(), spell_id); + CharacterDatabase.PExecute("DELETE FROM character_spell WHERE spell = '%u'", spell_id); + continue; + } + addSpell(spell_id, fields[1].GetBool(), false, false, fields[2].GetBool()); } while( result->NextRow() ); @@ -15871,6 +15933,82 @@ void Player::_LoadSpells(QueryResult *result) } } +void Player::_LoadTalents(QueryResult *result) +{ + //QueryResult *result = CharacterDatabase.PQuery("SELECT talent_id, current_rank, spec FROM character_talent WHERE guid = '%u'",GetGUIDLow()); + if (result) + { + do + { + Field *fields = result->Fetch(); + + uint32 talent_id = fields[0].GetUInt32(); + TalentEntry const *talentInfo = sTalentStore.LookupEntry( talent_id ); + + if (!talentInfo) + { + sLog.outError("Player::_LoadTalents:Player (GUID: %u) has invalid talent_id: %u , this talent will be deleted from character_talent",GetGUIDLow(), talent_id ); + CharacterDatabase.PExecute("DELETE FROM character_talent WHERE talent_id = '%u'", talent_id); + continue; + } + + TalentTabEntry const *talentTabInfo = sTalentTabStore.LookupEntry( talentInfo->TalentTab ); + + if (!talentTabInfo) + { + sLog.outError("Player::_LoadTalents:Player (GUID: %u) has invalid talentTabInfo: %u for talentID: %u , this talent will be deleted from character_talent",GetGUIDLow(), talentInfo->TalentTab, talentInfo->TalentID ); + CharacterDatabase.PExecute("DELETE FROM character_talent WHERE talent_id = '%u'", talent_id); + continue; + } + + // prevent load talent for different class (cheating) + if ((getClassMask() & talentTabInfo->ClassMask) == 0) + { + sLog.outError("Player::_LoadTalents:Player (GUID: %u) has talent with ClassMask: %u , but Player's ClassMask is: %u , talentID: %u , this talent will be deleted from character_talent",GetGUIDLow(), talentTabInfo->ClassMask, getClassMask() ,talentInfo->TalentID ); + CharacterDatabase.PExecute("DELETE FROM character_talent WHERE guid = '%u' AND talent_id = '%u'", GetGUIDLow(), talent_id); + continue; + } + + uint32 currentRank = fields[1].GetUInt32(); + + if (currentRank > MAX_TALENT_RANK || talentInfo->RankID[currentRank] == 0) + { + sLog.outError("Player::_LoadTalents:Player (GUID: %u) has invalid talent rank: %u , talentID: %u , this talent will be deleted from character_talent",GetGUIDLow(), currentRank, talentInfo->TalentID ); + CharacterDatabase.PExecute("DELETE FROM character_talent WHERE guid = '%u' AND talent_id = '%u'", GetGUIDLow(), talent_id); + continue; + } + + uint32 spec = fields[2].GetUInt32(); + + if (spec > MAX_TALENT_SPEC_COUNT) + { + sLog.outError("Player::_LoadTalents:Player (GUID: %u) has invalid talent spec: %u, spec will be deleted from character_talent", GetGUIDLow(), spec); + CharacterDatabase.PExecute("DELETE FROM character_talent WHERE spec = '%u' ", spec); + continue; + } + + if (spec >= m_specsCount) + { + sLog.outError("Player::_LoadTalents:Player (GUID: %u) has invalid talent spec: %u , this spec will be deleted from character_talent.", GetGUIDLow(), spec); + CharacterDatabase.PExecute("DELETE FROM character_talent WHERE guid = '%u' AND spec = '%u' ", GetGUIDLow(), spec); + continue; + } + + if (m_activeSpec == spec) + addSpell(talentInfo->RankID[currentRank], true,false,false,false); + else + { + PlayerTalent talent; + talent.currentRank = currentRank; + talent.m_talentEntry = talentInfo; + talent.state = PLAYERSPELL_UNCHANGED; + m_talents[spec][talentInfo->TalentID] = talent; + } + } + while (result->NextRow()); + delete result; + } +} void Player::_LoadGroup(QueryResult *result) { //QueryResult *result = CharacterDatabase.PQuery("SELECT groupId FROM group_member WHERE memberGuid='%u'", GetGUIDLow()); @@ -16362,6 +16500,7 @@ void Player::SaveToDB() _SaveEquipmentSets(); GetSession()->SaveTutorialsData(); // changed only while character in game _SaveGlyphs(); + _SaveTalents(); CharacterDatabase.CommitTransaction(); @@ -16708,12 +16847,17 @@ void Player::_SaveSpells() { for (PlayerSpellMap::iterator itr = m_spells.begin(), next = m_spells.begin(); itr != m_spells.end();) { - if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.state == PLAYERSPELL_CHANGED) - CharacterDatabase.PExecute("DELETE FROM character_spell WHERE guid = '%u' and spell = '%u'", GetGUIDLow(), itr->first); + uint32 talentCosts = GetTalentSpellCost(itr->first); - // add only changed/new not dependent spells - if (!itr->second.dependent && (itr->second.state == PLAYERSPELL_NEW || itr->second.state == PLAYERSPELL_CHANGED)) - CharacterDatabase.PExecute("INSERT INTO character_spell (guid,spell,active,disabled) VALUES ('%u', '%u', '%u', '%u')", GetGUIDLow(), itr->first, itr->second.active ? 1 : 0,itr->second.disabled ? 1 : 0); + if (!talentCosts) + { + if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.state == PLAYERSPELL_CHANGED) + CharacterDatabase.PExecute("DELETE FROM character_spell WHERE guid = '%u' and spell = '%u'", GetGUIDLow(), itr->first); + + // add only changed/new not dependent spells + if (!itr->second.dependent && (itr->second.state == PLAYERSPELL_NEW || itr->second.state == PLAYERSPELL_CHANGED)) + CharacterDatabase.PExecute("INSERT INTO character_spell (guid,spell,active,disabled) VALUES ('%u', '%u', '%u', '%u')", GetGUIDLow(), itr->first, itr->second.active ? 1 : 0,itr->second.disabled ? 1 : 0); + } if (itr->second.state == PLAYERSPELL_REMOVED) m_spells.erase(itr++); @@ -16726,6 +16870,30 @@ void Player::_SaveSpells() } } +void Player::_SaveTalents() +{ + for (int32 i = 0; i < MAX_TALENT_SPEC_COUNT; ++i) + { + for (PlayerTalentMap::iterator itr = m_talents[i].begin(); itr != m_talents[i].end();) + { + if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.state == PLAYERSPELL_CHANGED) + CharacterDatabase.PExecute("DELETE FROM character_talent WHERE guid = '%u' and talent_id = '%u' and spec = '%u'", GetGUIDLow(),itr->first, i); + + // add only changed/new talents + if (itr->second.state == PLAYERSPELL_NEW || itr->second.state == PLAYERSPELL_CHANGED) + CharacterDatabase.PExecute("INSERT INTO character_talent (guid, talent_id, current_rank , spec) VALUES ('%u', '%u', '%u', '%u')", GetGUIDLow(), itr->first, itr->second.currentRank, i); + + if (itr->second.state == PLAYERSPELL_REMOVED) + m_talents[i].erase(itr++); + else + { + itr->second.state = PLAYERSPELL_UNCHANGED; + ++itr; + } + } + } +} + void Player::outDebugValues() const { if(!sLog.IsOutDebug()) // optimize disabled debug output @@ -20798,14 +20966,9 @@ void Player::LearnTalent(uint32 talentId, uint32 talentRank) // find current max talent rank uint32 curtalent_maxrank = 0; - for(int32 k = MAX_TALENT_RANK-1; k > -1; --k) - { - if(talentInfo->RankID[k] && HasSpell(talentInfo->RankID[k])) - { - curtalent_maxrank = k + 1; - break; - } - } + PlayerTalentMap::iterator itr = m_talents[m_activeSpec].find(talentId); + if (itr != m_talents[m_activeSpec].end() && itr->second.state != PLAYERSPELL_REMOVED) + curtalent_maxrank = itr->second.currentRank + 1; // we already have same or higher talent rank learned if(curtalent_maxrank >= (talentRank + 1)) @@ -20821,11 +20984,12 @@ void Player::LearnTalent(uint32 talentId, uint32 talentRank) if(TalentEntry const *depTalentInfo = sTalentStore.LookupEntry(talentInfo->DependsOn)) { bool hasEnoughRank = false; - for (int i = talentInfo->DependsOnRank; i < MAX_TALENT_RANK; ++i) + PlayerTalentMap::iterator dependsOnTalent = m_talents[m_activeSpec].find(depTalentInfo->TalentID); + if (dependsOnTalent != m_talents[m_activeSpec].end() && dependsOnTalent->second.state != PLAYERSPELL_REMOVED) { - if (depTalentInfo->RankID[i] != 0) - if (HasSpell(depTalentInfo->RankID[i])) - hasEnoughRank = true; + PlayerTalent depTalent = (*dependsOnTalent).second; + if (depTalent.currentRank >= talentInfo->DependsOnRank) + hasEnoughRank = true; } if (!hasEnoughRank) @@ -20839,28 +21003,9 @@ void Player::LearnTalent(uint32 talentId, uint32 talentRank) uint32 tTab = talentInfo->TalentTab; if (talentInfo->Row > 0) { - unsigned int numRows = sTalentStore.GetNumRows(); - for (unsigned int i = 0; i < numRows; ++i) // Loop through all talents. - { - // Someday, someone needs to revamp - const TalentEntry *tmpTalent = sTalentStore.LookupEntry(i); - if (tmpTalent) // the way talents are tracked - { - if (tmpTalent->TalentTab == tTab) - { - for (int j = 0; j < MAX_TALENT_RANK; ++j) - { - if (tmpTalent->RankID[j] != 0) - { - if (HasSpell(tmpTalent->RankID[j])) - { - spentPoints += j + 1; - } - } - } - } - } - } + for (PlayerTalentMap::const_iterator iter = m_talents[m_activeSpec].begin(); iter != m_talents[m_activeSpec].end(); ++iter) + if (iter->second.state != PLAYERSPELL_REMOVED && iter->second.m_talentEntry->TalentTab == tTab) + spentPoints += iter->second.currentRank + 1; } // not have required min points spent in talent tree @@ -21101,34 +21246,19 @@ void Player::BuildPlayerTalentsInfoData(WorldPacket *data) for(uint32 i = 0; i < 3; ++i) { uint32 talentTabId = talentTabIds[i]; - - for(uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId) + for(PlayerTalentMap::iterator iter = m_talents[specIdx].begin(); iter != m_talents[specIdx].end(); ++iter) { - TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId); - if(!talentInfo) + PlayerTalent talent = (*iter).second; + + if (talent.state == PLAYERSPELL_REMOVED) continue; // skip another tab talents - if(talentInfo->TalentTab != talentTabId) - continue; - - // find max talent rank - int32 curtalent_maxrank = -1; - for(int32 k = MAX_TALENT_RANK-1; k > -1; --k) - { - if(talentInfo->RankID[k] && HasSpell(talentInfo->RankID[k])) - { - curtalent_maxrank = k; - break; - } - } - - // not learned talent - if(curtalent_maxrank < 0) + if(talent.m_talentEntry->TalentTab != talentTabId) continue; - *data << uint32(talentInfo->TalentID); // Talent.dbc - *data << uint8(curtalent_maxrank); // talentMaxRank (0-4) + *data << uint32(talent.m_talentEntry->TalentID); // Talent.dbc + *data << uint8(talent.currentRank); // talentMaxRank (0-4) ++talentIdCount; } @@ -21403,13 +21533,75 @@ void Player::ActivateSpec(uint8 specNum) if(specNum >= GetSpecsCount()) return; - // unlearn GetActiveSpec() talents (not learned in specNum); - // learn specNum talents + UnsummonPetTemporaryIfAny(); ApplyGlyphs(false); + // copy of new talent spec (we will use it as model for converting current tlanet state to new) + PlayerTalentMap tempSpec = m_talents[specNum]; + + // copy old spec talents to new one, must be before spec switch to have previous spec num(as m_activeSpec) + m_talents[specNum] = m_talents[m_activeSpec]; + SetActiveSpec(specNum); + // remove all talent spells that don't exist in next spec but exist in old + for (PlayerTalentMap::iterator specIter = m_talents[m_activeSpec].begin(); specIter != m_talents[m_activeSpec].end();) + { + PlayerTalent& talent = (*specIter).second; + + if (talent.state == PLAYERSPELL_REMOVED) + { + ++specIter; + continue; + } + + PlayerTalentMap::iterator iterTempSpec = tempSpec.find(specIter->first); + + // remove any talent rank if talent not listed in temp spec + if (iterTempSpec == tempSpec.end() || iterTempSpec->second.state == PLAYERSPELL_REMOVED) + { + for(int r = 0; r < MAX_TALENT_RANK; ++r) + if (talent.m_talentEntry->RankID[r]) + removeSpell(talent.m_talentEntry->RankID[r],!IsPassiveSpell(talent.m_talentEntry->RankID[r]),false); + + specIter = m_talents[m_activeSpec].begin(); + } + else + ++specIter; + } + + // now new spec data have only talents (maybe different rank) as in temp spec data, sync ranks then. + for (PlayerTalentMap::const_iterator tempIter = tempSpec.begin(); tempIter != tempSpec.end(); ++tempIter) + { + PlayerTalent const& talent = (*tempIter).second; + + // removed state talent already unlearned in prev. loop + // but we need restore it if it deleted for finish removed-marked data in DB + if (talent.state == PLAYERSPELL_REMOVED) + { + m_talents[m_activeSpec][tempIter->first] = talent; + continue; + } + + // learn talent spells if they not in new spec (old spec copy) + // and if they have different rank + PlayerTalentMap::iterator specIter = m_talents[m_activeSpec].find(tempIter->first); + if (specIter != m_talents[m_activeSpec].end() && specIter->second.state != PLAYERSPELL_REMOVED) + { + if ((*specIter).second.currentRank != talent.currentRank) + learnSpell(talent.m_talentEntry->RankID[talent.currentRank], false); + } + else + learnSpell(talent.m_talentEntry->RankID[talent.currentRank], false); + + // sync states - original state is changed in addSpell that learnSpell calls + specIter = m_talents[m_activeSpec].find(tempIter->first); + (*specIter).second.state = talent.state; + } + + InitTalentForLevel(); + // recheck action buttons (not checked at loading/spec copy) ActionButtonList const& currentActionButtonList = m_actionButtons[m_activeSpec]; for(ActionButtonList::const_iterator itr = currentActionButtonList.begin(); itr != currentActionButtonList.end(); ++itr) @@ -21418,11 +21610,17 @@ void Player::ActivateSpec(uint8 specNum) if (!IsActionButtonDataValid(itr->first,itr->second.GetAction(),itr->second.GetType(), this, false)) removeActionButton(m_activeSpec,itr->first); + ResummonPetTemporaryUnSummonedIfAny(); + ApplyGlyphs(true); SendInitialActionButtons(); - InitTalentForLevel(); + Powers pw = getPowerType(); + if(pw != POWER_MANA) + SetPower(POWER_MANA, 0); + + SetPower(pw, 0); } void Player::UpdateSpecCount(uint8 count) diff --git a/src/game/Player.h b/src/game/Player.h index 18c93ac23b5..127273f4d6c 100644 --- a/src/game/Player.h +++ b/src/game/Player.h @@ -96,7 +96,15 @@ struct PlayerSpell bool disabled : 1; // first rank has been learned in result talent learn but currently talent unlearned, save max learned ranks }; +struct PlayerTalent +{ + PlayerSpellState state; + TalentEntry const *m_talentEntry; + uint32 currentRank; +}; + typedef UNORDERED_MAP PlayerSpellMap; +typedef UNORDERED_MAP PlayerTalentMap; // Spell modifier (used for modify other spells) struct SpellModifier @@ -893,7 +901,8 @@ enum PlayerLoginQueryIndex PLAYER_LOGIN_QUERY_LOADGLYPHS = 22, PLAYER_LOGIN_QUERY_LOADMAILS = 23, PLAYER_LOGIN_QUERY_LOADMAILEDITEMS = 24, - MAX_PLAYER_LOGIN_QUERY = 25 + PLAYER_LOGIN_QUERY_LOADTALENTS = 25, + MAX_PLAYER_LOGIN_QUERY = 26 }; enum PlayerDelayedOperations @@ -2314,6 +2323,7 @@ class MANGOS_DLL_SPEC Player : public Unit void _LoadGroup(QueryResult *result); void _LoadSkills(QueryResult *result); void _LoadSpells(QueryResult *result); + void _LoadTalents(QueryResult *result); void _LoadFriendList(QueryResult *result); bool _LoadHomeBind(QueryResult *result); void _LoadDeclinedNames(QueryResult *result); @@ -2338,6 +2348,7 @@ class MANGOS_DLL_SPEC Player : public Unit void _SaveEquipmentSets(); void _SaveBGData(); void _SaveGlyphs(); + void _SaveTalents(); void _SetCreateBits(UpdateMask *updateMask, Player *target) const; void _SetUpdateBits(UpdateMask *updateMask, Player *target) const; @@ -2389,6 +2400,7 @@ class MANGOS_DLL_SPEC Player : public Unit PlayerMails m_mail; PlayerSpellMap m_spells; + PlayerTalentMap m_talents[MAX_TALENT_SPEC_COUNT]; SpellCooldowns m_spellCooldowns; uint32 m_lastPotionId; // last used health/mana potion in combat, that block next potion use diff --git a/src/shared/revision_nr.h b/src/shared/revision_nr.h index 67546f67c63..e7a9e4d8dd5 100644 --- a/src/shared/revision_nr.h +++ b/src/shared/revision_nr.h @@ -1,4 +1,4 @@ #ifndef __REVISION_NR_H__ #define __REVISION_NR_H__ - #define REVISION_NR "9660" + #define REVISION_NR "9661" #endif // __REVISION_NR_H__ diff --git a/src/shared/revision_sql.h b/src/shared/revision_sql.h index daac74de647..47bfd616812 100644 --- a/src/shared/revision_sql.h +++ b/src/shared/revision_sql.h @@ -1,6 +1,6 @@ #ifndef __REVISION_SQL_H__ #define __REVISION_SQL_H__ - #define REVISION_DB_CHARACTERS "required_9646_01_characters_characters" + #define REVISION_DB_CHARACTERS "required_9661_01_characters_character_talent" #define REVISION_DB_MANGOS "required_9656_02_mangos_mangos_string" #define REVISION_DB_REALMD "required_9010_01_realmd_realmlist" #endif // __REVISION_SQL_H__