/* * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "KillRewarder.h" #include "Creature.h" #include "DB2Stores.h" #include "FlatSet.h" #include "Formulas.h" #include "Group.h" #include "Guild.h" #include "GuildMgr.h" #include "Pet.h" #include "Player.h" #include "Scenario.h" #include "SpellAuraEffects.h" #include // == KillRewarder ==================================================== // KillRewarder encapsulates logic of rewarding player upon kill with: // * XP; // * honor; // * reputation; // * kill credit (for quest objectives). // Rewarding is initiated in two cases: when player kills unit in Unit::Kill() // and on battlegrounds in Battleground::RewardXPAtKill(). // // Rewarding algorithm is: // 1. Initialize internal variables to default values. // 2. In case when player is in group, initialize variables necessary for group calculations: // 2.1. _count - number of alive group members within reward distance; // 2.2. _sumLevel - sum of levels of alive group members within reward distance; // 2.3. _maxLevel - maximum level of alive group member within reward distance; // 2.4. _maxNotGrayMember - maximum level of alive group member within reward distance, // for whom victim is not gray; // 2.5. _isFullXP - flag identifying that for all group members victim is not gray, // so 100% XP will be rewarded (50% otherwise). // 3. Reward killer (and group, if necessary). // 3.1. If killer is in group, reward group. // 3.1.1. Initialize initial XP amount based on maximum level of group member, // for whom victim is not gray. // 3.1.2. Alter group rate if group is in raid (not for battlegrounds). // 3.1.3. Reward each group member (even dead) within reward distance (see 4. for more details). // 3.2. Reward single killer (not group case). // 3.2.1. Initialize initial XP amount based on killer's level. // 3.2.2. Reward killer (see 4. for more details). // 4. Reward player. // 4.1. Give honor (player must be alive and not on BG). // 4.2. Give XP. // 4.2.1. If player is in group, adjust XP: // * set to 0 if player's level is more than maximum level of not gray member; // * cut XP in half if _isFullXP is false. // 4.2.2. Apply auras modifying rewarded XP. // 4.2.3. Give XP to player. // 4.2.4. If player has pet, reward pet with XP (100% for single player, 50% for group case). // 4.3. Give reputation (player must not be on BG). // 4.4. Give kill credit (player must not be in group, or he must be alive or without corpse). // 5. Credit instance encounter. // 6. Update guild achievements. // 7. Scenario credit KillRewarder::KillRewarder(Trinity::IteratorPair killers, Unit* victim, bool isBattleGround) : // 1. Initialize internal variables to default values. _killers(killers), _victim(victim), _groupRate(1.0f), _maxNotGrayMember(nullptr), _count(0), _sumLevel(0), _xp(0), _isFullXP(false), _maxLevel(0), _isBattleGround(isBattleGround), _isPvP(false) { // mark the credit as pvp if victim is player if (victim->GetTypeId() == TYPEID_PLAYER) _isPvP = true; // or if its owned by player and its not a vehicle else if (victim->GetCharmerOrOwnerGUID().IsPlayer()) _isPvP = !victim->IsVehicle(); } inline void KillRewarder::_InitGroupData(Player const* killer) { if (Group const* group = killer->GetGroup()) { // 2. In case when player is in group, initialize variables necessary for group calculations: for (GroupReference const& itr : group->GetMembers()) { Player* member = itr.GetSource(); if (killer == member || (member->IsAtGroupRewardDistance(_victim) && member->IsAlive())) { const uint8 lvl = member->GetLevel(); // 2.1. _count - number of alive group members within reward distance; ++_count; // 2.2. _sumLevel - sum of levels of alive group members within reward distance; _sumLevel += lvl; // 2.3. _maxLevel - maximum level of alive group member within reward distance; if (_maxLevel < lvl) _maxLevel = lvl; // 2.4. _maxNotGrayMember - maximum level of alive group member within reward distance, // for whom victim is not gray; uint32 grayLevel = Trinity::XP::GetGrayLevel(lvl); if (_victim->GetLevelForTarget(member) > grayLevel && (!_maxNotGrayMember || _maxNotGrayMember->GetLevel() < lvl)) _maxNotGrayMember = member; } } // 2.5. _isFullXP - flag identifying that for all group members victim is not gray, // so 100% XP will be rewarded (50% otherwise). _isFullXP = _maxNotGrayMember && (_maxLevel == _maxNotGrayMember->GetLevel()); } else _count = 1; } inline void KillRewarder::_InitXP(Player* player, Player const* killer) { // Get initial value of XP for kill. // XP is given: // * on battlegrounds; // * otherwise, not in PvP; // * not if killer is on vehicle. if (_isBattleGround || (!_isPvP && !killer->GetVehicle())) _xp = Trinity::XP::Gain(player, _victim, _isBattleGround); } inline void KillRewarder::_RewardHonor(Player* player) { // Rewarded player must be alive. if (player->IsAlive()) player->RewardHonor(_victim, _count, -1, HonorGainSource::Kill); } inline void KillRewarder::_RewardXP(Player* player, float rate) { uint32 xp(_xp); if (player->GetGroup()) { // 4.2.1. If player is in group, adjust XP: // * set to 0 if player's level is more than maximum level of not gray member; // * cut XP in half if _isFullXP is false. if (_maxNotGrayMember && player->IsAlive() && _maxNotGrayMember->GetLevel() >= player->GetLevel()) xp = _isFullXP ? uint32(xp * rate) : // Reward FULL XP if all group members are not gray. uint32(xp * rate / 2) + 1; // Reward only HALF of XP if some of group members are gray. else xp = 0; } if (xp) { // 4.2.2. Apply auras modifying rewarded XP (SPELL_AURA_MOD_XP_PCT and SPELL_AURA_MOD_XP_FROM_CREATURE_TYPE). xp *= player->GetTotalAuraMultiplier(SPELL_AURA_MOD_XP_PCT); xp *= player->GetTotalAuraMultiplierByMiscValue(SPELL_AURA_MOD_XP_FROM_CREATURE_TYPE, int32(_victim->GetCreatureType())); // 4.2.3. Give XP to player. player->GiveXP(xp, _victim, _groupRate); if (Pet* pet = player->GetPet()) // 4.2.4. If player has pet, reward pet with XP (100% for single player, 50% for group case). pet->GivePetXP(player->GetGroup() ? xp / 2 : xp); } } inline void KillRewarder::_RewardReputation(Player* player, float rate) { // 4.3. Give reputation (player must not be on BG). // Even dead players and corpses are rewarded. player->RewardReputation(_victim, rate); } inline void KillRewarder::_RewardKillCredit(Player* player) { // 4.4. Give kill credit (player must not be in group, or he must be alive or without corpse). if (!player->GetGroup() || player->IsAlive() || !player->GetCorpse()) { if (Creature* target = _victim->ToCreature()) { player->KilledMonster(target); player->UpdateCriteria(CriteriaType::KillAnyCreature, target->GetCreatureType(), 1, 0, target); } } } void KillRewarder::_RewardPlayer(Player* player, bool isDungeon) { // 4. Reward player. if (!_isBattleGround) { // 4.1. Give honor (player must be alive and not on BG). _RewardHonor(player); // 4.1.1 Send player killcredit for quests with PlayerSlain if (_victim->GetTypeId() == TYPEID_PLAYER) player->KilledPlayerCredit(_victim->GetGUID()); } // Give XP only in PvE or in battlegrounds. // Give reputation and kill credit only in PvE. if (!_isPvP || _isBattleGround) { float const rate = player->GetGroup() ? _groupRate * float(player->GetLevel()) / _sumLevel : // Group rate depends on summary level. 1.0f; // Personal rate is 100%. if (_xp) // 4.2. Give XP. _RewardXP(player, rate); if (!_isBattleGround) { // If killer is in dungeon then all members receive full reputation at kill. _RewardReputation(player, isDungeon ? 1.0f : rate); _RewardKillCredit(player); } } } void KillRewarder::_RewardGroup(Group const* group, Player const* killer) { if (_maxLevel) { if (_maxNotGrayMember) // 3.1.1. Initialize initial XP amount based on maximum level of group member, // for whom victim is not gray. _InitXP(_maxNotGrayMember, killer); // To avoid unnecessary calculations and calls, // proceed only if XP is not ZERO or player is not on battleground // (battleground rewards only XP, that's why). if (!_isBattleGround || _xp) { bool const isDungeon = !_isPvP && sMapStore.LookupEntry(killer->GetMapId())->IsDungeon(); if (!_isBattleGround) { // 3.1.2. Alter group rate if group is in raid (not for battlegrounds). bool const isRaid = !_isPvP && sMapStore.LookupEntry(killer->GetMapId())->IsRaid() && group->isRaidGroup(); _groupRate = Trinity::XP::xp_in_group_rate(_count, isRaid); } // 3.1.3. Reward each group member (even dead or corpse) within reward distance. for (GroupReference const& itr : group->GetMembers()) { Player* member = itr.GetSource(); // Killer may not be at reward distance, check directly if (killer == member || member->IsAtGroupRewardDistance(_victim)) _RewardPlayer(member, isDungeon); } } } } void KillRewarder::Reward() { Trinity::Containers::FlatSet, boost::container::small_vector> processedGroups; for (Player* killer : _killers) { _InitGroupData(killer); // 3. Reward killer (and group, if necessary). if (Group* group = killer->GetGroup()) { if (!processedGroups.insert(group).second) continue; // 3.1. If killer is in group, reward group. _RewardGroup(group, killer); } else { // 3.2. Reward single killer (not group case). // 3.2.1. Initialize initial XP amount based on killer's level. _InitXP(killer, killer); // To avoid unnecessary calculations and calls, // proceed only if XP is not ZERO or player is not on battleground // (battleground rewards only XP, that's why). if (!_isBattleGround || _xp) // 3.2.2. Reward killer. _RewardPlayer(killer, false); } } // 5. Credit instance encounter. // 6. Update guild achievements. // 7. Credit scenario criterias if (Creature* victim = _victim->ToCreature()) { if (_killers.begin() != _killers.end()) { if (ObjectGuid::LowType guildId = victim->GetMap()->GetOwnerGuildId()) if (Guild* guild = sGuildMgr->GetGuildById(guildId)) guild->UpdateCriteria(CriteriaType::KillCreature, victim->GetEntry(), 1, 0, victim, *_killers.begin()); if (Scenario* scenario = victim->GetScenario()) scenario->UpdateCriteria(CriteriaType::KillCreature, victim->GetEntry(), 1, 0, victim, *_killers.begin()); } } }