From 44f466030bf99a262789098dbaa1a88817537dcc Mon Sep 17 00:00:00 2001 From: Ovahlord Date: Wed, 18 Jun 2025 16:53:29 +0200 Subject: [PATCH] Core/Quests: ported quest level scaling and reward money calculation from Classic --- .../world/4.3.4/2025_06_18_03_world.sql | 11 + .../game/Entities/Creature/GossipDef.cpp | 7 +- src/server/game/Entities/Player/Player.cpp | 73 ++++--- src/server/game/Entities/Player/Player.h | 2 + src/server/game/Globals/ObjectMgr.cpp | 37 ++-- src/server/game/Handlers/LFGHandler.cpp | 26 ++- src/server/game/Quests/QuestDef.cpp | 190 ++++++++++-------- src/server/game/Quests/QuestDef.h | 20 +- src/server/game/Spells/SpellEffects.cpp | 2 +- src/server/scripts/Commands/cs_quest.cpp | 2 +- 10 files changed, 212 insertions(+), 158 deletions(-) create mode 100644 sql/updates/world/4.3.4/2025_06_18_03_world.sql diff --git a/sql/updates/world/4.3.4/2025_06_18_03_world.sql b/sql/updates/world/4.3.4/2025_06_18_03_world.sql new file mode 100644 index 0000000000..c3c7875efa --- /dev/null +++ b/sql/updates/world/4.3.4/2025_06_18_03_world.sql @@ -0,0 +1,11 @@ +ALTER TABLE `quest_template_addon` + ADD `QuestMaxScalingLevel` INT UNSIGNED NOT NULL DEFAULT 0, + ADD `RewardMoneyDifficulty` INT UNSIGNED NOT NULL DEFAULT 0, + ADD `RewardMoneyMultiplier` FLOAT NOT NULL DEFAULT 1, + ADD `RequiredMoney` INT NOT NULL DEFAULT 0; + +-- Convert negative reward money values to RequiredMoney +UPDATE `quest_template_addon` AS qta +INNER JOIN `quest_template` AS qt + ON qt.`ID` = qta.`ID` +SET qta.`RequiredMoney` = qt.`RewardMoney` WHERE qt.`RewardMoney` < 0; diff --git a/src/server/game/Entities/Creature/GossipDef.cpp b/src/server/game/Entities/Creature/GossipDef.cpp index 74e387f8a2..5504ed2a06 100644 --- a/src/server/game/Entities/Creature/GossipDef.cpp +++ b/src/server/game/Entities/Creature/GossipDef.cpp @@ -455,7 +455,7 @@ void PlayerMenu::SendQuestQueryResponse(Quest const* quest) const _session->SendPacket(&quest->QueryData[static_cast(_session->GetSessionDbLocaleIndex())]); else { - WorldPacket queryPacket = quest->BuildQueryData(_session->GetSessionDbLocaleIndex()); + WorldPacket queryPacket = quest->BuildQueryData(_session->GetSessionDbLocaleIndex(), _session->GetPlayer()); _session->SendPacket(&queryPacket); } @@ -546,7 +546,10 @@ void PlayerMenu::SendQuestGiverRequestItems(Quest const* quest, ObjectGuid npcGU packet.AutoLaunched = autoLaunched; packet.QuestFlags = quest->GetFlags(); packet.SuggestPartyMembers = quest->GetSuggestedPlayers(); - packet.MoneyToGet = quest->GetRewOrReqMoney() < 0 ? -quest->GetRewOrReqMoney() : 0; + + int32 moneyReward = quest->GetMoneyReward(_session->GetPlayer()); + if (moneyReward < 0) + packet.MoneyToGet = std::abs(moneyReward); QuestStatusData data; QuestStatusMap statusMap = _session->GetPlayer()->getQuestStatusMap(); diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index d703a8203f..cc57cc191d 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -13876,11 +13876,10 @@ bool Player::CanCompleteQuest(uint32 quest_id) if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_TIMED) && q_status.Timer == 0) return false; - if (qInfo->GetRewOrReqMoney() < 0) - { - if (!HasEnoughMoney(-int64(qInfo->GetRewOrReqMoney()))) + int32 rewardMoney = qInfo->GetMoneyReward(this); + if (rewardMoney < 0) + if (!HasEnoughMoney(-int64(rewardMoney))) return false; - } uint32 repFacId = qInfo->GetRepObjectiveFaction(); if (repFacId && GetReputationMgr().GetReputation(repFacId) < qInfo->GetRepObjectiveValue()) @@ -13945,8 +13944,10 @@ bool Player::CanRewardQuest(Quest const* quest, bool msg) return false; // prevent receive reward with low money and GetRewOrReqMoney() < 0 - if (quest->GetRewOrReqMoney() < 0 && !HasEnoughMoney(-int64(quest->GetRewOrReqMoney()))) - return false; + int32 rewardMoney = quest->GetMoneyReward(this); + if (rewardMoney < 0) + if (!HasEnoughMoney(-int64(rewardMoney))) + return false; // dungeon finder quests cannot be rewarded when hit weekly currency limit if (quest->IsDFQuest()) @@ -14189,10 +14190,35 @@ void Player::IncompleteQuest(uint32 quest_id) SetQuestStatus(quest_id, QUEST_STATUS_INCOMPLETE); uint16 log_slot = FindQuestSlot(quest_id); - if (log_slot < MAX_QUEST_LOG_SIZE) - RemoveQuestSlotState(log_slot, QUEST_STATE_COMPLETE); + if (log_slot < MAX_QUEST_LOG_SIZE) RemoveQuestSlotState(log_slot, QUEST_STATE_COMPLETE); } } +uint32 Player::GetQuestMoneyReward(Quest const* quest) const +{ + uint32 moneyReward = quest->GetMoneyReward(this) * sWorld->getRate(RATE_MONEY_QUEST); + if (IsMaxLevel()) + moneyReward += quest->GetRewMoneyMaxLevel(this) * sWorld->getRate(RATE_MONEY_MAX_LEVEL_QUEST); + + return moneyReward; +} + +uint32 Player::GetQuestXPReward(Quest const* quest) +{ + bool rewarded = IsQuestRewarded(quest->GetQuestId()) && !quest->IsDFQuest(); + + // Not give XP in case already completed once repeatable quest + if (rewarded) + return 0; + + uint32 XP = quest->XPValue(this) * sWorld->getRate(RATE_XP_QUEST); + + // handle SPELL_AURA_MOD_XP_QUEST_PCT auras + Unit::AuraEffectList const& ModXPPctAuras = GetAuraEffectsByType(SPELL_AURA_MOD_XP_QUEST_PCT); + for (Unit::AuraEffectList::const_iterator i = ModXPPctAuras.begin(); i != ModXPPctAuras.end(); ++i) + AddPct(XP, (*i)->GetAmount()); + + return XP; +} void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, bool announce) { @@ -14283,30 +14309,22 @@ void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, if (log_slot < MAX_QUEST_LOG_SIZE) SetQuestSlot(log_slot, 0); - bool rewarded = IsQuestRewarded(quest_id) && !quest->IsDFQuest(); - - // Not give XP in case already completed once repeatable quest - uint32 XP = rewarded ? 0 : uint32(quest->GetXPReward(this) * sWorld->getRate(RATE_XP_QUEST)); - - // handle SPELL_AURA_MOD_XP_QUEST_PCT auras - Unit::AuraEffectList const& ModXPPctAuras = GetAuraEffectsByType(SPELL_AURA_MOD_XP_QUEST_PCT); - for (Unit::AuraEffectList::const_iterator i = ModXPPctAuras.begin(); i != ModXPPctAuras.end(); ++i) - AddPct(XP, (*i)->GetAmount()); + uint32 XP = GetQuestXPReward(quest); if (!IsMaxLevel()) GiveXP(XP, nullptr); if (Guild* guild = GetGuild()) { - uint32 _xp = quest->GetXPReward(this); + uint32 _xp = XP; uint32 guildRep = std::max(uint32(1), uint32(_xp / 450)); guild->GiveXP(uint32(_xp * sWorld->getRate(RATE_XP_QUEST) * sWorld->getRate(RATE_XP_QUEST_GUILD_MODIFIER)), this); guild->GiveReputation(guildRep, this); guild->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUESTS_GUILD, 0, 0, 0, nullptr, this); } - // Give player extra money if GetRewOrReqMoney > 0 and get ReqMoney if negative - if (int32 moneyRew = quest->GetRewOrReqMoney(this)) + int32 moneyRew = GetQuestMoneyReward(quest); + if (moneyRew) { ModifyMoney(moneyRew); @@ -15840,13 +15858,14 @@ void Player::MoneyChanged(uint64 value) continue; Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid); - if (qInfo && qInfo->GetRewOrReqMoney() < 0) + int32 requiredMoney = qInfo ? qInfo->GetMoneyReward(this) : 0; + if (qInfo && requiredMoney < 0) { QuestStatusData& q_status = m_QuestStatus[questid]; if (q_status.Status == QUEST_STATUS_INCOMPLETE) { - if (int64(value) >= -int64(qInfo->GetRewOrReqMoney())) + if (int64(value) >= -int64(requiredMoney)) { if (CanCompleteQuest(questid)) CompleteQuest(questid); @@ -15854,7 +15873,7 @@ void Player::MoneyChanged(uint64 value) } else if (q_status.Status == QUEST_STATUS_COMPLETE) { - if (int64(value) < -int64(qInfo->GetRewOrReqMoney())) + if (int64(value) < -int64(requiredMoney)) IncompleteQuest(questid); } } @@ -15991,14 +16010,10 @@ void Player::SendQuestReward(Quest const* quest, Creature const* questGiver, uin uint32 questId = quest->GetQuestId(); sGameEventMgr->HandleQuestComplete(questId); - uint32 XP = 0; - if (!IsMaxLevel()) - XP = xp; - WorldPackets::Quest::QuestGiverQuestComplete packet; packet.QuestID = questId; - packet.XPReward = XP; - packet.MoneyReward = quest->GetRewOrReqMoney(this); + packet.MoneyReward = GetQuestMoneyReward(quest); + packet.XPReward = IsMaxLevel() ? 0 : xp; packet.NumSkillUpsReward = quest->GetRewardSkillPoints(); packet.TalentReward = quest->GetBonusTalents(); packet.SkillLineIDReward = quest->GetRewardSkillId(); diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 0059277ea4..db0d9e4444 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1314,6 +1314,8 @@ class TC_GAME_API Player : public Unit, public GridObject void AbandonQuest(uint32 quest_id); void CompleteQuest(uint32 quest_id); void IncompleteQuest(uint32 quest_id); + uint32 GetQuestMoneyReward(Quest const* quest) const; + uint32 GetQuestXPReward(Quest const* quest); void RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, bool announce = true); void SetRewardedQuest(uint32 quest_id); void FailQuest(uint32 quest_id); diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index ab48c3c7d2..421ee22a6f 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -4067,37 +4067,38 @@ void ObjectMgr::LoadQuests() _exclusiveQuestGroups.clear(); QueryResult result = WorldDatabase.Query("SELECT " - //0 1 2 3 4 5 6 + // 0 1 2 3 4 5 6 "ID, QuestType, QuestLevel, MinLevel, QuestSortID, QuestInfoID, SuggestedGroupNum, " - //7 8 9 10 + // 7 8 9 10 "RequiredFactionId1, RequiredFactionId2, RequiredFactionValue1, RequiredFactionValue2, " - //11 12 13 14 15 16 17 18 - "RewardNextQuest, RewardXPDifficulty, RewardMoney, RewardBonusMoney, RewardDisplaySpell, RewardSpell, RewardHonor, RewardKillHonor, " - //19 20 21 22 23 24 + // 11 12 13 14 15 16 + "RewardNextQuest, RewardXPDifficulty, RewardDisplaySpell, RewardSpell, RewardHonor, RewardKillHonor, " + // 17 18 19 20 21 22 "StartItem, Flags, MinimapTargetMark, RewardTitle, RequiredPlayerKills, RewardTalents, " - //25 26 27 28 29 30 + // 23 24 25 26 27 28 "RewardArenaPoints, RewardSkillId, RewardSkillPoints, RewardReputationMask, QuestGiverPortrait, QuestTurnInPortrait, " - //31 32 33 34 35 36 37 38 + // 29 30 31 32 33 34 35 36 "RewardItem1, RewardAmount1, RewardItem2, RewardAmount2, RewardItem3, RewardAmount3, RewardItem4, RewardAmount4, " - //39 40 41 42 43 44 45 46 47 48 49 50 + // 37 38 39 40 41 42 43 44 45 46 47 48 "RewardChoiceItemID1, RewardChoiceItemQuantity1, RewardChoiceItemID2, RewardChoiceItemQuantity2, RewardChoiceItemID3, RewardChoiceItemQuantity3, RewardChoiceItemID4, RewardChoiceItemQuantity4, RewardChoiceItemID5, RewardChoiceItemQuantity5, RewardChoiceItemID6, RewardChoiceItemQuantity6, " - //51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 + // 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 "RewardFactionID1, RewardFactionValue1, RewardFactionOverride1, RewardFactionID2, RewardFactionValue2, RewardFactionOverride2, RewardFactionID3, RewardFactionValue3, RewardFactionOverride3, RewardFactionID4, RewardFactionValue4, RewardFactionOverride4, RewardFactionID5, RewardFactionValue5, RewardFactionOverride5, " - //66 67 68 69 70 71 72 73 74 + // 64 65 66 67 68 69 70 71 72 "POIContinent, POIx, POIy, POIPriority, LogTitle, LogDescription, QuestDescription, AreaDescription, QuestCompletionLog, " - //75 76 77 78 79 80 81 82 + // 73 74 75 76 77 78 79 80 "RequiredNpcOrGo1, RequiredNpcOrGo2, RequiredNpcOrGo3, RequiredNpcOrGo4, RequiredNpcOrGoCount1, RequiredNpcOrGoCount2, RequiredNpcOrGoCount3, RequiredNpcOrGoCount4, " - //83 84 85 86 87 88 89 90 + // 81 82 83 84 85 86 87 88 "ItemDrop1, ItemDrop2, ItemDrop3, ItemDrop4, ItemDropQuantity1, ItemDropQuantity2, ItemDropQuantity3, ItemDropQuantity4, " - //91 92 93 94 95 96 97 98 99 100 101 102 + // 89 90 91 92 93 94 95 96 97 98 99 100 "RequiredItemId1, RequiredItemId2, RequiredItemId3, RequiredItemId4, RequiredItemId5, RequiredItemId6, RequiredItemCount1, RequiredItemCount2, RequiredItemCount3, RequiredItemCount4, RequiredItemCount5, RequiredItemCount6, " - //103 104 105 106 107 108 109 110 111 112 113 114 115 + // 101 102 103 104 105 106 107 108 109 110 111 112 113 "RequiredSpell, ObjectiveText1, ObjectiveText2, ObjectiveText3, ObjectiveText4, RewardCurrencyId1, RewardCurrencyId2, RewardCurrencyId3, RewardCurrencyId4, RewardCurrencyCount1, RewardCurrencyCount2, RewardCurrencyCount3, RewardCurrencyCount4, " - //116 117 118 119 120 121 122 123 + // 114 115 116 117 118 119 120 121 "RequiredCurrencyId1, RequiredCurrencyId2, RequiredCurrencyId3, RequiredCurrencyId4, RequiredCurrencyCount1, RequiredCurrencyCount2, RequiredCurrencyCount3, RequiredCurrencyCount4, " - //124 125 126 127 128 129 + // 122 123 124 125 126 127 "QuestGiverTextWindow, QuestGiverTargetName, QuestTurnTextWindow, QuestTurnTargetName, SoundAccept, SoundTurnIn" " FROM quest_template"); + if (!result) { TC_LOG_ERROR("server.loading", ">> Loaded 0 quests definitions. DB table `quest_template` is empty."); @@ -4142,8 +4143,8 @@ void ObjectMgr::LoadQuests() // 0 1 2 3 4 5 6 7 8 9 { "ID, MaxLevel, AllowableClasses, SourceSpellID, PrevQuestID, NextQuestID, ExclusiveGroup, BreadcrumbForQuestId, RewardMailTemplateID, RewardMailDelay," - // 10 11 12 13 14 15 16 17 18 19 20 - " RequiredSkillID, RequiredSkillPoints, RequiredMinRepFaction, RequiredMaxRepFaction, RequiredMinRepValue, RequiredMaxRepValue, ProvidedItemCount, SpecialFlags, AllowableRaces, TimeAllowed, AbandonSpellID", "quest_template_addon", "template addons", &Quest::LoadQuestTemplateAddon }, + // 10 11 12 13 14 15 16 17 18 19 20 + " RequiredSkillID, RequiredSkillPoints, RequiredMinRepFaction, RequiredMaxRepFaction, RequiredMinRepValue, RequiredMaxRepValue, ProvidedItemCount, SpecialFlags, AllowableRaces, TimeAllowed, AbandonSpellID, QuestMaxScalingLevel, RewardMoneyDifficulty, RewardMoneyMultiplier, RequiredMoney", "quest_template_addon", "template addons", &Quest::LoadQuestTemplateAddon }, // 0 1 { "QuestId, RewardMailSenderEntry", "quest_mail_sender", "mail sender entries", &Quest::LoadQuestMailSender } diff --git a/src/server/game/Handlers/LFGHandler.cpp b/src/server/game/Handlers/LFGHandler.cpp index d26903504d..d8c3a57845 100644 --- a/src/server/game/Handlers/LFGHandler.cpp +++ b/src/server/game/Handlers/LFGHandler.cpp @@ -193,8 +193,10 @@ void WorldSession::SendLfgPlayerLockInfo() if (currentQuest) { - playerDungeonInfo.Rewards.RewardMoney = currentQuest->GetRewOrReqMoney(_player); - playerDungeonInfo.Rewards.RewardXP = _player->getLevel() < sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) ? currentQuest->GetXPReward(_player) : 0; + playerDungeonInfo.Rewards.RewardMoney = _player->GetQuestMoneyReward(currentQuest); + if (!_player->IsMaxLevel()) + playerDungeonInfo.Rewards.RewardXP = _player->GetQuestXPReward(currentQuest); + for (uint8 i = 0; i < QUEST_REWARDS_COUNT; ++i) if (uint32 itemId = currentQuest->RewardItemId[i]) playerDungeonInfo.Rewards.Item.emplace_back(itemId, currentQuest->RewardItemIdCount[i]); @@ -213,8 +215,10 @@ void WorldSession::SendLfgPlayerLockInfo() if (shortageQuest) { playerDungeonInfo.ShortageEligible = true; - playerDungeonInfo.ShortageReward.RewardMoney = shortageQuest->GetRewOrReqMoney(_player);; - playerDungeonInfo.ShortageReward.RewardXP = _player->getLevel() < sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) ? shortageQuest->GetXPReward(_player) : 0; + playerDungeonInfo.ShortageReward.RewardMoney = _player->GetQuestMoneyReward(currentQuest); + if (!_player->IsMaxLevel()) + playerDungeonInfo.ShortageReward.RewardXP = _player->GetQuestXPReward(currentQuest); + for (uint8 i = 0; i < QUEST_REWARDS_COUNT; ++i) if (uint32 itemId = shortageQuest->RewardItemId[i]) playerDungeonInfo.ShortageReward.Item.emplace_back(itemId, shortageQuest->RewardItemIdCount[i]); @@ -493,18 +497,18 @@ void WorldSession::SendLfgPlayerReward(lfg::LfgPlayerRewardData const& rewardDat WorldPackets::LFG::LFGPlayerReward lfgPlayerReward; lfgPlayerReward.QueuedSlot = rewardData.rdungeonEntry; lfgPlayerReward.ActualSlot = rewardData.sdungeonEntry; - uint32 rewardMoney = rewardData.quest->GetRewOrReqMoney(_player); - uint32 rewardExp = _player->getLevel() < sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) ? rewardData.quest->GetXPReward(_player) : 0; + lfgPlayerReward.RewardMoney = _player->GetQuestMoneyReward(rewardData.quest); + + if (!_player->IsMaxLevel()) + lfgPlayerReward.AddedXP = _player->GetQuestXPReward(rewardData.quest); if (rewardData.shortageQuest) { - rewardMoney += rewardData.shortageQuest->GetRewOrReqMoney(_player); - rewardExp += _player->getLevel() < sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) ? rewardData.shortageQuest->GetXPReward(_player) : 0; + lfgPlayerReward.RewardMoney += _player->GetQuestMoneyReward(rewardData.shortageQuest); + if (!_player->IsMaxLevel()) + lfgPlayerReward.AddedXP += _player->GetQuestXPReward(rewardData.shortageQuest); } - lfgPlayerReward.RewardMoney = rewardMoney; - lfgPlayerReward.AddedXP = rewardExp; - for (uint8 i = 0; i < QUEST_REWARDS_COUNT; ++i) if (uint32 itemId = rewardData.quest->RewardItemId[i]) lfgPlayerReward.Rewards.emplace_back(itemId, rewardData.quest->RewardItemIdCount[i], false); diff --git a/src/server/game/Quests/QuestDef.cpp b/src/server/game/Quests/QuestDef.cpp index b607546f2b..5dadbb6ee3 100644 --- a/src/server/game/Quests/QuestDef.cpp +++ b/src/server/game/Quests/QuestDef.cpp @@ -16,6 +16,7 @@ */ #include "QuestDef.h" +#include "DBCEnums.h" #include "DBCStores.h" #include "Field.h" #include "Log.h" @@ -40,29 +41,27 @@ Quest::Quest(Field* questRecord) _requiredFactionValue2 = questRecord[10].GetInt32(); _rewardNextQuest = questRecord[11].GetUInt32(); _rewardXPDifficulty = questRecord[12].GetUInt8(); - _rewardMoney = questRecord[13].GetInt32(); - _rewardBonusMoney = questRecord[14].GetUInt32(); - _rewardDisplaySpell = questRecord[15].GetUInt32(); - _rewardSpell = questRecord[16].GetInt32(); - _rewardHonor = questRecord[17].GetUInt32(); - _rewardKillHonor = questRecord[18].GetFloat(); - _startItem = questRecord[19].GetUInt32(); - _flags = questRecord[20].GetUInt32(); - _minimapTargetMark = questRecord[21].GetUInt8(); - _rewardTitleId = questRecord[22].GetUInt8(); - _requiredPlayerKills = questRecord[23].GetUInt8(); - _rewardTalents = questRecord[24].GetUInt8(); - _rewardArenaPoints = questRecord[25].GetUInt16(); - _rewardSkillId = questRecord[26].GetUInt16(); - _rewardSkillPoints = questRecord[27].GetUInt8(); - _rewardReputationMask = questRecord[28].GetUInt8(); - _questGiverPortrait = questRecord[29].GetUInt32(); - _questTurnInPortrait = questRecord[30].GetUInt32(); + _rewardDisplaySpell = questRecord[13].GetUInt32(); + _rewardSpell = questRecord[14].GetInt32(); + _rewardHonor = questRecord[15].GetUInt32(); + _rewardKillHonor = questRecord[16].GetFloat(); + _startItem = questRecord[17].GetUInt32(); + _flags = questRecord[18].GetUInt32(); + _minimapTargetMark = questRecord[19].GetUInt8(); + _rewardTitleId = questRecord[20].GetUInt8(); + _requiredPlayerKills = questRecord[21].GetUInt8(); + _rewardTalents = questRecord[22].GetUInt8(); + _rewardArenaPoints = questRecord[23].GetUInt16(); + _rewardSkillId = questRecord[24].GetUInt16(); + _rewardSkillPoints = questRecord[25].GetUInt8(); + _rewardReputationMask = questRecord[26].GetUInt8(); + _questGiverPortrait = questRecord[27].GetUInt32(); + _questTurnInPortrait = questRecord[28].GetUInt32(); for (uint32 i = 0; i < QUEST_REWARDS_COUNT; ++i) { - RewardItemId[i] = questRecord[31 + i * 2].GetUInt32(); - RewardItemIdCount[i] = questRecord[32 + i * 2].GetUInt16(); + RewardItemId[i] = questRecord[29 + i * 2].GetUInt32(); + RewardItemIdCount[i] = questRecord[30 + i * 2].GetUInt16(); if (RewardItemId[i]) ++_rewItemsCount; @@ -70,8 +69,8 @@ Quest::Quest(Field* questRecord) for (uint32 i = 0; i < QUEST_REWARD_CHOICES_COUNT; ++i) { - RewardChoiceItemId[i] = questRecord[39 + i * 2].GetUInt32(); - RewardChoiceItemCount[i] = questRecord[40 + i * 2].GetUInt16(); + RewardChoiceItemId[i] = questRecord[37 + i * 2].GetUInt32(); + RewardChoiceItemCount[i] = questRecord[38 + i * 2].GetUInt16(); if (RewardChoiceItemId[i]) ++_rewChoiceItemsCount; @@ -79,26 +78,26 @@ Quest::Quest(Field* questRecord) for (uint32 i = 0; i < QUEST_REPUTATIONS_COUNT; ++i) { - RewardFactionId[i] = questRecord[51 + i * 3].GetUInt16(); - RewardFactionValueId[i] = questRecord[52 + i * 3].GetInt32(); - RewardFactionValueIdOverride[i] = questRecord[53 + i * 3].GetInt32(); + RewardFactionId[i] = questRecord[49 + i * 3].GetUInt16(); + RewardFactionValueId[i] = questRecord[50 + i * 3].GetInt32(); + RewardFactionValueIdOverride[i] = questRecord[51 + i * 3].GetInt32(); } - _poiContinent = questRecord[66].GetUInt16(); - _poiX = questRecord[67].GetFloat(); - _poiY = questRecord[68].GetFloat(); - _poiPriority = questRecord[69].GetUInt32(); - _title = questRecord[70].GetString(); - _objectives = questRecord[71].GetString(); - _details = questRecord[72].GetString(); - _areaDescription = questRecord[73].GetString(); - _completedText = questRecord[74].GetString(); + _poiContinent = questRecord[64].GetUInt16(); + _poiX = questRecord[65].GetFloat(); + _poiY = questRecord[66].GetFloat(); + _poiPriority = questRecord[67].GetUInt32(); + _title = questRecord[68].GetString(); + _objectives = questRecord[69].GetString(); + _details = questRecord[70].GetString(); + _areaDescription = questRecord[71].GetString(); + _completedText = questRecord[72].GetString(); for (uint32 i = 0; i < QUEST_OBJECTIVES_COUNT; ++i) { - RequiredNpcOrGo[i] = questRecord[75 + i].GetInt32(); - RequiredNpcOrGoCount[i] = questRecord[79 + i].GetUInt16(); - ObjectiveText[i] = questRecord[104 + i].GetString(); + RequiredNpcOrGo[i] = questRecord[73 + i].GetInt32(); + RequiredNpcOrGoCount[i] = questRecord[77 + i].GetUInt16(); + ObjectiveText[i] = questRecord[102 + i].GetString(); if (RequiredNpcOrGo[i]) ++_reqNpcOrGoCount; @@ -106,14 +105,14 @@ Quest::Quest(Field* questRecord) for (uint32 i = 0; i < QUEST_SOURCE_ITEM_IDS_COUNT; ++i) { - ItemDrop[i] = questRecord[83 + i].GetUInt32(); - ItemDropQuantity[i] = questRecord[87 + i].GetUInt16(); + ItemDrop[i] = questRecord[81 + i].GetUInt32(); + ItemDropQuantity[i] = questRecord[85 + i].GetUInt16(); } for (uint32 i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i) { - RequiredItemId[i] = questRecord[91 + i].GetUInt32(); - RequiredItemCount[i] = questRecord[97 + i].GetUInt16(); + RequiredItemId[i] = questRecord[89 + i].GetUInt32(); + RequiredItemCount[i] = questRecord[95 + i].GetUInt16(); if (RequiredItemId[i]) ++_reqItemsCount; @@ -129,12 +128,12 @@ Quest::Quest(Field* questRecord) OfferRewardEmoteDelay[i] = 0; } - _requiredSpell = questRecord[103].GetUInt32(); + _requiredSpell = questRecord[101].GetUInt32(); for (uint32 i = 0; i < QUEST_REWARD_CURRENCY_COUNT; ++i) { - RewardCurrencyId[i] = questRecord[108 + i].GetUInt16(); - RewardCurrencyCount[i] = questRecord[112 + i].GetUInt32(); + RewardCurrencyId[i] = questRecord[106 + i].GetUInt16(); + RewardCurrencyCount[i] = questRecord[110 + i].GetUInt32(); if (RewardCurrencyId[i]) ++_rewCurrencyCount; @@ -142,19 +141,19 @@ Quest::Quest(Field* questRecord) for (uint32 i = 0; i < QUEST_REQUIRED_CURRENCY_COUNT; ++i) { - RequiredCurrencyId[i] = questRecord[116 + i].GetUInt16(); - RequiredCurrencyCount[i] = questRecord[120 + i].GetUInt32(); + RequiredCurrencyId[i] = questRecord[114 + i].GetUInt16(); + RequiredCurrencyCount[i] = questRecord[118 + i].GetUInt32(); if (RequiredCurrencyId[i]) ++_reqCurrencyCount; } - _questGiverTextWindow = questRecord[124].GetString(); - _questGiverTargetName = questRecord[125].GetString(); - _questTurnTextWindow = questRecord[126].GetString(); - _questTurnTargetName = questRecord[127].GetString(); - _soundAccept = questRecord[128].GetUInt16(); - _soundTurnIn = questRecord[129].GetUInt16(); + _questGiverTextWindow = questRecord[122].GetString(); + _questGiverTargetName = questRecord[123].GetString(); + _questTurnTextWindow = questRecord[124].GetString(); + _questTurnTargetName = questRecord[125].GetString(); + _soundAccept = questRecord[126].GetUInt16(); + _soundTurnIn = questRecord[127].GetUInt16(); } void Quest::LoadQuestDetails(Field* fields) @@ -233,6 +232,10 @@ void Quest::LoadQuestTemplateAddon(Field* fields) _allowableRaces = fields[18].GetUInt32(); _timeAllowed = fields[19].GetInt32(); _questAbandonSpell = fields[20].GetUInt32(); + _maxScalingLevel = fields[21].GetUInt32(); + _rewardMoneyDifficulty = fields[22].GetUInt32(); + _rewardMoneyMultiplier = fields[23].GetFloat(); + _requiredMoney = fields[24].GetInt32(); } void Quest::LoadQuestMailSender(Field* fields) @@ -240,37 +243,42 @@ void Quest::LoadQuestMailSender(Field* fields) _rewardMailSenderEntry = fields[1].GetUInt32(); } -uint32 Quest::GetXPReward(Player const* player) const +uint32 Quest::XPValue(Player const* player) const { - if (!player) - return 0; - - return Quest::CalcXPReward(player->getLevel(), _level, _rewardXPDifficulty); + return XPValue(player ? player->getLevel() : 0, GetQuestLevelForPlayer(player), _level, _rewardXPDifficulty); } -/*static*/ uint32 Quest::CalcXPReward(uint8 playerLevel, int32 targetLevel, uint8 xpDifficulty) +/*static*/ uint32 Quest::XPValue(uint8 playerLevel, uint32 questLevel, int32 unscaledQuestLevel, uint32 xpDifficulty, float xpMultiplier /*= 1.0f*/) { - int32 effectiveLevel = (targetLevel == -1 ? playerLevel : targetLevel); - const QuestXPEntry* xpentry = sQuestXPStore.LookupEntry(effectiveLevel); - if (!xpentry) + QuestXPEntry const* questXp = sQuestXPStore.LookupEntry(questLevel); + if (!questXp || xpDifficulty >= MAX_QUEST_DIFFICULTY) return 0; - int32 diffFactor = 2 * (effectiveLevel - playerLevel) + 20; + int32 diffFactor = 2 * (questLevel - (unscaledQuestLevel == -1 ? 0 : -5) - playerLevel) + 10; if (diffFactor < 1) diffFactor = 1; else if (diffFactor > 10) diffFactor = 10; - uint32 xp = RoundXPValue(diffFactor * xpentry->Difficulty[xpDifficulty] / 10); + uint32 xp = RoundXPValue(diffFactor * questXp->Difficulty[xpDifficulty] * xpMultiplier / 10); + if (sWorld->getIntConfig(CONFIG_MIN_QUEST_SCALED_XP_RATIO)) { - uint32 minScaledXP = RoundXPValue(xpentry->Difficulty[xpDifficulty]) * sWorld->getIntConfig(CONFIG_MIN_QUEST_SCALED_XP_RATIO) / 100; + uint32 minScaledXP = RoundXPValue(questXp->Difficulty[xpDifficulty] * xpMultiplier) * sWorld->getIntConfig(CONFIG_MIN_QUEST_SCALED_XP_RATIO) / 100; xp = std::max(minScaledXP, xp); } return xp; } +uint32 Quest::GetQuestLevelForPlayer(Player const* player) const +{ + if (_level != -1) + return static_cast(_level); + + return std::clamp(player->getLevel(), _minLevel, _maxScalingLevel); +} + /*static*/ bool Quest::IsTakingQuestEnabled(uint32 questId) { if (!sQuestPoolMgr->IsQuestActive(questId)) @@ -279,25 +287,39 @@ uint32 Quest::GetXPReward(Player const* player) const return true; } -int32 Quest::GetRewOrReqMoney(Player const* player) const +int32 Quest::GetMoneyReward(Player const* player) const { - // RequiredMoney: the amount is the negative copper sum. - if (_rewardMoney < 0) - return _rewardMoney; + // Some quests cost money instead of rewarding it + if (_requiredMoney < 0) + return _requiredMoney; - // RewardMoney: the positive amount - if (!player || !player->IsMaxLevel()) - return int32(_rewardMoney * sWorld->getRate(RATE_MONEY_QUEST)); - else // At level cap, the money reward is the maximum amount between normal and bonus money reward - return std::max(int32(GetRewMoneyMaxLevel()), int32(_rewardMoney * sWorld->getRate(RATE_MONEY_QUEST))); + if (std::array const* money = sObjectMgr->GetQuestMoneyRewards(player ? GetQuestLevelForPlayer(player) : _level)) + return money->at(GetRewMoneyDifficulty()) * GetRewMoneyMultiplier(); + + return 0; +} + +uint32 Quest::GetRewMoneyMaxLevel(Player const* player /*= nullptr*/) const +{ + // If Quest has flag to not give money on max level, it's 0 + if (HasFlag(QUEST_FLAGS_NO_MONEY_FOR_XP)) + return 0; + + uint32 maxQuestLevel = _level == -1 ? _maxScalingLevel : _level; + + // Query initialization during startup does not provide a valid player so we just go for the expansion's max level + uint8 playerLevel = player ? player->getLevel() : DBCManager::GetMaxLevelForExpansion(CURRENT_EXPANSION); + + // The conversion rate is XP * 6 copper + return XPValue(playerLevel, maxQuestLevel, _level, _rewardXPDifficulty) * 6; } void Quest::BuildQuestRewards(WorldPackets::Quest::QuestRewards& rewards, Player* player) const { rewards.ChoiceItemCount = GetRewChoiceItemsCount(); rewards.ItemCount = GetReqItemsCount(); - rewards.Money = GetRewOrReqMoney(player); - rewards.XP = GetXPReward(player) * sWorld->getRate(RATE_XP_QUEST); + rewards.Money = player->GetQuestMoneyReward(this); + rewards.XP = player->GetQuestXPReward(this); rewards.Title = GetCharTitleId(); rewards.FactionFlags = GetRewardReputationMask(); rewards.SpellCompletionDisplayID = GetRewSpell(); @@ -336,16 +358,6 @@ void Quest::BuildQuestRewards(WorldPackets::Quest::QuestRewards& rewards, Player } } -uint32 Quest::GetRewMoneyMaxLevel() const -{ - // If Quest has flag to not give money on max level, it's 0 - if (HasFlag(QUEST_FLAGS_NO_MONEY_FOR_XP)) - return 0; - - // Else, return the rewarded copper sum modified by the rate - return uint32(_rewardBonusMoney * sWorld->getRate(RATE_MONEY_MAX_LEVEL_QUEST)); -} - bool Quest::IsAutoAccept() const { return !sWorld->getBoolConfig(CONFIG_QUEST_IGNORE_AUTO_ACCEPT) && HasFlag(QUEST_FLAGS_AUTO_ACCEPT); @@ -429,12 +441,12 @@ void Quest::InitializeQueryData() WorldPacket queryTemp; for (uint8 loc = LOCALE_enUS; loc < TOTAL_LOCALES; ++loc) { - queryTemp = BuildQueryData(static_cast(loc)); + queryTemp = BuildQueryData(static_cast(loc), nullptr); QueryData[loc] = queryTemp; } } -WorldPacket Quest::BuildQueryData(LocaleConstant loc) const +WorldPacket Quest::BuildQueryData(LocaleConstant loc, Player const* player) const { WorldPackets::Quest::QueryQuestInfoResponse response; @@ -487,8 +499,8 @@ WorldPacket Quest::BuildQueryData(LocaleConstant loc) const response.Info.RewardNextQuest = GetNextQuestInChain(); response.Info.RewardXPDifficulty = GetXPId(); - response.Info.RewardMoney = GetRewOrReqMoney(); - response.Info.RewardBonusMoney = GetRewMoneyMaxLevel(); + response.Info.RewardMoney = GetMoneyReward(player); + response.Info.RewardBonusMoney = GetRewMoneyMaxLevel(player); response.Info.RewardDisplaySpell = GetRewSpell(); response.Info.RewardSpell = GetRewSpellCast(); diff --git a/src/server/game/Quests/QuestDef.h b/src/server/game/Quests/QuestDef.h index 58a18f9f3d..3049d3a3ff 100644 --- a/src/server/game/Quests/QuestDef.h +++ b/src/server/game/Quests/QuestDef.h @@ -237,8 +237,11 @@ class TC_GAME_API Quest void LoadQuestTemplateAddon(Field* fields); void LoadQuestMailSender(Field* fields); - uint32 GetXPReward(Player const* player) const; - static uint32 CalcXPReward(uint8 playerLevel, int32 targetLevel, uint8 xpDifficulty); + uint32 XPValue(Player const* player) const; + static uint32 XPValue(uint8 playerLevel, uint32 questLevel, int32 unscaledQuestLevel, uint32 xpDifficulty, float xpMultiplier = 1.0f); + int32 GetMoneyReward(Player const* player) const; + uint32 GetRewMoneyMaxLevel(Player const* player = nullptr) const; + uint32 GetQuestLevelForPlayer(Player const* player) const; bool HasFlag(uint32 flag) const { return (_flags & flag) != 0; } void SetFlag(uint32 flag) { _flags |= flag; } @@ -298,10 +301,8 @@ class TC_GAME_API Quest std::string const& GetQuestGiverTargetName() const { return _questGiverTargetName; } std::string const& GetQuestTurnTextWindow() const { return _questTurnTextWindow; } std::string const& GetQuestTurnTargetName() const { return _questTurnTargetName; } - int32 GetRewOrReqMoney(Player const* player = nullptr) const; uint32 GetRewHonorAddition() const { return _rewardHonor; } float GetRewHonorMultiplier() const { return _rewardKillHonor; } - uint32 GetRewMoneyMaxLevel() const; // use in XP calculation at client uint32 GetRewSpell() const { return _rewardDisplaySpell; } int32 GetRewSpellCast() const { return _rewardSpell; } uint32 GetRewMailTemplateId() const { return _rewardMailTemplateId; } @@ -373,6 +374,9 @@ class TC_GAME_API Quest uint32 GetRewCurrencyCount() const { return _rewCurrencyCount; } uint32 GetReqCurrencyCount() const { return _reqCurrencyCount; } uint32 GetQuestAbandonSpell() const { return _questAbandonSpell; } + uint32 GetQuestMaxScalingLevel() const { return _maxScalingLevel; } + uint32 GetRewMoneyDifficulty() const { return _rewardMoneyDifficulty; } + float GetRewMoneyMultiplier() const { return _rewardMoneyMultiplier; } void SetEventIdForQuest(uint16 eventId) { _eventIdForQuest = eventId; } uint16 GetEventIdForQuest() const { return _eventIdForQuest; } @@ -383,7 +387,7 @@ class TC_GAME_API Quest static void AddQuestLevelToTitle(std::string& title, int32 level); void InitializeQueryData(); - WorldPacket BuildQueryData(LocaleConstant loc) const; + WorldPacket BuildQueryData(LocaleConstant loc, Player const* player) const; std::vector DependentPreviousQuests; std::vector DependentBreadcrumbQuests; @@ -428,8 +432,6 @@ class TC_GAME_API Quest std::string _completedText; uint32 _rewardHonor = 0; float _rewardKillHonor = 0.f; - int32 _rewardMoney = 0; - uint32 _rewardBonusMoney = 0; uint32 _rewardDisplaySpell = 0; int32 _rewardSpell = 0; uint32 _poiContinent = 0; @@ -478,6 +480,10 @@ class TC_GAME_API Quest uint32 _allowableRaces = 0; uint32 _timeAllowed = 0; uint32 _questAbandonSpell = 0; + uint32 _maxScalingLevel = 0; + uint32 _rewardMoneyDifficulty = 0; + float _rewardMoneyMultiplier = 0.0f; + int32 _requiredMoney = 0; // Static value which is not calculated during runtime }; struct QuestStatusData diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 787f578871..4e1c6770dc 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -1773,7 +1773,7 @@ void Spell::EffectOpenLock(SpellEffIndex effIndex) if (uint8 xpDifficulty = goInfo->chest.xpDifficulty) { int32 xpLevel = goInfo->chest.xpMinLevel; - player->GiveXP(Quest::CalcXPReward(player->getLevel(), xpLevel, xpDifficulty), nullptr); + player->GiveXP(Quest::XPValue(player->getLevel(), xpLevel, xpLevel, xpDifficulty), nullptr); } } } diff --git a/src/server/scripts/Commands/cs_quest.cpp b/src/server/scripts/Commands/cs_quest.cpp index 97e2c3516f..ae4617304e 100644 --- a/src/server/scripts/Commands/cs_quest.cpp +++ b/src/server/scripts/Commands/cs_quest.cpp @@ -256,7 +256,7 @@ public: } // If the quest requires money - int32 ReqOrRewMoney = quest->GetRewOrReqMoney(player); + int32 ReqOrRewMoney = quest->GetMoneyReward(player); if (ReqOrRewMoney < 0) player->ModifyMoney(-int64(ReqOrRewMoney));