*Add Dual Spec support, original by EnderGT, thanks to Thyros and XTElite1 for helping me test :)

--HG--
branch : trunk
This commit is contained in:
maximius
2009-08-14 17:24:52 -07:00
parent c7aa2f64e3
commit 2fd68eab44
12 changed files with 532 additions and 42 deletions
+348 -25
View File
@@ -424,9 +424,17 @@ Player::Player (WorldSession *session): Unit(), m_achievementMgr(this), m_reputa
m_lastPotionId = 0;
m_activeSpec = 0;
m_specsCount = 1;
m_specsCount = 0;
for (uint8 i = 0; i < BASEMOD_END; ++i)
for (uint8 i = 0; i < MAX_TALENT_SPECS; ++i)
{
for (int g = 0; g < MAX_GLYPH_SLOT_INDEX; ++g)
m_Glyphs[i][g] = 0;
m_talents[i] = new PlayerTalentMap();
}
for (int i = 0; i < BASEMOD_END; ++i)
{
m_auraBaseMod[i][FLAT_MOD] = 0.0f;
m_auraBaseMod[i][PCT_MOD] = 1.0f;
@@ -492,6 +500,13 @@ Player::~Player ()
for (PlayerSpellMap::const_iterator itr = m_spells.begin(); itr != m_spells.end(); ++itr)
delete itr->second;
for (uint8 i = 0; i < MAX_TALENT_SPECS; ++i)
{
for (PlayerTalentMap::const_iterator itr = m_talents[i]->begin(); itr != m_talents[i]->end(); ++itr)
delete itr->second;
delete m_talents[i];
}
//all mailed items should be deleted, also all mail should be deallocated
for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end();++itr)
delete *itr;
@@ -2532,6 +2547,12 @@ void Player::InitTalentForLevel()
}
else
{
if (level < 40 || m_specsCount == 0)
{
m_specsCount = 1;
m_activeSpec = 0;
}
uint32 talentPointsForLevel = CalculateTalentsPoints();
// if used more that have then reset
@@ -2847,6 +2868,73 @@ void Player::AddNewMailDeliverTime(time_t deliver_time)
}
}
bool Player::AddTalent(uint32 spell_id, uint8 spec, bool learning)
{
SpellEntry const *spellInfo = sSpellStore.LookupEntry(spell_id);
if (!spellInfo)
{
// do character spell book cleanup (all characters)
if(!IsInWorld() && !learning) // spell load case
{
sLog.outError("Player::addSpell: Non-existed in SpellStore spell #%u request, deleting for all characters in `character_spell`.",spell_id);
CharacterDatabase.PExecute("DELETE FROM character_talent WHERE spell = '%u'",spell_id);
}
else
sLog.outError("Player::addSpell: Non-existed in SpellStore spell #%u request.",spell_id);
return false;
}
if(!SpellMgr::IsSpellValid(spellInfo,this,false))
{
// do character spell book cleanup (all characters)
if(!IsInWorld() && !learning) // spell load case
{
sLog.outError("Player::addTalent: Broken spell #%u learning not allowed, deleting for all characters in `character_talent`.",spell_id);
CharacterDatabase.PExecute("DELETE FROM character_talent WHERE spell = '%u'",spell_id);
}
else
sLog.outError("Player::addTalent: Broken spell #%u learning not allowed.",spell_id);
return false;
}
PlayerTalentMap::iterator itr = m_talents[spec]->find(spell_id);
if (itr != m_talents[spec]->end())
{
itr->second->state = PLAYERSPELL_UNCHANGED;
}
else if(TalentSpellPos const* talentPos = GetTalentSpellPos(spell_id))
{
if(TalentEntry const *talentInfo = sTalentStore.LookupEntry( talentPos->talent_id ))
{
for(int i=0; i < MAX_TALENT_RANK; ++i)
{
// skip learning spell and no rank spell case
uint32 rankSpellId = talentInfo->RankID[i];
if(!rankSpellId || rankSpellId==spell_id)
continue;
PlayerTalentMap::iterator itr = m_talents[spec]->find(rankSpellId);
if (itr != m_talents[spec]->end())
{
itr->second->state = PLAYERSPELL_REMOVED;
}
}
}
PlayerSpellState state = learning ? PLAYERSPELL_NEW : PLAYERSPELL_UNCHANGED;
PlayerTalent *newtalent = new PlayerTalent();
newtalent->state = state;
newtalent->spec = spec;
(*m_talents[spec])[spell_id] = newtalent;
return true;
}
return false;
}
bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependent, bool disabled)
{
SpellEntry const *spellInfo = sSpellStore.LookupEntry(spell_id);
@@ -3681,7 +3769,7 @@ bool Player::resetTalents(bool no_cost)
if( (getClassMask() & talentTabInfo->ClassMask) == 0 )
continue;
for (int j = 0; j < MAX_TALENT_RANK; j++)
/* for (int j = 0; j < MAX_TALENT_RANK; j++)
{
for(PlayerSpellMap::iterator itr = GetSpellMap().begin(); itr != GetSpellMap().end();)
{
@@ -3710,6 +3798,12 @@ bool Player::resetTalents(bool no_cost)
else
++itr;
}
} */
PlayerTalentMap::iterator itr2 = m_talents[m_activeSpec]->begin();
for (; itr2 != m_talents[m_activeSpec]->end(); ++itr2)
{
removeSpell(itr2->first, !IsPassiveSpell(itr2->first),false);
itr2->second->state = PLAYERSPELL_REMOVED;
}
}
@@ -3945,6 +4039,12 @@ bool Player::HasSpell(uint32 spell) const
!itr->second->disabled);
}
bool Player::HasTalent(uint32 spell, uint8 spec) const
{
PlayerTalentMap::const_iterator itr = m_talents[spec]->find(spell);
return (itr != m_talents[spec]->end() && itr->second->state != PLAYERSPELL_REMOVED);
}
bool Player::HasActiveSpell(uint32 spell) const
{
PlayerSpellMap::const_iterator itr = m_spells.find(spell);
@@ -5671,12 +5771,12 @@ int16 Player::GetSkillTempBonusValue(uint32 skill) const
return 0;
}
void Player::SendInitialActionButtons() const
void Player::SendActionButtons(uint32 spec) const
{
sLog.outDetail( "Initializing Action Buttons for '%u'", GetGUIDLow() );
sLog.outDetail( "Sending Action Buttons for '%u' spec '%u'", GetGUIDLow(), spec );
WorldPacket data(SMSG_ACTION_BUTTONS, 1+(MAX_ACTION_BUTTONS*4));
data << uint8(0); // can be 0, 1, 2 (talent spec)
data << uint8(spec); // can be 0, 1, 2 (talent spec)
for(int button = 0; button < MAX_ACTION_BUTTONS; ++button)
{
ActionButtonList::const_iterator itr = m_actionButtons.find(button);
@@ -5687,7 +5787,7 @@ void Player::SendInitialActionButtons() const
}
GetSession()->SendPacket( &data );
sLog.outDetail( "Action Buttons for '%u' Initialized", GetGUIDLow() );
sLog.outDetail( "Action Buttons for '%u' spec '%u' Sent", GetGUIDLow(), spec );
}
ActionButton* Player::addActionButton(uint8 button, uint32 action, uint8 type)
@@ -14521,8 +14621,8 @@ float Player::GetFloatValueFromDB(uint16 index, uint64 guid)
bool Player::LoadFromDB( uint32 guid, SqlQueryHolder *holder )
{
//// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
//QueryResult *result = CharacterDatabase.PQuery("SELECT guid, account, data, name, race, class, gender, level, xp, money, playerBytes, playerBytes2, playerFlags, position_x, position_y, position_z, map, orientation, taximask, cinematic, totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost, resettalents_time, trans_x, trans_y, trans_z, trans_o, transguid, extra_flags, stable_slots, at_login, zone, online, death_expire_time, taxi_path, dungeon_difficulty, arena_pending_points FROM characters WHERE guid = '%u'", guid);
//// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
//QueryResult *result = CharacterDatabase.PQuery("SELECT guid, account, data, name, race, class, gender, level, xp, money, playerBytes, playerBytes2, playerFlags, position_x, position_y, position_z, map, orientation, taximask, cinematic, totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost, resettalents_time, trans_x, trans_y, trans_z, trans_o, transguid, extra_flags, stable_slots, at_login, zone, online, death_expire_time, taxi_path, dungeon_difficulty, arena_pending_points, instance_id, speccount, activespec FROM characters WHERE guid = '%u'", guid);
QueryResult *result = holder->GetResult(PLAYER_LOGIN_QUERY_LOADFROM);
if(!result)
@@ -14544,6 +14644,13 @@ bool Player::LoadFromDB( uint32 guid, SqlQueryHolder *holder )
return false;
}
m_specsCount = fields[42].GetUInt32();
m_activeSpec = fields[43].GetUInt32();
// sanity check
if (m_specsCount < 2) // Maybe better to check MAX_TALENT_SPECS?
m_activeSpec = 0;
Object::_Create( guid, 0, HIGHGUID_PLAYER );
m_name = fields[3].GetCppString();
@@ -14964,8 +15071,10 @@ bool Player::LoadFromDB( uint32 guid, SqlQueryHolder *holder )
//mails are loaded only when needed ;-) - when player in game click on mailbox.
//_LoadMail();
_LoadTalents(holder->GetResult(PLAYER_LOGIN_QUERY_LOADTALENTS));
_LoadSpells(holder->GetResult(PLAYER_LOGIN_QUERY_LOADSPELLS));
_LoadGlyphs(holder->GetResult(PLAYER_LOGIN_QUERY_LOADGLYPHS));
_LoadAuras(holder->GetResult(PLAYER_LOGIN_QUERY_LOADAURAS), time_diff);
_LoadGlyphAuras();
// add ghost flag (must be after aura load: PLAYER_FLAGS_GHOST set in aura)
@@ -16124,7 +16233,7 @@ void Player::SaveToDB()
"taximask, online, cinematic, "
"totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost, resettalents_time, "
"trans_x, trans_y, trans_z, trans_o, transguid, extra_flags, stable_slots, at_login, zone, "
"death_expire_time, taxi_path, arena_pending_points, latency) VALUES ("
"death_expire_time, taxi_path, arena_pending_points, latency, speccount, activespec) VALUES ("
<< GetGUIDLow() << ", "
<< GetSession()->GetAccountId() << ", '"
<< sql_name << "', "
@@ -16207,6 +16316,10 @@ void Player::SaveToDB()
ss << m_taxi.SaveTaxiDestinationsToString() << "', ";
ss << "'0', "; // arena_pending_points
ss << GetSession()->GetLatency();
ss << ", ";
ss << uint32(m_specsCount);
ss << ", ";
ss << uint32(m_activeSpec);
ss << ")";
CharacterDatabase.Execute( ss.str().c_str() );
@@ -16218,6 +16331,7 @@ void Player::SaveToDB()
_SaveInventory();
_SaveQuestStatus();
_SaveDailyQuestStatus();
_SaveTalents();
_SaveSpells();
_SaveSpellCooldowns();
_SaveActions();
@@ -16226,6 +16340,7 @@ void Player::SaveToDB()
m_reputationMgr.SaveToDB();
_SaveEquipmentSets();
GetSession()->SaveTutorialsData(); // changed only while character in game
_SaveGlyphs();
CharacterDatabase.CommitTransaction();
@@ -16253,14 +16368,14 @@ void Player::_SaveActions()
switch (itr->second.uState)
{
case ACTIONBUTTON_NEW:
CharacterDatabase.PExecute("INSERT INTO character_action (guid,button,action,type) VALUES ('%u', '%u', '%u', '%u')",
GetGUIDLow(), (uint32)itr->first, (uint32)itr->second.GetAction(), (uint32)itr->second.GetType() );
CharacterDatabase.PExecute("INSERT INTO character_action (guid,spec,button,action,type) VALUES ('%u', '%u', '%u', '%u', '%u')",
GetGUIDLow(), (uint32)m_activeSpec, (uint32)itr->first, (uint32)itr->second.GetAction(), (uint32)itr->second.GetType() );
itr->second.uState = ACTIONBUTTON_UNCHANGED;
++itr;
break;
case ACTIONBUTTON_CHANGED:
CharacterDatabase.PExecute("UPDATE character_action SET action = '%u', type = '%u' WHERE guid= '%u' AND button= '%u' ",
(uint32)itr->second.GetAction(), (uint32)itr->second.GetType(), GetGUIDLow(), (uint32)itr->first );
CharacterDatabase.PExecute("UPDATE character_action SET action = '%u', type = '%u' WHERE guid = '%u' AND button = '%u' AND spec = '%u'",
(uint32)itr->second.GetAction(), (uint32)itr->second.GetType(), GetGUIDLow(), (uint32)itr->first, (uint32)m_activeSpec);
itr->second.uState = ACTIONBUTTON_UNCHANGED;
++itr;
break;
@@ -20944,7 +21059,9 @@ void Player::LearnTalent(uint32 talentId, uint32 talentRank)
// learn! (other talent ranks will unlearned at learning)
learnSpell(spellid, false);
sLog.outDetail("TalentID: %u Rank: %u Spell: %u\n", talentId, talentRank, spellid);
AddTalent(spellid, m_activeSpec, true);
sLog.outDetail("TalentID: %u Rank: %u Spell: %u Spec: %u\n", talentId, talentRank, spellid, m_activeSpec);
// update free talent points
SetFreeTalentPoints(CurTalentPoints - (talentRank - curtalent_maxrank + 1));
@@ -21188,7 +21305,7 @@ void Player::BuildPlayerTalentsInfoData(WorldPacket *data)
int32 curtalent_maxrank = -1;
for(int32 k = MAX_TALENT_RANK-1; k > -1; --k)
{
if(talentInfo->RankID[k] && HasSpell(talentInfo->RankID[k]))
if(talentInfo->RankID[k] && HasTalent(talentInfo->RankID[k], specIdx))
{
curtalent_maxrank = k;
break;
@@ -21211,7 +21328,7 @@ void Player::BuildPlayerTalentsInfoData(WorldPacket *data)
*data << uint8(MAX_GLYPH_SLOT_INDEX); // glyphs count
for(uint8 i = 0; i < MAX_GLYPH_SLOT_INDEX; ++i)
*data << uint16(GetGlyph(i)); // GlyphProperties.dbc
*data << uint16(m_Glyphs[specIdx][i]); // GlyphProperties.dbc
}
}
}
@@ -21466,14 +21583,6 @@ void Player::DeleteEquipmentSet(uint64 setGuid)
}
}
void Player::ActivateSpec(uint32 specNum)
{
if(GetActiveSpec() == specNum)
return;
resetTalents(true);
}
void Player::RemoveAtLoginFlag( AtLoginFlags f, bool in_db_also /*= false*/ )
{
m_atLoginFlags &= ~f;
@@ -21507,3 +21616,217 @@ void Player::SetMap(Map * map)
Unit::SetMap(map);
m_mapRef.link(map, this);
}
void Player::_LoadGlyphs(QueryResult *result)
{
// SetPQuery(PLAYER_LOGIN_QUERY_LOADGLYPHS, "SELECT spec, glyph1, glyph2, glyph3, glyph4, glyph5, glyph6 from character_glyphs WHERE guid = '%u'", GUID_LOPART(m_guid));
if (!result)
return;
do
{
Field *fields = result->Fetch();
uint8 spec = fields[0].GetUInt8();
if (spec >= m_specsCount)
continue;
m_Glyphs[spec][0] = fields[1].GetUInt32();
m_Glyphs[spec][1] = fields[2].GetUInt32();
m_Glyphs[spec][2] = fields[3].GetUInt32();
m_Glyphs[spec][3] = fields[4].GetUInt32();
m_Glyphs[spec][4] = fields[5].GetUInt32();
m_Glyphs[spec][5] = fields[6].GetUInt32();
} while (result->NextRow());
delete result;
}
void Player::_SaveGlyphs()
{
CharacterDatabase.PExecute("DELETE FROM character_glyphs WHERE guid='%u'",GetGUIDLow());
for (uint8 spec = 0; spec < m_specsCount; ++spec)
{
CharacterDatabase.PExecute("INSERT INTO character_glyphs VALUES('%u', '%u', '%u', '%u', '%u', '%u', '%u', '%u')",
GetGUIDLow(), spec, m_Glyphs[spec][0], m_Glyphs[spec][1], m_Glyphs[spec][2], m_Glyphs[spec][3], m_Glyphs[spec][4], m_Glyphs[spec][5]);
}
}
void Player::_LoadTalents(QueryResult *result)
{
// SetPQuery(PLAYER_LOGIN_QUERY_LOADTALENTS, "SELECT spell, spec FROM character_talent WHERE guid = '%u'", GUID_LOPART(m_guid));
if (result)
{
do
{
Field *fields = result->Fetch();
AddTalent(fields[0].GetUInt32(), fields[1].GetUInt32(), false);
}
while( result->NextRow() );
delete result;
}
}
void Player::_SaveTalents()
{
for (uint8 i = 0; i < MAX_TALENT_SPECS; ++i)
{
for (PlayerTalentMap::iterator itr = m_talents[i]->begin(), next = 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 spell = '%u' and spec = '%u'", GetGUIDLow(), itr->first, itr->second->spec);
if (itr->second->state == PLAYERSPELL_NEW || itr->second->state == PLAYERSPELL_CHANGED)
CharacterDatabase.PExecute("INSERT INTO character_talent (guid,spell,spec) VALUES ('%u', '%u', '%u')", GetGUIDLow(), itr->first, itr->second->spec);
if (itr->second->state == PLAYERSPELL_REMOVED)
{
delete itr->second;
m_talents[i]->erase(itr++);
}
else
{
itr->second->state = PLAYERSPELL_UNCHANGED;
++itr;
}
}
}
}
void Player::UpdateSpecCount(uint8 count)
{
if(GetSpecsCount() == count)
return;
if(count == 1)
{
_SaveActions(); // make sure the button list is cleaned up
// active spec becomes only spec?
CharacterDatabase.PExecute("DELETE FROM character_action WHERE spec<>'%u' AND guid='%u'",m_activeSpec, GetGUIDLow());
CharacterDatabase.PExecute("UPDATE character_action SET spec='%u' WHERE guid='%u'", m_activeSpec, GetGUIDLow());
}
else if (count == 2)
{
_SaveActions(); // make sure the button list is cleaned up
for(ActionButtonList::iterator itr = m_actionButtons.begin(); itr != m_actionButtons.end(); ++itr)
{
CharacterDatabase.PExecute("INSERT INTO character_action (guid,button,action,type,spec) VALUES ('%u', '%u', '%u', '%u', '%u')",
GetGUIDLow(), (uint32)itr->first, (uint32)itr->second.GetAction(), (uint32)itr->second.GetType(), count );
}
}
else
{
return;
}
SetSpecsCount(count);
SendTalentsInfoData(false);
}
void Player::ActivateSpec(uint8 spec)
{
if(GetActiveSpec() == spec)
return;
if(GetSpecsCount() != MAX_TALENT_SPECS)
return;
uint32 const* talentTabIds = GetTalentTabPages(getClass());
for(uint8 i = 0; i < 3; ++i)
{
uint32 talentTabId = talentTabIds[i];
for(uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId)
{
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
if(!talentInfo)
continue;
// skip another tab talents
if(talentInfo->TalentTab != talentTabId)
continue;
// find max talent rank
int32 curtalent_maxrank = -1;
for(int32 k = 4; k > -1; --k)
{
if(talentInfo->RankID[k] && HasTalent(talentInfo->RankID[k], m_activeSpec))
{
removeSpell(talentInfo->RankID[k],true);
}
}
}
}
// set glyphs
for (uint8 slot = 0; slot < MAX_GLYPH_SLOT_INDEX; ++slot)
{
// remove secondary glyph
if(uint32 oldglyph = m_Glyphs[m_activeSpec][slot])
{
if(GlyphPropertiesEntry const *old_gp = sGlyphPropertiesStore.LookupEntry(oldglyph))
{
RemoveAurasDueToSpell(old_gp->SpellId);
}
}
}
SetActiveSpec(spec);
for(uint8 i = 0; i < 3; ++i)
{
uint32 talentTabId = talentTabIds[i];
for(uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId)
{
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
if(!talentInfo)
continue;
// skip another tab talents
if(talentInfo->TalentTab != talentTabId)
continue;
// find max talent rank
int32 curtalent_maxrank = -1;
for(int32 k = 4; k > -1; --k)
{
if(talentInfo->RankID[k] && HasTalent(talentInfo->RankID[k], m_activeSpec))
{
learnSpell(talentInfo->RankID[k], false);
}
}
}
}
// set glyphs
for (uint8 slot = 0; slot < MAX_GLYPH_SLOT_INDEX; ++slot)
{
uint32 glyph = m_Glyphs[m_activeSpec][slot];
// apply primary glyph
if (glyph)
{
if (GlyphPropertiesEntry const *gp = sGlyphPropertiesStore.LookupEntry(glyph))
{
CastSpell(this, gp->SpellId, true);
}
}
SetGlyph(slot, glyph);
}
InitTalentForLevel();
QueryResult *result = CharacterDatabase.PQuery("SELECT button,action,type FROM character_action WHERE guid = '%u' AND spec = '%u' ORDER BY button", GetGUIDLow(), m_activeSpec);
if (result)
{
_LoadActions(result);
}
UnsummonPetTemporaryIfAny();
SendActionButtons(m_activeSpec);
SetPower(getPowerType(), 0);
}