diff --git a/sql/characters.sql b/sql/characters.sql index 9aee7b2d2..1e0c77c4b 100644 --- a/sql/characters.sql +++ b/sql/characters.sql @@ -21,7 +21,7 @@ DROP TABLE IF EXISTS `character_db_version`; CREATE TABLE `character_db_version` ( - `required_12150_01_characters_saved_variables` bit(1) default NULL + `required_12161_01_characters_characters` bit(1) default NULL ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED COMMENT='Last applied sql update to DB'; -- @@ -221,6 +221,7 @@ CREATE TABLE `characters` ( `rest_bonus` float NOT NULL default '0', `resettalents_cost` int(11) unsigned NOT NULL default '0', `resettalents_time` bigint(20) unsigned NOT NULL default '0', + `primary_trees` varchar(10) NOT NULL DEFAULT '0 0 ', `trans_x` float NOT NULL default '0', `trans_y` float NOT NULL default '0', `trans_z` float NOT NULL default '0', diff --git a/sql/updates/12161_01_characters_characters.sql b/sql/updates/12161_01_characters_characters.sql new file mode 100644 index 000000000..e7d29c780 --- /dev/null +++ b/sql/updates/12161_01_characters_characters.sql @@ -0,0 +1,3 @@ +ALTER TABLE character_db_version CHANGE COLUMN required_12150_01_characters_saved_variables required_12161_01_characters_characters bit; + +ALTER TABLE `characters` ADD `primary_trees` varchar(10) NOT NULL DEFAULT '0 0 ' AFTER `resettalents_time`; diff --git a/src/game/CharacterHandler.cpp b/src/game/CharacterHandler.cpp index b1ad6df00..5e877e154 100644 --- a/src/game/CharacterHandler.cpp +++ b/src/game/CharacterHandler.cpp @@ -71,7 +71,7 @@ bool LoginQueryHolder::Initialize() // !!! NOTE: including unused `zone`,`online` res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADFROM, "SELECT guid, account, 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," + "resettalents_time, primary_trees, trans_x, trans_y, trans_z, trans_o, transguid, extra_flags, stable_slots, at_login, zone, online, death_expire_time, taxi_path, dungeon_difficulty," "totalKills, todayKills, yesterdayKills, chosenTitle, watchedFaction, drunk," "health, power1, power2, power3, power4, power5, specCount, activeSpec, exploredZones, equipmentCache, knownTitles, actionBars, slot FROM characters WHERE guid = '%u'", m_guid.GetCounter()); res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADGROUP, "SELECT groupId FROM group_member WHERE memberGuid ='%u'", m_guid.GetCounter()); diff --git a/src/game/DBCStructure.h b/src/game/DBCStructure.h index 72086356f..e317f1a6b 100644 --- a/src/game/DBCStructure.h +++ b/src/game/DBCStructure.h @@ -2128,8 +2128,9 @@ struct SummonPropertiesEntry uint32 Flags; // 5 m_flags (enum SummonPropFlags) }; -#define MAX_TALENT_RANK 3 +#define MAX_TALENT_RANK 5 #define MAX_PET_TALENT_RANK 3 // use in calculations, expected <= MAX_TALENT_RANK +#define MAX_TALENT_TABS 3 struct TalentEntry { @@ -2138,11 +2139,13 @@ struct TalentEntry uint32 Row; // 2 m_tierID uint32 Col; // 3 m_columnIndex uint32 RankID[MAX_TALENT_RANK]; // 4-6 m_spellRank - uint32 DependsOn[MAX_TALENT_RANK]; // 9-11 m_prereqTalent (Talent.dbc) // - uint8 DependsOnRank[MAX_TALENT_RANK]; // 11-13 part of prev field // - uint8 needAddInSpellBook; // 14 m_flags also need disable higest ranks on reset talent tree - uint32 unk1; // 15 m_requiredSpellID - //uint64 allowForPet; // 16 m_categoryMask its a 64 bit mask for pet 1<GetPackGUID(); + data << plr->GetObjectGuid(); if (sWorld.getConfig(CONFIG_BOOL_TALENTS_INSPECTING) || _player->isGameMaster()) plr->BuildPlayerTalentsInfoData(&data); @@ -1123,6 +1124,13 @@ void WorldSession::HandleInspectOpcode(WorldPacket& recv_data) } plr->BuildEnchantmentsInfoData(&data); + if (Guild* guild = sGuildMgr.GetGuildById(plr->GetGuildId())) + { + data << uint64(0/*guild->GetGUID()*/); + data << uint32(0/*guild->GetLevel()*/); + data << uint64(0/*guild->GetXP()*/); + data << uint32(0/*guild->GetMembersCount()*/); // number of members + } SendPacket(&data); } diff --git a/src/game/Opcodes.cpp b/src/game/Opcodes.cpp index 48815d8a3..f89eccc18 100644 --- a/src/game/Opcodes.cpp +++ b/src/game/Opcodes.cpp @@ -343,7 +343,7 @@ void InitializeOpcodes() OPCODE(CMSG_DESTROYITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleDestroyItemOpcode ); OPCODE(SMSG_INVENTORY_CHANGE_FAILURE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); OPCODE(SMSG_OPEN_CONTAINER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); - //OPCODE(CMSG_INSPECT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleInspectOpcode ); + OPCODE(CMSG_INSPECT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleInspectOpcode ); //OPCODE(SMSG_INSPECT_RESULTS_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); //OPCODE(CMSG_INITIATE_TRADE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleInitiateTradeOpcode ); //OPCODE(CMSG_BEGIN_TRADE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBeginTradeOpcode ); @@ -655,7 +655,7 @@ void InitializeOpcodes() OPCODE(SMSG_PERIODICAURALOG, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); OPCODE(SMSG_SPELLDAMAGESHIELD, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); OPCODE(SMSG_SPELLNONMELEEDAMAGELOG, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); - //OPCODE(CMSG_LEARN_TALENT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLearnTalentOpcode ); + OPCODE(CMSG_LEARN_TALENT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLearnTalentOpcode ); //OPCODE(SMSG_RESURRECT_FAILED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); OPCODE(CMSG_TOGGLE_PVP, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTogglePvP ); //OPCODE(SMSG_ZONE_UNDER_ATTACK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); @@ -744,7 +744,7 @@ void InitializeOpcodes() //OPCODE(SMSG_GAMEOBJECT_RESET_STATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); OPCODE(CMSG_REPAIR_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleRepairItemOpcode ); OPCODE(SMSG_CHAT_PLAYER_NOT_FOUND, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); - //OPCODE(MSG_TALENT_WIPE_CONFIRM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTalentWipeConfirmOpcode ); + OPCODE(MSG_TALENT_WIPE_CONFIRM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTalentWipeConfirmOpcode ); //OPCODE(SMSG_SUMMON_REQUEST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); //OPCODE(CMSG_SUMMON_RESPONSE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSummonResponseOpcode ); //OPCODE(MSG_DEV_SHOWLABEL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); @@ -1072,7 +1072,7 @@ void InitializeOpcodes() //OPCODE(SMSG_USERLIST_REMOVE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); //OPCODE(SMSG_USERLIST_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); //OPCODE(CMSG_CLEAR_CHANNEL_WATCH, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); - //OPCODE(SMSG_INSPECT_RESULTS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_INSPECT_RESULTS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); //OPCODE(SMSG_GOGOGO_OBSOLETE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); //OPCODE(SMSG_ECHO_PARTY_SQUELCH, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); //OPCODE(CMSG_SET_TITLE_SUFFIX, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); @@ -1206,7 +1206,7 @@ void InitializeOpcodes() //OPCODE(CMSG_REQUEST_VEHICLE_PREV_SEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); //OPCODE(CMSG_REQUEST_VEHICLE_NEXT_SEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); //OPCODE(CMSG_REQUEST_VEHICLE_SWITCH_SEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); - //OPCODE(CMSG_PET_LEARN_TALENT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetLearnTalent ); + OPCODE(CMSG_PET_LEARN_TALENT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetLearnTalent ); //OPCODE(CMSG_PET_UNLEARN_TALENTS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); OPCODE(SMSG_SET_PHASE_SHIFT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); OPCODE(SMSG_ALL_ACHIEVEMENT_DATA, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); @@ -1276,9 +1276,9 @@ void InitializeOpcodes() //OPCODE(CMSG_SAVE_EQUIPMENT_SET, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleEquipmentSetSaveOpcode ); //OPCODE(CMSG_ON_MISSILE_TRAJECTORY_COLLISION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); //OPCODE(SMSG_NOTIFY_MISSILE_TRAJECTORY_COLLISION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); - //OPCODE(SMSG_TALENT_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); - //OPCODE(CMSG_LEARN_TALENT_GROUP, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLearnPreviewTalents ); - //OPCODE(CMSG_PET_LEARN_TALENT_GROUP, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLearnPreviewTalentsPet ); + OPCODE(SMSG_TALENT_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_LEARN_TALENT_GROUP, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLearnPreviewTalents ); + OPCODE(CMSG_PET_LEARN_TALENT_GROUP, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLearnPreviewTalentsPet ); //OPCODE(CMSG_SET_ACTIVE_TALENT_GROUP_OBSOLETE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); //OPCODE(CMSG_GM_GRANT_ACHIEVEMENT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); //OPCODE(CMSG_GM_REMOVE_ACHIEVEMENT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); diff --git a/src/game/Opcodes.h b/src/game/Opcodes.h index ffa524b36..8a70574b5 100644 --- a/src/game/Opcodes.h +++ b/src/game/Opcodes.h @@ -328,7 +328,7 @@ enum Opcodes CMSG_DESTROYITEM = 0x4A27, // 4.3.4 15595 SMSG_INVENTORY_CHANGE_FAILURE = 0x2236, // 4.3.4 15595 SMSG_OPEN_CONTAINER = 0x4714, // 4.3.4 15595 - CMSG_INSPECT = 0x1115, + CMSG_INSPECT = 0x0927, // 4.3.4 15595 SMSG_INSPECT_RESULTS_UPDATE = 0x1116, CMSG_INITIATE_TRADE = 0x1117, CMSG_BEGIN_TRADE = 0x1118, @@ -640,7 +640,7 @@ enum Opcodes SMSG_PERIODICAURALOG = 0x0416, // 4.3.4 15595 SMSG_SPELLDAMAGESHIELD = 0x2927, // 4.3.4 15595 SMSG_SPELLNONMELEEDAMAGELOG = 0x4315, // 4.3.4 15595 - CMSG_LEARN_TALENT = 0x1252, + CMSG_LEARN_TALENT = 0x0306, // 4.3.4 15595 SMSG_RESURRECT_FAILED = 0x1253, CMSG_TOGGLE_PVP = 0x6815, // 4.3.4 15595 SMSG_ZONE_UNDER_ATTACK = 0x1255, @@ -729,7 +729,7 @@ enum Opcodes SMSG_GAMEOBJECT_RESET_STATE = 0x12A8, CMSG_REPAIR_ITEM = 0x2917, // 4.3.4 15595 SMSG_CHAT_PLAYER_NOT_FOUND = 0x2526, // 4.3.4 15595 - MSG_TALENT_WIPE_CONFIRM = 0x12AB, + MSG_TALENT_WIPE_CONFIRM = 0x0107, // 4.3.4 15595 SMSG_SUMMON_REQUEST = 0x12AC, CMSG_SUMMON_RESPONSE = 0x12AD, MSG_DEV_SHOWLABEL = 0x12AE, @@ -1057,7 +1057,7 @@ enum Opcodes SMSG_USERLIST_REMOVE = 0x13F2, SMSG_USERLIST_UPDATE = 0x13F3, CMSG_CLEAR_CHANNEL_WATCH = 0x13F4, - SMSG_INSPECT_RESULTS = 0x13F5, + SMSG_INSPECT_RESULTS = 0x4014, // 4.3.4 15595 SMSG_GOGOGO_OBSOLETE = 0x13F6, SMSG_ECHO_PARTY_SQUELCH = 0x13F7, CMSG_SET_TITLE_SUFFIX = 0x13F8, @@ -1191,7 +1191,7 @@ enum Opcodes CMSG_REQUEST_VEHICLE_PREV_SEAT = 0x1478, CMSG_REQUEST_VEHICLE_NEXT_SEAT = 0x1479, CMSG_REQUEST_VEHICLE_SWITCH_SEAT = 0x147A, - CMSG_PET_LEARN_TALENT = 0x147B, + CMSG_PET_LEARN_TALENT = 0x6725, // 4.3.4 15595 CMSG_PET_UNLEARN_TALENTS = 0x147C, SMSG_SET_PHASE_SHIFT = 0x70A0, // 4.3.4 15595 SMSG_ALL_ACHIEVEMENT_DATA = 0x58B1, // 4.3.4 15595 @@ -1261,9 +1261,9 @@ enum Opcodes CMSG_SAVE_EQUIPMENT_SET = 0x14BE, CMSG_ON_MISSILE_TRAJECTORY_COLLISION = 0x14BF, SMSG_NOTIFY_MISSILE_TRAJECTORY_COLLISION = 0x14C0, - SMSG_TALENT_UPDATE = 0x14C1, - CMSG_LEARN_TALENT_GROUP = 0x14C2, - CMSG_PET_LEARN_TALENT_GROUP = 0x14C3, + SMSG_TALENT_UPDATE = 0x6F26, // 4.3.4 15595 + CMSG_LEARN_TALENT_GROUP = 0x2415, // 4.3.4 15595 + CMSG_PET_LEARN_TALENT_GROUP = 0x6E24, // 4.3.4 15595 CMSG_SET_ACTIVE_TALENT_GROUP_OBSOLETE = 0x14C4, CMSG_GM_GRANT_ACHIEVEMENT = 0x14C5, CMSG_GM_REMOVE_ACHIEVEMENT = 0x14C6, diff --git a/src/game/Player.cpp b/src/game/Player.cpp index 2b9cd34bb..72fd65d37 100644 --- a/src/game/Player.cpp +++ b/src/game/Player.cpp @@ -416,6 +416,7 @@ Player::Player(WorldSession* session): Unit(), m_mover(this), m_camera(this), m_ m_usedTalentCount = 0; m_questRewardTalentCount = 0; + m_freeTalentPoints = 0; m_regenTimer = 0; m_weaponChangeTimer = 0; @@ -535,6 +536,8 @@ Player::Player(WorldSession* session): Unit(), m_mover(this), m_camera(this), m_ m_activeSpec = 0; m_specsCount = 1; + for (int i = 0; i < MAX_TALENT_SPEC_COUNT; ++i) + m_talentsPrimaryTree[i] = 0; for (int i = 0; i < BASEMOD_END; ++i) { @@ -2637,6 +2640,12 @@ void Player::UpdateFreeTalentPoints(bool resetIfNeed) } else { + if (m_specsCount == 0) + { + m_specsCount = 1; + m_activeSpec = 0; + } + uint32 talentPointsForLevel = CalculateTalentsPoints(); // if used more that have then reset @@ -3837,6 +3846,17 @@ bool Player::resetTalents(bool no_cost, bool all_specs) iter = m_talents[m_activeSpec].begin(); } + // Remove spec specific spells + for (uint32 i = 0; i < MAX_TALENT_TABS; ++i) + { + std::vector const* specSpells = GetTalentTreePrimarySpells(GetTalentTabPages(getClass())[i]); + if (specSpells) + for (size_t i = 0; i < specSpells->size(); ++i) + removeSpell(specSpells->at(i), true); + } + + m_talentsPrimaryTree[m_activeSpec] = 0; + // for not current spec just mark removed all saved to DB case and drop not saved if (all_specs) { @@ -15193,11 +15213,11 @@ bool Player::LoadFromDB(ObjectGuid guid, SqlQueryHolder* holder) // SELECT guid, account, name, race, class, gender, level, xp, money, playerBytes, playerBytes2, playerFlags," // 12 13 14 15 16 17 18 19 20 21 22 23 24 //"position_x, position_y, position_z, map, orientation, taximask, cinematic, totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost," - // 25 26 27 28 29 30 31 32 33 34 35 36 37 38 - //"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," - // 39 40 41 42 43 44 + // 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 + //"resettalents_time, primary_trees, trans_x, trans_y, trans_z, trans_o, transguid, extra_flags, stable_slots, at_login, zone, online, death_expire_time, taxi_path, dungeon_difficulty," + // 40 41 42 43 44 45 //"totalKills, todayKills, yesterdayKills, chosenTitle, watchedFaction, drunk," - // 45 46 47 48 49 50 51 52 53 54 55 56 57 + // 46 47 48 49 50 51 52 53 54 55 56 57 58 //"health, power1, power2, power3, power4, power5, specCount, activeSpec, exploredZones, equipmentCache, knownTitles, actionBars, slot FROM characters WHERE guid = '%u'", GUID_LOPART(m_guid)); QueryResult *result = holder->GetResult(PLAYER_LOGIN_QUERY_LOADFROM); @@ -15248,8 +15268,8 @@ bool Player::LoadFromDB(ObjectGuid guid, SqlQueryHolder* holder) SetUInt32Value(UNIT_FIELD_LEVEL, fields[6].GetUInt8()); SetUInt32Value(PLAYER_XP, fields[7].GetUInt32()); - _LoadIntoDataField(fields[53].GetString(), PLAYER_EXPLORED_ZONES_1, PLAYER_EXPLORED_ZONES_SIZE); - _LoadIntoDataField(fields[55].GetString(), PLAYER__FIELD_KNOWN_TITLES, KNOWN_TITLES_SIZE*2); + _LoadIntoDataField(fields[54].GetString(), PLAYER_EXPLORED_ZONES_1, PLAYER_EXPLORED_ZONES_SIZE); + _LoadIntoDataField(fields[56].GetString(), PLAYER__FIELD_KNOWN_TITLES, KNOWN_TITLES_SIZE*2); InitDisplayIds(); // model, scale and model data @@ -15267,17 +15287,17 @@ bool Player::LoadFromDB(ObjectGuid guid, SqlQueryHolder* holder) SetUInt32Value(PLAYER_BYTES, fields[9].GetUInt32()); SetUInt32Value(PLAYER_BYTES_2, fields[10].GetUInt32()); - m_drunk = fields[44].GetUInt16(); + m_drunk = fields[45].GetUInt16(); SetUInt16Value(PLAYER_BYTES_3, 0, (m_drunk & 0xFFFE) | gender); SetUInt32Value(PLAYER_FLAGS, fields[11].GetUInt32()); - SetInt32Value(PLAYER_FIELD_WATCHED_FACTION_INDEX, fields[43].GetInt32()); + SetInt32Value(PLAYER_FIELD_WATCHED_FACTION_INDEX, fields[44].GetInt32()); // Action bars state - SetByteValue(PLAYER_FIELD_BYTES, 2, fields[56].GetUInt8()); + SetByteValue(PLAYER_FIELD_BYTES, 2, fields[57].GetUInt8()); - m_slot = fields[57].GetUInt8(); + m_slot = fields[58].GetUInt8(); // cleanup inventory related item value fields (its will be filled correctly in _LoadInventory) for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot) @@ -15307,11 +15327,11 @@ bool Player::LoadFromDB(ObjectGuid guid, SqlQueryHolder* holder) InitPrimaryProfessions(); // to max set before any spell loaded // init saved position, and fix it later if problematic - uint32 transGUID = fields[30].GetUInt32(); + uint32 transGUID = fields[31].GetUInt32(); Relocate(fields[12].GetFloat(), fields[13].GetFloat(), fields[14].GetFloat(), fields[16].GetFloat()); SetLocationMapId(fields[15].GetUInt32()); - uint32 difficulty = fields[38].GetUInt32(); + uint32 difficulty = fields[39].GetUInt32(); if (difficulty >= MAX_DUNGEON_DIFFICULTY) difficulty = DUNGEON_DIFFICULTY_NORMAL; SetDungeonDifficulty(Difficulty(difficulty)); // may be changed in _LoadGroup @@ -15336,9 +15356,9 @@ bool Player::LoadFromDB(ObjectGuid guid, SqlQueryHolder* holder) SetArenaTeamInfoField(arena_slot, ArenaTeamInfoType(j), 0); } - SetUInt32Value(PLAYER_FIELD_LIFETIME_HONORBALE_KILLS, fields[39].GetUInt32()); - SetUInt16Value(PLAYER_FIELD_KILLS, 0, fields[40].GetUInt16()); - SetUInt16Value(PLAYER_FIELD_KILLS, 1, fields[41].GetUInt16()); + SetUInt32Value(PLAYER_FIELD_LIFETIME_HONORBALE_KILLS, fields[40].GetUInt32()); + SetUInt16Value(PLAYER_FIELD_KILLS, 0, fields[41].GetUInt16()); + SetUInt16Value(PLAYER_FIELD_KILLS, 1, fields[42].GetUInt16()); _LoadBoundInstances(holder->GetResult(PLAYER_LOGIN_QUERY_LOADBOUNDINSTANCES)); @@ -15411,7 +15431,7 @@ bool Player::LoadFromDB(ObjectGuid guid, SqlQueryHolder* holder) if (transGUID != 0) { - m_movementInfo.SetTransportData(ObjectGuid(HIGHGUID_MO_TRANSPORT, transGUID), fields[26].GetFloat(), fields[27].GetFloat(), fields[28].GetFloat(), fields[29].GetFloat(), 0, -1); + m_movementInfo.SetTransportData(ObjectGuid(HIGHGUID_MO_TRANSPORT, transGUID), fields[27].GetFloat(), fields[28].GetFloat(), fields[29].GetFloat(), fields[30].GetFloat(), 0, -1); if (!MaNGOS::IsValidMapCoord( GetPositionX() + m_movementInfo.GetTransportPos()->x, GetPositionY() + m_movementInfo.GetTransportPos()->y, @@ -15524,22 +15544,22 @@ bool Player::LoadFromDB(ObjectGuid guid, SqlQueryHolder* holder) m_taxi.LoadTaxiMask(fields[17].GetString()); // must be before InitTaxiNodesForLevel - uint32 extraflags = fields[31].GetUInt32(); + uint32 extraflags = fields[32].GetUInt32(); - m_stableSlots = fields[32].GetUInt32(); + m_stableSlots = fields[33].GetUInt32(); if (m_stableSlots > MAX_PET_STABLES) { sLog.outError("Player can have not more %u stable slots, but have in DB %u", MAX_PET_STABLES, uint32(m_stableSlots)); m_stableSlots = MAX_PET_STABLES; } - m_atLoginFlags = fields[33].GetUInt32(); + m_atLoginFlags = fields[34].GetUInt32(); - m_deathExpireTime = (time_t)fields[36].GetUInt64(); + m_deathExpireTime = (time_t)fields[37].GetUInt64(); if (m_deathExpireTime > now + MAX_DEATH_COUNT * DEATH_EXPIRE_STEP) m_deathExpireTime = now + MAX_DEATH_COUNT * DEATH_EXPIRE_STEP - 1; - std::string taxi_nodes = fields[37].GetCppString(); + std::string taxi_nodes = fields[38].GetCppString(); // clear channel spell data (if saved at channel spell casting) SetChannelObjectGuid(ObjectGuid()); @@ -15601,8 +15621,8 @@ bool Player::LoadFromDB(ObjectGuid guid, SqlQueryHolder* holder) _LoadMailedItems(holder->GetResult(PLAYER_LOGIN_QUERY_LOADMAILEDITEMS)); UpdateNextMailTimeAndUnreads(); - m_specsCount = fields[51].GetUInt8(); - m_activeSpec = fields[52].GetUInt8(); + m_specsCount = fields[52].GetUInt8(); + m_activeSpec = fields[53].GetUInt8(); _LoadGlyphs(holder->GetResult(PLAYER_LOGIN_QUERY_LOADGLYPHS)); @@ -15643,7 +15663,7 @@ bool Player::LoadFromDB(ObjectGuid guid, SqlQueryHolder* holder) // check PLAYER_CHOSEN_TITLE compatibility with PLAYER__FIELD_KNOWN_TITLES // note: PLAYER__FIELD_KNOWN_TITLES updated at quest status loaded - uint32 curTitle = fields[42].GetUInt32(); + uint32 curTitle = fields[43].GetUInt32(); if (curTitle && !HasTitle(curTitle)) curTitle = 0; @@ -15711,19 +15731,33 @@ bool Player::LoadFromDB(ObjectGuid guid, SqlQueryHolder* holder) UpdateAllStats(); // restore remembered power/health values (but not more max values) - uint32 savedhealth = fields[45].GetUInt32(); + uint32 savedhealth = fields[46].GetUInt32(); SetHealth(savedhealth > GetMaxHealth() ? GetMaxHealth() : savedhealth); static_assert(MAX_STORED_POWERS == 5, "Query not updated."); for (uint32 i = 0; i < MAX_STORED_POWERS; ++i) { - uint32 savedpower = fields[46 + i].GetUInt32(); + uint32 savedpower = fields[47 + i].GetUInt32(); SetPowerByIndex(i, std::min(savedpower, GetMaxPowerByIndex(i))); } DEBUG_FILTER_LOG(LOG_FILTER_PLAYER_STATS, "The value of player %s after load item and aura is: ", m_name.c_str()); outDebugStatsValues(); + // must be after loading spells and talents + Tokens talentTrees = StrSplit(fields[26].GetString(), " "); + for (uint8 i = 0; i < MAX_TALENT_SPEC_COUNT; ++i) + { + if (i >= talentTrees.size()) + break; + + uint32 talentTree = atol(talentTrees[i].c_str()); + if (sTalentTabStore.LookupEntry(talentTree)) + m_talentsPrimaryTree[i] = talentTree; + else if (i == m_activeSpec) + SetAtLoginFlag(AT_LOGIN_RESET_TALENTS); // invalid tree, reset talents + } + // all fields read delete result; @@ -15993,8 +16027,6 @@ void Player::_LoadGlyphs(QueryResult* result) while (result->NextRow()); delete result; - - } void Player::LoadCorpse() @@ -17034,7 +17066,7 @@ void Player::SaveToDB() SqlStatement uberInsert = CharacterDatabase.CreateStatement(insChar, "INSERT INTO characters (guid,account,name,race,class,gender,level,xp,money,playerBytes,playerBytes2,playerFlags," "map, dungeon_difficulty, position_x, position_y, position_z, orientation, " "taximask, online, cinematic, " - "totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost, resettalents_time, " + "totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost, resettalents_time, primary_trees, " "trans_x, trans_y, trans_z, trans_o, transguid, extra_flags, stable_slots, at_login, zone, " "death_expire_time, taxi_path, totalKills, " "todayKills, yesterdayKills, chosenTitle, watchedFaction, drunk, health, power1, power2, power3, " @@ -17042,7 +17074,7 @@ void Player::SaveToDB() "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, " "?, ?, ?, ?, ?, ?, " "?, ?, ?, " - "?, ?, ?, ?, ?, ?, ?, " + "?, ?, ?, ?, ?, ?, ?, ?, " "?, ?, ?, ?, ?, ?, ?, ?, ?, " "?, ?, ?, " "?, ?, ?, ?, ?, ?, ?, ?, ?, " @@ -17098,6 +17130,10 @@ void Player::SaveToDB() // save, but in tavern/city uberInsert.addUInt32(m_resetTalentsCost); uberInsert.addUInt64(uint64(m_resetTalentsTime)); + ss.str(""); + for (int i = 0; i < MAX_TALENT_SPEC_COUNT; ++i) + ss << m_talentsPrimaryTree[i] << " "; + uberInsert.addString(ss); uberInsert.addFloat(finiteAlways(m_movementInfo.GetTransportPos()->x)); uberInsert.addFloat(finiteAlways(m_movementInfo.GetTransportPos()->y)); @@ -17129,7 +17165,6 @@ void Player::SaveToDB() uberInsert.addUInt32(GetUInt32Value(PLAYER_CHOSEN_TITLE)); - // FIXME: at this moment send to DB as unsigned, including unit32(-1) uberInsert.addUInt32(GetUInt32Value(PLAYER_FIELD_WATCHED_FACTION_INDEX)); @@ -17358,20 +17393,20 @@ void Player::_SaveGlyphs() { SqlStatement stmt = CharacterDatabase.CreateStatement(insertGlyph, "INSERT INTO character_glyphs (guid, spec, slot, glyph) VALUES (?, ?, ?, ?)"); stmt.PExecute(GetGUIDLow(), spec, slot, m_glyphs[spec][slot].GetId()); + break; } - break; case GLYPH_CHANGED: { SqlStatement stmt = CharacterDatabase.CreateStatement(updateGlyph, "UPDATE character_glyphs SET glyph = ? WHERE guid = ? AND spec = ? AND slot = ?"); stmt.PExecute(m_glyphs[spec][slot].GetId(), GetGUIDLow(), spec, slot); + break; } - break; case GLYPH_DELETED: { SqlStatement stmt = CharacterDatabase.CreateStatement(deleteGlyph, "DELETE FROM character_glyphs WHERE guid = ? AND spec = ? AND slot = ?"); stmt.PExecute(GetGUIDLow(), spec, slot); + break; } - break; case GLYPH_UNCHANGED: break; } @@ -21382,25 +21417,21 @@ uint32 Player::GetBarberShopCost(uint8 newhairstyle, uint8 newhaircolor, uint8 n void Player::InitGlyphsForLevel() { - for (uint32 i = 0; i < sGlyphSlotStore.GetNumRows(); ++i) + uint32 slot = 0; + for (uint32 i = 0; i < sGlyphSlotStore.GetNumRows() && slot < MAX_GLYPH_SLOT_INDEX; ++i) if (GlyphSlotEntry const* gs = sGlyphSlotStore.LookupEntry(i)) - if (gs->Order) - SetGlyphSlot(gs->Order - 1, gs->Id); + SetGlyphSlot(slot++, gs->Id); uint32 level = getLevel(); uint32 value = 0; // 0x3F = 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 for 80 level - if (level >= 15) - value |= (0x01 | 0x02); - if (level >= 30) - value |= 0x08; + if (level >= 25) + value |= 0x01 | 0x02 | 0x40; if (level >= 50) - value |= 0x04; - if (level >= 70) - value |= 0x10; - if (level >= 80) - value |= 0x20; + value |= 0x04 | 0x08 | 0x80; + if (level >= 75) + value |= 0x10 | 0x20 | 0x100; SetUInt32Value(PLAYER_GLYPHS_ENABLED, value); } @@ -21665,10 +21696,30 @@ Item* Player::ConvertItem(Item* item, uint32 newItemId) uint32 Player::CalculateTalentsPoints() const { - uint32 base_level = getClass() == CLASS_DEATH_KNIGHT ? 55 : 9; - uint32 base_talent = getLevel() <= base_level ? 0 : getLevel() - base_level; + // this dbc file has entries only up to level 100 + NumTalentsAtLevelEntry const* count = sNumTalentsAtLevelStore.LookupEntry(std::min(getLevel(), 100)); + if (!count) + return 0; - uint32 talentPointsForLevel = base_talent + m_questRewardTalentCount; + float baseForLevel = count->Talents; + + if (getClass() != CLASS_DEATH_KNIGHT) + return uint32(baseForLevel * sWorld.getConfig(CONFIG_FLOAT_RATE_TALENT)); + + // Death Knight starting level + // hardcoded here - number of quest awarded talents is equal to number of talents any other class would have at level 55 + if (getLevel() < 55) + return 0; + + NumTalentsAtLevelEntry const* dkBase = sNumTalentsAtLevelStore.LookupEntry(55); + if (!dkBase) + return 0; + + float talentPointsForLevel = count->Talents - dkBase->Talents; + talentPointsForLevel += float(m_questRewardTalentCount); + + if (talentPointsForLevel > baseForLevel) + talentPointsForLevel = baseForLevel; return uint32(talentPointsForLevel * sWorld.getConfig(CONFIG_FLOAT_RATE_TALENT)); } @@ -21990,29 +22041,29 @@ SpellEntry const* Player::GetKnownTalentRankById(int32 talentId) const return NULL; } -void Player::LearnTalent(uint32 talentId, uint32 talentRank) +bool Player::LearnTalent(uint32 talentId, uint32 talentRank) { uint32 CurTalentPoints = GetFreeTalentPoints(); if (CurTalentPoints == 0) - return; + return false; if (talentRank >= MAX_TALENT_RANK) - return; + return false; TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId); if (!talentInfo) - return; + return false; TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TalentTab); if (!talentTabInfo) - return; + return false; // prevent learn talent for different class (cheating) if ((getClassMask() & talentTabInfo->ClassMask) == 0) - return; + return false; // find current max talent rank uint32 curtalent_maxrank = 0; @@ -22021,14 +22072,14 @@ void Player::LearnTalent(uint32 talentId, uint32 talentRank) // we already have same or higher talent rank learned if (curtalent_maxrank >= (talentRank + 1)) - return; + return false; // check if we have enough talent points if (CurTalentPoints < (talentRank - curtalent_maxrank + 1)) - return; + return false; // Check if it requires another talent - /*if (talentInfo->DependsOn > 0) + if (talentInfo->DependsOn > 0) { if (TalentEntry const* depTalentInfo = sTalentStore.LookupEntry(talentInfo->DependsOn)) { @@ -22042,40 +22093,75 @@ void Player::LearnTalent(uint32 talentId, uint32 talentRank) } if (!hasEnoughRank) - return; + return false; } - }*/ + } // Find out how many points we have in this field uint32 spentPoints = 0; + uint32 primaryTreeTalents = 0; uint32 tTab = talentInfo->TalentTab; - if (talentInfo->Row > 0) + bool isMainTree = m_talentsPrimaryTree[m_activeSpec] == tTab || !m_talentsPrimaryTree[m_activeSpec]; + + if (talentInfo->Row > 0 || !isMainTree) { - for (PlayerTalentMap::const_iterator iter = m_talents[m_activeSpec].begin(); iter != m_talents[m_activeSpec].end(); ++iter) - if (iter->second.state != PLAYERSPELL_REMOVED && iter->second.talentEntry->TalentTab == tTab) - spentPoints += iter->second.currentRank + 1; + for (uint32 i = 0; i < sTalentStore.GetNumRows(); i++) // Loop through all talents. + { + if (TalentEntry const* tmpTalent = sTalentStore.LookupEntry(i)) // Someday, someone needs to revamp the way talents are tracked + { + for (uint8 rank = 0; rank < MAX_TALENT_RANK; rank++) + { + if (tmpTalent->RankID[rank] != 0) + { + if (HasSpell(tmpTalent->RankID[rank])) + { + if (tmpTalent->TalentTab == tTab) + spentPoints += (rank + 1); + if (tmpTalent->TalentTab == m_talentsPrimaryTree[m_activeSpec]) + primaryTreeTalents += (rank + 1); + } + } + } + } + } } // not have required min points spent in talent tree if (spentPoints < (talentInfo->Row * MAX_TALENT_RANK)) - return; + return false; + + // player has not spent 31 talents in main tree before attempting to learn other tree's talents + if (!isMainTree && primaryTreeTalents < REQ_PRIMARY_TREE_TALENTS) + return false; // spell not set in talent.dbc uint32 spellid = talentInfo->RankID[talentRank]; if (spellid == 0) { sLog.outError("Talent.dbc have for talent: %u Rank: %u spell id = 0", talentId, talentRank); - return; + return false; } // already known if (HasSpell(spellid)) - return; + return false; // learn! (other talent ranks will unlearned at learning) learnSpell(spellid, false); DETAIL_LOG("TalentID: %u Rank: %u Spell: %u\n", talentId, talentRank, spellid); + + // set talent tree for player + if (!m_talentsPrimaryTree[m_activeSpec]) + { + m_talentsPrimaryTree[m_activeSpec] = talentInfo->TalentTab; + std::vector const* specSpells = GetTalentTreePrimarySpells(talentInfo->TalentTab); + if (specSpells) + for (size_t i = 0; i < specSpells->size(); ++i) + learnSpell(specSpells->at(i), false); + } + + return true; } void Player::LearnPetTalent(ObjectGuid petGuid, uint32 talentId, uint32 talentRank) @@ -22142,7 +22228,7 @@ void Player::LearnPetTalent(ObjectGuid petGuid, uint32 talentId, uint32 talentRa return; // Check if it requires another talent - /*if (talentInfo->DependsOn > 0) + if (talentInfo->DependsOn > 0) { if (TalentEntry const* depTalentInfo = sTalentStore.LookupEntry(talentInfo->DependsOn)) { @@ -22156,7 +22242,7 @@ void Player::LearnPetTalent(ObjectGuid petGuid, uint32 talentId, uint32 talentRa if (!hasEnoughRank) return; } - }*/ + } // Find out how many points we have in this field uint32 spentPoints = 0; @@ -22267,9 +22353,13 @@ void Player::BuildPlayerTalentsInfoData(WorldPacket* data) if (m_specsCount) { + if (m_specsCount > MAX_TALENT_SPEC_COUNT) + m_specsCount = MAX_TALENT_SPEC_COUNT; + // loop through all specs (only 1 for now) for (uint32 specIdx = 0; specIdx < m_specsCount; ++specIdx) { + *data << uint32(m_talentsPrimaryTree[specIdx]); uint8 talentIdCount = 0; size_t pos = data->wpos(); *data << uint8(talentIdCount); // [PH], talentIdCount @@ -22277,7 +22367,7 @@ void Player::BuildPlayerTalentsInfoData(WorldPacket* data) // find class talent tabs (all players have 3 talent tabs) uint32 const* talentTabIds = GetTalentTabPages(getClass()); - for (uint32 i = 0; i < 3; ++i) + for (uint32 i = 0; i < MAX_TALENT_TABS; ++i) { uint32 talentTabId = talentTabIds[i]; for (PlayerTalentMap::iterator iter = m_talents[specIdx].begin(); iter != m_talents[specIdx].end(); ++iter) @@ -22636,6 +22726,15 @@ void Player::ActivateSpec(uint8 specNum) // prevent deletion of action buttons by client at spell unlearn or by player while spec change in progress SendLockActionButtons(); + // Remove spec specific spells + for (uint32 i = 0; i < MAX_TALENT_TABS; ++i) + { + std::vector const* specSpells = GetTalentTreePrimarySpells(GetTalentTabPages(getClass())[i]); + if (specSpells) + for (size_t i = 0; i < specSpells->size(); ++i) + removeSpell(specSpells->at(i), true); + } + ApplyGlyphs(false); // copy of new talent spec (we will use it as model for converting current tlanet state to new) @@ -22738,6 +22837,11 @@ void Player::ActivateSpec(uint8 specNum) ResummonPetTemporaryUnSummonedIfAny(); + std::vector const* specSpells = GetTalentTreePrimarySpells(m_talentsPrimaryTree[m_activeSpec]); + if (specSpells) + for (size_t i = 0; i < specSpells->size(); ++i) + learnSpell(specSpells->at(i), false); + ApplyGlyphs(true); SendInitialActionButtons(); @@ -22747,6 +22851,9 @@ void Player::ActivateSpec(uint8 specNum) SetPower(POWER_MANA, 0); SetPower(pw, 0); + + if (!sTalentTabStore.LookupEntry(m_talentsPrimaryTree[m_activeSpec])) + resetTalents(true); } void Player::UpdateSpecCount(uint8 count) diff --git a/src/game/Player.h b/src/game/Player.h index 4625923ed..8fd37d8c9 100644 --- a/src/game/Player.h +++ b/src/game/Player.h @@ -1614,16 +1614,18 @@ class MANGOS_DLL_SPEC Player : public Unit void learnQuestRewardedSpells(Quest const* quest); void learnSpellHighRank(uint32 spellid); - uint32 GetFreeTalentPoints() const { return GetUInt32Value(PLAYER_CHARACTER_POINTS); } - void SetFreeTalentPoints(uint32 points) { SetUInt32Value(PLAYER_CHARACTER_POINTS, points); } + uint32 GetFreeTalentPoints() const { return m_freeTalentPoints; } + void SetFreeTalentPoints(uint32 points) { m_freeTalentPoints = points; } void UpdateFreeTalentPoints(bool resetIfNeed = true); + uint32 GetPrimaryTalentTree(uint8 spec) const { return m_talentsPrimaryTree[spec]; } + void SetPrimaryTalentTree(uint8 spec, uint32 tree) { m_talentsPrimaryTree[spec] = tree; } bool resetTalents(bool no_cost = false, bool all_specs = false); uint32 resetTalentsCost() const; void InitTalentForLevel(); void BuildPlayerTalentsInfoData(WorldPacket* data); void BuildPetTalentsInfoData(WorldPacket* data); void SendTalentsInfoData(bool pet); - void LearnTalent(uint32 talentId, uint32 talentRank); + bool LearnTalent(uint32 talentId, uint32 talentRank); void LearnPetTalent(ObjectGuid petGuid, uint32 talentId, uint32 talentRank); uint32 CalculateTalentsPoints() const; @@ -1638,7 +1640,7 @@ class MANGOS_DLL_SPEC Player : public Unit void InitGlyphsForLevel(); void SetGlyphSlot(uint8 slot, uint32 slottype) { SetUInt32Value(PLAYER_FIELD_GLYPH_SLOTS_1 + slot, slottype); } - uint32 GetGlyphSlot(uint8 slot) { return GetUInt32Value(PLAYER_FIELD_GLYPH_SLOTS_1 + slot); } + uint32 GetGlyphSlot(uint8 slot) const { return GetUInt32Value(PLAYER_FIELD_GLYPH_SLOTS_1 + slot); } void SetGlyph(uint8 slot, uint32 glyph) { m_glyphs[m_activeSpec][slot].SetId(glyph); } uint32 GetGlyph(uint8 slot) { return m_glyphs[m_activeSpec][slot].GetId(); } void ApplyGlyph(uint8 slot, bool apply); @@ -2508,6 +2510,7 @@ class MANGOS_DLL_SPEC Player : public Unit PlayerMails m_mail; PlayerSpellMap m_spells; PlayerTalentMap m_talents[MAX_TALENT_SPEC_COUNT]; + uint32 m_talentsPrimaryTree[MAX_TALENT_SPEC_COUNT]; SpellCooldowns m_spellCooldowns; uint32 m_lastPotionId; // last used health/mana potion in combat, that block next potion use @@ -2582,6 +2585,7 @@ class MANGOS_DLL_SPEC Player : public Unit // Transports Transport* m_transport; + uint32 m_freeTalentPoints; uint32 m_resetTalentsCost; time_t m_resetTalentsTime; uint32 m_usedTalentCount; diff --git a/src/game/SharedDefines.h b/src/game/SharedDefines.h index 36c1780ca..e4cd9742c 100644 --- a/src/game/SharedDefines.h +++ b/src/game/SharedDefines.h @@ -638,7 +638,8 @@ enum SpellAttributesEx10 }; #define MAX_TALENT_SPEC_COUNT 2 -#define MAX_GLYPH_SLOT_INDEX 6 +#define MAX_GLYPH_SLOT_INDEX 9 +#define REQ_PRIMARY_TREE_TALENTS 31 enum SheathTypes { diff --git a/src/game/SkillHandler.cpp b/src/game/SkillHandler.cpp index 7c811662d..b7a1b4cf6 100644 --- a/src/game/SkillHandler.cpp +++ b/src/game/SkillHandler.cpp @@ -27,18 +27,39 @@ void WorldSession::HandleLearnTalentOpcode(WorldPacket& recv_data) { + DEBUG_LOG("CMSG_LEARN_PREVIEW_TALENTS"); + uint32 talent_id, requested_rank; recv_data >> talent_id >> requested_rank; - _player->LearnTalent(talent_id, requested_rank); - _player->SendTalentsInfoData(false); + if (_player->LearnTalent(talent_id, requested_rank)) + _player->SendTalentsInfoData(false); + else + sLog.outError("WorldSession::HandleLearnTalentOpcode: learn talent %u rank %u failed for %s (account %u)", talent_id, requested_rank, GetPlayerName(), GetAccountId()); } void WorldSession::HandleLearnPreviewTalents(WorldPacket& recvPacket) { DEBUG_LOG("CMSG_LEARN_PREVIEW_TALENTS"); + int32 tabPage; uint32 talentsCount; + recvPacket >> tabPage; // talent tree + + // prevent cheating (selecting new tree with points already in another) + if (tabPage >= 0) // -1 if player already has specialization + { + if (TalentTabEntry const* talentTabEntry = sTalentTabStore.LookupEntry(_player->GetPrimaryTalentTree(_player->GetActiveSpec()))) + { + if (talentTabEntry->tabpage != tabPage) + { + recvPacket.rfinish(); + sLog.outError("WorldSession::HandleLearnPreviewTalents: tabPage != talent tabPage for %s (account %u)", GetPlayerName(), GetAccountId()); + return; + } + } + } + recvPacket >> talentsCount; uint32 talentId, talentRank; @@ -47,7 +68,12 @@ void WorldSession::HandleLearnPreviewTalents(WorldPacket& recvPacket) { recvPacket >> talentId >> talentRank; - _player->LearnTalent(talentId, talentRank); + if (!_player->LearnTalent(talentId, talentRank)) + { + recvPacket.rfinish(); + sLog.outError("WorldSession::HandleLearnPreviewTalents: learn talent %u rank %u tab %u failed for %s (account %u)", talentId, talentRank, tabPage, GetPlayerName(), GetAccountId()); + break; + } } _player->SendTalentsInfoData(false); diff --git a/src/game/SpellEffects.cpp b/src/game/SpellEffects.cpp index 5803714f4..3c4c94316 100644 --- a/src/game/SpellEffects.cpp +++ b/src/game/SpellEffects.cpp @@ -8794,6 +8794,27 @@ void Spell::EffectApplyGlyph(SpellEffectEntry const* effect) Player* player = (Player*)m_caster; + // glyph sockets level requirement + uint8 minLevel = 0; + switch (m_glyphIndex) + { + case 0: + case 1: + case 6: minLevel = 25; break; + case 2: + case 3: + case 7: minLevel = 50; break; + case 4: + case 5: + case 8: minLevel = 75; break; + } + + if (minLevel && m_caster->getLevel() < minLevel) + { + SendCastResult(SPELL_FAILED_GLYPH_SOCKET_LOCKED); + return; + } + // apply new one if(uint32 glyph = effect->EffectMiscValue) { diff --git a/src/game/SpellHandler.cpp b/src/game/SpellHandler.cpp index 0ab28d649..45dcee778 100644 --- a/src/game/SpellHandler.cpp +++ b/src/game/SpellHandler.cpp @@ -403,6 +403,7 @@ void WorldSession::HandleCastSpellOpcode(WorldPacket& recvPacket) Spell* spell = new Spell(mover, spellInfo, false); spell->m_cast_count = cast_count; // set count of casts + spell->m_glyphIndex = glyphIndex; spell->prepare(&targets); } diff --git a/src/shared/revision_nr.h b/src/shared/revision_nr.h index a08d1cc6f..f97440b2d 100644 --- a/src/shared/revision_nr.h +++ b/src/shared/revision_nr.h @@ -1,4 +1,4 @@ #ifndef __REVISION_NR_H__ #define __REVISION_NR_H__ - #define REVISION_NR "12160" + #define REVISION_NR "12161" #endif // __REVISION_NR_H__ diff --git a/src/shared/revision_sql.h b/src/shared/revision_sql.h index b9f1405fb..a124195f9 100644 --- a/src/shared/revision_sql.h +++ b/src/shared/revision_sql.h @@ -1,6 +1,6 @@ #ifndef __REVISION_SQL_H__ #define __REVISION_SQL_H__ - #define REVISION_DB_CHARACTERS "required_12150_01_characters_saved_variables" + #define REVISION_DB_CHARACTERS "required_12161_01_characters_characters" #define REVISION_DB_MANGOS "required_12150_01_mangos_mangos_string" #define REVISION_DB_REALMD "required_12112_01_realmd_account_access" #endif // __REVISION_SQL_H__