mirror of
https://github.com/mangosfour/server.git
synced 2025-12-14 07:37:01 +00:00
[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:
parent
ce40dedaf0
commit
7fb7c47de5
8 changed files with 322 additions and 71 deletions
|
|
@ -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`
|
||||||
--
|
--
|
||||||
|
|
|
||||||
13
sql/updates/9661_01_characters_character_talent.sql
Normal file
13
sql/updates/9661_01_characters_character_talent.sql
Normal 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;
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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__
|
||||||
|
|
|
||||||
|
|
@ -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__
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue