[9661] Implement dual talent specializations in talent switch part.

* Implement talent specs switch functionality.
* Only gossip part still not implemented.
* At update server to this commit or later character talents will be reset
  with some spam in logs about wrong places talents in character_spell.
  It can be ignored as part of conversion to new table support.

Thanks to all getmangos.com community members who take part
in creating and updating original dual spec patch.

Signed-off-by: VladimirMangos <vladimir@getmangos.com>
This commit is contained in:
Laise 2010-04-02 23:34:20 +04:00 committed by VladimirMangos
parent ce40dedaf0
commit 7fb7c47de5
8 changed files with 322 additions and 71 deletions

View file

@ -21,7 +21,7 @@
DROP TABLE IF EXISTS `character_db_version`; DROP TABLE IF EXISTS `character_db_version`;
CREATE TABLE `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'; ) 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 */; /*!40000 ALTER TABLE `character_spell_cooldown` ENABLE KEYS */;
UNLOCK TABLES; 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` -- Table structure for table `character_ticket`
-- --

View file

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

View file

@ -100,6 +100,7 @@ pkgdata_DATA = \
9651_01_mangos_quest_poi.sql \ 9651_01_mangos_quest_poi.sql \
9656_01_mangos_command.sql \ 9656_01_mangos_command.sql \
9656_02_mangos_mangos_string.sql \ 9656_02_mangos_mangos_string.sql \
9661_01_characters_character_talent.sql \
README README
## Additional files to include when running 'make dist' ## Additional files to include when running 'make dist'
@ -180,4 +181,5 @@ EXTRA_DIST = \
9651_01_mangos_quest_poi.sql \ 9651_01_mangos_quest_poi.sql \
9656_01_mangos_command.sql \ 9656_01_mangos_command.sql \
9656_02_mangos_mangos_string.sql \ 9656_02_mangos_mangos_string.sql \
9661_01_characters_character_talent.sql \
README README

View file

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

View file

@ -3095,6 +3095,28 @@ bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependen
if (talentPos) 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 // update used talent points count
m_usedTalentCount += GetTalentSpellCost(talentPos); m_usedTalentCount += GetTalentSpellCost(talentPos);
UpdateFreeTalentPoints(false); 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); TalentSpellPos const* talentPos = GetTalentSpellPos(spell_id);
if (talentPos) 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 // free talent points
uint32 talentCosts = GetTalentSpellCost(talentPos); 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 ); TalentTabEntry const *talentTabInfo = sTalentTabStore.LookupEntry( talentInfo->TalentTab );
if (!talentTabInfo) if (!talentTabInfo)
{
iter = m_talents[m_activeSpec].erase(iter);
continue; continue;
}
// unlearn only talents for character class // 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, // 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 // to prevent unexpected lost normal learned spell skip another class talents
if ((getClassMask() & talentTabInfo->ClassMask) == 0) if ((getClassMask() & talentTabInfo->ClassMask) == 0)
{
++iter;
continue; continue;
}
for (int j = 0; j < MAX_TALENT_RANK; ++j) for (int j = 0; j < MAX_TALENT_RANK; ++j)
if (talentInfo->RankID[j]) if (talentInfo->RankID[j])
removeSpell(talentInfo->RankID[j],!IsPassiveSpell(talentInfo->RankID[j]),false); removeSpell(talentInfo->RankID[j],!IsPassiveSpell(talentInfo->RankID[j]),false);
iter = m_talents[m_activeSpec].begin();
} }
UpdateFreeTalentPoints(false); 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_skills WHERE guid = '%u'",guid);
CharacterDatabase.PExecute("DELETE FROM character_spell 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_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 character_ticket WHERE guid = '%u'",guid);
CharacterDatabase.PExecute("DELETE FROM item_instance WHERE owner_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); 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)); _LoadQuestStatus(holder->GetResult(PLAYER_LOGIN_QUERY_LOADQUESTSTATUS));
_LoadDailyQuestStatus(holder->GetResult(PLAYER_LOGIN_QUERY_LOADDAILYQUESTSTATUS)); _LoadDailyQuestStatus(holder->GetResult(PLAYER_LOGIN_QUERY_LOADDAILYQUESTSTATUS));
_LoadTalents(holder->GetResult(PLAYER_LOGIN_QUERY_LOADTALENTS));
// after spell and quest load // after spell and quest load
InitTalentForLevel(); InitTalentForLevel();
learnDefaultSpells(); learnDefaultSpells();
@ -15863,6 +15917,14 @@ void Player::_LoadSpells(QueryResult *result)
uint32 spell_id = fields[0].GetUInt32(); 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()); addSpell(spell_id, fields[1].GetBool(), false, false, fields[2].GetBool());
} }
while( result->NextRow() ); 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) void Player::_LoadGroup(QueryResult *result)
{ {
//QueryResult *result = CharacterDatabase.PQuery("SELECT groupId FROM group_member WHERE memberGuid='%u'", GetGUIDLow()); //QueryResult *result = CharacterDatabase.PQuery("SELECT groupId FROM group_member WHERE memberGuid='%u'", GetGUIDLow());
@ -16362,6 +16500,7 @@ void Player::SaveToDB()
_SaveEquipmentSets(); _SaveEquipmentSets();
GetSession()->SaveTutorialsData(); // changed only while character in game GetSession()->SaveTutorialsData(); // changed only while character in game
_SaveGlyphs(); _SaveGlyphs();
_SaveTalents();
CharacterDatabase.CommitTransaction(); CharacterDatabase.CommitTransaction();
@ -16707,6 +16846,10 @@ void Player::_SaveSkills()
void Player::_SaveSpells() void Player::_SaveSpells()
{ {
for (PlayerSpellMap::iterator itr = m_spells.begin(), next = m_spells.begin(); itr != m_spells.end();) for (PlayerSpellMap::iterator itr = m_spells.begin(), next = m_spells.begin(); itr != m_spells.end();)
{
uint32 talentCosts = GetTalentSpellCost(itr->first);
if (!talentCosts)
{ {
if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.state == PLAYERSPELL_CHANGED) 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); CharacterDatabase.PExecute("DELETE FROM character_spell WHERE guid = '%u' and spell = '%u'", GetGUIDLow(), itr->first);
@ -16714,6 +16857,7 @@ void Player::_SaveSpells()
// add only changed/new not dependent spells // add only changed/new not dependent spells
if (!itr->second.dependent && (itr->second.state == PLAYERSPELL_NEW || itr->second.state == PLAYERSPELL_CHANGED)) 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); 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) if (itr->second.state == PLAYERSPELL_REMOVED)
m_spells.erase(itr++); 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 void Player::outDebugValues() const
{ {
if(!sLog.IsOutDebug()) // optimize disabled debug output if(!sLog.IsOutDebug()) // optimize disabled debug output
@ -20798,14 +20966,9 @@ void Player::LearnTalent(uint32 talentId, uint32 talentRank)
// find current max talent rank // find current max talent rank
uint32 curtalent_maxrank = 0; uint32 curtalent_maxrank = 0;
for(int32 k = MAX_TALENT_RANK-1; k > -1; --k) PlayerTalentMap::iterator itr = m_talents[m_activeSpec].find(talentId);
{ if (itr != m_talents[m_activeSpec].end() && itr->second.state != PLAYERSPELL_REMOVED)
if(talentInfo->RankID[k] && HasSpell(talentInfo->RankID[k])) curtalent_maxrank = itr->second.currentRank + 1;
{
curtalent_maxrank = k + 1;
break;
}
}
// we already have same or higher talent rank learned // we already have same or higher talent rank learned
if(curtalent_maxrank >= (talentRank + 1)) if(curtalent_maxrank >= (talentRank + 1))
@ -20821,10 +20984,11 @@ void Player::LearnTalent(uint32 talentId, uint32 talentRank)
if(TalentEntry const *depTalentInfo = sTalentStore.LookupEntry(talentInfo->DependsOn)) if(TalentEntry const *depTalentInfo = sTalentStore.LookupEntry(talentInfo->DependsOn))
{ {
bool hasEnoughRank = false; 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) PlayerTalent depTalent = (*dependsOnTalent).second;
if (HasSpell(depTalentInfo->RankID[i])) if (depTalent.currentRank >= talentInfo->DependsOnRank)
hasEnoughRank = true; hasEnoughRank = true;
} }
@ -20839,28 +21003,9 @@ void Player::LearnTalent(uint32 talentId, uint32 talentRank)
uint32 tTab = talentInfo->TalentTab; uint32 tTab = talentInfo->TalentTab;
if (talentInfo->Row > 0) if (talentInfo->Row > 0)
{ {
unsigned int numRows = sTalentStore.GetNumRows(); for (PlayerTalentMap::const_iterator iter = m_talents[m_activeSpec].begin(); iter != m_talents[m_activeSpec].end(); ++iter)
for (unsigned int i = 0; i < numRows; ++i) // Loop through all talents. if (iter->second.state != PLAYERSPELL_REMOVED && iter->second.m_talentEntry->TalentTab == tTab)
{ spentPoints += iter->second.currentRank + 1;
// 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;
}
}
}
}
}
}
} }
// not have required min points spent in talent tree // 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) for(uint32 i = 0; i < 3; ++i)
{ {
uint32 talentTabId = talentTabIds[i]; uint32 talentTabId = talentTabIds[i];
for(PlayerTalentMap::iterator iter = m_talents[specIdx].begin(); iter != m_talents[specIdx].end(); ++iter)
for(uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId)
{ {
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId); PlayerTalent talent = (*iter).second;
if(!talentInfo)
if (talent.state == PLAYERSPELL_REMOVED)
continue; continue;
// skip another tab talents // skip another tab talents
if(talentInfo->TalentTab != talentTabId) if(talent.m_talentEntry->TalentTab != talentTabId)
continue; continue;
// find max talent rank *data << uint32(talent.m_talentEntry->TalentID); // Talent.dbc
int32 curtalent_maxrank = -1; *data << uint8(talent.currentRank); // talentMaxRank (0-4)
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)
continue;
*data << uint32(talentInfo->TalentID); // Talent.dbc
*data << uint8(curtalent_maxrank); // talentMaxRank (0-4)
++talentIdCount; ++talentIdCount;
} }
@ -21403,13 +21533,75 @@ void Player::ActivateSpec(uint8 specNum)
if(specNum >= GetSpecsCount()) if(specNum >= GetSpecsCount())
return; return;
// unlearn GetActiveSpec() talents (not learned in specNum); UnsummonPetTemporaryIfAny();
// learn specNum talents
ApplyGlyphs(false); 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); 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) // recheck action buttons (not checked at loading/spec copy)
ActionButtonList const& currentActionButtonList = m_actionButtons[m_activeSpec]; ActionButtonList const& currentActionButtonList = m_actionButtons[m_activeSpec];
for(ActionButtonList::const_iterator itr = currentActionButtonList.begin(); itr != currentActionButtonList.end(); ++itr) 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)) if (!IsActionButtonDataValid(itr->first,itr->second.GetAction(),itr->second.GetType(), this, false))
removeActionButton(m_activeSpec,itr->first); removeActionButton(m_activeSpec,itr->first);
ResummonPetTemporaryUnSummonedIfAny();
ApplyGlyphs(true); ApplyGlyphs(true);
SendInitialActionButtons(); SendInitialActionButtons();
InitTalentForLevel(); Powers pw = getPowerType();
if(pw != POWER_MANA)
SetPower(POWER_MANA, 0);
SetPower(pw, 0);
} }
void Player::UpdateSpecCount(uint8 count) void Player::UpdateSpecCount(uint8 count)

View file

@ -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 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<uint32, PlayerSpell> PlayerSpellMap; typedef UNORDERED_MAP<uint32, PlayerSpell> PlayerSpellMap;
typedef UNORDERED_MAP<uint32, PlayerTalent> PlayerTalentMap;
// Spell modifier (used for modify other spells) // Spell modifier (used for modify other spells)
struct SpellModifier struct SpellModifier
@ -893,7 +901,8 @@ enum PlayerLoginQueryIndex
PLAYER_LOGIN_QUERY_LOADGLYPHS = 22, PLAYER_LOGIN_QUERY_LOADGLYPHS = 22,
PLAYER_LOGIN_QUERY_LOADMAILS = 23, PLAYER_LOGIN_QUERY_LOADMAILS = 23,
PLAYER_LOGIN_QUERY_LOADMAILEDITEMS = 24, PLAYER_LOGIN_QUERY_LOADMAILEDITEMS = 24,
MAX_PLAYER_LOGIN_QUERY = 25 PLAYER_LOGIN_QUERY_LOADTALENTS = 25,
MAX_PLAYER_LOGIN_QUERY = 26
}; };
enum PlayerDelayedOperations enum PlayerDelayedOperations
@ -2314,6 +2323,7 @@ class MANGOS_DLL_SPEC Player : public Unit
void _LoadGroup(QueryResult *result); void _LoadGroup(QueryResult *result);
void _LoadSkills(QueryResult *result); void _LoadSkills(QueryResult *result);
void _LoadSpells(QueryResult *result); void _LoadSpells(QueryResult *result);
void _LoadTalents(QueryResult *result);
void _LoadFriendList(QueryResult *result); void _LoadFriendList(QueryResult *result);
bool _LoadHomeBind(QueryResult *result); bool _LoadHomeBind(QueryResult *result);
void _LoadDeclinedNames(QueryResult *result); void _LoadDeclinedNames(QueryResult *result);
@ -2338,6 +2348,7 @@ class MANGOS_DLL_SPEC Player : public Unit
void _SaveEquipmentSets(); void _SaveEquipmentSets();
void _SaveBGData(); void _SaveBGData();
void _SaveGlyphs(); void _SaveGlyphs();
void _SaveTalents();
void _SetCreateBits(UpdateMask *updateMask, Player *target) const; void _SetCreateBits(UpdateMask *updateMask, Player *target) const;
void _SetUpdateBits(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; PlayerMails m_mail;
PlayerSpellMap m_spells; PlayerSpellMap m_spells;
PlayerTalentMap m_talents[MAX_TALENT_SPEC_COUNT];
SpellCooldowns m_spellCooldowns; SpellCooldowns m_spellCooldowns;
uint32 m_lastPotionId; // last used health/mana potion in combat, that block next potion use uint32 m_lastPotionId; // last used health/mana potion in combat, that block next potion use

View file

@ -1,4 +1,4 @@
#ifndef __REVISION_NR_H__ #ifndef __REVISION_NR_H__
#define __REVISION_NR_H__ #define __REVISION_NR_H__
#define REVISION_NR "9660" #define REVISION_NR "9661"
#endif // __REVISION_NR_H__ #endif // __REVISION_NR_H__

View file

@ -1,6 +1,6 @@
#ifndef __REVISION_SQL_H__ #ifndef __REVISION_SQL_H__
#define __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_MANGOS "required_9656_02_mangos_mangos_string"
#define REVISION_DB_REALMD "required_9010_01_realmd_realmlist" #define REVISION_DB_REALMD "required_9010_01_realmd_realmlist"
#endif // __REVISION_SQL_H__ #endif // __REVISION_SQL_H__