feat(Core/Scripting): Add OnPlayerBeforeGetLevelForXPGain hook (#25295)

This commit is contained in:
Axel Cocat 2026-04-24 08:17:31 +02:00 committed by GitHub
parent 0f2841ddcf
commit cd3cccc961
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 60 additions and 15 deletions

View file

@ -67,7 +67,7 @@
KillRewarder::KillRewarder(Player* killer, Unit* victim, bool isBattleGround) :
// 1. Initialize internal variables to default values.
_killer(killer), _victim(victim), _group(killer->GetGroup()),
_groupRate(1.0f), _maxNotGrayMember(nullptr), _count(0), _aliveSumLevel(0), _sumLevel(0), _xp(0),
_groupRate(1.0f), _maxNotGrayMember(nullptr), _maxNotGrayMemberLevel(0), _count(0), _aliveSumLevel(0), _sumLevel(0), _xp(0),
_isFullXP(false), _maxLevel(0), _isBattleGround(isBattleGround), _isPvP(false)
{
// mark the credit as pvp if victim is player
@ -89,7 +89,7 @@ void KillRewarder::_InitGroupData()
if (Player* member = itr->GetSource())
if ((_killer == member || member->IsAtGroupRewardDistance(_victim)))
{
const uint8 lvl = member->GetLevel();
const uint8 lvl = _GetPlayerLevel(member);
if (member->IsAlive())
{
// 2.1. _count - number of alive group members within reward distance;
@ -104,9 +104,10 @@ void KillRewarder::_InitGroupData()
// 2.4. _maxNotGrayMember - maximum level of alive group member within reward distance,
// for whom victim is not gray;
uint32 grayLevel = Acore::XP::GetGrayLevel(lvl);
if (_victim->GetLevel() > grayLevel && (!_maxNotGrayMember || _maxNotGrayMember->GetLevel() < lvl))
if (_victim->GetLevel() > grayLevel && (!_maxNotGrayMember || _maxNotGrayMemberLevel < lvl))
{
_maxNotGrayMember = member;
_maxNotGrayMemberLevel = lvl;
}
}
// 2.5. _sumLevel - sum of levels of group members within reward distance;
@ -114,7 +115,7 @@ void KillRewarder::_InitGroupData()
}
// 2.6. _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());
_isFullXP = _maxNotGrayMember && (_maxLevel == _maxNotGrayMemberLevel);
}
else
_count = 1;
@ -153,7 +154,7 @@ void KillRewarder::_RewardXP(Player* player, float rate)
// * 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())
_maxNotGrayMemberLevel >= _GetPlayerLevel(player))
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.
@ -208,8 +209,8 @@ void KillRewarder::_RewardPlayer(Player* player, bool isDungeon)
// Give reputation and kill credit only in PvE.
if (!_isPvP || _isBattleGround)
{
float xpRate = _group ? _groupRate * float(player->GetLevel()) / _aliveSumLevel : /*Personal rate is 100%.*/ 1.0f; // Group rate depends on the sum of levels.
sScriptMgr->OnPlayerRewardKillRewarder(player, this, isDungeon, xpRate); // Personal rate is 100%.
float xpRate = _group ? _groupRate * float(_GetPlayerLevel(player)) / _aliveSumLevel : /*Personal rate is 100%.*/ 1.0f; // Group rate depends on the sum of levels.
sScriptMgr->OnPlayerRewardKillRewarder(player, this, isDungeon, xpRate); // Personal rate is 100%.
if (_xp)
{
@ -264,6 +265,13 @@ void KillRewarder::_RewardGroup()
}
}
uint8 KillRewarder::_GetPlayerLevel(Player const* player)
{
uint8 level = player->GetLevel();
sScriptMgr->OnPlayerBeforeGetLevelForXPGain(player, level);
return level;
}
void KillRewarder::Reward()
{
// 3. Reward killer (and group, if necessary).

View file

@ -43,12 +43,14 @@ private:
void _RewardKillCredit(Player* player);
void _RewardPlayer(Player* player, bool isDungeon);
void _RewardGroup();
uint8 _GetPlayerLevel(Player const* player);
Player* _killer;
Unit* _victim;
Group* _group;
float _groupRate;
Player* _maxNotGrayMember;
uint8 _maxNotGrayMemberLevel;
uint32 _count;
uint32 _aliveSumLevel;
uint32 _sumLevel;

View file

@ -2370,6 +2370,7 @@ void Player::GiveXP(uint32 xp, Unit* victim, float group_rate, bool isLFGReward)
}
uint8 level = GetLevel();
sScriptMgr->OnPlayerBeforeGetLevelForXPGain(this, level);
// Favored experience increase START
uint32 zone = GetZoneId();
@ -5775,17 +5776,20 @@ void Player::CheckAreaExploreAndOutdoor()
if (areaEntry->area_level > 0)
{
if (GetLevel() >= sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
uint8 playerLevel = GetLevel();
sScriptMgr->OnPlayerBeforeGetLevelForXPGain(this, playerLevel);
if (playerLevel >= sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
{
SendExplorationExperience(areaId, 0);
}
else
{
int32 diff = int32(GetLevel()) - areaEntry->area_level;
int32 diff = int32(playerLevel) - areaEntry->area_level;
uint32 XP = 0;
if (diff < -5)
{
XP = uint32(sObjectMgr->GetBaseXP(GetLevel() + 5) * sWorld->getRate(RATE_XP_EXPLORE));
XP = uint32(sObjectMgr->GetBaseXP(playerLevel + 5) * sWorld->getRate(RATE_XP_EXPLORE));
}
else if (diff > 5)
{

View file

@ -1435,8 +1435,11 @@ bool Player::TakeQuestSourceItem(uint32 questId, bool msg)
uint32 Player::CalculateQuestRewardXP(Quest const* quest)
{
uint8 level = GetLevel();
sScriptMgr->OnPlayerBeforeGetLevelForXPGain(this, level);
// apply world quest rate
uint32 xp = uint32(quest->XPValue(GetLevel()) * GetQuestRate(quest->IsDFQuest()));
uint32 xp = uint32(quest->XPValue(level) * GetQuestRate(quest->IsDFQuest()));
// handle SPELL_AURA_MOD_XP_QUEST_PCT auras
xp *= GetTotalAuraMultiplier(SPELL_AURA_MOD_XP_QUEST_PCT);

View file

@ -23,6 +23,7 @@
#include "ObjectMgr.h"
#include "Opcodes.h"
#include "Player.h"
#include "ScriptMgr.h"
#include "WorldPacket.h"
#include "WorldSession.h"
@ -188,10 +189,13 @@ void WorldSession::HandleLfgPlayerLockInfoRequestOpcode(WorldPacket& /*recvData*
if (quest)
{
uint8 playerLevel = GetPlayer() ? GetPlayer()->GetLevel() : 0;
uint8 playerLevelForXP = playerLevel;
sScriptMgr->OnPlayerBeforeGetLevelForXPGain(GetPlayer(), playerLevelForXP);
data << uint8(done);
data << uint32(quest->GetRewOrReqMoney(playerLevel));
if (!GetPlayer()->IsMaxLevel())
data << uint32(quest->XPValue(playerLevel));
if (playerLevelForXP < GetPlayer()->GetUInt32Value(PLAYER_FIELD_MAX_LEVEL))
data << uint32(quest->XPValue(playerLevelForXP));
else
data << uint32(0);
data << uint32(0);
@ -479,6 +483,8 @@ void WorldSession::SendLfgPlayerReward(lfg::LfgPlayerRewardData const& rewardDat
uint8 itemNum = rewardData.quest->GetRewItemsCount();
uint8 playerLevel = GetPlayer() ? GetPlayer()->GetLevel() : 0;
uint8 playerLevelForXP = playerLevel;
sScriptMgr->OnPlayerBeforeGetLevelForXPGain(GetPlayer(), playerLevelForXP);
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
@ -486,7 +492,7 @@ void WorldSession::SendLfgPlayerReward(lfg::LfgPlayerRewardData const& rewardDat
data << uint8(rewardData.done);
data << uint32(1);
data << uint32(rewardData.quest->GetRewOrReqMoney(playerLevel));
data << uint32(rewardData.quest->XPValue(playerLevel));
data << uint32(rewardData.quest->XPValue(playerLevelForXP));
data << uint32(0);
data << uint32(0);
data << uint8(itemNum);

View file

@ -21,6 +21,7 @@
#include "Creature.h"
#include "Log.h"
#include "Player.h"
#include "ScriptMgr.h"
#include "World.h"
uint32 Acore::XP::BaseGain(uint8 pl_level, uint8 mob_level, ContentLevels content)
@ -79,7 +80,9 @@ uint32 Acore::XP::Gain(Player* player, Unit* unit, bool isBattleGround /*= false
{
float xpMod = 1.0f;
gain = BaseGain(player->GetLevel(), unit->GetLevel(), GetContentLevelsForMapAndZone(unit->GetMapId(), unit->GetZoneId()));
uint8 playerLevel = player->GetLevel();
sScriptMgr->OnPlayerBeforeGetLevelForXPGain(player, playerLevel);
gain = BaseGain(playerLevel, unit->GetLevel(), GetContentLevelsForMapAndZone(unit->GetMapId(), unit->GetZoneId()));
if (gain && creature)
{

View file

@ -18,6 +18,9 @@
#include "PlayerScript.h"
#include "ScriptMgr.h"
#include "ScriptMgrMacros.h"
#include "World.h"
#include <algorithm>
void ScriptMgr::OnPlayerBeforeDurabilityRepair(Player* player, ObjectGuid npcGUID, ObjectGuid itemGUID, float& discountMod, uint8 guildBank)
{
@ -930,6 +933,12 @@ void ScriptMgr::OnPlayerLearnTaxiNode(Player const* player, uint32 nodeId)
CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_LEARN_TAXI_NODE, script->OnPlayerLearnTaxiNode(player, nodeId));
}
void ScriptMgr::OnPlayerBeforeGetLevelForXPGain(Player const* player, uint8& level)
{
CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_BEFORE_GET_LEVEL_FOR_XP_GAIN, script->OnPlayerBeforeGetLevelForXPGain(player, level));
level = std::clamp(level, uint8(1), uint8(sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)));
}
PlayerScript::PlayerScript(const char* name, std::vector<uint16> enabledHooks)
: ScriptObject(name, PLAYERHOOK_END)
{

View file

@ -211,6 +211,7 @@ enum PlayerHook
PLAYERHOOK_ON_GIVE_REPUTATION,
PLAYERHOOK_ON_GET_REPUTATION_PRICE_DISCOUNT,
PLAYERHOOK_ON_LEARN_TAXI_NODE,
PLAYERHOOK_ON_BEFORE_GET_LEVEL_FOR_XP_GAIN,
PLAYERHOOK_END
};
@ -829,6 +830,14 @@ public:
* @param nodeId The id of the learned taxi node
*/
virtual void OnPlayerLearnTaxiNode(Player const* /*player*/, uint32 /*nodeId*/) {}
/**
* @brief This hook is called when XP is calculated for the player, and is used to modify the player level used in the XP formulas.
*
* @param player Contains information about the Player
* @param level The level that should be used for XP gain calculations
*/
virtual void OnPlayerBeforeGetLevelForXPGain(Player const* /*player*/, uint8& /*level*/) {}
};
#endif

View file

@ -467,6 +467,7 @@ public: /* PlayerScript */
void OnPlayerGetReputationPriceDiscount(Player const* player, Creature const* creature, float& discount);
void OnPlayerGetReputationPriceDiscount(Player const* player, FactionTemplateEntry const* factionTemplate, float& discount);
void OnPlayerLearnTaxiNode(Player const* player, uint32 nodeId);
void OnPlayerBeforeGetLevelForXPGain(Player const* player, uint8& level);
// Anti cheat
void AnticheatSetCanFlybyServer(Player* player, bool apply);