Core/Quests: ported quest level scaling and reward money calculation from Classic
This commit is contained in:
1 parent
1dfee7e072
commit
44f466030b
10 files changed
+212
-158
No files matched your search
@@ -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;
|
||||
@@ -455,7 +455,7 @@ void PlayerMenu::SendQuestQueryResponse(Quest const* quest) const
|
||||
_session->SendPacket(&quest->QueryData[static_cast<uint32>(_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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1314,6 +1314,8 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
|
||||
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);
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<uint32>(_level);
|
||||
|
||||
return std::clamp<int32>(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<uint32, MAX_QUEST_DIFFICULTY> 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<LocaleConstant>(loc));
|
||||
queryTemp = BuildQueryData(static_cast<LocaleConstant>(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();
|
||||
|
||||
|
||||
@@ -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<uint32> DependentPreviousQuests;
|
||||
std::vector<uint32> 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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
Reference in new issue
Block a user