diff --git a/sql/updates/world/master/2026_05_26_05_world_2024_02_13_00_world.sql b/sql/updates/world/master/2026_05_26_05_world_2024_02_13_00_world.sql new file mode 100644 index 000000000..e56e9a09e --- /dev/null +++ b/sql/updates/world/master/2026_05_26_05_world_2024_02_13_00_world.sql @@ -0,0 +1,2 @@ +-- +DELETE FROM `trinity_string` WHERE `entry` IN (216, 1126); diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 8bd3512bb..6ac9c10a2 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -3537,6 +3537,9 @@ void Player::RemoveArenaSpellCooldowns(bool removeActivePetCooldowns) uint32 Player::GetNextResetTalentsCost() const { + if (sWorld->getBoolConfig(CONFIG_NO_RESET_TALENT_COST)) + return 0; + // The first time reset costs 1 gold if (GetTalentResetCost() < 1*GOLD) return 1*GOLD; @@ -3568,27 +3571,25 @@ uint32 Player::GetNextResetTalentsCost() const } } -bool Player::ResetTalents(bool noCost) +void Player::IncreaseResetTalentsCostAndCounters(uint32 lastResetTalentsCost) { - sScriptMgr->OnPlayerTalentsReset(this, noCost); + if (lastResetTalentsCost > 0) // We don't want to reset the accumulated talent reset cost if we decide to temporarily enable CONFIG_NO_RESET_TALENT_COST + SetTalentResetCost(lastResetTalentsCost); + + SetTalentResetTime(GameTime::GetGameTime()); + + UpdateCriteria(CriteriaType::MoneySpentOnRespecs, lastResetTalentsCost); + UpdateCriteria(CriteriaType::TotalRespecs, 1); +} + +bool Player::ResetTalents(bool involuntarily /*= false*/) +{ + sScriptMgr->OnPlayerTalentsReset(this, involuntarily); // not need after this call if (HasAtLoginFlag(AT_LOGIN_RESET_TALENTS)) RemoveAtLoginFlag(AT_LOGIN_RESET_TALENTS, true); - uint32 cost = 0; - - if (!noCost && !sWorld->getBoolConfig(CONFIG_NO_RESET_TALENT_COST)) - { - cost = GetNextResetTalentsCost(); - - if (!HasEnoughMoney(uint64(cost))) - { - SendBuyError(BUY_ERR_NOT_ENOUGHT_MONEY, nullptr, 0, 0); - return false; - } - } - RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT, true); for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId) @@ -3615,23 +3616,8 @@ bool Player::ResetTalents(bool noCost) _SaveSpells(trans); CharacterDatabase.CommitTransaction(trans); - if (!noCost) - { - ModifyMoney(-(int64)cost); - UpdateCriteria(CriteriaType::MoneySpentOnRespecs, cost); - UpdateCriteria(CriteriaType::TotalRespecs, 1); - - SetTalentResetCost(cost); - SetTalentResetTime(GameTime::GetGameTime()); - } - - /* when prev line will dropped use next line - if (Pet* pet = GetPet()) - { - if (pet->getPetType() == HUNTER_PET && !pet->GetCreatureTemplate()->IsTameable(CanTameExoticPets())) - RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT, true); - } - */ + if (involuntarily) + SendDirectMessage(WorldPackets::Talent::TalentsInvoluntarilyReset(false).Write()); return true; } @@ -14563,7 +14549,7 @@ void Player::OnGossipSelect(WorldObject* source, int32 gossipOptionId, uint32 me break; case GossipOptionNpc::TalentMaster: PlayerTalkClass->SendCloseGossip(); - SendRespecWipeConfirm(guid, sWorld->getBoolConfig(CONFIG_NO_RESET_TALENT_COST) ? 0 : GetNextResetTalentsCost(), SPEC_RESET_TALENTS); + SendRespecWipeConfirm(guid, GetNextResetTalentsCost(), SPEC_RESET_TALENTS); break; case GossipOptionNpc::Stablemaster: SetStableMaster(guid); @@ -14571,7 +14557,7 @@ void Player::OnGossipSelect(WorldObject* source, int32 gossipOptionId, uint32 me break; case GossipOptionNpc::PetSpecializationMaster: PlayerTalkClass->SendCloseGossip(); - SendRespecWipeConfirm(guid, sWorld->getBoolConfig(CONFIG_NO_RESET_TALENT_COST) ? 0 : GetNextResetTalentsCost(), SPEC_RESET_PET_TALENTS); + SendRespecWipeConfirm(guid, GetNextResetTalentsCost(), SPEC_RESET_PET_TALENTS); break; case GossipOptionNpc::GuildBanker: if (Guild* const guild = GetGuild()) diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 65379567e..f88a5e47e 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -2095,6 +2095,7 @@ class TC_GAME_API Player final : public Unit, public GridObject bool ResetTalents(bool noCost = false); void ResetPvpTalents(); uint32 GetNextResetTalentsCost() const; + void IncreaseResetTalentsCostAndCounters(uint32 lastResetTalentsCost); void InitTalentForLevel(); void SendTalentsInfoData(); TalentLearnResult LearnTalent(uint32 talentId, int32* spellOnCooldown); diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index 99fc82ff8..0ee972276 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -1599,7 +1599,6 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder const& holder) pCurrChar->ResetTalents(true); pCurrChar->ResetTalentSpecialization(); pCurrChar->SendTalentsInfoData(); // original talents send already in to SendInitialPacketsBeforeAddToMap, resend reset state - SendNotification(LANG_RESET_TALENTS); } bool firstLogin = pCurrChar->HasAtLoginFlag(AT_LOGIN_FIRST); diff --git a/src/server/game/Handlers/SkillHandler.cpp b/src/server/game/Handlers/SkillHandler.cpp index 2d51a1fa0..2aeaace30 100644 --- a/src/server/game/Handlers/SkillHandler.cpp +++ b/src/server/game/Handlers/SkillHandler.cpp @@ -79,7 +79,7 @@ void WorldSession::HandleConfirmRespecWipeOpcode(WorldPackets::Talent::ConfirmRe Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(confirmRespecWipe.RespecMaster, UNIT_NPC_FLAG_TRAINER, UNIT_NPC_FLAG_2_NONE); if (!unit) { - TC_LOG_DEBUG("network", "WORLD: HandleConfirmRespecWipeOpcode - {} not found or you can't interact with him.", confirmRespecWipe.RespecMaster.ToString()); + TC_LOG_DEBUG("network", "WORLD: HandleConfirmRespecWipeOpcode - {} not found or you can't interact with him.", confirmRespecWipe.RespecMaster); return; } @@ -92,15 +92,25 @@ void WorldSession::HandleConfirmRespecWipeOpcode(WorldPackets::Talent::ConfirmRe if (!unit->CanResetTalents(_player)) return; + int64 cost = _player->GetNextResetTalentsCost(); + if (!_player->HasEnoughMoney(cost)) + return; // // silently return, client should display the error by itself + // remove fake death if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); if (!_player->ResetTalents()) + { + _player->SendRespecWipeConfirm(ObjectGuid::Empty, 0, static_cast(confirmRespecWipe.RespecType)); return; + } + _player->ModifyMoney(-cost); + _player->IncreaseResetTalentsCostAndCounters(cost); _player->SendTalentsInfoData(); - unit->CastSpell(_player, 14867, true); //spell: "Untalent Visual Effect" + + unit->CastSpell(_player, 14867 /*SPELL_UNTALENT_VISUAL_EFFECT*/, true); } void WorldSession::HandleUnlearnSkillOpcode(WorldPackets::Spells::UnlearnSkill& packet) diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h index fb0eb2d60..08b783d05 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -258,7 +258,7 @@ enum TrinityStrings LANG_RESET_TALENTS_ONLINE = 213, LANG_RESET_TALENTS_OFFLINE = 214, LANG_RESET_SPELLS = 215, - LANG_RESET_TALENTS = 216, + // unused = 216, LANG_RESETALL_UNKNOWN_CASE = 217, LANG_RESETALL_SPELLS = 218, @@ -899,7 +899,7 @@ enum TrinityStrings LANG_NO_PET_FOUND = 1123, LANG_WRONG_PET_TYPE = 1124, LANG_COMMAND_LEARN_PET_TALENTS = 1125, - LANG_RESET_PET_TALENTS = 1126, + // unused = 1126, LANG_RESET_PET_TALENTS_ONLINE = 1127, LANG_TAXINODE_ENTRY_LIST_CHAT = 1128, LANG_TAXINODE_ENTRY_LIST_CONSOLE = 1129, diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 7a9d6138a..931ce7de6 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -2017,9 +2017,9 @@ void ScriptMgr::OnPlayerFreeTalentPointsChanged(Player* player, uint32 points) FOREACH_SCRIPT(PlayerScript)->OnFreeTalentPointsChanged(player, points); } -void ScriptMgr::OnPlayerTalentsReset(Player* player, bool noCost) +void ScriptMgr::OnPlayerTalentsReset(Player* player, bool involuntarily) { - FOREACH_SCRIPT(PlayerScript)->OnTalentsReset(player, noCost); + FOREACH_SCRIPT(PlayerScript)->OnTalentsReset(player, involuntarily); } void ScriptMgr::OnPlayerMoneyChanged(Player* player, int64& amount) @@ -2914,7 +2914,7 @@ void PlayerScript::OnFreeTalentPointsChanged(Player* /*player*/, uint32 /*points { } -void PlayerScript::OnTalentsReset(Player* /*player*/, bool /*noCost*/) +void PlayerScript::OnTalentsReset(Player* /*player*/, bool /*involuntarily*/) { } diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index 1284a566b..72995d3cc 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -714,7 +714,7 @@ class TC_GAME_API PlayerScript : public ScriptObject virtual void OnFreeTalentPointsChanged(Player* player, uint32 points); // Called when a player's talent points are reset (right before the reset is done) - virtual void OnTalentsReset(Player* player, bool noCost); + virtual void OnTalentsReset(Player* player, bool involuntarily); // Called when a player's money is modified (before the modification is done) virtual void OnMoneyChanged(Player* player, int64& amount); @@ -1223,7 +1223,7 @@ class TC_GAME_API ScriptMgr void OnPlayerKilledByCreature(Creature* killer, Player* killed); void OnPlayerLevelChanged(Player* player, uint8 oldLevel); void OnPlayerFreeTalentPointsChanged(Player* player, uint32 newPoints); - void OnPlayerTalentsReset(Player* player, bool noCost); + void OnPlayerTalentsReset(Player* player, bool involuntarily); void OnPlayerMoneyChanged(Player* player, int64& amount); void OnPlayerMoneyLimit(Player* player, int64 amount); void OnGivePlayerXP(Player* player, uint32& amount, Unit* victim); diff --git a/src/server/game/Server/Packets/TalentPackets.cpp b/src/server/game/Server/Packets/TalentPackets.cpp index 4fd5cf7d4..b3d3bbf4f 100644 --- a/src/server/game/Server/Packets/TalentPackets.cpp +++ b/src/server/game/Server/Packets/TalentPackets.cpp @@ -130,6 +130,13 @@ void ConfirmRespecWipe::Read() _worldPacket >> RespecType; } +WorldPacket const* TalentsInvoluntarilyReset::Write() +{ + _worldPacket << Bits<1>(IsPetTalents); + + return &_worldPacket; +} + WorldPacket const* LearnTalentFailed::Write() { _worldPacket << Bits<4>(Reason); diff --git a/src/server/game/Server/Packets/TalentPackets.h b/src/server/game/Server/Packets/TalentPackets.h index a61e804c5..8a502e60d 100644 --- a/src/server/game/Server/Packets/TalentPackets.h +++ b/src/server/game/Server/Packets/TalentPackets.h @@ -115,6 +115,16 @@ namespace WorldPackets uint8 RespecType = 0; }; + class TalentsInvoluntarilyReset final : public ServerPacket + { + public: + explicit TalentsInvoluntarilyReset(bool isPetTalents) : ServerPacket(SMSG_TALENTS_INVOLUNTARILY_RESET, 1), IsPetTalents(isPetTalents) { } + + WorldPacket const* Write() override; + + bool IsPetTalents = false; + }; + class LearnTalentFailed final : public ServerPacket { public: diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 64e0ce8a5..9ae73c5d0 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -2663,7 +2663,7 @@ void Spell::EffectUntrainTalents() if (!unitTarget || m_caster->GetTypeId() == TYPEID_PLAYER) return; - unitTarget->ToPlayer()->SendRespecWipeConfirm(m_caster->GetGUID(), sWorld->getBoolConfig(CONFIG_NO_RESET_TALENT_COST) ? 0 : unitTarget->ToPlayer()->GetNextResetTalentsCost(), SPEC_RESET_TALENTS); + unitTarget->ToPlayer()->SendRespecWipeConfirm(m_caster->GetGUID(), unitTarget->ToPlayer()->GetNextResetTalentsCost(), SPEC_RESET_TALENTS); } void Spell::EffectTeleUnitsFaceCaster() diff --git a/src/server/scripts/Commands/cs_reset.cpp b/src/server/scripts/Commands/cs_reset.cpp index 92cd2f7db..5ffc11edd 100644 --- a/src/server/scripts/Commands/cs_reset.cpp +++ b/src/server/scripts/Commands/cs_reset.cpp @@ -220,10 +220,9 @@ public: Unit* owner = creature->GetOwner(); if (owner && owner->GetTypeId() == TYPEID_PLAYER && creature->ToPet()->IsPermanentPetFor(owner->ToPlayer())) { - creature->ToPet()->resetTalents(); + creature->ToPet()->resetTalents(true); owner->ToPlayer()->SendTalentsInfoData(true); - ChatHandler(owner->ToPlayer()->GetSession()).SendSysMessage(LANG_RESET_PET_TALENTS); if (!handler->GetSession() || handler->GetSession()->GetPlayer() != owner->ToPlayer()) handler->PSendSysMessage(LANG_RESET_PET_TALENTS_ONLINE, handler->GetNameLink(owner->ToPlayer()).c_str()); } @@ -241,13 +240,12 @@ public: target->ResetTalents(true); target->ResetTalentSpecialization(); target->SendTalentsInfoData(); - ChatHandler(target->GetSession()).SendSysMessage(LANG_RESET_TALENTS); if (!handler->GetSession() || handler->GetSession()->GetPlayer() != target) handler->PSendSysMessage(LANG_RESET_TALENTS_ONLINE, handler->GetNameLink(target).c_str()); /* TODO: 6.x remove/update pet talents Pet* pet = target->GetPet(); - Pet::resetTalentsForAllPetsOf(target, pet); + Pet::resetTalentsForAllPetsOf(target, pet, true); if (pet) target->SendTalentsInfoData(true); */