diff --git a/data/sql/updates/pending_db_world/rev_1637516999973651100.sql b/data/sql/updates/pending_db_world/rev_1637516999973651100.sql new file mode 100644 index 000000000..d032cb4a7 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1637516999973651100.sql @@ -0,0 +1,13 @@ +INSERT INTO `version_db_world` (`sql_rev`) VALUES ('1637516999973651100'); + +UPDATE `acore_string` SET `content_default` = 'Quest %s (%u) removed.', `locale_deDE` = '', `locale_zhCN` = '' WHERE `entry` = 473; +UPDATE `acore_string` SET `content_default` = 'Quest %s (%u) rewarded.', `locale_deDE` = '', `locale_zhCN` = '' WHERE `entry` = 474; +UPDATE `acore_string` SET `content_default` = 'Quest %s (%u) completed.', `locale_deDE` = '', `locale_zhCN` = '' WHERE `entry` = 475; +UPDATE `acore_string` SET `content_default` = 'Quest %s (%u) is already active.', `locale_deDE` = '', `locale_zhCN` = '' WHERE `entry` = 476; + +DELETE FROM `acore_string` WHERE `entry` IN (1516, 5067, 5068, 5069); +INSERT INTO `acore_string` (`entry`, `content_default`) VALUES +(1516, 'Quest ID %u does not exist'), +(5067, 'Quest %s (%u) added.'), +(5068, 'Quest %s (%u) not found in quest log.'), +(5069, 'The quest must be active and complete before rewarding'); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 427314812..30875a93c 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -366,6 +366,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_UDP_RESTORE_DELETE_INFO, "UPDATE characters SET name = ?, account = ?, deleteDate = NULL, deleteInfos_Name = NULL, deleteInfos_Account = NULL WHERE deleteDate IS NOT NULL AND guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_ZONE, "UPDATE characters SET zone = ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_LEVEL, "UPDATE characters SET level = ?, xp = 0 WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_XP_ACCUMULATIVE, "UPDATE characters SET xp = xp + ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_INVALID_ACHIEV_PROGRESS_CRITERIA, "DELETE FROM character_achievement_progress WHERE criteria = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_INVALID_ACHIEVMENT, "DELETE FROM character_achievement WHERE achievement = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_ADDON, "INSERT INTO addons (name, crc) VALUES (?, ?)", CONNECTION_ASYNC); @@ -425,6 +426,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() "INNER JOIN character_inventory ci ON ci.guid = c.guid " "INNER JOIN item_instance ii ON ii.guid = ci.item " "LEFT JOIN character_inventory cb ON cb.item = ci.bag WHERE ii.itemEntry = ? LIMIT ?", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_CHAR_INVENTORY_ITEM_BY_ENTRY_AND_OWNER, "SELECT ci.item FROM character_inventory ci INNER JOIN item_instance ii ON ii.guid = ci.item WHERE ii.itemEntry = ? AND ii.owner_guid = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_MAIL_ITEMS_BY_ENTRY, "SELECT mi.item_guid, m.sender, m.receiver, cs.account, cs.name, cr.account, cr.name " "FROM mail m INNER JOIN mail_items mi ON mi.mail_id = m.id INNER JOIN item_instance ii ON ii.guid = mi.item_guid " "INNER JOIN characters cs ON cs.guid = m.sender INNER JOIN characters cr ON cr.guid = m.receiver WHERE ii.itemEntry = ? LIMIT ?", CONNECTION_SYNCH); @@ -488,8 +490,11 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_DEL_CHAR_TALENT, "DELETE FROM character_talent WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_SKILLS, "DELETE FROM character_skills WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UDP_CHAR_HONOR_POINTS, "UPDATE characters SET totalHonorPoints = ? WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_UDP_CHAR_HONOR_POINTS_ACCUMULATIVE, "UPDATE characters SET totalHonorPoints = totalHonorPoints + ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UDP_CHAR_ARENA_POINTS, "UPDATE characters SET arenaPoints = ? WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_UDP_CHAR_ARENA_POINTS_ACCUMULATIVE, "UPDATE characters SET arenaPoints = arenaPoints + ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UDP_CHAR_MONEY, "UPDATE characters SET money = ? WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_UDP_CHAR_MONEY_ACCUMULATIVE, "UPDATE characters SET money = money + ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_CHAR_REMOVE_GHOST, "UPDATE characters SET playerFlags = (playerFlags & (~16)) WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_CHAR_ACTION, "INSERT INTO character_action (guid, spec, button, action, type) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_CHAR_ACTION, "UPDATE character_action SET action = ?, type = ? WHERE guid = ? AND button = ? AND spec = ?", CONNECTION_ASYNC); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index b4203cfdf..5efb51b86 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -290,6 +290,7 @@ enum CharacterDatabaseStatements : uint32 CHAR_UDP_RESTORE_DELETE_INFO, CHAR_UPD_ZONE, CHAR_UPD_LEVEL, + CHAR_UPD_XP_ACCUMULATIVE, CHAR_DEL_INVALID_ACHIEV_PROGRESS_CRITERIA, CHAR_DEL_INVALID_ACHIEVMENT, CHAR_INS_ADDON, @@ -352,6 +353,7 @@ enum CharacterDatabaseStatements : uint32 CHAR_SEL_AUCTIONHOUSE_COUNT_ITEM, CHAR_SEL_GUILD_BANK_COUNT_ITEM, CHAR_SEL_CHAR_INVENTORY_ITEM_BY_ENTRY, + CHAR_SEL_CHAR_INVENTORY_ITEM_BY_ENTRY_AND_OWNER, CHAR_SEL_MAIL_ITEMS_BY_ENTRY, CHAR_SEL_AUCTIONHOUSE_ITEM_BY_ENTRY, CHAR_SEL_GUILD_BANK_ITEM_BY_ENTRY, @@ -413,8 +415,11 @@ enum CharacterDatabaseStatements : uint32 CHAR_DEL_CHAR_TALENT, CHAR_DEL_CHAR_SKILLS, CHAR_UDP_CHAR_HONOR_POINTS, + CHAR_UDP_CHAR_HONOR_POINTS_ACCUMULATIVE, CHAR_UDP_CHAR_ARENA_POINTS, + CHAR_UDP_CHAR_ARENA_POINTS_ACCUMULATIVE, CHAR_UDP_CHAR_MONEY, + CHAR_UDP_CHAR_MONEY_ACCUMULATIVE, CHAR_UPD_CHAR_REMOVE_GHOST, // pussywizard CHAR_INS_CHAR_ACTION, CHAR_UPD_CHAR_ACTION, diff --git a/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp b/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp index 1aae781a3..3e474b434 100644 --- a/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp +++ b/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp @@ -116,3 +116,28 @@ ChatCommandResult Acore::Impl::ChatCommands::ArgInfo::TryConsu return std::nullopt; } + +struct QuestVisitor +{ + using value_type = Quest const*; + value_type operator()(Hyperlink quest_template) const { return quest_template->Quest; }; + value_type operator()(uint32 questId) const { return sObjectMgr->GetQuestTemplate(questId); }; +}; + +ChatCommandResult Acore::Impl::ChatCommands::ArgInfo::TryConsume(Quest const*& data, ChatHandler const* handler, std::string_view args) +{ + Variant, uint32> val; + ChatCommandResult result = ArgInfo::TryConsume(val, handler, args); + + if (!result || (data = val.visit(QuestVisitor()))) + { + return result; + } + + if (uint32* id = std::get_if(&val)) + { + return FormatAcoreString(handler, LANG_CMDPARSER_QUEST_NO_EXIST, *id); + } + + return std::nullopt; +} diff --git a/src/server/game/Chat/ChatCommands/ChatCommandArgs.h b/src/server/game/Chat/ChatCommands/ChatCommandArgs.h index ec83b7375..6b75e2355 100644 --- a/src/server/game/Chat/ChatCommands/ChatCommandArgs.h +++ b/src/server/game/Chat/ChatCommands/ChatCommandArgs.h @@ -319,6 +319,12 @@ namespace Acore::Impl::ChatCommands static ChatCommandResult TryConsume(SpellInfo const*&, ChatHandler const*, std::string_view); }; + // Quest const* from quest id or link + template <> + struct AC_GAME_API ArgInfo + { + static ChatCommandResult TryConsume(Quest const*&, ChatHandler const*, std::string_view); + }; } #endif diff --git a/src/server/game/Entities/Creature/GossipDef.cpp b/src/server/game/Entities/Creature/GossipDef.cpp index 10ba048bd..5ed50ee2e 100644 --- a/src/server/game/Entities/Creature/GossipDef.cpp +++ b/src/server/game/Entities/Creature/GossipDef.cpp @@ -424,8 +424,9 @@ void PlayerMenu::SendQuestGiverQuestDetails(Quest const* quest, ObjectGuid npcGU data << uint32(0); } - data << uint32(quest->GetRewOrReqMoney(_session->GetPlayer())); - data << uint32(quest->XPValue(_session->GetPlayer()) * _session->GetPlayer()->GetQuestRate()); + uint8 playerLevel = _session->GetPlayer() ? _session->GetPlayer()->getLevel() : 0; + data << uint32(quest->GetRewOrReqMoney(playerLevel)); + data << uint32(quest->XPValue(playerLevel) * _session->GetPlayer()->GetQuestRate()); } // rewarded honor points. Multiply with 10 to satisfy client @@ -506,7 +507,7 @@ void PlayerMenu::SendQuestQueryResponse(Quest const* quest) const if (quest->HasFlag(QUEST_FLAGS_HIDDEN_REWARDS)) data << uint32(0); // Hide money rewarded else - data << uint32(quest->GetRewOrReqMoney(_session->GetPlayer())); // reward money (below max lvl) + data << uint32(quest->GetRewOrReqMoney(_session->GetPlayer() ? _session->GetPlayer()->getLevel() : 0)); // reward money (below max lvl) data << uint32(quest->GetRewMoneyMaxLevel()); // used in XP calculation at client data << uint32(quest->GetRewSpell()); // reward spell, this spell will display (icon) (cast if RewSpellCast == 0) @@ -650,8 +651,10 @@ void PlayerMenu::SendQuestGiverOfferReward(Quest const* quest, ObjectGuid npcGUI data << uint32(0); } - data << uint32(quest->GetRewOrReqMoney(_session->GetPlayer())); - data << uint32(quest->XPValue(_session->GetPlayer()) * _session->GetPlayer()->GetQuestRate()); + uint8 playerLevel = _session->GetPlayer() ? _session->GetPlayer()->getLevel() : 0; + + data << uint32(quest->GetRewOrReqMoney(playerLevel)); + data << uint32(quest->XPValue(playerLevel) * _session->GetPlayer()->GetQuestRate()); // rewarded honor points. Multiply with 10 to satisfy client data << uint32(10 * quest->CalculateHonorGain(_session->GetPlayer()->GetQuestLevel(quest))); diff --git a/src/server/game/Entities/Player/PlayerQuest.cpp b/src/server/game/Entities/Player/PlayerQuest.cpp index 840b334d3..9d8cb0bfb 100644 --- a/src/server/game/Entities/Player/PlayerQuest.cpp +++ b/src/server/game/Entities/Player/PlayerQuest.cpp @@ -736,7 +736,7 @@ void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, bool rewarded = IsQuestRewarded(quest_id) && !quest->IsDFQuest(); // Not give XP in case already completed once repeatable quest - uint32 XP = rewarded ? 0 : uint32(quest->XPValue(this) * GetQuestRate()); + uint32 XP = rewarded ? 0 : uint32(quest->XPValue(getLevel()) * GetQuestRate()); // handle SPELL_AURA_MOD_XP_QUEST_PCT auras Unit::AuraEffectList const& ModXPPctAuras = GetAuraEffectsByType(SPELL_AURA_MOD_XP_QUEST_PCT); @@ -754,7 +754,7 @@ void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, } // Give player extra money if GetRewOrReqMoney > 0 and get ReqMoney if negative - if (int32 rewOrReqMoney = quest->GetRewOrReqMoney(this)) + if (int32 rewOrReqMoney = quest->GetRewOrReqMoney(getLevel())) { moneyRew += rewOrReqMoney; } @@ -2292,12 +2292,12 @@ void Player::SendQuestReward(Quest const* quest, uint32 XP) if (getLevel() < sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)) { data << uint32(XP); - data << uint32(quest->GetRewOrReqMoney(this)); + data << uint32(quest->GetRewOrReqMoney(getLevel())); } else { data << uint32(0); - data << uint32(quest->GetRewOrReqMoney(this) + quest->GetRewMoneyMaxLevel()); + data << uint32(quest->GetRewOrReqMoney(getLevel()) + quest->GetRewMoneyMaxLevel()); } data << uint32(10 * quest->CalculateHonorGain(GetQuestLevel(quest))); diff --git a/src/server/game/Handlers/LFGHandler.cpp b/src/server/game/Handlers/LFGHandler.cpp index 04b320297..12b9c0b1f 100644 --- a/src/server/game/Handlers/LFGHandler.cpp +++ b/src/server/game/Handlers/LFGHandler.cpp @@ -204,9 +204,10 @@ void WorldSession::HandleLfgPlayerLockInfoRequestOpcode(WorldPacket& /*recvData* if (quest) { + uint8 playerLevel = GetPlayer() ? GetPlayer()->getLevel() : 0; data << uint8(done); - data << uint32(quest->GetRewOrReqMoney(GetPlayer())); - data << uint32(quest->XPValue(GetPlayer())); + data << uint32(quest->GetRewOrReqMoney(playerLevel)); + data << uint32(quest->XPValue(playerLevel)); data << uint32(0); data << uint32(0); data << uint8(quest->GetRewItemsCount()); @@ -491,13 +492,15 @@ void WorldSession::SendLfgPlayerReward(lfg::LfgPlayerRewardData const& rewardDat uint8 itemNum = rewardData.quest->GetRewItemsCount(); + uint8 playerLevel = GetPlayer() ? GetPlayer()->getLevel() : 0; + WorldPacket data(SMSG_LFG_PLAYER_REWARD, 4 + 4 + 1 + 4 + 4 + 4 + 4 + 4 + 1 + itemNum * (4 + 4 + 4)); data << uint32(rewardData.rdungeonEntry); // Random Dungeon Finished data << uint32(rewardData.sdungeonEntry); // Dungeon Finished data << uint8(rewardData.done); data << uint32(1); - data << uint32(rewardData.quest->GetRewOrReqMoney(GetPlayer())); - data << uint32(rewardData.quest->XPValue(GetPlayer())); + data << uint32(rewardData.quest->GetRewOrReqMoney(playerLevel)); + data << uint32(rewardData.quest->XPValue(playerLevel)); data << uint32(0); data << uint32(0); data << uint8(itemNum); diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h index a7f54710f..f82f1f513 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -1114,7 +1114,8 @@ enum AcoreStrings LANG_CMDPARSER_ITEM_NO_EXIST = 1513, LANG_CMDPARSER_SPELL_NO_EXIST = 1514, LANG_CMDPARSER_EXACT_SEQ_MISMATCH = 1515, - // FREE IDS 1516-1499 + LANG_CMDPARSER_QUEST_NO_EXIST = 1516, + // FREE IDS 1517-1499 // Ticket Strings 2000-2030 LANG_COMMAND_TICKETNEW = 2000, @@ -1225,7 +1226,11 @@ enum AcoreStrings LANG_COMMAND_CACHE_REFRESH = 5065, LANG_COMMAND_CACHE_NOT_FOUND = 5066, - // Room for more strings 5063-9999 + LANG_COMMAND_QUEST_ADD = 5067, + LANG_COMMAND_QUEST_NOT_FOUND_IN_LOG = 5068, + LANG_COMMAND_QUEST_NOT_COMPLETE = 5069, + + // Room for more strings 5070-9999 // Level requirement notifications LANG_SAY_REQ = 6604, diff --git a/src/server/game/Quests/QuestDef.cpp b/src/server/game/Quests/QuestDef.cpp index 23025d9c8..aa9611d47 100644 --- a/src/server/game/Quests/QuestDef.cpp +++ b/src/server/game/Quests/QuestDef.cpp @@ -184,38 +184,47 @@ void Quest::LoadQuestTemplateAddon(Field* fields) Flags |= QUEST_FLAGS_AUTO_ACCEPT; } -uint32 Quest::XPValue(Player* player) const +uint32 Quest::XPValue(uint8 playerLevel) const { - if (player) + int32 quest_level = (Level == -1 ? playerLevel : Level); + const QuestXPEntry* xpentry = sQuestXPStore.LookupEntry(quest_level); + if (!xpentry) { - int32 quest_level = (Level == -1 ? player->getLevel() : Level); - const QuestXPEntry* xpentry = sQuestXPStore.LookupEntry(quest_level); - if (!xpentry) - return 0; - - int32 diffFactor = 2 * (quest_level - player->getLevel()) + 20; - if (diffFactor < 1) - diffFactor = 1; - else if (diffFactor > 10) - diffFactor = 10; - - uint32 xp = diffFactor * xpentry->Exp[RewardXPDifficulty] / 10; - if (xp <= 100) - xp = 5 * ((xp + 2) / 5); - else if (xp <= 500) - xp = 10 * ((xp + 5) / 10); - else if (xp <= 1000) - xp = 25 * ((xp + 12) / 25); - else - xp = 50 * ((xp + 25) / 50); - - return xp; + return 0; } - return 0; + int32 diffFactor = 2 * (quest_level - playerLevel) + 20; + if (diffFactor < 1) + { + diffFactor = 1; + } + else if (diffFactor > 10) + { + diffFactor = 10; + } + + uint32 xp = diffFactor * xpentry->Exp[RewardXPDifficulty] / 10; + if (xp <= 100) + { + xp = 5 * ((xp + 2) / 5); + } + else if (xp <= 500) + { + xp = 10 * ((xp + 5) / 10); + } + else if (xp <= 1000) + { + xp = 25 * ((xp + 12) / 25); + } + else + { + xp = 50 * ((xp + 25) / 50); + } + + return xp; } -int32 Quest::GetRewOrReqMoney(Player* player /*= nullptr*/) const +int32 Quest::GetRewOrReqMoney(uint8 playerLevel) const { int32 rewardedMoney = RewardMoney; if (rewardedMoney < 0) @@ -223,9 +232,9 @@ int32 Quest::GetRewOrReqMoney(Player* player /*= nullptr*/) const return rewardedMoney; } - if (player && RewardMoneyDifficulty) + if (playerLevel && RewardMoneyDifficulty) { - if (uint32 questRewardedMoney = sObjectMgr->GetQuestMoneyReward(player->getLevel(), RewardMoneyDifficulty)) + if (uint32 questRewardedMoney = sObjectMgr->GetQuestMoneyReward(playerLevel, RewardMoneyDifficulty)) { rewardedMoney = questRewardedMoney; } diff --git a/src/server/game/Quests/QuestDef.h b/src/server/game/Quests/QuestDef.h index 82842d620..9198b5f7d 100644 --- a/src/server/game/Quests/QuestDef.h +++ b/src/server/game/Quests/QuestDef.h @@ -211,7 +211,7 @@ public: void LoadQuestOfferReward(Field* fields); void LoadQuestTemplateAddon(Field* fields); - uint32 XPValue(Player* player) const; + uint32 XPValue(uint8 playerLevel = 0) const; [[nodiscard]] bool HasFlag(uint32 flag) const { return (Flags & flag) != 0; } void SetFlag(uint32 flag) { Flags |= flag; } @@ -260,7 +260,7 @@ public: [[nodiscard]] std::string const& GetRequestItemsText() const { return RequestItemsText; } [[nodiscard]] std::string const& GetAreaDescription() const { return AreaDescription; } [[nodiscard]] std::string const& GetCompletedText() const { return CompletedText; } - [[nodiscard]] int32 GetRewOrReqMoney(Player* player = nullptr) const; + [[nodiscard]] int32 GetRewOrReqMoney(uint8 playerLevel = 0) const; [[nodiscard]] uint32 GetRewHonorAddition() const { return RewardHonor; } [[nodiscard]] float GetRewHonorMultiplier() const { return RewardKillHonor; } [[nodiscard]] uint32 GetRewMoneyMaxLevel() const; // use in XP calculation at client diff --git a/src/server/scripts/Commands/cs_quest.cpp b/src/server/scripts/Commands/cs_quest.cpp index bfe9a1c22..7f2173058 100644 --- a/src/server/scripts/Commands/cs_quest.cpp +++ b/src/server/scripts/Commands/cs_quest.cpp @@ -28,10 +28,6 @@ EndScriptData */ #include "ReputationMgr.h" #include "ScriptMgr.h" -#if AC_COMPILER == AC_COMPILER_GNU -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif - using namespace Acore::ChatCommands; class quest_commandscript : public CommandScript @@ -43,50 +39,33 @@ public: { static ChatCommandTable questCommandTable = { - { "add", SEC_GAMEMASTER, false, &HandleQuestAdd, "" }, - { "complete", SEC_GAMEMASTER, false, &HandleQuestComplete, "" }, - { "remove", SEC_GAMEMASTER, false, &HandleQuestRemove, "" }, - { "reward", SEC_GAMEMASTER, false, &HandleQuestReward, "" }, + { "add", HandleQuestAdd, SEC_GAMEMASTER, Console::Yes }, + { "complete", HandleQuestComplete, SEC_GAMEMASTER, Console::Yes }, + { "remove", HandleQuestRemove, SEC_GAMEMASTER, Console::Yes }, + { "reward", HandleQuestReward, SEC_GAMEMASTER, Console::Yes }, }; static ChatCommandTable commandTable = { - { "quest", SEC_GAMEMASTER, false, nullptr, "", questCommandTable }, + { "quest", questCommandTable }, }; return commandTable; } - static bool HandleQuestAdd(ChatHandler* handler, const char* args) + static bool HandleQuestAdd(ChatHandler* handler, Quest const* quest, Optional playerTarget) { - Player* player = handler->getSelectedPlayer(); - if (!player) + if (!playerTarget) { - handler->SendSysMessage(LANG_NO_CHAR_SELECTED); + playerTarget = PlayerIdentifier::FromTargetOrSelf(handler); + } + + if (!playerTarget) + { + handler->SendSysMessage(LANG_PLAYER_NOT_FOUND); handler->SetSentErrorMessage(true); return false; } - // .addquest #entry' - // number or [name] Shift-click form |color|Hquest:quest_id:quest_level|h[name]|h|r - char* cId = handler->extractKeyFromLink((char*)args, "Hquest"); - if (!cId) - return false; - - uint32 entry = atol(cId); - - Quest const* quest = sObjectMgr->GetQuestTemplate(entry); - - if (!quest) - { - handler->PSendSysMessage(LANG_COMMAND_QUEST_NOTFOUND, entry); - handler->SetSentErrorMessage(true); - return false; - } - - if (player->IsActiveQuest(entry)) - { - handler->PSendSysMessage("This quest is already active!"); - return false; - } + uint32 entry = quest->GetQuestId(); // check item starting quest (it can work incorrectly if added without item in inventory) ItemTemplateContainer const* itc = sObjectMgr->GetItemTemplateStore(); @@ -99,32 +78,77 @@ public: return false; } - // ok, normal (creature/GO starting) quest - if (player->CanAddQuest(quest, true)) - player->AddQuestAndCheckCompletion(quest, nullptr); + if (Player* player = playerTarget->GetConnectedPlayer()) + { + if (player->IsActiveQuest(entry)) + { + handler->PSendSysMessage(LANG_COMMAND_QUEST_ACTIVE, quest->GetTitle().c_str(), entry); + handler->SetSentErrorMessage(true); + return false; + } + // ok, normal (creature/GO starting) quest + if (player->CanAddQuest(quest, true)) + { + player->AddQuestAndCheckCompletion(quest, nullptr); + } + } + else + { + ObjectGuid::LowType guid = playerTarget->GetGUID().GetCounter(); + QueryResult result = CharacterDatabase.PQuery("SELECT 1 FROM character_queststatus WHERE guid = %u AND quest = %u", guid, entry); + + if (result) + { + handler->PSendSysMessage(LANG_COMMAND_QUEST_ACTIVE, quest->GetTitle().c_str(), entry); + handler->SetSentErrorMessage(true); + return false; + } + + uint8 index = 0; + + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CHAR_QUESTSTATUS); + stmt->setUInt32(index++, guid); + stmt->setUInt32(index++, entry); + stmt->setUInt8(index++, 1); + stmt->setBool(index++, false); + stmt->setUInt32(index++, 0); + + for (uint8 i = 0; i < QUEST_OBJECTIVES_COUNT; i++) + { + stmt->setUInt16(index++, 0); + } + + for (uint8 i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; i++) + { + stmt->setUInt16(index++, 0); + } + + stmt->setUInt16(index, 0); + + CharacterDatabase.Execute(stmt); + } + + handler->PSendSysMessage(LANG_COMMAND_QUEST_ADD, quest->GetTitle().c_str(), entry); + handler->SetSentErrorMessage(false); return true; } - static bool HandleQuestRemove(ChatHandler* handler, const char* args) + static bool HandleQuestRemove(ChatHandler* handler, Quest const* quest, Optional playerTarget) { - Player* player = handler->getSelectedPlayer(); - if (!player) + if (!playerTarget) { - handler->SendSysMessage(LANG_NO_CHAR_SELECTED); + playerTarget = PlayerIdentifier::FromTargetOrSelf(handler); + } + + if (!playerTarget) + { + handler->SendSysMessage(LANG_PLAYER_NOT_FOUND); handler->SetSentErrorMessage(true); return false; } - // .removequest #entry' - // number or [name] Shift-click form |color|Hquest:quest_id:quest_level|h[name]|h|r - char* cId = handler->extractKeyFromLink((char*)args, "Hquest"); - if (!cId) - return false; - - uint32 entry = atol(cId); - - Quest const* quest = sObjectMgr->GetQuestTemplate(entry); + uint32 entry = quest->GetQuestId(); if (!quest) { @@ -133,166 +157,580 @@ public: return false; } - // remove all quest entries for 'entry' from quest log - for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) + if (Player* player = playerTarget->GetConnectedPlayer()) { - uint32 logQuest = player->GetQuestSlotQuestId(slot); - if (logQuest == entry) + // remove all quest entries for 'entry' from quest log + for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) { - player->SetQuestSlot(slot, 0); - - // we ignore unequippable quest items in this case, its' still be equipped - player->TakeQuestSourceItem(logQuest, false); - - if (quest->HasFlag(QUEST_FLAGS_FLAGS_PVP)) + uint32 logQuest = player->GetQuestSlotQuestId(slot); + if (logQuest == entry) { - player->pvpInfo.IsHostile = player->pvpInfo.IsInHostileArea || player->HasPvPForcingQuest(); - player->UpdatePvPState(); + player->SetQuestSlot(slot, 0); + + // we ignore unequippable quest items in this case, its' still be equipped + player->TakeQuestSourceItem(logQuest, false); + + if (quest->HasFlag(QUEST_FLAGS_FLAGS_PVP)) + { + player->pvpInfo.IsHostile = player->pvpInfo.IsInHostileArea || player->HasPvPForcingQuest(); + player->UpdatePvPState(); + } } } + + player->RemoveRewardedQuest(entry); + player->RemoveActiveQuest(entry, false); + } + else + { + ObjectGuid::LowType guid = playerTarget->GetGUID().GetCounter(); + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_REWARDED_BY_QUEST); + stmt->setUInt32(0, guid); + stmt->setUInt32(1, entry); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_BY_QUEST); + stmt->setUInt32(0, guid); + stmt->setUInt32(1, entry); + trans->Append(stmt); + + for (uint32 const& requiredItem : quest->RequiredItemId) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_INVENTORY_ITEM_BY_ENTRY_AND_OWNER); + stmt->setUInt32(0, requiredItem); + stmt->setUInt32(1, guid); + + PreparedQueryResult result = CharacterDatabase.Query(stmt); + + if (result) + { + Field* fields = result->Fetch(); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY_BY_ITEM); + stmt->setUInt32(0, fields[0].GetUInt32()); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); + stmt->setUInt32(0, fields[0].GetUInt32()); + trans->Append(stmt); + } + } + + CharacterDatabase.CommitTransaction(trans); } - player->RemoveRewardedQuest(entry); - player->RemoveActiveQuest(entry, false); - - handler->SendSysMessage(LANG_COMMAND_QUEST_REMOVED); + handler->PSendSysMessage(LANG_COMMAND_QUEST_REMOVED, quest->GetTitle().c_str(), entry); + handler->SetSentErrorMessage(false); return true; } - static bool HandleQuestComplete(ChatHandler* handler, const char* args) + static bool HandleQuestComplete(ChatHandler* handler, Quest const* quest, Optional playerTarget) { - Player* player = handler->getSelectedPlayer(); - if (!player) + if (!playerTarget) { - handler->SendSysMessage(LANG_NO_CHAR_SELECTED); + playerTarget = PlayerIdentifier::FromTargetOrSelf(handler); + } + + if (!playerTarget) + { + handler->SendSysMessage(LANG_PLAYER_NOT_FOUND); handler->SetSentErrorMessage(true); return false; } - // .quest complete #entry - // number or [name] Shift-click form |color|Hquest:quest_id:quest_level|h[name]|h|r - char* cId = handler->extractKeyFromLink((char*)args, "Hquest"); - if (!cId) - return false; + uint32 entry = quest->GetQuestId(); - uint32 entry = atol(cId); - - Quest const* quest = sObjectMgr->GetQuestTemplate(entry); - - // If player doesn't have the quest - if (!quest || player->GetQuestStatus(entry) == QUEST_STATUS_NONE) + if (Player* player = playerTarget->GetConnectedPlayer()) { - handler->PSendSysMessage(LANG_COMMAND_QUEST_NOTFOUND, entry); - handler->SetSentErrorMessage(true); - return false; - } - - // Add quest items for quests that require items - for (uint8 x = 0; x < QUEST_ITEM_OBJECTIVES_COUNT; ++x) - { - uint32 id = quest->RequiredItemId[x]; - uint32 count = quest->RequiredItemCount[x]; - if (!id || !count) - continue; - - uint32 curItemCount = player->GetItemCount(id, true); - - ItemPosCountVec dest; - uint8 msg = player->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, id, count - curItemCount); - if (msg == EQUIP_ERR_OK) + // If player doesn't have the quest + if (player->GetQuestStatus(entry) == QUEST_STATUS_NONE) { - Item* item = player->StoreNewItem(dest, id, true); - player->SendNewItem(item, count - curItemCount, true, false); + handler->PSendSysMessage(LANG_COMMAND_QUEST_NOTFOUND, entry); + handler->SetSentErrorMessage(true); + return false; } - } - // All creature/GO slain/casted (not required, but otherwise it will display "Creature slain 0/10") - for (uint8 i = 0; i < QUEST_OBJECTIVES_COUNT; ++i) - { - int32 creature = quest->RequiredNpcOrGo[i]; - uint32 creatureCount = quest->RequiredNpcOrGoCount[i]; - - if (creature > 0) + // Add quest items for quests that require items + for (uint8 x = 0; x < QUEST_ITEM_OBJECTIVES_COUNT; ++x) { - if (CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(creature)) + uint32 id = quest->RequiredItemId[x]; + uint32 count = quest->RequiredItemCount[x]; + if (!id || !count) + { + continue; + } + + uint32 curItemCount = player->GetItemCount(id, true); + + ItemPosCountVec dest; + uint8 msg = player->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, id, count - curItemCount); + if (msg == EQUIP_ERR_OK) + { + Item* item = player->StoreNewItem(dest, id, true); + player->SendNewItem(item, count - curItemCount, true, false); + } + } + + // All creature/GO slain/casted (not required, but otherwise it will display "Creature slain 0/10") + for (uint8 i = 0; i < QUEST_OBJECTIVES_COUNT; ++i) + { + int32 creature = quest->RequiredNpcOrGo[i]; + uint32 creatureCount = quest->RequiredNpcOrGoCount[i]; + + if (creature > 0) + { + if (CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(creature)) + { + for (uint16 z = 0; z < creatureCount; ++z) + { + player->KilledMonster(creatureInfo, ObjectGuid::Empty); + } + } + } + else if (creature < 0) + { for (uint16 z = 0; z < creatureCount; ++z) - player->KilledMonster(creatureInfo, ObjectGuid::Empty); + { + player->KillCreditGO(creature); + } + } } - else if (creature < 0) - for (uint16 z = 0; z < creatureCount; ++z) - player->KillCreditGO(creature); - } - // If the quest requires reputation to complete - if (uint32 repFaction = quest->GetRepObjectiveFaction()) + // If the quest requires reputation to complete + if (uint32 repFaction = quest->GetRepObjectiveFaction()) + { + uint32 repValue = quest->GetRepObjectiveValue(); + uint32 curRep = player->GetReputationMgr().GetReputation(repFaction); + if (curRep < repValue) + { + if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(repFaction)) + { + player->GetReputationMgr().SetReputation(factionEntry, repValue); + } + } + } + + // If the quest requires a SECOND reputation to complete + if (uint32 repFaction = quest->GetRepObjectiveFaction2()) + { + uint32 repValue2 = quest->GetRepObjectiveValue2(); + uint32 curRep = player->GetReputationMgr().GetReputation(repFaction); + if (curRep < repValue2) + { + if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(repFaction)) + { + player->GetReputationMgr().SetReputation(factionEntry, repValue2); + } + } + } + + // If the quest requires money + int32 ReqOrRewMoney = quest->GetRewOrReqMoney(player->getLevel()); + if (ReqOrRewMoney < 0) + { + player->ModifyMoney(-ReqOrRewMoney); + } + + player->CompleteQuest(entry); + } + else { - uint32 repValue = quest->GetRepObjectiveValue(); - uint32 curRep = player->GetReputationMgr().GetReputation(repFaction); - if (curRep < repValue) - if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(repFaction)) - player->GetReputationMgr().SetReputation(factionEntry, repValue); - } + ObjectGuid::LowType guid = playerTarget->GetGUID().GetCounter(); + QueryResult result = CharacterDatabase.PQuery("SELECT 1 FROM character_queststatus WHERE guid = %u AND quest = %u", guid, entry); - // If the quest requires a SECOND reputation to complete - if (uint32 repFaction = quest->GetRepObjectiveFaction2()) - { - uint32 repValue2 = quest->GetRepObjectiveValue2(); - uint32 curRep = player->GetReputationMgr().GetReputation(repFaction); - if (curRep < repValue2) - if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(repFaction)) - player->GetReputationMgr().SetReputation(factionEntry, repValue2); - } + if (!result) + { + handler->PSendSysMessage(LANG_COMMAND_QUEST_NOT_FOUND_IN_LOG, quest->GetTitle(), entry); + handler->SetSentErrorMessage(true); + return false; + } - // If the quest requires money - int32 ReqOrRewMoney = quest->GetRewOrReqMoney(player); - if (ReqOrRewMoney < 0) - player->ModifyMoney(-ReqOrRewMoney); + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + + typedef std::pair items; + std::vector questItems; + + for (uint8 x = 0; x < QUEST_ITEM_OBJECTIVES_COUNT; ++x) + { + uint32 id = quest->RequiredItemId[x]; + uint32 count = quest->RequiredItemCount[x]; + if (!id || !count) + { + continue; + } + + questItems.push_back(std::pair(id, count)); + } + + if (!questItems.empty()) + { + MailSender sender(MAIL_NORMAL, guid, MAIL_STATIONERY_GM); + // fill mail + MailDraft draft(quest->GetTitle(), std::string()); + + for (auto itr : questItems) + { + if (Item* item = Item::CreateItem(itr.first, itr.second)) + { + item->SaveToDB(trans); + draft.AddItem(item); + } + } + + draft.SendMailTo(trans, MailReceiver(nullptr, guid), sender); + } + + uint8 index = 0; + + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CHAR_QUESTSTATUS); + stmt->setUInt32(index++, guid); + stmt->setUInt32(index++, entry); + stmt->setUInt8(index++, 1); + stmt->setBool(index++, quest->HasFlag(QUEST_FLAGS_EXPLORATION)); + stmt->setUInt32(index++, 0); + + for (uint8 i = 0; i < QUEST_OBJECTIVES_COUNT; i++) + { + stmt->setUInt16(index++, quest->RequiredNpcOrGoCount[i]); + } + + for (uint8 i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; i++) + { + // Will be updated once they loot the items from the mailbox. + stmt->setUInt16(index++, 0); + } + + stmt->setUInt16(index, 0); + + trans->Append(stmt); + + // If the quest requires reputation to complete, set the player rep to the required amount. + if (uint32 repFaction = quest->GetRepObjectiveFaction()) + { + uint32 repValue = quest->GetRepObjectiveValue(); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_REP_BY_FACTION); + stmt->setUInt32(0, repFaction); + stmt->setUInt32(1, guid); + PreparedQueryResult result = CharacterDatabase.Query(stmt); + + if (result) + { + Field* fields = result->Fetch(); + uint32 curRep = fields[0].GetUInt32(); + + if (curRep < repValue) + { + if (sFactionStore.LookupEntry(repFaction)) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_REP_FACTION_CHANGE); + stmt->setUInt32(0, repFaction); + stmt->setUInt32(1, repValue); + stmt->setUInt32(2, repFaction); + stmt->setUInt32(3, guid); + trans->Append(stmt); + } + } + } + } + + // If the quest requires reputation to complete, set the player rep to the required amount. + if (uint32 repFaction = quest->GetRepObjectiveFaction2()) + { + uint32 repValue = quest->GetRepObjectiveValue(); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_REP_BY_FACTION); + stmt->setUInt32(0, repFaction); + stmt->setUInt32(1, guid); + PreparedQueryResult result = CharacterDatabase.Query(stmt); + + if (result) + { + Field* fields = result->Fetch(); + uint32 curRep = fields[0].GetUInt32(); + + if (curRep < repValue) + { + if (sFactionStore.LookupEntry(repFaction)) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_REP_FACTION_CHANGE); + stmt->setUInt32(0, repFaction); + stmt->setUInt32(1, repValue); + stmt->setUInt32(2, repFaction); + stmt->setUInt32(3, guid); + trans->Append(stmt); + } + } + } + } + + CharacterDatabase.CommitTransaction(trans); + } // check if Quest Tracker is enabled if (sWorld->getBoolConfig(CONFIG_QUEST_ENABLE_QUEST_TRACKER)) { // prepare Quest Tracker datas auto stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_QUEST_TRACK_GM_COMPLETE); - stmt->setUInt32(0, quest->GetQuestId()); - stmt->setUInt32(1, player->GetGUID().GetCounter()); + stmt->setUInt32(0, entry); + stmt->setUInt32(1, playerTarget->GetGUID().GetCounter()); // add to Quest Tracker CharacterDatabase.Execute(stmt); } - player->CompleteQuest(entry); + handler->PSendSysMessage(LANG_COMMAND_QUEST_COMPLETE, quest->GetTitle().c_str(), entry); + handler->SetSentErrorMessage(false); return true; } - static bool HandleQuestReward(ChatHandler* handler, char const* args) + static bool HandleQuestReward(ChatHandler* handler, Quest const* quest, Optional playerTarget) { - Player* player = handler->getSelectedPlayer(); - if (!player) + if (!playerTarget) { - handler->SendSysMessage(LANG_NO_CHAR_SELECTED); + playerTarget = PlayerIdentifier::FromTargetOrSelf(handler); + } + + if (!playerTarget) + { + handler->SendSysMessage(LANG_PLAYER_NOT_FOUND); handler->SetSentErrorMessage(true); return false; } - // .quest reward #entry - // number or [name] Shift-click form |color|Hquest:quest_id:quest_level|h[name]|h|r - char* cId = handler->extractKeyFromLink((char*)args, "Hquest"); - if (!cId) - return false; + uint32 entry = quest->GetQuestId(); - uint32 entry = atol(cId); - - Quest const* quest = sObjectMgr->GetQuestTemplate(entry); - - // If player doesn't have the quest - if (!quest || player->GetQuestStatus(entry) != QUEST_STATUS_COMPLETE) + if (Player* player = playerTarget->GetConnectedPlayer()) { - handler->PSendSysMessage(LANG_COMMAND_QUEST_NOTFOUND, entry); - handler->SetSentErrorMessage(true); - return false; + // If player doesn't have the quest + if (player->GetQuestStatus(entry) != QUEST_STATUS_COMPLETE) + { + handler->PSendSysMessage(LANG_COMMAND_QUEST_NOTFOUND, entry); + handler->SetSentErrorMessage(true); + return false; + } + + player->RewardQuest(quest, 0, player); + } + else + { + // Achievement criteria updates correctly the next time a quest is rewarded. + // Titles are already awarded correctly the next time they login (only one quest awards title - 11549). + // Rewarded talent points (Death Knights) and spells (e.g Druid forms) are also granted on login. + // No reputation gains - too troublesome to calculate them when the player is offline. + + ObjectGuid::LowType guid = playerTarget->GetGUID().GetCounter(); + uint8 charLevel = sCharacterCache->GetCharacterLevelByGuid(ObjectGuid(HighGuid::Player, guid)); + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + CharacterDatabasePreparedStatement* stmt; + + QueryResult result = CharacterDatabase.PQuery("SELECT 1 FROM character_queststatus WHERE guid = %u AND quest = %u AND status = 1", guid, entry); + + if (!result) + { + handler->SendSysMessage(LANG_COMMAND_QUEST_NOT_COMPLETE); + handler->SetSentErrorMessage(true); + return false; + } + + for (uint32 const& requiredItem : quest->RequiredItemId) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_INVENTORY_ITEM_BY_ENTRY_AND_OWNER); + stmt->setUInt32(0, requiredItem); + stmt->setUInt32(1, guid); + + PreparedQueryResult result = CharacterDatabase.Query(stmt); + + if (result) + { + Field* fields = result->Fetch(); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY_BY_ITEM); + stmt->setUInt32(0, fields[0].GetUInt32()); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); + stmt->setUInt32(0, fields[0].GetUInt32()); + trans->Append(stmt); + } + } + + for (uint32 const& sourceItem : quest->ItemDrop) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_INVENTORY_ITEM_BY_ENTRY_AND_OWNER); + stmt->setUInt32(0, sourceItem); + stmt->setUInt32(1, guid); + + PreparedQueryResult result = CharacterDatabase.Query(stmt); + + if (result) + { + Field* fields = result->Fetch(); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY_BY_ITEM); + stmt->setUInt32(0, fields[0].GetUInt32()); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); + stmt->setUInt32(0, fields[0].GetUInt32()); + trans->Append(stmt); + } + } + + typedef std::pair items; + std::vector questRewardItems; + + if (quest->GetRewChoiceItemsCount()) + { + for (uint32 const& itemId : quest->RewardChoiceItemId) + { + uint8 index = 0; + questRewardItems.push_back(std::pair(itemId, quest->RewardChoiceItemCount[index++])); + } + } + + if (quest->GetRewItemsCount()) + { + for (uint32 const& itemId : quest->RewardItemId) + { + uint8 index = 0; + questRewardItems.push_back(std::pair(itemId, quest->RewardItemIdCount[index++])); + } + } + + if (!questRewardItems.empty()) + { + MailSender sender(MAIL_NORMAL, guid, MAIL_STATIONERY_GM); + // fill mail + MailDraft draft(quest->GetTitle(), "This quest has been manually rewarded to you. This mail contains your quest rewards."); + + for (auto itr : questRewardItems) + { + if (!itr.first || !itr.second) + { + continue; + } + + // Skip invalid items. + if (!sObjectMgr->GetItemTemplate(itr.first)) + { + continue; + } + + if (Item* item = Item::CreateItem(itr.first, itr.second)) + { + item->SaveToDB(trans); + draft.AddItem(item); + } + } + + draft.SendMailTo(trans, MailReceiver(nullptr, guid), sender); + } + + // Send quest giver mail, if any. + if (uint32 mail_template_id = quest->GetRewMailTemplateId()) + { + if (quest->GetRewMailSenderEntry() != 0) + { + MailDraft(mail_template_id).SendMailTo(trans, MailReceiver(nullptr, guid), quest->GetRewMailSenderEntry(), MAIL_CHECK_MASK_HAS_BODY, quest->GetRewMailDelaySecs()); + } + } + + if (quest->IsDaily() || quest->IsDFQuest()) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_DAILYQUESTSTATUS); + stmt->setUInt32(0, guid); + stmt->setUInt32(1, entry); + stmt->setUInt64(2, time(nullptr)); + trans->Append(stmt); + } + else if (quest->IsWeekly()) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_WEEKLYQUESTSTATUS); + stmt->setUInt32(0, guid); + stmt->setUInt32(1, entry); + trans->Append(stmt); + } + else if (quest->IsMonthly()) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_MONTHLYQUESTSTATUS); + stmt->setUInt32(0, guid); + stmt->setUInt32(1, entry); + trans->Append(stmt); + } + else if (quest->IsSeasonal()) + { + // We can't know which event is the quest linked to, so we can't do anything about this. + /* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_SEASONALQUESTSTATUS); + stmt->setUInt32(0, guid); + stmt->setUInt32(1, entry); + stmt->setUInt32(2, event_id); + trans->Append(stmt);*/ + } + + if (uint32 honor = quest->CalculateHonorGain(charLevel)) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_UDP_CHAR_HONOR_POINTS_ACCUMULATIVE); + stmt->setUInt32(0, honor); + stmt->setUInt32(1, guid); + trans->Append(stmt); + } + + if (quest->GetRewArenaPoints()) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_UDP_CHAR_ARENA_POINTS_ACCUMULATIVE); + stmt->setUInt32(0, quest->GetRewArenaPoints()); + stmt->setUInt32(1, guid); + trans->Append(stmt); + } + + int32 rewMoney = 0; + + if (charLevel >= sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)) + { + rewMoney = quest->GetRewMoneyMaxLevel(); + } + else + { + // Some experience might get lost on level up. + uint32 xp = uint32(quest->XPValue(charLevel) * sWorld->getRate(RATE_XP_QUEST)); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_XP_ACCUMULATIVE); + stmt->setUInt32(0, xp); + stmt->setUInt32(1, guid); + trans->Append(stmt); + } + + if (int32 rewOrReqMoney = quest->GetRewOrReqMoney(charLevel)) + { + rewMoney += rewOrReqMoney; + } + + // Only reward money, don't subtract, let's not cause an overflow... + if (rewMoney > 0) + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UDP_CHAR_MONEY_ACCUMULATIVE); + stmt->setUInt32(0, rewMoney); + stmt->setUInt32(1, guid); + trans->Append(stmt); + } + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_QUESTSTATUS_REWARDED); + stmt->setUInt32(0, guid); + stmt->setUInt32(1, entry); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_BY_QUEST); + stmt->setUInt32(0, guid); + stmt->setUInt32(1, entry); + trans->Append(stmt); + + CharacterDatabase.CommitTransaction(trans); } - player->RewardQuest(quest, 0, player); + handler->PSendSysMessage(LANG_COMMAND_QUEST_REWARDED, quest->GetTitle().c_str(), entry); + handler->SetSentErrorMessage(false); return true; } };