Core/Mechanics: refactor XP (and in fact, honor/reputation/kill credit) rewarding routine and get rid of copy/pasted code.

This commit is contained in:
Azazel
2011-03-13 15:28:45 +06:00
parent 986a09c121
commit 6ad4044f05
7 changed files with 267 additions and 225 deletions
+3 -86
View File
@@ -1949,91 +1949,8 @@ void Battleground::SetBracket(PvPDifficultyEntry const* bracketEntry)
SetLevelRange(bracketEntry->minLevel,bracketEntry->maxLevel);
}
void Battleground::RewardXPAtKill(Player* plr, Player* victim)
void Battleground::RewardXPAtKill(Player* killer, Player* victim)
{
if (!sWorld->getBoolConfig(CONFIG_BG_XP_FOR_KILL) || !plr || !victim)
return;
uint32 xp = 0;
Player* member_with_max_level = NULL;
Player* not_gray_member_with_max_level = NULL;
if (Group *pGroup = plr->GetGroup())//should be always in a raid group while in any bg
{
uint32 count = 0;
uint32 sum_level = 0;
for (GroupReference *itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next())
{
Player* member = itr->getSource();
if (!member || !member->isAlive()) // only for alive
continue;
if (!member->IsAtGroupRewardDistance(victim)) // at req. distance
continue;
++count;
sum_level += member->getLevel();
if (!member_with_max_level || member_with_max_level->getLevel() < member->getLevel())
member_with_max_level = member;
uint32 gray_level = Trinity::XP::GetGrayLevel(member->getLevel());
if (victim->getLevel() > gray_level && (!not_gray_member_with_max_level
|| not_gray_member_with_max_level->getLevel() < member->getLevel()))
not_gray_member_with_max_level = member;
}
if (member_with_max_level)
{
xp = !not_gray_member_with_max_level ? 0 : Trinity::XP::Gain(not_gray_member_with_max_level, victim);
if (!xp)
return;
float group_rate = 1.0f;
for (GroupReference *itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next())
{
Player* pGroupGuy = itr->getSource();
if (!pGroupGuy)
continue;
if (!pGroupGuy->IsAtGroupRewardDistance(victim))
continue; // member (alive or dead) or his corpse at req. distance
float rate = group_rate * float(pGroupGuy->getLevel()) / sum_level;
// XP updated only for alive group member
if (pGroupGuy->isAlive() && not_gray_member_with_max_level && pGroupGuy->getLevel() <= not_gray_member_with_max_level->getLevel())
{
uint32 itr_xp = (member_with_max_level == not_gray_member_with_max_level) ? uint32(xp * rate) : uint32((xp * rate / 2) + 1);
// handle SPELL_AURA_MOD_XP_PCT auras
Unit::AuraEffectList const& ModXPPctAuras = plr->GetAuraEffectsByType(SPELL_AURA_MOD_XP_PCT);
for (Unit::AuraEffectList::const_iterator i = ModXPPctAuras.begin(); i != ModXPPctAuras.end(); ++i)
AddPctN(itr_xp, (*i)->GetAmount());
pGroupGuy->GiveXP(itr_xp, victim);
if (Pet* pet = pGroupGuy->GetPet())
pet->GivePetXP(itr_xp / 2);
}
}
}
}
else//should be always in a raid group while in any BG, but you never know...
{
xp = Trinity::XP::Gain(plr, victim);
if (!xp)
return;
// handle SPELL_AURA_MOD_XP_PCT auras
Unit::AuraEffectList const& ModXPPctAuras = plr->GetAuraEffectsByType(SPELL_AURA_MOD_XP_PCT);
for (Unit::AuraEffectList::const_iterator i = ModXPPctAuras.begin(); i != ModXPPctAuras.end(); ++i)
AddPctN(xp, (*i)->GetAmount());
plr->GiveXP(xp, victim);
if (Pet* pet = plr->GetPet())
pet->GivePetXP(xp);
}
if (sWorld->getBoolConfig(CONFIG_BG_XP_FOR_KILL) && killer && victim)
killer->RewardPlayerAndGroupAtKill(victim, true);
}
+1 -1
View File
@@ -584,7 +584,7 @@ class Battleground
/* virtual score-array - get's used in bg-subclasses */
int32 m_TeamScores[BG_TEAMS_COUNT];
void RewardXPAtKill(Player* plr, Player* victim);
void RewardXPAtKill(Player* killer, Player* victim);
bool CanAwardArenaPoints() const { return m_LevelMin >= BG_AWARD_ARENA_POINTS_MIN_LEVEL; }
protected:
+225 -112
View File
@@ -381,6 +381,229 @@ void TradeData::SetAccepted(bool state, bool crosssend /*= false*/)
}
}
// == KillRewarder ====================================================
// KillRewarder incapsulates 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.
KillRewarder::KillRewarder(Player* killer, Unit* victim, bool isBattleGround) :
// 1. Initialize internal variables to default values.
_killer(killer), _victim(victim), _isBattleGround(isBattleGround),
_isPvP(victim->isCharmedOwnedByPlayerOrPlayer()), _group(killer->GetGroup()), _groupRate(1.0f),
_maxLevel(0), _maxNotGrayMember(NULL), _count(0), _sumLevel(0), _isFullXP(false), _xp(0)
{
_InitGroupData();
}
inline void KillRewarder::_InitGroupData()
{
if (_group)
{
// 2. In case when player is in group, initialize variables necessary for group calculations:
for (GroupReference *itr = _group->GetFirstMember(); itr != NULL; itr = itr->next())
if (Player* member = itr->getSource())
if (member->isAlive() && member->IsAtGroupRewardDistance(_victim))
{
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->getLevel() > 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 = (_maxLevel == _maxNotGrayMember->getLevel());
}
else
_count = 1;
}
inline void KillRewarder::_InitXP(Player* player)
{
// 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);
}
inline void KillRewarder::_RewardHonor(Player* player)
{
// Rewarded player must be alive.
if (player->isAlive())
player->RewardHonor(_victim, _count, -1, true);
}
inline void KillRewarder::_RewardXP(Player* player, float rate)
{
uint32 xp(_xp);
if (_group)
// 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).
Unit::AuraEffectList const& auras = player->GetAuraEffectsByType(SPELL_AURA_MOD_XP_PCT);
for (Unit::AuraEffectList::const_iterator i = auras.begin(); i != auras.end(); ++i)
AddPctN(xp, (*i)->GetAmount());
// 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(_group ? 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 (!_group || player->isAlive() || !player->GetCorpse())
if (_victim->GetTypeId() == TYPEID_UNIT)
player->KilledMonster(_victim->ToCreature()->GetCreatureInfo(), _victim->GetGUID());
}
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);
// Give XP only in PvE or in battlegrounds.
// Give reputation and kill credit only in PvE.
if (!_isPvP || _isBattleGround)
{
const float rate = _group ?
_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()
{
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);
// 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)
{
const bool isDungeon = !_isPvP && sMapStore.LookupEntry(_killer->GetMapId())->IsDungeon();
if (!_isBattleGround)
{
// 3.1.2. Alter group rate if group is in raid (not for battlegrounds).
const bool 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 *itr = _group->GetFirstMember(); itr != NULL; itr = itr->next())
if (Player* member = itr->getSource())
if (member->IsAtGroupRewardDistance(_victim))
_RewardPlayer(member, isDungeon);
}
}
}
void KillRewarder::Reward()
{
// 3. Reward killer (and group, if necessary).
if (_group)
// 3.1. If killer is in group, reward group.
_RewardGroup();
else
{
// 3.2. Reward single killer (not group case).
// 3.2.1. Initialize initial XP amount based on killer's level.
_InitXP(_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.
if (Creature* victim = _victim->ToCreature())
if (victim->IsDungeonBoss())
if (InstanceScript* instance = _victim->GetInstanceScript())
instance->UpdateEncounterState(ENCOUNTER_CREDIT_KILL_CREATURE, _victim->GetEntry(), _victim);
}
// == Player ====================================================
UpdateMask Player::updateVisualBits;
@@ -21969,119 +22192,9 @@ bool Player::GetsRecruitAFriendBonus(bool forXP)
return recruitAFriend;
}
bool Player::RewardPlayerAndGroupAtKill(Unit* pVictim)
void Player::RewardPlayerAndGroupAtKill(Unit* pVictim, bool isBattleGround)
{
bool PvP = pVictim->isCharmedOwnedByPlayerOrPlayer();
// prepare data for near group iteration (PvP and !PvP cases)
uint32 xp = 0;
bool honored_kill = false;
if (Group *pGroup = GetGroup())
{
uint32 count = 0;
uint32 sum_level = 0;
Player* member_with_max_level = NULL;
Player* not_gray_member_with_max_level = NULL;
pGroup->GetDataForXPAtKill(pVictim,count,sum_level,member_with_max_level,not_gray_member_with_max_level);
if (member_with_max_level)
{
// PvP kills doesn't yield experience
// also no XP gained if there is no member below gray level
xp = (PvP || !not_gray_member_with_max_level || GetVehicle()) ? 0 : Trinity::XP::Gain(not_gray_member_with_max_level, pVictim);
/// skip in check PvP case (for speed, not used)
bool is_raid = PvP ? false : sMapStore.LookupEntry(GetMapId())->IsRaid() && pGroup->isRaidGroup();
bool is_dungeon = PvP ? false : sMapStore.LookupEntry(GetMapId())->IsDungeon();
float group_rate = Trinity::XP::xp_in_group_rate(count,is_raid);
for (GroupReference *itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next())
{
Player* pGroupGuy = itr->getSource();
if (!pGroupGuy)
continue;
if (!pGroupGuy->IsAtGroupRewardDistance(pVictim))
continue; // member (alive or dead) or his corpse at req. distance
// honor can be in PvP and !PvP (racial leader) cases (for alive)
if (pGroupGuy->isAlive() && pGroupGuy->RewardHonor(pVictim, count, -1, true) && pGroupGuy == this)
honored_kill = true;
// xp and reputation only in !PvP case
if (!PvP)
{
float rate = group_rate * float(pGroupGuy->getLevel()) / sum_level;
// if is in dungeon then all receive full reputation at kill
// rewarded any alive/dead/near_corpse group member
pGroupGuy->RewardReputation(pVictim,is_dungeon ? 1.0f : rate);
// XP updated only for alive group member
if (pGroupGuy->isAlive() && not_gray_member_with_max_level &&
pGroupGuy->getLevel() <= not_gray_member_with_max_level->getLevel())
{
uint32 itr_xp = (member_with_max_level == not_gray_member_with_max_level) ? uint32(xp * rate) : uint32((xp * rate / 2) + 1);
// handle SPELL_AURA_MOD_XP_PCT auras
Unit::AuraEffectList const& ModXPPctAuras = GetAuraEffectsByType(SPELL_AURA_MOD_XP_PCT);
for (Unit::AuraEffectList::const_iterator i = ModXPPctAuras.begin(); i != ModXPPctAuras.end(); ++i)
AddPctN(itr_xp, (*i)->GetAmount());
pGroupGuy->GiveXP(itr_xp, pVictim, group_rate);
if (Pet* pet = pGroupGuy->GetPet())
pet->GivePetXP(itr_xp / 2);
}
// quest objectives updated only for alive group member or dead but with not released body
if (pGroupGuy->isAlive()|| !pGroupGuy->GetCorpse())
{
// normal creature (not pet/etc) can be only in !PvP case
if (pVictim->GetTypeId() == TYPEID_UNIT)
pGroupGuy->KilledMonster(pVictim->ToCreature()->GetCreatureInfo(), pVictim->GetGUID());
}
}
}
}
}
else // if (!pGroup)
{
xp = (PvP || GetVehicle()) ? 0 : Trinity::XP::Gain(this, pVictim);
// honor can be in PvP and !PvP (racial leader) cases
if (RewardHonor(pVictim, 1, -1, true))
honored_kill = true;
// xp and reputation only in !PvP case
if (!PvP)
{
RewardReputation(pVictim,1);
// handle SPELL_AURA_MOD_XP_PCT auras
Unit::AuraEffectList const& ModXPPctAuras = GetAuraEffectsByType(SPELL_AURA_MOD_XP_PCT);
for (Unit::AuraEffectList::const_iterator i = ModXPPctAuras.begin(); i != ModXPPctAuras.end(); ++i)
AddPctN(xp, (*i)->GetAmount());
GiveXP(xp, pVictim);
if (Pet* pet = GetPet())
pet->GivePetXP(xp);
// normal creature (not pet/etc) can be only in !PvP case
if (pVictim->GetTypeId() == TYPEID_UNIT)
KilledMonster(pVictim->ToCreature()->GetCreatureInfo(), pVictim->GetGUID());
}
}
// Credit encounter in instance
if (Creature* victim = pVictim->ToCreature())
if (victim->IsDungeonBoss())
if (InstanceScript* instance = pVictim->GetInstanceScript())
instance->UpdateEncounterState(ENCOUNTER_CREDIT_KILL_CREATURE, pVictim->GetEntry(), pVictim);
return xp || honored_kill;
KillRewarder(this, pVictim, isBattleGround).Reward();
}
void Player::RewardPlayerAndGroupAtEvent(uint32 creature_id, WorldObject* pRewardSource)
+37 -2
View File
@@ -988,6 +988,41 @@ class TradeData
uint64 m_items[TRADE_SLOT_COUNT]; // traded itmes from m_player side including non-traded slot
};
class KillRewarder
{
public:
KillRewarder(Player* killer, Unit* victim, bool isBattleGround);
void Reward();
private:
void _InitXP(Player* player);
void _InitGroupData();
void _RewardHonor(Player* player);
void _RewardXP(Player* player, float rate);
void _RewardReputation(Player* player, float rate);
void _RewardKillCredit(Player* player);
void _RewardPlayer(Player* player, bool isDungeon);
void _RewardGroup();
Player* _killer;
Unit* _victim;
bool _isBattleGround;
bool _isPvP;
Group* _group;
float _groupRate;
uint8 _maxLevel;
Player* _maxNotGrayMember;
uint32 _count;
uint32 _sumLevel;
bool _isFullXP;
uint32 _xp;
};
class Player : public Unit, public GridObject<Player>
{
friend class WorldSession;
@@ -1935,8 +1970,8 @@ class Player : public Unit, public GridObject<Player>
bool IsAtGroupRewardDistance(WorldObject const* pRewardSource) const;
bool IsAtRecruitAFriendDistance(WorldObject const* pOther) const;
bool RewardPlayerAndGroupAtKill(Unit* pVictim);
void RewardPlayerAndGroupAtEvent(uint32 creature_id,WorldObject* pRewardSource);
void RewardPlayerAndGroupAtKill(Unit* pVictim, bool isBattleGround);
void RewardPlayerAndGroupAtEvent(uint32 creature_id, WorldObject* pRewardSource);
bool isHonorOrXPTarget(Unit* pVictim);
bool GetsRecruitAFriendBonus(bool forXP);
+1 -1
View File
@@ -15057,7 +15057,7 @@ void Unit::Kill(Unit *pVictim, bool durabilityLoss)
loot->generateMoneyLoot(creature->GetCreatureInfo()->mingold,creature->GetCreatureInfo()->maxgold);
}
player->RewardPlayerAndGroupAtKill(pVictim);
player->RewardPlayerAndGroupAtKill(pVictim, false);
}
// Do KILL and KILLED procs. KILL proc is called only for the unit who landed the killing blow (and its owner - for pets and totems) regardless of who tapped the victim
-22
View File
@@ -1161,28 +1161,6 @@ void Group::SetTargetIcon(uint8 id, uint64 whoGuid, uint64 targetGuid)
BroadcastPacket(&data, true);
}
void Group::GetDataForXPAtKill(Unit const* victim, uint32& count,uint32& sum_level, Player* & member_with_max_level, Player* & not_gray_member_with_max_level)
{
for (GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next())
{
Player* member = itr->getSource();
if (!member || !member->isAlive()) // only for alive
continue;
if (!member->IsAtGroupRewardDistance(victim)) // at req. distance
continue;
++count;
sum_level += member->getLevel();
if (!member_with_max_level || member_with_max_level->getLevel() < member->getLevel())
member_with_max_level = member;
uint32 gray_level = Trinity::XP::GetGrayLevel(member->getLevel());
if (victim->getLevel() > gray_level && (!not_gray_member_with_max_level || not_gray_member_with_max_level->getLevel() < member->getLevel()))
not_gray_member_with_max_level = member;
}
}
void Group::SendTargetIconList(WorldSession *session)
{
if (!session)
-1
View File
@@ -232,7 +232,6 @@ class Group
MemberSlotList const& GetMemberSlots() const;
GroupReference* GetFirstMember();
uint32 GetMembersCount() const;
void GetDataForXPAtKill(Unit const* victim, uint32& count,uint32& sum_level, Player* & member_with_max_level, Player* & not_gray_member_with_max_level);
uint8 GetMemberGroup(uint64 guid) const;
void ConvertToLFG();