Advertisement
Guest User

mail_extended

a guest
Jan 29th, 2010
173
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 78.36 KB | None | 0 0
  1. diff --git a/sql/patches/ext_mail.sql b/sql/patches/ext_mail.sql
  2. new file mode 100644
  3. index 0000000..779aa02
  4. --- /dev/null
  5. +++ b/sql/patches/ext_mail.sql
  6. @@ -0,0 +1,18 @@
  7. +DROP TABLE IF EXISTS `mail_external`;
  8. +CREATE TABLE `mail_external` (
  9. + `id` bigint(20) unsigned NOT NULL auto_increment,
  10. + `receiver` bigint(20) unsigned NOT NULL,
  11. + `subject` varchar(200) default 'Support Message',
  12. + `message` varchar(500) default 'Support Message',
  13. + `money` bigint(20) unsigned NOT NULL default '0',
  14. + PRIMARY KEY (`id`)
  15. +) ENGINE=MyISAM DEFAULT CHARSET=utf8;
  16. +
  17. +DROP TABLE IF EXISTS `mail_external_items`;
  18. +CREATE TABLE `mail_external_items` (
  19. + `id` int(10) unsigned NOT NULL auto_increment,
  20. + `mail_id` int(10) unsigned NOT NULL,
  21. + `item` int(11) NOT NULL,
  22. + `count` int(11) NOT NULL,
  23. + PRIMARY KEY (`id`)
  24. +) ENGINE=MyISAM DEFAULT CHARSET=latin1;
  25. diff --git a/src/game/CharacterHandler.cpp b/src/game/CharacterHandler.cpp
  26. index d3b54a4..0f041e8 100644
  27. --- a/src/game/CharacterHandler.cpp
  28. +++ b/src/game/CharacterHandler.cpp
  29. @@ -72,7 +72,7 @@ bool LoginQueryHolder::Initialize()
  30. "position_x, position_y, position_z, map, orientation, taximask, cinematic, totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost,"
  31. "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,"
  32. "arenaPoints, totalHonorPoints, todayHonorPoints, yesterdayHonorPoints, totalKills, todayKills, yesterdayKills, chosenTitle, knownCurrencies, watchedFaction, drunk,"
  33. - "health, power1, power2, power3, power4, power5, power6, power7 FROM characters WHERE guid = '%u'", GUID_LOPART(m_guid));
  34. + "health, power1, power2, power3, power4, power5, power6, power7, speccount, activespec FROM characters WHERE guid = '%u'", GUID_LOPART(m_guid));
  35. res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADGROUP, "SELECT leaderGuid FROM group_member WHERE memberGuid ='%u'", GUID_LOPART(m_guid));
  36. res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADBOUNDINSTANCES, "SELECT id, permanent, map, difficulty, resettime FROM character_instance LEFT JOIN instance ON instance = id WHERE guid = '%u'", GUID_LOPART(m_guid));
  37. res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADAURAS, "SELECT caster_guid,spell,effect_index,stackcount,amount,maxduration,remaintime,remaincharges FROM character_aura WHERE guid = '%u'", GUID_LOPART(m_guid));
  38. @@ -81,7 +81,7 @@ bool LoginQueryHolder::Initialize()
  39. res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADDAILYQUESTSTATUS,"SELECT quest,time FROM character_queststatus_daily WHERE guid = '%u'", GUID_LOPART(m_guid));
  40. res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADREPUTATION, "SELECT faction,standing,flags FROM character_reputation WHERE guid = '%u'", GUID_LOPART(m_guid));
  41. res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADINVENTORY, "SELECT data,bag,slot,item,item_template FROM character_inventory JOIN item_instance ON character_inventory.item = item_instance.guid WHERE character_inventory.guid = '%u' ORDER BY bag,slot", GUID_LOPART(m_guid));
  42. - res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADACTIONS, "SELECT button,action,type FROM character_action WHERE guid = '%u' ORDER BY button", GUID_LOPART(m_guid));
  43. + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADACTIONS, "SELECT a.button,a.action,a.type FROM character_action as a, characters as c WHERE a.guid = c.guid AND a.spec = c.activespec AND a.guid = '%u' ORDER BY button", GUID_LOPART(m_guid));
  44. res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADMAILCOUNT, "SELECT COUNT(id) FROM mail WHERE receiver = '%u' AND (checked & 1)=0 AND deliver_time <= '" UI64FMTD "'", GUID_LOPART(m_guid), (uint64)time(NULL));
  45. res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADMAILDATE, "SELECT MIN(deliver_time) FROM mail WHERE receiver = '%u' AND (checked & 1)=0", GUID_LOPART(m_guid));
  46. res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADSOCIALLIST, "SELECT friend,flags,note FROM character_social WHERE guid = '%u' LIMIT 255", GUID_LOPART(m_guid));
  47. @@ -96,6 +96,8 @@ bool LoginQueryHolder::Initialize()
  48. res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADCRITERIAPROGRESS,"SELECT criteria, counter, date FROM character_achievement_progress WHERE guid = '%u'", GUID_LOPART(m_guid));
  49. 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));
  50. 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));
  51. + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADGLYPHS, "SELECT spec, glyph1, glyph2, glyph3, glyph4, glyph5, glyph6 FROM character_glyphs WHERE guid='%u'", GUID_LOPART(m_guid));
  52. + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADTALENTS, "SELECT spell, spec FROM character_talent WHERE guid='%u'", GUID_LOPART(m_guid));
  53. res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADACCOUNTDATA, "SELECT type, time, data FROM character_account_data WHERE guid='%u'", GUID_LOPART(m_guid));
  54. res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADSKILLS, "SELECT skill, value, max FROM character_skills WHERE guid = '%u'", GUID_LOPART(m_guid));
  55.  
  56. diff --git a/src/game/GossipDef.h b/src/game/GossipDef.h
  57. index 68744db..65cc47c 100644
  58. --- a/src/game/GossipDef.h
  59. +++ b/src/game/GossipDef.h
  60. @@ -48,6 +48,7 @@ enum Gossip_Option
  61. GOSSIP_OPTION_ARMORER = 15, //UNIT_NPC_FLAG_ARMORER (4096)
  62. GOSSIP_OPTION_UNLEARNTALENTS = 16, //UNIT_NPC_FLAG_TRAINER (16) (bonus option for GOSSIP_OPTION_TRAINER)
  63. GOSSIP_OPTION_UNLEARNPETSKILLS = 17, //UNIT_NPC_FLAG_TRAINER (16) (bonus option for GOSSIP_OPTION_TRAINER)
  64. + GOSSIP_OPTION_LEARNDUALSPEC = 19, //UNIT_NPC_FLAG_TRAINER (bonus option for GOSSIP_OPTION_TRAINER)
  65. GOSSIP_OPTION_MAX
  66. };
  67.  
  68. diff --git a/src/game/Level3.cpp b/src/game/Level3.cpp
  69. index f319cf0..d64ef55 100644
  70. --- a/src/game/Level3.cpp
  71. +++ b/src/game/Level3.cpp
  72. @@ -4449,10 +4449,10 @@ bool ChatHandler::HandleResetSpellsCommand(const char * args)
  73.  
  74. if(target)
  75. {
  76. - target->resetSpells();
  77. + target->resetSpells(/* bool myClassOnly */);
  78.  
  79. ChatHandler(target).SendSysMessage(LANG_RESET_SPELLS);
  80. - if(!m_session || m_session->GetPlayer()!=target)
  81. + if(!m_session || m_session->GetPlayer() != target)
  82. PSendSysMessage(LANG_RESET_SPELLS_ONLINE,GetNameLink(target).c_str());
  83. }
  84. else
  85. diff --git a/src/game/Mail.cpp b/src/game/Mail.cpp
  86. index 0509240..14c7a33 100644
  87. --- a/src/game/Mail.cpp
  88. +++ b/src/game/Mail.cpp
  89. @@ -984,3 +984,86 @@ void MailDraft::SendMailTo(MailReceiver const& receiver, MailSender const& sende
  90. else if (!m_items.empty())
  91. deleteIncludedItems();
  92. }
  93. +
  94. +void WorldSession::SendExternalMails()
  95. +{
  96. + sLog.outString("EXTERNAL MAIL> Sending mails in queue...");
  97. + QueryResult *result = CharacterDatabase.Query("SELECT e.id, e.receiver, e.subject, e.message, e.money, i.item, i.count FROM mail_external e LEFT JOIN mail_external_items i ON e.id = i.mail_id ORDER BY e.id;");
  98. + if(!result)
  99. + {
  100. + sLog.outString("EXTERNAL MAIL> No mails in queue...");
  101. + delete result;
  102. + return;
  103. + }
  104. + else
  105. + {
  106. + uint32 last_id = 0;
  107. + MailDraft* mail = NULL;
  108. + uint32 last_receiver_guid;
  109. +
  110. + do
  111. + {
  112. + Field *fields = result->Fetch();
  113. + uint32 id = fields[0].GetUInt32();
  114. + uint64 receiver_guid = fields[1].GetUInt64();
  115. + std::string subject = fields[2].GetString();
  116. + std::string message = fields[3].GetString();
  117. + uint32 money = fields[4].GetUInt32();
  118. + uint32 itemId = fields[5].GetUInt32();
  119. + uint32 itemCount = fields[6].GetUInt32();
  120. +
  121. + Player *receiver = sObjectMgr.GetPlayer( receiver_guid );
  122. +
  123. + if (id != last_id)
  124. + {
  125. + // send mail
  126. + if (last_id != 0)
  127. + {
  128. + sLog.outString("EXTERNAL MAIL> Sending mail to character with guid %d", last_receiver_guid);
  129. + mail->SendMailTo( MailReceiver(last_receiver_guid), MailSender(MAIL_NORMAL, 0, MAIL_STATIONERY_GM), MAIL_CHECK_MASK_RETURNED);
  130. + delete mail;
  131. + CharacterDatabase.PExecute("DELETE mail_external AS e, mail_external_items AS i FROM mail_external AS e, mail_external_items AS i WHERE i.mail_id = e.id AND e.id = %u;", last_id);
  132. + sLog.outString("EXTERNAL MAIL> Mail sent");
  133. + }
  134. +
  135. + // create new mail
  136. + uint32 itemTextId = !message.empty() ? sObjectMgr.CreateItemText( message ) : 0;
  137. + mail = new MailDraft( subject, itemTextId );
  138. +
  139. + if(money)
  140. + {
  141. + sLog.outString("EXTERNAL MAIL> Adding money");
  142. + mail->AddMoney(money);
  143. + }
  144. + }
  145. +
  146. + if (itemId)
  147. + {
  148. + sLog.outString("EXTERNAL MAIL> Adding %u of item with id %u", itemCount, itemId);
  149. + Item* mailItem = Item::CreateItem( itemId, itemCount, receiver );
  150. + mailItem->SaveToDB();
  151. + mail->AddItem(mailItem);
  152. + }
  153. +
  154. + last_id = id;
  155. + last_receiver_guid = receiver_guid;
  156. +
  157. + }
  158. + while( result->NextRow() );
  159. +
  160. + // we only send a mail when mail_id!=last_mail_id, so we need to send the very last mail here:
  161. + if (last_id != 0)
  162. + {
  163. + // send last mail
  164. + sLog.outString("EXTERNAL MAIL> Sending mail to character with guid %d", last_receiver_guid);
  165. +
  166. + mail->SendMailTo( MailReceiver(last_receiver_guid), MailSender(MAIL_NORMAL, 0, MAIL_STATIONERY_GM), MAIL_CHECK_MASK_RETURNED);
  167. + delete mail;
  168. + CharacterDatabase.PExecute("DELETE mail_external AS e, mail_external_items AS i FROM mail_external AS e, mail_external_items AS i WHERE i.mail_id = e.id AND e.id = %u;", last_id);
  169. + sLog.outString("EXTERNAL MAIL> Mail sent");
  170. + }
  171. + }
  172. +
  173. + delete result;
  174. + sLog.outString("EXTERNAL MAIL> All Mails Sent...");
  175. +}
  176. diff --git a/src/game/Player.cpp b/src/game/Player.cpp
  177. index 71276f5..945c1b5 100644
  178. --- a/src/game/Player.cpp
  179. +++ b/src/game/Player.cpp
  180. @@ -464,6 +464,14 @@ Player::Player (WorldSession *session): Unit(), m_achievementMgr(this), m_reputa
  181. m_activeSpec = 0;
  182. m_specsCount = 1;
  183.  
  184. + for (uint8 i = 0; i < MAX_TALENT_SPECS; ++i)
  185. + {
  186. + for (int g = 0; g < MAX_GLYPH_SLOT_INDEX; ++g)
  187. + m_Glyphs[i][g] = 0;
  188. +
  189. + m_talents[i] = new PlayerTalentMap();
  190. + }
  191. +
  192. for (int i = 0; i < BASEMOD_END; ++i)
  193. {
  194. m_auraBaseMod[i][FLAT_MOD] = 0.0f;
  195. @@ -518,6 +526,13 @@ Player::~Player ()
  196. for (PlayerSpellMap::const_iterator itr = m_spells.begin(); itr != m_spells.end(); ++itr)
  197. delete itr->second;
  198.  
  199. + for (uint8 i = 0; i < MAX_TALENT_SPECS; ++i)
  200. + {
  201. + for (PlayerTalentMap::const_iterator itr = m_talents[i]->begin(); itr != m_talents[i]->end(); ++itr)
  202. + delete itr->second;
  203. + delete m_talents[i];
  204. + }
  205. +
  206. //all mailed items should be deleted, also all mail should be deallocated
  207. for (PlayerMails::const_iterator itr = m_mail.begin(); itr != m_mail.end();++itr)
  208. delete *itr;
  209. @@ -2522,6 +2537,12 @@ void Player::InitTalentForLevel()
  210. }
  211. else
  212. {
  213. + if (level < sWorld.getConfig(CONFIG_MIN_DUALSPEC_LEVEL) || m_specsCount == 0)
  214. + {
  215. + m_specsCount = 1;
  216. + m_activeSpec = 0;
  217. + }
  218. +
  219. uint32 talentPointsForLevel = CalculateTalentsPoints();
  220.  
  221. // if used more that have then reset
  222. @@ -2840,7 +2861,7 @@ void Player::AddNewMailDeliverTime(time_t deliver_time)
  223. }
  224. }
  225.  
  226. -bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependent, bool disabled)
  227. +bool Player::AddTalent(uint32 spell_id, uint8 spec, bool learning)
  228. {
  229. SpellEntry const *spellInfo = sSpellStore.LookupEntry(spell_id);
  230. if (!spellInfo)
  231. @@ -2848,6 +2869,69 @@ bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependen
  232. // do character spell book cleanup (all characters)
  233. if(!IsInWorld() && !learning) // spell load case
  234. {
  235. + sLog.outError("Player::AddTalent: Non-existed in SpellStore spell #%u request, deleting for all characters in `character_spell`.",spell_id);
  236. + CharacterDatabase.PExecute("DELETE FROM character_talent WHERE spell = '%u'",spell_id);
  237. + }
  238. + else
  239. + sLog.outError("Player::AddTalent: Non-existed in SpellStore spell #%u request.",spell_id);
  240. +
  241. + return false;
  242. + }
  243. +
  244. + if(!SpellMgr::IsSpellValid(spellInfo,this,false))
  245. + {
  246. + // do character spell book cleanup (all characters)
  247. + if(!IsInWorld() && !learning) // spell load case
  248. + {
  249. + sLog.outError("Player::addTalent: Broken spell #%u learning not allowed, deleting for all characters in `character_talent`.",spell_id);
  250. + CharacterDatabase.PExecute("DELETE FROM character_talent WHERE spell = '%u'",spell_id);
  251. + }
  252. + else
  253. + sLog.outError("Player::addTalent: Broken spell #%u learning not allowed.",spell_id);
  254. +
  255. + return false;
  256. + }
  257. +
  258. + PlayerTalentMap::iterator itr = m_talents[spec]->find(spell_id);
  259. + if (itr != m_talents[spec]->end())
  260. + itr->second->state = PLAYERSPELL_UNCHANGED;
  261. + else if (TalentSpellPos const *talentPos = GetTalentSpellPos(spell_id))
  262. + {
  263. + if (TalentEntry const *talentInfo = sTalentStore.LookupEntry( talentPos->talent_id ))
  264. + {
  265. + for(uint8 rank = 0; rank < MAX_TALENT_RANK; ++rank)
  266. + {
  267. + // skip learning spell and no rank spell case
  268. + uint32 rankSpellId = talentInfo->RankID[rank];
  269. + if(!rankSpellId || rankSpellId == spell_id)
  270. + continue;
  271. +
  272. + PlayerTalentMap::iterator itr = m_talents[spec]->find(rankSpellId);
  273. + if (itr != m_talents[spec]->end())
  274. + itr->second->state = PLAYERSPELL_REMOVED;
  275. + }
  276. + }
  277. +
  278. + PlayerSpellState state = learning ? PLAYERSPELL_NEW : PLAYERSPELL_UNCHANGED;
  279. + PlayerTalent *newtalent = new PlayerTalent();
  280. +
  281. + newtalent->state = state;
  282. + newtalent->spec = spec;
  283. +
  284. + (*m_talents[spec])[spell_id] = newtalent;
  285. + return true;
  286. + }
  287. + return false;
  288. +}
  289. +
  290. +bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependent, bool disabled)
  291. +{
  292. + SpellEntry const *spellInfo = sSpellStore.LookupEntry(spell_id);
  293. + if (!spellInfo)
  294. + {
  295. + // do character spell book cleanup (all characters)
  296. + if (!IsInWorld() && !learning) // spell load case
  297. + {
  298. sLog.outError("Player::addSpell: Non-existed in SpellStore spell #%u request, deleting for all characters in `character_spell`.",spell_id);
  299. CharacterDatabase.PExecute("DELETE FROM character_spell WHERE spell = '%u'",spell_id);
  300. }
  301. @@ -2857,7 +2941,7 @@ bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependen
  302. return false;
  303. }
  304.  
  305. - if(!SpellMgr::IsSpellValid(spellInfo,this,false))
  306. + if (!SpellMgr::IsSpellValid(spellInfo,this,false))
  307. {
  308. // do character spell book cleanup (all characters)
  309. if(!IsInWorld() && !learning) // spell load case
  310. @@ -2898,10 +2982,10 @@ bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependen
  311. }
  312.  
  313. // not do anything if already known in expected state
  314. - if(itr->second->state != PLAYERSPELL_REMOVED && itr->second->active == active &&
  315. + if (itr->second->state != PLAYERSPELL_REMOVED && itr->second->active == active &&
  316. itr->second->dependent == dependent && itr->second->disabled == disabled)
  317. {
  318. - if(!IsInWorld() && !learning) // explicitly load from DB and then exist in it already and set correctly
  319. + if (!IsInWorld() && !learning) // explicitly load from DB and then exist in it already and set correctly
  320. itr->second->state = PLAYERSPELL_UNCHANGED;
  321.  
  322. return false;
  323. @@ -2917,21 +3001,21 @@ bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependen
  324. }
  325.  
  326. // update active state for known spell
  327. - if(itr->second->active != active && itr->second->state != PLAYERSPELL_REMOVED && !itr->second->disabled)
  328. + if (itr->second->active != active && itr->second->state != PLAYERSPELL_REMOVED && !itr->second->disabled)
  329. {
  330. itr->second->active = active;
  331.  
  332. - if(!IsInWorld() && !learning && !dependent_set) // explicitly load from DB and then exist in it already and set correctly
  333. + if (!IsInWorld() && !learning && !dependent_set) // explicitly load from DB and then exist in it already and set correctly
  334. itr->second->state = PLAYERSPELL_UNCHANGED;
  335. - else if(itr->second->state != PLAYERSPELL_NEW)
  336. + else if (itr->second->state != PLAYERSPELL_NEW)
  337. itr->second->state = PLAYERSPELL_CHANGED;
  338.  
  339. - if(active)
  340. + if (active)
  341. {
  342. if (IsPassiveSpell(spell_id) && IsNeedCastPassiveSpellAtLearn(spellInfo))
  343. - CastSpell (this,spell_id,true);
  344. + CastSpell(this, spell_id, true);
  345. }
  346. - else if(IsInWorld())
  347. + else if (IsInWorld())
  348. {
  349. if(next_active_spell_id)
  350. {
  351. @@ -2939,7 +3023,7 @@ bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependen
  352. WorldPacket data(SMSG_SUPERCEDED_SPELL, 4 + 4);
  353. data << uint32(spell_id);
  354. data << uint32(next_active_spell_id);
  355. - GetSession()->SendPacket( &data );
  356. + GetSession()->SendPacket(&data);
  357. }
  358. else
  359. {
  360. @@ -2952,18 +3036,18 @@ bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependen
  361. return active; // learn (show in spell book if active now)
  362. }
  363.  
  364. - if(itr->second->disabled != disabled && itr->second->state != PLAYERSPELL_REMOVED)
  365. + if (itr->second->disabled != disabled && itr->second->state != PLAYERSPELL_REMOVED)
  366. {
  367. - if(itr->second->state != PLAYERSPELL_NEW)
  368. + if (itr->second->state != PLAYERSPELL_NEW)
  369. itr->second->state = PLAYERSPELL_CHANGED;
  370. itr->second->disabled = disabled;
  371.  
  372. - if(disabled)
  373. + if (disabled)
  374. return false;
  375.  
  376. disabled_case = true;
  377. }
  378. - else switch(itr->second->state)
  379. + else switch (itr->second->state)
  380. {
  381. case PLAYERSPELL_UNCHANGED: // known saved spell
  382. return false;
  383. @@ -2977,7 +3061,7 @@ bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependen
  384. default: // known not saved yet spell (new or modified)
  385. {
  386. // can be in case spell loading but learned at some previous spell loading
  387. - if(!IsInWorld() && !learning && !dependent_set)
  388. + if (!IsInWorld() && !learning && !dependent_set)
  389. itr->second->state = PLAYERSPELL_UNCHANGED;
  390.  
  391. return false;
  392. @@ -2985,18 +3069,19 @@ bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependen
  393. }
  394. }
  395.  
  396. - if(!disabled_case) // skip new spell adding if spell already known (disabled spells case)
  397. + // skip new spell adding if spell already known (disabled spells case)
  398. + if (!disabled_case)
  399. {
  400. // talent: unlearn all other talent ranks (high and low)
  401. - if(TalentSpellPos const* talentPos = GetTalentSpellPos(spell_id))
  402. + if (TalentSpellPos const *talentPos = GetTalentSpellPos(spell_id))
  403. {
  404. - if(TalentEntry const *talentInfo = sTalentStore.LookupEntry( talentPos->talent_id ))
  405. + if (TalentEntry const *talentInfo = sTalentStore.LookupEntry( talentPos->talent_id ))
  406. {
  407. - for(int i=0; i < MAX_TALENT_RANK; ++i)
  408. + for(uint8 rank = 0; rank < MAX_TALENT_RANK; ++rank)
  409. {
  410. // skip learning spell and no rank spell case
  411. - uint32 rankSpellId = talentInfo->RankID[i];
  412. - if(!rankSpellId || rankSpellId==spell_id)
  413. + uint32 rankSpellId = talentInfo->RankID[rank];
  414. + if (!rankSpellId || rankSpellId == spell_id)
  415. continue;
  416.  
  417. removeSpell(rankSpellId,false,false);
  418. @@ -3004,9 +3089,9 @@ bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependen
  419. }
  420. }
  421. // non talent spell: learn low ranks (recursive call)
  422. - else if(uint32 prev_spell = sSpellMgr.GetPrevSpellInChain(spell_id))
  423. + else if (uint32 prev_spell = sSpellMgr.GetPrevSpellInChain(spell_id))
  424. {
  425. - if(!IsInWorld() || disabled) // at spells loading, no output, but allow save
  426. + if (!IsInWorld() || disabled) // at spells loading, no output, but allow save
  427. addSpell(prev_spell,active,true,true,disabled);
  428. else // at normal learning
  429. learnSpell(prev_spell,true);
  430. @@ -3019,21 +3104,21 @@ bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependen
  431. newspell->disabled = disabled;
  432.  
  433. // replace spells in action bars and spellbook to bigger rank if only one spell rank must be accessible
  434. - if(newspell->active && !newspell->disabled && !SpellMgr::canStackSpellRanks(spellInfo) && sSpellMgr.GetSpellRank(spellInfo->Id) != 0)
  435. + if (newspell->active && !newspell->disabled && !SpellMgr::canStackSpellRanks(spellInfo) && sSpellMgr.GetSpellRank(spellInfo->Id) != 0)
  436. {
  437. - for( PlayerSpellMap::iterator itr2 = m_spells.begin(); itr2 != m_spells.end(); ++itr2 )
  438. + for (PlayerSpellMap::iterator itr2 = m_spells.begin(); itr2 != m_spells.end(); ++itr2 )
  439. {
  440. - if(itr2->second->state == PLAYERSPELL_REMOVED) continue;
  441. + if (itr2->second->state == PLAYERSPELL_REMOVED) continue;
  442. SpellEntry const *i_spellInfo = sSpellStore.LookupEntry(itr2->first);
  443. if(!i_spellInfo) continue;
  444.  
  445. - if( sSpellMgr.IsRankSpellDueToSpell(spellInfo,itr2->first) )
  446. + if (sSpellMgr.IsRankSpellDueToSpell(spellInfo,itr2->first) )
  447. {
  448. - if(itr2->second->active)
  449. + if (itr2->second->active)
  450. {
  451. - if(sSpellMgr.IsHighRankOfSpell(spell_id,itr2->first))
  452. + if (sSpellMgr.IsHighRankOfSpell(spell_id,itr2->first))
  453. {
  454. - if(IsInWorld()) // not send spell (re-/over-)learn packets at loading
  455. + if (IsInWorld()) // not send spell (re-/over-)learn packets at loading
  456. {
  457. WorldPacket data(SMSG_SUPERCEDED_SPELL, 4 + 4);
  458. data << uint32(itr2->first);
  459. @@ -3043,13 +3128,13 @@ bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependen
  460.  
  461. // mark old spell as disable (SMSG_SUPERCEDED_SPELL replace it in client by new)
  462. itr2->second->active = false;
  463. - if(itr2->second->state != PLAYERSPELL_NEW)
  464. + if (itr2->second->state != PLAYERSPELL_NEW)
  465. itr2->second->state = PLAYERSPELL_CHANGED;
  466. superceded_old = true; // new spell replace old in action bars and spell book.
  467. }
  468. - else if(sSpellMgr.IsHighRankOfSpell(itr2->first,spell_id))
  469. + else if (sSpellMgr.IsHighRankOfSpell(itr2->first,spell_id))
  470. {
  471. - if(IsInWorld()) // not send spell (re-/over-)learn packets at loading
  472. + if (IsInWorld()) // not send spell (re-/over-)learn packets at loading
  473. {
  474. WorldPacket data(SMSG_SUPERCEDED_SPELL, 4 + 4);
  475. data << uint32(spell_id);
  476. @@ -3059,7 +3144,7 @@ bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependen
  477.  
  478. // mark new spell as disable (not learned yet for client and will not learned)
  479. newspell->active = false;
  480. - if(newspell->state != PLAYERSPELL_NEW)
  481. + if (newspell->state != PLAYERSPELL_NEW)
  482. newspell->state = PLAYERSPELL_CHANGED;
  483. }
  484. }
  485. @@ -3101,7 +3186,7 @@ bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependen
  486. // update free primary prof.points (if any, can be none in case GM .learn prof. learning)
  487. if (uint32 freeProfs = GetFreePrimaryProfessionPoints())
  488. {
  489. - if(sSpellMgr.IsPrimaryProfessionFirstRankSpell(spell_id))
  490. + if (sSpellMgr.IsPrimaryProfessionFirstRankSpell(spell_id))
  491. SetFreePrimaryProfessions(freeProfs-1);
  492. }
  493.  
  494. @@ -3125,7 +3210,7 @@ bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependen
  495. if (skill_max_value < new_skill_max_value)
  496. skill_max_value = new_skill_max_value;
  497.  
  498. - SetSkill(spellLearnSkill->skill,skill_value,skill_max_value);
  499. + SetSkill(spellLearnSkill->skill, skill_value, skill_max_value);
  500. }
  501. else
  502. {
  503. @@ -3143,16 +3228,16 @@ bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependen
  504. // lockpicking/runeforging special case, not have ABILITY_LEARNED_ON_GET_RACE_OR_CLASS_SKILL
  505. ((pSkill->id==SKILL_LOCKPICKING || pSkill->id==SKILL_RUNEFORGING) && _spell_idx->second->max_value==0))
  506. {
  507. - switch(GetSkillRangeType(pSkill,_spell_idx->second->racemask!=0))
  508. + switch (GetSkillRangeType(pSkill,_spell_idx->second->racemask!=0))
  509. {
  510. case SKILL_RANGE_LANGUAGE:
  511. - SetSkill(pSkill->id, 300, 300 );
  512. + SetSkill(pSkill->id, 300, 300);
  513. break;
  514. case SKILL_RANGE_LEVEL:
  515. - SetSkill(pSkill->id, 1, GetMaxSkillValueForLevel() );
  516. + SetSkill(pSkill->id, 1, GetMaxSkillValueForLevel());
  517. break;
  518. case SKILL_RANGE_MONO:
  519. - SetSkill(pSkill->id, 1, 1 );
  520. + SetSkill(pSkill->id, 1, 1);
  521. break;
  522. default:
  523. break;
  524. @@ -3169,9 +3254,9 @@ bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependen
  525. if (!itr2->second.autoLearned)
  526. {
  527. if (!IsInWorld() || !itr2->second.active) // at spells loading, no output, but allow save
  528. - addSpell(itr2->second.spell,itr2->second.active,true,true,false);
  529. + addSpell(itr2->second.spell, itr2->second.active, true, true, false);
  530. else // at normal learning
  531. - learnSpell(itr2->second.spell,true);
  532. + learnSpell(itr2->second.spell, true);
  533. }
  534. }
  535.  
  536. @@ -3280,7 +3365,7 @@ void Player::removeSpell(uint32 spell_id, bool disabled, bool learn_low_rank, bo
  537.  
  538. // free talent points
  539. uint32 talentCosts = GetTalentSpellCost(spell_id);
  540. - if(talentCosts > 0)
  541. + if (talentCosts > 0)
  542. {
  543. if(talentCosts < m_usedTalentCount)
  544. m_usedTalentCount -= talentCosts;
  545. @@ -3297,7 +3382,7 @@ void Player::removeSpell(uint32 spell_id, bool disabled, bool learn_low_rank, bo
  546. }
  547.  
  548. // remove dependent skill
  549. - SpellLearnSkillNode const* spellLearnSkill = sSpellMgr.GetSpellLearnSkill(spell_id);
  550. + SpellLearnSkillNode const *spellLearnSkill = sSpellMgr.GetSpellLearnSkill(spell_id);
  551. if(spellLearnSkill)
  552. {
  553. uint32 prev_spell = sSpellMgr.GetPrevSpellInChain(spell_id);
  554. @@ -3306,7 +3391,7 @@ void Player::removeSpell(uint32 spell_id, bool disabled, bool learn_low_rank, bo
  555. else
  556. {
  557. // search prev. skill setting by spell ranks chain
  558. - SpellLearnSkillNode const* prevSkill = sSpellMgr.GetSpellLearnSkill(prev_spell);
  559. + SpellLearnSkillNode const *prevSkill = sSpellMgr.GetSpellLearnSkill(prev_spell);
  560. while(!prevSkill && prev_spell)
  561. {
  562. prev_spell = sSpellMgr.GetPrevSpellInChain(prev_spell);
  563. @@ -3320,13 +3405,13 @@ void Player::removeSpell(uint32 spell_id, bool disabled, bool learn_low_rank, bo
  564. uint32 skill_value = GetPureSkillValue(prevSkill->skill);
  565. uint32 skill_max_value = GetPureMaxSkillValue(prevSkill->skill);
  566.  
  567. - if (skill_value > prevSkill->value)
  568. + if (skill_value > prevSkill->value)
  569. skill_value = prevSkill->value;
  570.  
  571. uint32 new_skill_max_value = prevSkill->maxvalue == 0 ? GetMaxSkillValueForLevel() : prevSkill->maxvalue;
  572.  
  573. if (skill_max_value > new_skill_max_value)
  574. - skill_max_value = new_skill_max_value;
  575. + skill_max_value = new_skill_max_value;
  576.  
  577. SetSkill(prevSkill->skill,skill_value,skill_max_value);
  578. }
  579. @@ -3350,7 +3435,7 @@ void Player::removeSpell(uint32 spell_id, bool disabled, bool learn_low_rank, bo
  580. ((pSkill->id==SKILL_LOCKPICKING || pSkill->id==SKILL_RUNEFORGING) && _spell_idx->second->max_value==0))
  581. {
  582. // not reset skills for professions and racial abilities
  583. - if ((pSkill->categoryId==SKILL_CATEGORY_SECONDARY || pSkill->categoryId==SKILL_CATEGORY_PROFESSION) &&
  584. + if ((pSkill->categoryId == SKILL_CATEGORY_SECONDARY || pSkill->categoryId == SKILL_CATEGORY_PROFESSION) &&
  585. (IsProfessionSkill(pSkill->id) || _spell_idx->second->racemask!=0))
  586. continue;
  587.  
  588. @@ -3408,6 +3493,12 @@ void Player::removeSpell(uint32 spell_id, bool disabled, bool learn_low_rank, bo
  589. }
  590. }
  591. }
  592. +
  593. + if(spell_id == 46917 && m_canTitanGrip)
  594. + SetCanTitanGrip(false);
  595. +
  596. + if(sWorld.getConfig(CONFIG_OFFHAND_CHECK_AT_SPELL_UNLEARN))
  597. + AutoUnequipOffhandIfNeed();
  598.  
  599. // remove from spell book if not replaced by lesser rank
  600. if (!prev_activate && sendUpdate)
  601. @@ -3586,7 +3677,7 @@ uint32 Player::resetTalentsCost() const
  602. bool Player::resetTalents(bool no_cost)
  603. {
  604. // not need after this call
  605. - if(HasAtLoginFlag(AT_LOGIN_RESET_TALENTS))
  606. + if (HasAtLoginFlag(AT_LOGIN_RESET_TALENTS))
  607. RemoveAtLoginFlag(AT_LOGIN_RESET_TALENTS,true);
  608.  
  609. uint32 talentPointsForLevel = CalculateTalentsPoints();
  610. @@ -3605,63 +3696,51 @@ bool Player::resetTalents(bool no_cost)
  611.  
  612. if (GetMoney() < cost)
  613. {
  614. - SendBuyError( BUY_ERR_NOT_ENOUGHT_MONEY, 0, 0, 0);
  615. + SendBuyError(BUY_ERR_NOT_ENOUGHT_MONEY, 0, 0, 0);
  616. return false;
  617. }
  618. }
  619.  
  620. - for (unsigned int i = 0; i < sTalentStore.GetNumRows(); ++i)
  621. + for (uint32 i = 0; i < sTalentStore.GetNumRows(); ++i)
  622. {
  623. TalentEntry const *talentInfo = sTalentStore.LookupEntry(i);
  624.  
  625. - if (!talentInfo) continue;
  626. + if (!talentInfo)
  627. + continue;
  628.  
  629. - TalentTabEntry const *talentTabInfo = sTalentTabStore.LookupEntry( talentInfo->TalentTab );
  630. + TalentTabEntry const *talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TalentTab);
  631.  
  632. - if(!talentTabInfo)
  633. + if (!talentTabInfo)
  634. continue;
  635.  
  636. // unlearn only talents for character class
  637. // some spell learned by one class as normal spells or know at creation but another class learn it as talent,
  638. // to prevent unexpected lost normal learned spell skip another class talents
  639. - if( (getClassMask() & talentTabInfo->ClassMask) == 0 )
  640. + if ((getClassMask() & talentTabInfo->ClassMask) == 0)
  641. continue;
  642.  
  643. - for (int j = 0; j < MAX_TALENT_RANK; ++j)
  644. + for (int8 rank = MAX_TALENT_RANK-1; rank >= 0; --rank)
  645. {
  646. - for(PlayerSpellMap::iterator itr = GetSpellMap().begin(); itr != GetSpellMap().end();)
  647. - {
  648. - if(itr->second->state == PLAYERSPELL_REMOVED || itr->second->disabled)
  649. - {
  650. - ++itr;
  651. - continue;
  652. - }
  653. -
  654. - // remove learned spells (all ranks)
  655. - uint32 itrFirstId = sSpellMgr.GetFirstSpellInChain(itr->first);
  656. -
  657. - // unlearn if first rank is talent or learned by talent
  658. - if (itrFirstId == talentInfo->RankID[j])
  659. - {
  660. - removeSpell(itr->first,!IsPassiveSpell(itr->first),false);
  661. - itr = GetSpellMap().begin();
  662. - continue;
  663. - }
  664. - else if (sSpellMgr.IsSpellLearnToSpell(talentInfo->RankID[j],itrFirstId))
  665. - {
  666. - removeSpell(itr->first,!IsPassiveSpell(itr->first));
  667. - itr = GetSpellMap().begin();
  668. - continue;
  669. - }
  670. - else
  671. - ++itr;
  672. - }
  673. + // skip non-existant talent ranks
  674. + if (talentInfo->RankID[rank] == 0)
  675. + continue;
  676. + removeSpell(talentInfo->RankID[rank], true);
  677. + if (const SpellEntry *_spellEntry = sSpellStore.LookupEntry(talentInfo->RankID[rank]))
  678. + for (uint8 i = 0; i < 3; ++i) // search through the SpellEntry for valid trigger spells
  679. + if (_spellEntry->EffectTriggerSpell[i] > 0 && _spellEntry->Effect[i] == SPELL_EFFECT_LEARN_SPELL)
  680. + removeSpell(_spellEntry->EffectTriggerSpell[i], true); // and remove any spells that the talent teaches
  681. + // if this talent rank can be found in the PlayerTalentMap, mark the talent as removed so it gets deleted
  682. + PlayerTalentMap::iterator plrTalent = m_talents[m_activeSpec]->find(talentInfo->RankID[rank]);
  683. + if (plrTalent != m_talents[m_activeSpec]->end())
  684. + plrTalent->second->state = PLAYERSPELL_REMOVED;
  685. }
  686. }
  687.  
  688. + _SaveSpells();
  689. +
  690. SetFreeTalentPoints(talentPointsForLevel);
  691.  
  692. - if(!no_cost)
  693. + if (!no_cost)
  694. {
  695. ModifyMoney(-(int32)cost);
  696. GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_FOR_TALENTS, cost);
  697. @@ -3681,14 +3760,6 @@ bool Player::resetTalents(bool no_cost)
  698. }
  699. */
  700.  
  701. -
  702. - if(m_canTitanGrip)
  703. - {
  704. - m_canTitanGrip = false;
  705. - if(sWorld.getConfig(CONFIG_OFFHAND_CHECK_AT_TALENTS_RESET))
  706. - AutoUnequipOffhandIfNeed();
  707. - }
  708. -
  709. return true;
  710. }
  711.  
  712. @@ -3891,6 +3962,12 @@ bool Player::HasSpell(uint32 spell) const
  713. !itr->second->disabled);
  714. }
  715.  
  716. +bool Player::HasTalent(uint32 spell, uint8 spec) const
  717. +{
  718. + PlayerTalentMap::const_iterator itr = m_talents[spec]->find(spell);
  719. + return (itr != m_talents[spec]->end() && itr->second->state != PLAYERSPELL_REMOVED);
  720. +}
  721. +
  722. bool Player::HasActiveSpell(uint32 spell) const
  723. {
  724. PlayerSpellMap::const_iterator itr = m_spells.find(spell);
  725. @@ -5624,12 +5701,12 @@ int16 Player::GetSkillTempBonusValue(uint32 skill) const
  726. return SKILL_TEMP_BONUS(GetUInt32Value(PLAYER_SKILL_BONUS_INDEX(itr->second.pos)));
  727. }
  728.  
  729. -void Player::SendInitialActionButtons() const
  730. +void Player::SendActionButtons(uint32 state) const
  731. {
  732. - sLog.outDetail( "Initializing Action Buttons for '%u'", GetGUIDLow() );
  733. + sLog.outDetail( "Sending Action Buttons for '%u' spec '%u'", GetGUIDLow(), m_activeSpec);
  734.  
  735. WorldPacket data(SMSG_ACTION_BUTTONS, 1+(MAX_ACTION_BUTTONS*4));
  736. - data << uint8(0); // can be 0, 1, 2 (talent spec)
  737. + data << uint8(state); // can be 0, 1, 2
  738. for(int button = 0; button < MAX_ACTION_BUTTONS; ++button)
  739. {
  740. ActionButtonList::const_iterator itr = m_actionButtons.find(button);
  741. @@ -5640,7 +5717,7 @@ void Player::SendInitialActionButtons() const
  742. }
  743.  
  744. GetSession()->SendPacket( &data );
  745. - sLog.outDetail( "Action Buttons for '%u' Initialized", GetGUIDLow() );
  746. + sLog.outDetail( "Action Buttons for '%u' spec '%u' Sent", GetGUIDLow(), m_activeSpec );
  747. }
  748.  
  749. bool Player::IsActionButtonDataValid(uint8 button, uint32 action, uint8 type, Player* player)
  750. @@ -5720,6 +5797,11 @@ void Player::removeActionButton(uint8 button)
  751. if (buttonItr==m_actionButtons.end())
  752. return;
  753.  
  754. + if (!buttonItr->second.canRemoveByClient)
  755. + {
  756. + buttonItr->second.canRemoveByClient = true;
  757. + return;
  758. + }
  759. if(buttonItr->second.uState==ACTIONBUTTON_NEW)
  760. m_actionButtons.erase(buttonItr); // new and not saved
  761. else
  762. @@ -12306,6 +12388,10 @@ void Player::PrepareGossipMenu(WorldObject *pSource, uint32 menuId)
  763. if (!pCreature->isCanTrainingOf(this, false))
  764. bCanTalk = false;
  765. break;
  766. + case GOSSIP_OPTION_LEARNDUALSPEC:
  767. + if(!(GetSpecsCount() == 1 && pCreature->isCanTrainingAndResetTalentsOf(this) && !(getLevel() < sWorld.getConfig(CONFIG_MIN_DUALSPEC_LEVEL))))
  768. + bCanTalk = false;
  769. + break;
  770. case GOSSIP_OPTION_UNLEARNTALENTS:
  771. if (!pCreature->isCanTrainingAndResetTalentsOf(this))
  772. bCanTalk = false;
  773. @@ -12505,6 +12591,29 @@ void Player::OnGossipSelect(WorldObject* pSource, uint32 gossipListId, uint32 me
  774. case GOSSIP_OPTION_TRAINER:
  775. GetSession()->SendTrainerList(guid);
  776. break;
  777. + case GOSSIP_OPTION_LEARNDUALSPEC:
  778. + if(GetSpecsCount() == 1 && !(getLevel() < sWorld.getConfig(CONFIG_MIN_DUALSPEC_LEVEL)))
  779. + {
  780. + if (GetMoney() < 10000000)
  781. + {
  782. + SendBuyError( BUY_ERR_NOT_ENOUGHT_MONEY, 0, 0, 0);
  783. + PlayerTalkClass->CloseGossip();
  784. + break;
  785. + }
  786. + else
  787. + {
  788. + ModifyMoney(-10000000);
  789. +
  790. + // Cast spells that teach dual spec
  791. + // Both are also ImplicitTarget self and must be cast by player
  792. + this->CastSpell(this,63680,true,NULL,NULL,this->GetGUID());
  793. + this->CastSpell(this,63624,true,NULL,NULL,this->GetGUID());
  794. +
  795. + // Should show another Gossip text with "Congratulations..."
  796. + PlayerTalkClass->CloseGossip();
  797. + }
  798. + }
  799. + break;
  800. case GOSSIP_OPTION_UNLEARNTALENTS:
  801. PlayerTalkClass->CloseGossip();
  802. SendTalentWipeConfirm(guid);
  803. @@ -14558,8 +14667,8 @@ bool Player::LoadFromDB( uint32 guid, SqlQueryHolder *holder )
  804. //"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,"
  805. // 40 41 42 43 44 45 46 47 48 49 50
  806. //"arenaPoints, totalHonorPoints, todayHonorPoints, yesterdayHonorPoints, totalKills, todayKills, yesterdayKills, chosenTitle, knownCurrencies, watchedFaction, drunk,"
  807. - // 51 52 53 54 55 56 57 58
  808. - //"health, power1, power2, power3, power4, power5, power6, power7 FROM characters WHERE guid = '%u'", GUID_LOPART(m_guid));
  809. + // 51 52 53 54 55 56 57 58 59 60
  810. + //"health, power1, power2, power3, power4, power5, power6, power7, speccount, activespec FROM characters WHERE guid = '%u'", GUID_LOPART(m_guid));
  811. QueryResult *result = holder->GetResult(PLAYER_LOGIN_QUERY_LOADFROM);
  812.  
  813. if(!result)
  814. @@ -14973,7 +15082,22 @@ bool Player::LoadFromDB( uint32 guid, SqlQueryHolder *holder )
  815.  
  816. // Mail
  817. _LoadMail();
  818. -
  819. +
  820. + m_specsCount = fields[59].GetUInt32();
  821. + m_activeSpec = fields[60].GetUInt32();
  822. +
  823. + // sanity check
  824. + if (m_specsCount > MAX_TALENT_SPECS || m_activeSpec > MAX_TALENT_SPEC ||
  825. + m_specsCount < MIN_TALENT_SPECS || m_activeSpec < MIN_TALENT_SPEC)
  826. + {
  827. + m_activeSpec = 0;
  828. + sLog.outError("Player %s(GUID: %u) has SpecCount = %u and ActiveSpec = %u.", GetName(), GetGUIDLow(), m_specsCount, m_activeSpec);
  829. + }
  830. +
  831. + _LoadTalents(holder->GetResult(PLAYER_LOGIN_QUERY_LOADTALENTS));
  832. +
  833. + _LoadGlyphs(holder->GetResult(PLAYER_LOGIN_QUERY_LOADGLYPHS));
  834. +
  835. _LoadAuras(holder->GetResult(PLAYER_LOGIN_QUERY_LOADAURAS), time_diff);
  836. _LoadGlyphAuras();
  837.  
  838. @@ -14981,7 +15105,7 @@ bool Player::LoadFromDB( uint32 guid, SqlQueryHolder *holder )
  839. if( HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST) )
  840. m_deathState = DEAD;
  841.  
  842. - _LoadSpells(holder->GetResult(PLAYER_LOGIN_QUERY_LOADSPELLS));
  843. + _LoadSpells(holder->GetResult(PLAYER_LOGIN_QUERY_LOADSPELLS));
  844.  
  845. // after spell load, learn rewarded spell if need also
  846. _LoadQuestStatus(holder->GetResult(PLAYER_LOGIN_QUERY_LOADQUESTSTATUS));
  847. @@ -14999,7 +15123,7 @@ bool Player::LoadFromDB( uint32 guid, SqlQueryHolder *holder )
  848. // update items with duration and realtime
  849. UpdateItemDuration(time_diff, true);
  850.  
  851. - _LoadActions(holder->GetResult(PLAYER_LOGIN_QUERY_LOADACTIONS));
  852. + _LoadActions(holder->GetResult(PLAYER_LOGIN_QUERY_LOADACTIONS), true);
  853.  
  854. // unread mails and next delivery time, actual mails not loaded
  855. _LoadMailInit(holder->GetResult(PLAYER_LOGIN_QUERY_LOADMAILCOUNT), holder->GetResult(PLAYER_LOGIN_QUERY_LOADMAILDATE));
  856. @@ -15179,12 +15303,8 @@ bool Player::isAllowedToLoot(Creature* creature)
  857. return !creature->hasLootRecipient();
  858. }
  859.  
  860. -void Player::_LoadActions(QueryResult *result)
  861. +void Player::_LoadActions(QueryResult *result, bool startup)
  862. {
  863. - m_actionButtons.clear();
  864. -
  865. - //QueryResult *result = CharacterDatabase.PQuery("SELECT button,action,type FROM character_action WHERE guid = '%u' ORDER BY button",GetGUIDLow());
  866. -
  867. if(result)
  868. {
  869. do
  870. @@ -15196,7 +15316,11 @@ void Player::_LoadActions(QueryResult *result)
  871. uint8 type = fields[2].GetUInt8();
  872.  
  873. if(ActionButton* ab = addActionButton(button, action, type))
  874. + {
  875. ab->uState = ACTIONBUTTON_UNCHANGED;
  876. + if(!startup) // Switching specs
  877. + ab->canRemoveByClient = false;
  878. + }
  879. else
  880. {
  881. sLog.outError( " ...at loading, and will deleted in DB also");
  882. @@ -16132,7 +16256,7 @@ void Player::SaveToDB()
  883. "trans_x, trans_y, trans_z, trans_o, transguid, extra_flags, stable_slots, at_login, zone, "
  884. "death_expire_time, taxi_path, arenaPoints, totalHonorPoints, todayHonorPoints, yesterdayHonorPoints, totalKills, "
  885. "todayKills, yesterdayKills, chosenTitle, knownCurrencies, watchedFaction, drunk, health, power1, power2, power3, "
  886. - "power4, power5, power6, power7) VALUES ("
  887. + "power4, power5, power6, power7, speccount, activespec) VALUES ("
  888. << GetGUIDLow() << ", "
  889. << GetSession()->GetAccountId() << ", '"
  890. << sql_name << "', "
  891. @@ -16239,6 +16363,10 @@ void Player::SaveToDB()
  892.  
  893. for(uint32 i = 0; i < MAX_POWERS; ++i)
  894. ss << "," << GetPower(Powers(i));
  895. + ss << ", ";
  896. + ss << uint32(m_specsCount);
  897. + ss << ", ";
  898. + ss << uint32(m_activeSpec);
  899.  
  900. ss << ")";
  901.  
  902. @@ -16251,6 +16379,7 @@ void Player::SaveToDB()
  903. _SaveInventory();
  904. _SaveQuestStatus();
  905. _SaveDailyQuestStatus();
  906. + _SaveTalents();
  907. _SaveSpells();
  908. _SaveSpellCooldowns();
  909. _SaveActions();
  910. @@ -16260,6 +16389,7 @@ void Player::SaveToDB()
  911. m_reputationMgr.SaveToDB();
  912. _SaveEquipmentSets();
  913. GetSession()->SaveTutorialsData(); // changed only while character in game
  914. + _SaveGlyphs();
  915.  
  916. CharacterDatabase.CommitTransaction();
  917.  
  918. @@ -16287,19 +16417,19 @@ void Player::_SaveActions()
  919. switch (itr->second.uState)
  920. {
  921. case ACTIONBUTTON_NEW:
  922. - CharacterDatabase.PExecute("INSERT INTO character_action (guid,button,action,type) VALUES ('%u', '%u', '%u', '%u')",
  923. - GetGUIDLow(), (uint32)itr->first, (uint32)itr->second.GetAction(), (uint32)itr->second.GetType() );
  924. + CharacterDatabase.PExecute("INSERT INTO character_action (guid,spec,button,action,type) VALUES ('%u', '%u', '%u', '%u', '%u')",
  925. + GetGUIDLow(), (uint32)m_activeSpec, (uint32)itr->first, (uint32)itr->second.GetAction(), (uint32)itr->second.GetType() );
  926. itr->second.uState = ACTIONBUTTON_UNCHANGED;
  927. ++itr;
  928. break;
  929. case ACTIONBUTTON_CHANGED:
  930. - CharacterDatabase.PExecute("UPDATE character_action SET action = '%u', type = '%u' WHERE guid= '%u' AND button= '%u' ",
  931. - (uint32)itr->second.GetAction(), (uint32)itr->second.GetType(), GetGUIDLow(), (uint32)itr->first );
  932. + CharacterDatabase.PExecute("UPDATE character_action SET action = '%u', type = '%u' WHERE guid = '%u' AND button = '%u' AND spec = '%u'",
  933. + (uint32)itr->second.GetAction(), (uint32)itr->second.GetType(), GetGUIDLow(), (uint32)itr->first, (uint32)m_activeSpec);
  934. itr->second.uState = ACTIONBUTTON_UNCHANGED;
  935. ++itr;
  936. break;
  937. case ACTIONBUTTON_DELETED:
  938. - CharacterDatabase.PExecute("DELETE FROM character_action WHERE guid = '%u' and button = '%u'", GetGUIDLow(), (uint32)itr->first );
  939. + CharacterDatabase.PExecute("DELETE FROM character_action WHERE guid = '%u' and button = '%u' and spec = '%u'", GetGUIDLow(), (uint32)itr->first, (uint32)m_activeSpec );
  940. m_actionButtons.erase(itr++);
  941. break;
  942. default:
  943. @@ -20681,13 +20811,13 @@ void Player::LearnTalent(uint32 talentId, uint32 talentRank)
  944. if( (getClassMask() & talentTabInfo->ClassMask) == 0 )
  945. return;
  946.  
  947. - // find current max talent rank
  948. - int32 curtalent_maxrank = 0;
  949. - for(int32 k = MAX_TALENT_RANK-1; k > -1; --k)
  950. + // find current max talent rank (0~5)
  951. + uint8 curtalent_maxrank = 0; // 0 = not learned any rank
  952. + for(int8 rank = MAX_TALENT_RANK-1; rank >= 0; --rank)
  953. {
  954. - if(talentInfo->RankID[k] && HasSpell(talentInfo->RankID[k]))
  955. + if(talentInfo->RankID[rank] && HasSpell(talentInfo->RankID[rank]))
  956. {
  957. - curtalent_maxrank = k + 1;
  958. + curtalent_maxrank = (rank + 1);
  959. break;
  960. }
  961. }
  962. @@ -20706,10 +20836,10 @@ void Player::LearnTalent(uint32 talentId, uint32 talentRank)
  963. if(TalentEntry const *depTalentInfo = sTalentStore.LookupEntry(talentInfo->DependsOn))
  964. {
  965. bool hasEnoughRank = false;
  966. - for (int i = talentInfo->DependsOnRank; i < MAX_TALENT_RANK; ++i)
  967. + for (uint8 rank = talentInfo->DependsOnRank; rank < MAX_TALENT_RANK; rank++)
  968. {
  969. - if (depTalentInfo->RankID[i] != 0)
  970. - if (HasSpell(depTalentInfo->RankID[i]))
  971. + if (depTalentInfo->RankID[rank] != 0)
  972. + if (HasSpell(depTalentInfo->RankID[rank]))
  973. hasEnoughRank = true;
  974. }
  975. if (!hasEnoughRank)
  976. @@ -20765,7 +20895,9 @@ void Player::LearnTalent(uint32 talentId, uint32 talentRank)
  977.  
  978. // learn! (other talent ranks will unlearned at learning)
  979. learnSpell(spellid, false);
  980. - sLog.outDetail("TalentID: %u Rank: %u Spell: %u\n", talentId, talentRank, spellid);
  981. + AddTalent(spellid, m_activeSpec, true);
  982. +
  983. + sLog.outDetail("TalentID: %u Rank: %u Spell: %u Spec: %u\n", talentId, talentRank, spellid, m_activeSpec);
  984.  
  985. // update free talent points
  986. SetFreeTalentPoints(CurTalentPoints - (talentRank - curtalent_maxrank + 1));
  987. @@ -20816,13 +20948,13 @@ void Player::LearnPetTalent(uint64 petGuid, uint32 talentId, uint32 talentRank)
  988. if(!((1 << pet_family->petTalentType) & talentTabInfo->petTalentMask))
  989. return;
  990.  
  991. - // find current max talent rank
  992. - int32 curtalent_maxrank = 0;
  993. - for(int32 k = MAX_TALENT_RANK-1; k > -1; --k)
  994. + // find current max talent rank (0~5)
  995. + uint8 curtalent_maxrank = 0; // 0 = not learned any rank
  996. + for(int8 rank = MAX_TALENT_RANK-1; rank >= 0; --rank)
  997. {
  998. - if(talentInfo->RankID[k] && pet->HasSpell(talentInfo->RankID[k]))
  999. + if(talentInfo->RankID[rank] && pet->HasSpell(talentInfo->RankID[rank]))
  1000. {
  1001. - curtalent_maxrank = k + 1;
  1002. + curtalent_maxrank = (rank + 1);
  1003. break;
  1004. }
  1005. }
  1006. @@ -20841,10 +20973,10 @@ void Player::LearnPetTalent(uint64 petGuid, uint32 talentId, uint32 talentRank)
  1007. if(TalentEntry const *depTalentInfo = sTalentStore.LookupEntry(talentInfo->DependsOn))
  1008. {
  1009. bool hasEnoughRank = false;
  1010. - for (int i = talentInfo->DependsOnRank; i < MAX_TALENT_RANK; ++i)
  1011. + for (uint8 rank = talentInfo->DependsOnRank; rank < MAX_TALENT_RANK; rank++)
  1012. {
  1013. - if (depTalentInfo->RankID[i] != 0)
  1014. - if (pet->HasSpell(depTalentInfo->RankID[i]))
  1015. + if (depTalentInfo->RankID[rank] != 0)
  1016. + if (pet->HasSpell(depTalentInfo->RankID[rank]))
  1017. hasEnoughRank = true;
  1018. }
  1019. if (!hasEnoughRank)
  1020. @@ -20867,13 +20999,13 @@ void Player::LearnPetTalent(uint64 petGuid, uint32 talentId, uint32 talentRank)
  1021. {
  1022. if (tmpTalent->TalentTab == tTab)
  1023. {
  1024. - for (int j = 0; j < MAX_TALENT_RANK; ++j)
  1025. + for (uint8 rank = 0; rank < MAX_TALENT_RANK; rank++)
  1026. {
  1027. - if (tmpTalent->RankID[j] != 0)
  1028. + if (tmpTalent->RankID[rank] != 0)
  1029. {
  1030. - if (pet->HasSpell(tmpTalent->RankID[j]))
  1031. + if (pet->HasSpell(tmpTalent->RankID[rank]))
  1032. {
  1033. - spentPoints += j + 1;
  1034. + spentPoints += (rank + 1);
  1035. }
  1036. }
  1037. }
  1038. @@ -21002,13 +21134,13 @@ void Player::BuildPlayerTalentsInfoData(WorldPacket *data)
  1039. if(talentInfo->TalentTab != talentTabId)
  1040. continue;
  1041.  
  1042. - // find max talent rank
  1043. - int32 curtalent_maxrank = -1;
  1044. - for(int32 k = MAX_TALENT_RANK-1; k > -1; --k)
  1045. + // find max talent rank (0~4)
  1046. + int8 curtalent_maxrank = -1;
  1047. + for(int8 rank = MAX_TALENT_RANK-1; rank >= 0; --rank)
  1048. {
  1049. - if(talentInfo->RankID[k] && HasSpell(talentInfo->RankID[k]))
  1050. + if(talentInfo->RankID[rank] && HasTalent(talentInfo->RankID[rank], specIdx))
  1051. {
  1052. - curtalent_maxrank = k;
  1053. + curtalent_maxrank = rank;
  1054. break;
  1055. }
  1056. }
  1057. @@ -21029,7 +21161,7 @@ void Player::BuildPlayerTalentsInfoData(WorldPacket *data)
  1058. *data << uint8(MAX_GLYPH_SLOT_INDEX); // glyphs count
  1059.  
  1060. for(uint8 i = 0; i < MAX_GLYPH_SLOT_INDEX; ++i)
  1061. - *data << uint16(GetGlyph(i)); // GlyphProperties.dbc
  1062. + *data << uint16(m_Glyphs[specIdx][i]); // GlyphProperties.dbc
  1063. }
  1064. }
  1065. }
  1066. @@ -21079,13 +21211,13 @@ void Player::BuildPetTalentsInfoData(WorldPacket *data)
  1067. if(talentInfo->TalentTab != talentTabId)
  1068. continue;
  1069.  
  1070. - // find max talent rank
  1071. - int32 curtalent_maxrank = -1;
  1072. - for(int32 k = 4; k > -1; --k)
  1073. + // find max talent rank (0~4)
  1074. + int8 curtalent_maxrank = -1;
  1075. + for(int8 rank = MAX_TALENT_RANK-1; rank >= 0; --rank)
  1076. {
  1077. - if(talentInfo->RankID[k] && pet->HasSpell(talentInfo->RankID[k]))
  1078. + if(talentInfo->RankID[rank] && pet->HasSpell(talentInfo->RankID[rank]))
  1079. {
  1080. - curtalent_maxrank = k;
  1081. + curtalent_maxrank = rank;
  1082. break;
  1083. }
  1084. }
  1085. @@ -21284,14 +21416,6 @@ void Player::DeleteEquipmentSet(uint64 setGuid)
  1086. }
  1087. }
  1088.  
  1089. -void Player::ActivateSpec(uint32 specNum)
  1090. -{
  1091. - if(GetActiveSpec() == specNum)
  1092. - return;
  1093. -
  1094. - resetTalents(true);
  1095. -}
  1096. -
  1097. void Player::RemoveAtLoginFlag( AtLoginFlags f, bool in_db_also /*= false*/ )
  1098. {
  1099. m_atLoginFlags &= ~f;
  1100. @@ -21386,6 +21510,245 @@ bool Player::IsImmunedToSpellEffect(SpellEntry const* spellInfo, uint32 index) c
  1101. return Unit::IsImmunedToSpellEffect(spellInfo, index);
  1102. }
  1103.  
  1104. +void Player::_LoadGlyphs(QueryResult *result)
  1105. +{
  1106. + // SetPQuery(PLAYER_LOGIN_QUERY_LOADGLYPHS, "SELECT spec, glyph1, glyph2, glyph3, glyph4, glyph5, glyph6 from character_glyphs WHERE guid = '%u'", GUID_LOPART(m_guid));
  1107. + if (!result)
  1108. + return;
  1109. +
  1110. + do
  1111. + {
  1112. + Field *fields = result->Fetch();
  1113. +
  1114. + uint8 spec = fields[0].GetUInt8();
  1115. + if (spec >= m_specsCount)
  1116. + continue;
  1117. +
  1118. + m_Glyphs[spec][0] = fields[1].GetUInt32();
  1119. + m_Glyphs[spec][1] = fields[2].GetUInt32();
  1120. + m_Glyphs[spec][2] = fields[3].GetUInt32();
  1121. + m_Glyphs[spec][3] = fields[4].GetUInt32();
  1122. + m_Glyphs[spec][4] = fields[5].GetUInt32();
  1123. + m_Glyphs[spec][5] = fields[6].GetUInt32();
  1124. +
  1125. + } while (result->NextRow());
  1126. +
  1127. + delete result;
  1128. +}
  1129. +
  1130. +void Player::_SaveGlyphs()
  1131. +{
  1132. + CharacterDatabase.PExecute("DELETE FROM character_glyphs WHERE guid='%u'",GetGUIDLow());
  1133. + for (uint8 spec = 0; spec < m_specsCount; ++spec)
  1134. + {
  1135. + CharacterDatabase.PExecute("INSERT INTO character_glyphs VALUES('%u', '%u', '%u', '%u', '%u', '%u', '%u', '%u')",
  1136. + 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]);
  1137. + }
  1138. +}
  1139. +
  1140. +void Player::_LoadTalents(QueryResult *result)
  1141. +{
  1142. + // SetPQuery(PLAYER_LOGIN_QUERY_LOADTALENTS, "SELECT spell, spec FROM character_talent WHERE guid = '%u'", GUID_LOPART(m_guid));
  1143. + if (result)
  1144. + {
  1145. + do
  1146. + {
  1147. + Field *fields = result->Fetch();
  1148. + AddTalent(fields[0].GetUInt32(), fields[1].GetUInt32(), false);
  1149. + }
  1150. + while( result->NextRow() );
  1151. +
  1152. + delete result;
  1153. + }
  1154. +}
  1155. +
  1156. +void Player::_SaveTalents()
  1157. +{
  1158. + for (uint8 i = 0; i < MAX_TALENT_SPECS; ++i)
  1159. + {
  1160. + for (PlayerTalentMap::iterator itr = m_talents[i]->begin(), next = m_talents[i]->begin(); itr != m_talents[i]->end();)
  1161. + {
  1162. + if (itr->second->state == PLAYERSPELL_REMOVED || itr->second->state == PLAYERSPELL_CHANGED)
  1163. + CharacterDatabase.PExecute("DELETE FROM character_talent WHERE guid = '%u' and spell = '%u' and spec = '%u'", GetGUIDLow(), itr->first, itr->second->spec);
  1164. +
  1165. + if (itr->second->state == PLAYERSPELL_NEW || itr->second->state == PLAYERSPELL_CHANGED)
  1166. + CharacterDatabase.PExecute("INSERT INTO character_talent (guid,spell,spec) VALUES ('%u', '%u', '%u')", GetGUIDLow(), itr->first, itr->second->spec);
  1167. +
  1168. + if (itr->second->state == PLAYERSPELL_REMOVED)
  1169. + {
  1170. + delete itr->second;
  1171. + m_talents[i]->erase(itr++);
  1172. + }
  1173. + else
  1174. + {
  1175. + itr->second->state = PLAYERSPELL_UNCHANGED;
  1176. + ++itr;
  1177. + }
  1178. + }
  1179. + }
  1180. +}
  1181. +
  1182. +void Player::UpdateSpecCount(uint8 count)
  1183. +{
  1184. + if(GetSpecsCount() == count)
  1185. + return;
  1186. +
  1187. + if(count == MIN_TALENT_SPECS)
  1188. + {
  1189. + _SaveActions(); // make sure the button list is cleaned up
  1190. + // active spec becomes only spec?
  1191. + CharacterDatabase.PExecute("DELETE FROM character_action WHERE spec<>'%u' AND guid='%u'",m_activeSpec, GetGUIDLow());
  1192. + m_activeSpec = 0;
  1193. + }
  1194. + else if (count == MAX_TALENT_SPECS)
  1195. + {
  1196. + _SaveActions(); // make sure the button list is cleaned up
  1197. + for(ActionButtonList::iterator itr = m_actionButtons.begin(); itr != m_actionButtons.end(); ++itr)
  1198. + {
  1199. + CharacterDatabase.PExecute("INSERT INTO character_action (guid,button,action,type,spec) VALUES ('%u', '%u', '%u', '%u', '%u')",
  1200. + GetGUIDLow(), (uint32)itr->first, (uint32)itr->second.GetAction(), (uint32)itr->second.GetType(), 1 );
  1201. + }
  1202. + }
  1203. + else
  1204. + {
  1205. + return;
  1206. + }
  1207. +
  1208. + SetSpecsCount(count);
  1209. +
  1210. + SendTalentsInfoData(false);
  1211. +}
  1212. +
  1213. +void Player::ActivateSpec(uint8 spec)
  1214. +{
  1215. + if(GetActiveSpec() == spec)
  1216. + return;
  1217. +
  1218. + if(GetSpecsCount() != MAX_TALENT_SPECS)
  1219. + return;
  1220. +
  1221. + _SaveActions();
  1222. +
  1223. + UnsummonPetTemporaryIfAny();
  1224. +
  1225. + for (uint32 i = 0; i < sTalentStore.GetNumRows(); ++i)
  1226. + {
  1227. + TalentEntry const *talentInfo = sTalentStore.LookupEntry(i);
  1228. +
  1229. + if (!talentInfo)
  1230. + continue;
  1231. +
  1232. + TalentTabEntry const *talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TalentTab);
  1233. +
  1234. + if (!talentTabInfo)
  1235. + continue;
  1236. +
  1237. + // unlearn only talents for character class
  1238. + // some spell learned by one class as normal spells or know at creation but another class learn it as talent,
  1239. + // to prevent unexpected lost normal learned spell skip another class talents
  1240. + if ((getClassMask() & talentTabInfo->ClassMask) == 0)
  1241. + continue;
  1242. +
  1243. + for (int8 rank = MAX_TALENT_RANK-1; rank >= 0; --rank)
  1244. + {
  1245. + // skip non-existant talent ranks
  1246. + if (talentInfo->RankID[rank] == 0)
  1247. + continue;
  1248. + removeSpell(talentInfo->RankID[rank], true); // removes the talent, and all dependant, learned, and chained spells..
  1249. + if (const SpellEntry *_spellEntry = sSpellStore.LookupEntry(talentInfo->RankID[rank]))
  1250. + for (uint8 i = 0; i < 3; ++i) // search through the SpellEntry for valid trigger spells
  1251. + if (_spellEntry->EffectTriggerSpell[i] > 0 && _spellEntry->Effect[i] == SPELL_EFFECT_LEARN_SPELL)
  1252. + removeSpell(_spellEntry->EffectTriggerSpell[i], true); // and remove any spells that the talent teaches
  1253. + // if this talent rank can be found in the PlayerTalentMap, mark the talent as removed so it gets deleted
  1254. + //PlayerTalentMap::iterator plrTalent = m_talents[m_activeSpec]->find(talentInfo->RankID[rank]);
  1255. + //if (plrTalent != m_talents[m_activeSpec]->end())
  1256. + // plrTalent->second->state = PLAYERSPELL_REMOVED;
  1257. + }
  1258. + }
  1259. + // set glyphs
  1260. + for (uint8 slot = 0; slot < MAX_GLYPH_SLOT_INDEX; ++slot)
  1261. + {
  1262. + // remove secondary glyph
  1263. + if(uint32 oldglyph = m_Glyphs[m_activeSpec][slot])
  1264. + {
  1265. + if(GlyphPropertiesEntry const *old_gp = sGlyphPropertiesStore.LookupEntry(oldglyph))
  1266. + {
  1267. + RemoveAurasDueToSpell(old_gp->SpellId);
  1268. + }
  1269. + }
  1270. + }
  1271. +
  1272. + SetActiveSpec(spec);
  1273. + uint32 spentTalents = 0;
  1274. +
  1275. + for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId)
  1276. + {
  1277. + TalentEntry const *talentInfo = sTalentStore.LookupEntry(talentId);
  1278. +
  1279. + if (!talentInfo)
  1280. + continue;
  1281. +
  1282. + TalentTabEntry const *talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TalentTab);
  1283. +
  1284. + if (!talentTabInfo)
  1285. + continue;
  1286. +
  1287. + // learn only talents for character class
  1288. + if ((getClassMask() & talentTabInfo->ClassMask) == 0)
  1289. + continue;
  1290. +
  1291. + // learn highest talent rank that exists in newly activated spec
  1292. + for (int8 rank = MAX_TALENT_RANK-1; rank >= 0; --rank)
  1293. + {
  1294. + // skip non-existant talent ranks
  1295. + if (talentInfo->RankID[rank] == 0)
  1296. + continue;
  1297. + // if the talent can be found in the newly activated PlayerTalentMap
  1298. + if (HasTalent(talentInfo->RankID[rank], m_activeSpec))
  1299. + {
  1300. + learnSpell(talentInfo->RankID[rank], false); // add the talent to the PlayerSpellMap
  1301. + spentTalents += (rank + 1); // increment the spentTalents count
  1302. + }
  1303. + }
  1304. + }
  1305. +
  1306. + // set glyphs
  1307. + for (uint8 slot = 0; slot < MAX_GLYPH_SLOT_INDEX; ++slot)
  1308. + {
  1309. + uint32 glyph = m_Glyphs[m_activeSpec][slot];
  1310. + // apply primary glyph
  1311. + if (glyph)
  1312. + {
  1313. + if (GlyphPropertiesEntry const *gp = sGlyphPropertiesStore.LookupEntry(glyph))
  1314. + {
  1315. + CastSpell(this, gp->SpellId, true);
  1316. + }
  1317. + }
  1318. + SetGlyph(slot, glyph);
  1319. + }
  1320. +
  1321. + m_usedTalentCount = spentTalents;
  1322. + InitTalentForLevel();
  1323. +
  1324. + _SaveSpells();
  1325. +
  1326. + m_actionButtons.clear();
  1327. + QueryResult *result = CharacterDatabase.PQuery("SELECT button,action,type FROM character_action WHERE guid = '%u' AND spec = '%u' ORDER BY button", GetGUIDLow(), m_activeSpec);
  1328. + if (result)
  1329. + {
  1330. + _LoadActions(result, false);
  1331. + }
  1332. +
  1333. + ResummonPetTemporaryUnSummonedIfAny();
  1334. + SendActionButtons(1);
  1335. +
  1336. + Powers pw = getPowerType();
  1337. + if(pw != POWER_MANA)
  1338. + SetPower(POWER_MANA, 0);
  1339. +
  1340. + SetPower(pw, 0);
  1341. +}
  1342. +
  1343. void Player::SetHomebindToCurrentPos()
  1344. {
  1345. m_homebindMapId = GetMapId();
  1346. diff --git a/src/game/Player.h b/src/game/Player.h
  1347. index d32f682..ac678b0 100644
  1348. --- a/src/game/Player.h
  1349. +++ b/src/game/Player.h
  1350. @@ -93,6 +93,12 @@ struct PlayerSpell
  1351. bool disabled : 1; // first rank has been learned in result talent learn but currently talent unlearned, save max learned ranks
  1352. };
  1353.  
  1354. +struct PlayerTalent
  1355. +{
  1356. + PlayerSpellState state : 8;
  1357. + uint8 spec : 8;
  1358. +};
  1359. +
  1360. // Spell modifier (used for modify other spells)
  1361. struct SpellModifier
  1362. {
  1363. @@ -118,6 +124,7 @@ struct SpellModifier
  1364. Spell const* lastAffected;
  1365. };
  1366.  
  1367. +typedef UNORDERED_MAP<uint32, PlayerTalent*> PlayerTalentMap;
  1368. typedef UNORDERED_MAP<uint32, PlayerSpell*> PlayerSpellMap;
  1369. typedef std::list<SpellModifier*> SpellModList;
  1370.  
  1371. @@ -161,10 +168,11 @@ enum ActionButtonType
  1372.  
  1373. struct ActionButton
  1374. {
  1375. - ActionButton() : packedData(0), uState( ACTIONBUTTON_NEW ) {}
  1376. + ActionButton() : packedData(0), uState( ACTIONBUTTON_NEW ), canRemoveByClient(true){}
  1377.  
  1378. uint32 packedData;
  1379. ActionButtonUpdateState uState;
  1380. + bool canRemoveByClient;
  1381.  
  1382. // helpers
  1383. ActionButtonType GetType() const { return ActionButtonType(ACTION_BUTTON_TYPE(packedData)); }
  1384. @@ -925,7 +933,9 @@ enum PlayerLoginQueryIndex
  1385. PLAYER_LOGIN_QUERY_LOADBGDATA = 21,
  1386. PLAYER_LOGIN_QUERY_LOADACCOUNTDATA = 22,
  1387. PLAYER_LOGIN_QUERY_LOADSKILLS = 23,
  1388. - MAX_PLAYER_LOGIN_QUERY = 24
  1389. + PLAYER_LOGIN_QUERY_LOADGLYPHS = 24,
  1390. + PLAYER_LOGIN_QUERY_LOADTALENTS = 25,
  1391. + MAX_PLAYER_LOGIN_QUERY = 26
  1392. };
  1393.  
  1394. enum PlayerDelayedOperations
  1395. @@ -1607,20 +1617,28 @@ class MANGOS_DLL_SPEC Player : public Unit
  1396. void LearnTalent(uint32 talentId, uint32 talentRank);
  1397. void LearnPetTalent(uint64 petGuid, uint32 talentId, uint32 talentRank);
  1398.  
  1399. + bool AddTalent(uint32 spell, uint8 spec, bool learning);
  1400. + bool HasTalent(uint32 spell_id, uint8 spec) const;
  1401. +
  1402. uint32 CalculateTalentsPoints() const;
  1403.  
  1404. // Dual Spec
  1405. + void UpdateSpecCount(uint8 count);
  1406. uint32 GetActiveSpec() { return m_activeSpec; }
  1407. - void SetActiveSpec(uint32 spec) { m_activeSpec = spec; }
  1408. - uint32 GetSpecsCount() { return m_specsCount; }
  1409. - void SetSpecsCount(uint32 count) { m_specsCount = count; }
  1410. - void ActivateSpec(uint32 specNum);
  1411. + void SetActiveSpec(uint8 spec){ m_activeSpec = spec; }
  1412. + uint8 GetSpecsCount() { return m_specsCount; }
  1413. + void SetSpecsCount(uint8 count) { m_specsCount = count; }
  1414. + void ActivateSpec(uint8 spec);
  1415.  
  1416. void InitGlyphsForLevel();
  1417. void SetGlyphSlot(uint8 slot, uint32 slottype) { SetUInt32Value(PLAYER_FIELD_GLYPH_SLOTS_1 + slot, slottype); }
  1418. uint32 GetGlyphSlot(uint8 slot) { return GetUInt32Value(PLAYER_FIELD_GLYPH_SLOTS_1 + slot); }
  1419. - void SetGlyph(uint8 slot, uint32 glyph) { SetUInt32Value(PLAYER_FIELD_GLYPHS_1 + slot, glyph); }
  1420. - uint32 GetGlyph(uint8 slot) { return GetUInt32Value(PLAYER_FIELD_GLYPHS_1 + slot); }
  1421. + void SetGlyph(uint8 slot, uint32 glyph)
  1422. + {
  1423. + m_Glyphs[m_activeSpec][slot] = glyph;
  1424. + SetUInt32Value(PLAYER_FIELD_GLYPHS_1 + slot, glyph);
  1425. + }
  1426. + uint32 GetGlyph(uint8 slot) { return m_Glyphs[m_activeSpec][slot]; }
  1427.  
  1428. uint32 GetFreePrimaryProfessionPoints() const { return GetUInt32Value(PLAYER_CHARACTER_POINTS2); }
  1429. void SetFreePrimaryProfessions(uint16 profs) { SetUInt32Value(PLAYER_CHARACTER_POINTS2, profs); }
  1430. @@ -1691,7 +1709,8 @@ class MANGOS_DLL_SPEC Player : public Unit
  1431. static bool IsActionButtonDataValid(uint8 button, uint32 action, uint8 type, Player* player);
  1432. ActionButton* addActionButton(uint8 button, uint32 action, uint8 type);
  1433. void removeActionButton(uint8 button);
  1434. - void SendInitialActionButtons() const;
  1435. + void SendInitialActionButtons() const { SendActionButtons(0); }
  1436. + void SendActionButtons(uint32 state) const;
  1437.  
  1438. PvPInfo pvpInfo;
  1439. void UpdatePvP(bool state, bool ovrride=false);
  1440. @@ -2347,7 +2366,7 @@ class MANGOS_DLL_SPEC Player : public Unit
  1441. /*** LOAD SYSTEM ***/
  1442. /*********************************************************/
  1443.  
  1444. - void _LoadActions(QueryResult *result);
  1445. + void _LoadActions(QueryResult *result, bool startup);
  1446. void _LoadAuras(QueryResult *result, uint32 timediff);
  1447. void _LoadGlyphAuras();
  1448. void _LoadBoundInstances(QueryResult *result);
  1449. @@ -2366,6 +2385,8 @@ class MANGOS_DLL_SPEC Player : public Unit
  1450. void _LoadArenaTeamInfo(QueryResult *result);
  1451. void _LoadEquipmentSets(QueryResult *result);
  1452. void _LoadBGData(QueryResult* result);
  1453. + void _LoadGlyphs(QueryResult *result);
  1454. + void _LoadTalents(QueryResult *result);
  1455.  
  1456. /*********************************************************/
  1457. /*** SAVE SYSTEM ***/
  1458. @@ -2381,6 +2402,8 @@ class MANGOS_DLL_SPEC Player : public Unit
  1459. void _SaveSpells();
  1460. void _SaveEquipmentSets();
  1461. void _SaveBGData();
  1462. + void _SaveGlyphs();
  1463. + void _SaveTalents();
  1464.  
  1465. void _SetCreateBits(UpdateMask *updateMask, Player *target) const;
  1466. void _SetUpdateBits(UpdateMask *updateMask, Player *target) const;
  1467. @@ -2433,11 +2456,14 @@ class MANGOS_DLL_SPEC Player : public Unit
  1468. PlayerMails m_mail;
  1469. PlayerSpellMap m_spells;
  1470. SpellCooldowns m_spellCooldowns;
  1471. + PlayerTalentMap *m_talents[MAX_TALENT_SPECS];
  1472. uint32 m_lastPotionId; // last used health/mana potion in combat, that block next potion use
  1473.  
  1474. uint32 m_activeSpec;
  1475. uint32 m_specsCount;
  1476.  
  1477. + uint32 m_Glyphs[MAX_TALENT_SPECS][MAX_GLYPH_SLOT_INDEX];
  1478. +
  1479. ActionButtonList m_actionButtons;
  1480.  
  1481. float m_auraBaseMod[BASEMOD_END][MOD_END];
  1482. diff --git a/src/game/SharedDefines.h b/src/game/SharedDefines.h
  1483. index 9074a2a..325f957 100644
  1484. --- a/src/game/SharedDefines.h
  1485. +++ b/src/game/SharedDefines.h
  1486. @@ -449,6 +449,10 @@ const uint32 ItemQualityColors[MAX_ITEM_QUALITY] = {
  1487. #define SPELL_ATTR_EX6_UNK30 0x40000000 // 30 not set in 3.0.3
  1488. #define SPELL_ATTR_EX6_UNK31 0x80000000 // 31 not set in 3.0.3
  1489.  
  1490. +#define MIN_TALENT_SPEC 0
  1491. +#define MAX_TALENT_SPEC 1
  1492. +#define MIN_TALENT_SPECS 1
  1493. +#define MAX_TALENT_SPECS 2
  1494. #define MAX_GLYPH_SLOT_INDEX 6
  1495.  
  1496. enum SheathTypes
  1497. diff --git a/src/game/Spell.h b/src/game/Spell.h
  1498. index 00878b4..83f7e52 100644
  1499. --- a/src/game/Spell.h
  1500. +++ b/src/game/Spell.h
  1501. @@ -327,6 +327,8 @@ class Spell
  1502. void EffectTitanGrip(uint32 i);
  1503. void EffectEnchantItemPrismatic(uint32 i);
  1504. void EffectPlayMusic(uint32 i);
  1505. + void EffectSpecCount(uint32 i);
  1506. + void EffectActivateSpec(uint32 i);
  1507.  
  1508. Spell( Unit* Caster, SpellEntry const *info, bool triggered, uint64 originalCasterGUID = 0, Spell** triggeringContainer = NULL );
  1509. ~Spell();
  1510. diff --git a/src/game/SpellEffects.cpp b/src/game/SpellEffects.cpp
  1511. index 5e18c1b..7f6ced2 100644
  1512. --- a/src/game/SpellEffects.cpp
  1513. +++ b/src/game/SpellEffects.cpp
  1514. @@ -218,8 +218,8 @@ pEffect SpellEffects[TOTAL_SPELL_EFFECTS]=
  1515. &Spell::EffectMilling, //158 SPELL_EFFECT_MILLING milling
  1516. &Spell::EffectRenamePet, //159 SPELL_EFFECT_ALLOW_RENAME_PET allow rename pet once again
  1517. &Spell::EffectNULL, //160 SPELL_EFFECT_160 unused
  1518. - &Spell::EffectNULL, //161 SPELL_EFFECT_TALENT_SPEC_COUNT second talent spec (learn/revert)
  1519. - &Spell::EffectNULL, //162 SPELL_EFFECT_TALENT_SPEC_SELECT activate primary/secondary spec
  1520. + &Spell::EffectSpecCount, //161 SPELL_EFFECT_TALENT_SPEC_COUNT second talent spec (learn/revert)
  1521. + &Spell::EffectActivateSpec, //162 SPELL_EFFECT_TALENT_SPEC_SELECT activate primary/secondary spec
  1522. };
  1523.  
  1524. void Spell::EffectNULL(uint32 /*i*/)
  1525. @@ -6965,3 +6965,19 @@ void Spell::EffectPlayMusic(uint32 i)
  1526. data << uint32(soundid);
  1527. ((Player*)unitTarget)->GetSession()->SendPacket(&data);
  1528. }
  1529. +
  1530. +void Spell::EffectSpecCount(uint32 /*eff_idx*/)
  1531. +{
  1532. + if(!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER)
  1533. + return;
  1534. +
  1535. + ((Player*)unitTarget)->UpdateSpecCount(damage);
  1536. +}
  1537. +
  1538. +void Spell::EffectActivateSpec(uint32 /*eff_idx*/)
  1539. +{
  1540. + if(!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER)
  1541. + return;
  1542. +
  1543. + ((Player*)unitTarget)->ActivateSpec(damage-1); // damage is 1 or 2, spec is 0 or 1
  1544. +}
  1545. diff --git a/src/game/World.cpp b/src/game/World.cpp
  1546. index a9e6f4b..9aee063 100644
  1547. --- a/src/game/World.cpp
  1548. +++ b/src/game/World.cpp
  1549. @@ -701,6 +701,8 @@ void World::LoadConfigSettings(bool reload)
  1550. m_configs[CONFIG_MAX_PLAYER_LEVEL] = MAX_LEVEL;
  1551. }
  1552.  
  1553. + m_configs[CONFIG_MIN_DUALSPEC_LEVEL] = sConfig.GetIntDefault("MinDualSpecLevel", 40);
  1554. +
  1555. m_configs[CONFIG_START_PLAYER_LEVEL] = sConfig.GetIntDefault("StartPlayerLevel", 1);
  1556. if(m_configs[CONFIG_START_PLAYER_LEVEL] < 1)
  1557. {
  1558. @@ -973,7 +975,7 @@ void World::LoadConfigSettings(bool reload)
  1559. m_configs[CONFIG_ARENA_SEASON_ID] = sConfig.GetIntDefault ("Arena.ArenaSeason.ID", 1);
  1560. m_configs[CONFIG_ARENA_SEASON_IN_PROGRESS] = sConfig.GetBoolDefault("Arena.ArenaSeason.InProgress", true);
  1561.  
  1562. - m_configs[CONFIG_OFFHAND_CHECK_AT_TALENTS_RESET] = sConfig.GetBoolDefault("OffhandCheckAtTalentsReset", false);
  1563. + m_configs[CONFIG_OFFHAND_CHECK_AT_SPELL_UNLEARN] = sConfig.GetBoolDefault("OffhandCheckAtSpellUnlearn", false);
  1564.  
  1565. if(int clientCacheId = sConfig.GetIntDefault("ClientCacheVersion", 0))
  1566. {
  1567. @@ -1003,6 +1005,10 @@ void World::LoadConfigSettings(bool reload)
  1568. m_configs[CONFIG_TIMERBAR_FIRE_GMLEVEL] = sConfig.GetIntDefault("TimerBar.Fire.GMLevel", SEC_CONSOLE);
  1569. m_configs[CONFIG_TIMERBAR_FIRE_MAX] = sConfig.GetIntDefault("TimerBar.Fire.Max", 1);
  1570.  
  1571. + // External Mail
  1572. + m_configs[CONFIG_EXTERNAL_MAIL] = sConfig.GetIntDefault("ExternalMail.Enabled", 0);
  1573. + m_configs[CONFIG_EXTERNAL_MAIL_INTERVAL] = sConfig.GetIntDefault("ExternalMail.Interval", 1);
  1574. +
  1575. m_VisibleUnitGreyDistance = sConfig.GetFloatDefault("Visibility.Distance.Grey.Unit", 1);
  1576. if(m_VisibleUnitGreyDistance > MAX_VISIBILITY_DISTANCE)
  1577. {
  1578. @@ -1484,6 +1490,7 @@ void World::SetInitialWorldSettings()
  1579. //Update "uptime" table based on configuration entry in minutes.
  1580. m_timers[WUPDATE_CORPSES].SetInterval(20*MINUTE*IN_MILISECONDS);
  1581. //erase corpses every 20 minutes
  1582. + m_timers[WUPDATE_EXT_MAIL].SetInterval(m_configs[CONFIG_EXTERNAL_MAIL_INTERVAL] * MINUTE * IN_MILISECONDS);
  1583.  
  1584. //to set mailtimer to return mails every day between 4 and 5 am
  1585. //mailtimer is increased when updating auctions
  1586. @@ -1678,6 +1685,13 @@ void World::Update(uint32 diff)
  1587. m_timers[WUPDATE_EVENTS].Reset();
  1588. }
  1589.  
  1590. + // External Mail
  1591. + if(m_configs[CONFIG_EXTERNAL_MAIL] != 0 && m_timers[WUPDATE_EXT_MAIL].Passed())
  1592. + {
  1593. + WorldSession::SendExternalMails();
  1594. + m_timers[WUPDATE_EXT_MAIL].Reset();
  1595. + }
  1596. +
  1597. /// </ul>
  1598. ///- Move all creatures with "delayed move" and remove and delete all objects with "delayed remove"
  1599. sMapMgr.DoDelayedMovesAndRemoves();
  1600. diff --git a/src/game/World.h b/src/game/World.h
  1601. index 170fc2a..d64e627 100644
  1602. --- a/src/game/World.h
  1603. +++ b/src/game/World.h
  1604. @@ -77,7 +77,8 @@ enum WorldTimers
  1605. WUPDATE_UPTIME = 4,
  1606. WUPDATE_CORPSES = 5,
  1607. WUPDATE_EVENTS = 6,
  1608. - WUPDATE_COUNT = 7
  1609. + WUPDATE_EXT_MAIL = 7,
  1610. + WUPDATE_COUNT = 8
  1611. };
  1612.  
  1613. /// Configuration elements
  1614. @@ -118,6 +119,7 @@ enum WorldConfigs
  1615. CONFIG_MIN_LEVEL_FOR_HEROIC_CHARACTER_CREATING,
  1616. CONFIG_SKIP_CINEMATICS,
  1617. CONFIG_MAX_PLAYER_LEVEL,
  1618. + CONFIG_MIN_DUALSPEC_LEVEL,
  1619. CONFIG_START_PLAYER_LEVEL,
  1620. CONFIG_START_HEROIC_PLAYER_LEVEL,
  1621. CONFIG_START_PLAYER_MONEY,
  1622. @@ -212,7 +214,7 @@ enum WorldConfigs
  1623. CONFIG_ARENA_QUEUE_ANNOUNCER_ENABLE,
  1624. CONFIG_ARENA_SEASON_ID,
  1625. CONFIG_ARENA_SEASON_IN_PROGRESS,
  1626. - CONFIG_OFFHAND_CHECK_AT_TALENTS_RESET,
  1627. + CONFIG_OFFHAND_CHECK_AT_SPELL_UNLEARN,
  1628. CONFIG_CLIENTCACHE_VERSION,
  1629. CONFIG_GUILD_EVENT_LOG_COUNT,
  1630. CONFIG_GUILD_BANK_EVENT_LOG_COUNT,
  1631. @@ -222,7 +224,12 @@ enum WorldConfigs
  1632. CONFIG_TIMERBAR_BREATH_MAX,
  1633. CONFIG_TIMERBAR_FIRE_GMLEVEL,
  1634. CONFIG_TIMERBAR_FIRE_MAX,
  1635. - CONFIG_VALUE_COUNT
  1636. +
  1637. + // External Mail
  1638. + CONFIG_EXTERNAL_MAIL,
  1639. + CONFIG_EXTERNAL_MAIL_INTERVAL,
  1640. +
  1641. + CONFIG_VALUE_COUNT,
  1642. };
  1643.  
  1644. /// Server rates
  1645. diff --git a/src/game/WorldSession.h b/src/game/WorldSession.h
  1646. index a1aad7b..b9d2a68 100644
  1647. --- a/src/game/WorldSession.h
  1648. +++ b/src/game/WorldSession.h
  1649. @@ -226,6 +226,9 @@ class MANGOS_DLL_SPEC WorldSession
  1650. //used with item_page table
  1651. bool SendItemInfo( uint32 itemid, WorldPacket data );
  1652.  
  1653. + // External mail
  1654. + static void SendExternalMails();
  1655. +
  1656. //auction
  1657. void SendAuctionHello( uint64 guid, Creature * unit );
  1658. void SendAuctionCommandResult( uint32 auctionId, uint32 Action, uint32 ErrorCode, uint32 bidError = 0);
  1659. diff --git a/src/mangosd/mangosd.conf.dist.in b/src/mangosd/mangosd.conf.dist.in
  1660. index 479a6ce..eb6ad69 100644
  1661. --- a/src/mangosd/mangosd.conf.dist.in
  1662. +++ b/src/mangosd/mangosd.conf.dist.in
  1663. @@ -595,6 +595,15 @@ LogColors = ""
  1664. # Mail delivery delay time for item sending
  1665. # Default: 3600 sec (1 hour)
  1666. #
  1667. +# ExternalMail.Enabled
  1668. +# Enable external mail delivery from mail_external table.
  1669. +# Default: 0 (disabled)
  1670. +# 1 (enabled)
  1671. +#
  1672. +# ExternalMail.Interval
  1673. +# Mail delivery delay time for item sending from mail_external table, in minutes.
  1674. +# Default: 1 minute
  1675. +#
  1676. # SkillChance.Prospecting
  1677. # For prospecting skillup impossible by default, but can be allowed as custom setting
  1678. # Default: 0 - no skilups
  1679. @@ -646,6 +655,7 @@ HeroicCharactersPerRealm = 1
  1680. MinLevelForHeroicCharacterCreating = 55
  1681. SkipCinematics = 0
  1682. MaxPlayerLevel = 80
  1683. +MinDualSpecLevel = 40
  1684. StartPlayerLevel = 1
  1685. StartHeroicPlayerLevel = 55
  1686. StartPlayerMoney = 0
  1687. @@ -677,9 +687,11 @@ MaxPrimaryTradeSkill = 2
  1688. MinPetitionSigns = 9
  1689. MaxGroupXPDistance = 74
  1690. MailDeliveryDelay = 3600
  1691. +ExternalMail.Enabled = 0
  1692. +ExternalMail.Interval = 1
  1693. SkillChance.Prospecting = 0
  1694. SkillChance.Milling = 0
  1695. -OffhandCheckAtTalentsReset = 0
  1696. +OffhandCheckAtSpellUnlearn = 0
  1697. ClientCacheVersion = 0
  1698. Event.Announce = 0
  1699. BeepAtStart = 1
  1700.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement