diff --git a/src/server/game/Entities/Player/KillerRewarder.cpp b/src/server/game/Entities/Player/KillerRewarder.cpp new file mode 100644 index 000000000..6155b4c02 --- /dev/null +++ b/src/server/game/Entities/Player/KillerRewarder.cpp @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3 + */ + +#include "Formulas.h" +#include "Group.h" +#include "Pet.h" +#include "Player.h" +#include "SpellAuraEffects.h" + +// 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. _aliveSumLevel - sum of levels of alive group members within reward distance; +// 2.3. _sumLevel - sum of levels of group members within reward distance; +// 2.4. _maxLevel - maximum level of alive group member within reward distance; +// 2.5. _maxNotGrayMember - maximum level of alive group member within reward distance, +// for whom victim is not gray; +// 2.6. _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), _group(killer->GetGroup()), + _groupRate(1.0f), _maxNotGrayMember(nullptr), _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 + 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(); + + _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 != nullptr; itr = itr->next()) + if (Player* member = itr->GetSource()) + if ((_killer == member || member->IsAtGroupRewardDistance(_victim))) + { + const uint8 lvl = member->getLevel(); + if (member->IsAlive()) + { + // 2.1. _count - number of alive group members within reward distance; + ++_count; + // 2.2. _aliveSumLevel - sum of levels of alive group members within reward distance; + _aliveSumLevel += 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 = Acore::XP::GetGrayLevel(lvl); + if (_victim->getLevel() > grayLevel && (!_maxNotGrayMember || _maxNotGrayMember->getLevel() < lvl)) + { + _maxNotGrayMember = member; + } + } + // 2.5. _sumLevel - sum of levels of group members within reward distance; + _sumLevel += lvl; + } + // 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()); + } + 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 (_victim && (_isBattleGround || (!_isPvP && !_killer->GetVehicle()))) + _xp = Acore::XP::Gain(player, _victim, _isBattleGround); + + if (_xp && !_isBattleGround && _victim) // pussywizard: npcs with relatively low hp give lower exp + if (_victim->GetTypeId() == TYPEID_UNIT) + if (const CreatureTemplate* ct = _victim->ToCreature()->GetCreatureTemplate()) + if (ct->ModHealth <= 0.75f && ct->ModHealth >= 0.0f) + _xp = uint32(_xp * ct->ModHealth); +} + +inline void KillRewarder::_RewardHonor(Player* player) +{ + // Rewarded player must be alive. + if (player->IsAlive()) + player->RewardHonor(_victim, _count, -1); +} + +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) + AddPct(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 (Creature* target = _victim->ToCreature()) + { + player->KilledMonster(target->GetCreatureTemplate(), target->GetGUID()); + player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE_TYPE, target->GetCreatureType(), 1, 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(); + } + // Give XP only in PvE or in battlegrounds. + // 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. + float reputationRate = _group ? _groupRate * float(player->getLevel()) / _sumLevel : /*Personal rate is 100%.*/ 1.0f; // Group rate depends on the sum of levels. + sScriptMgr->OnRewardKillRewarder(player, isDungeon, xpRate); // Personal rate is 100%. + + if (_xp) + { + // 4.2. Give XP. + _RewardXP(player, xpRate); + } + if (!_isBattleGround) + { + // If killer is in dungeon then all members receive full reputation at kill. + _RewardReputation(player, isDungeon ? 1.0f : reputationRate); + _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 = Acore::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 != nullptr; itr = itr->next()) + { + if (Player* member = itr->GetSource()) + { + if (_killer == member || member->IsAtGroupRewardDistance(_victim)) + { + _RewardPlayer(member, isDungeon); + // Xinef: only count players + //if (_victim->GetTypeId() == TYPEID_PLAYER) + // member->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_SPECIAL_PVP_KILL, 1, 0, _victim); + } + } + } + } + } +} + +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. + if (_killer->IsInMap(_victim)) // pussywizard: killer may be on other map (crashfix), when killing in a group same map is required, so its not a problem + _RewardPlayer(_killer, false); + } + + // 5. Credit instance encounter. + if (Creature* victim = _victim->ToCreature()) + if (victim->IsDungeonBoss()) + if (Map* map = _victim->FindMap()) + map->UpdateEncounterState(ENCOUNTER_CREDIT_KILL_CREATURE, _victim->GetEntry(), _victim); +} diff --git a/src/server/game/Entities/Player/KillerRewarder.h b/src/server/game/Entities/Player/KillerRewarder.h new file mode 100644 index 000000000..c96ef2cda --- /dev/null +++ b/src/server/game/Entities/Player/KillerRewarder.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3 + */ + +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; + Group* _group; + float _groupRate; + Player* _maxNotGrayMember; + uint32 _count; + uint32 _aliveSumLevel; + uint32 _sumLevel; + uint32 _xp; + bool _isFullXP; + uint8 _maxLevel; + bool _isBattleGround; + bool _isPvP; +}; diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index b82ba54a4..b9ce309ac 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -154,562 +154,6 @@ enum CharacterCustomizeFlags static uint32 copseReclaimDelay[MAX_DEATH_COUNT] = { 30, 60, 120 }; -// == PlayerTaxi ================================================ - -PlayerTaxi::PlayerTaxi() : _taxiSegment(0) -{ - memset(m_taximask, 0, sizeof(m_taximask)); -} - -void PlayerTaxi::InitTaxiNodesForLevel(uint32 race, uint32 chrClass, uint8 level) -{ - // class specific initial known nodes - switch (chrClass) - { - case CLASS_DEATH_KNIGHT: - { - for (uint8 i = 0; i < TaxiMaskSize; ++i) - m_taximask[i] |= sOldContinentsNodesMask[i]; - break; - } - } - - // race specific initial known nodes: capital and taxi hub masks - switch (race) - { - case RACE_HUMAN: - SetTaximaskNode(2); - break; // Human - case RACE_ORC: - SetTaximaskNode(23); - break; // Orc - case RACE_DWARF: - SetTaximaskNode(6); - break; // Dwarf - case RACE_NIGHTELF: - SetTaximaskNode(26); - SetTaximaskNode(27); - break; // Night Elf - case RACE_UNDEAD_PLAYER: - SetTaximaskNode(11); - break;// Undead - case RACE_TAUREN: - SetTaximaskNode(22); - break; // Tauren - case RACE_GNOME: - SetTaximaskNode(6); - break; // Gnome - case RACE_TROLL: - SetTaximaskNode(23); - break; // Troll - case RACE_BLOODELF: - SetTaximaskNode(82); - break; // Blood Elf - case RACE_DRAENEI: - SetTaximaskNode(94); - break; // Draenei - } - - // new continent starting masks (It will be accessible only at new map) - switch (Player::TeamIdForRace(race)) - { - case TEAM_ALLIANCE: - SetTaximaskNode(100); - break; - case TEAM_HORDE: - SetTaximaskNode(99); - break; - default: - break; - } - // level dependent taxi hubs - if (level >= 68) - SetTaximaskNode(213); //Shattered Sun Staging Area -} - -void PlayerTaxi::LoadTaxiMask(std::string const& data) -{ - Tokenizer tokens(data, ' '); - - uint8 index; - Tokenizer::const_iterator iter; - for (iter = tokens.begin(), index = 0; - (index < TaxiMaskSize) && (iter != tokens.end()); ++iter, ++index) - { - // load and set bits only for existed taxi nodes - m_taximask[index] = sTaxiNodesMask[index] & uint32(atol(*iter)); - } -} - -void PlayerTaxi::AppendTaximaskTo(ByteBuffer& data, bool all) -{ - if (all) - { - for (uint8 i = 0; i < TaxiMaskSize; i++) - data << uint32(sTaxiNodesMask[i]); // all existed nodes - } - else - { - for (uint8 i = 0; i < TaxiMaskSize; i++) - data << uint32(m_taximask[i]); // known nodes - } -} - -bool PlayerTaxi::LoadTaxiDestinationsFromString(const std::string& values, TeamId teamId) -{ - ClearTaxiDestinations(); - - Tokenizer tokens(values, ' '); - - for (Tokenizer::const_iterator iter = tokens.begin(); iter != tokens.end(); ++iter) - { - uint32 node = uint32(atol(*iter)); - AddTaxiDestination(node); - } - - // Check integrity - if (m_TaxiDestinations.size() < 3) - return false; - - // xinef: current segment is saved as last destination in db - _taxiSegment = m_TaxiDestinations[m_TaxiDestinations.size() - 1]; - m_TaxiDestinations.pop_back(); - - for (size_t i = 0; i < m_TaxiDestinations.size() - 1; ++i) - { - uint32 cost; - uint32 path; - sObjectMgr->GetTaxiPath(m_TaxiDestinations[i], m_TaxiDestinations[i + 1], path, cost); - if (!path) - return false; - } - - // can't load taxi path without mount set (quest taxi path?) - if (!sObjectMgr->GetTaxiMountDisplayId(GetTaxiSource(), teamId, true)) - return false; - - return true; -} - -std::string PlayerTaxi::SaveTaxiDestinationsToString() -{ - if (m_TaxiDestinations.empty()) - return ""; - - std::ostringstream ss; - - for (size_t i = 0; i < m_TaxiDestinations.size(); ++i) - ss << m_TaxiDestinations[i] << ' '; - - ss << _taxiSegment << ' '; - return ss.str(); -} - -uint32 PlayerTaxi::GetCurrentTaxiPath() const -{ - if (m_TaxiDestinations.size() < 2 || m_TaxiDestinations.size() <= _taxiSegment + 1) - return 0; - - uint32 path; - uint32 cost; - - sObjectMgr->GetTaxiPath(m_TaxiDestinations[_taxiSegment], m_TaxiDestinations[_taxiSegment + 1], path, cost); - - return path; -} - -std::ostringstream& operator<< (std::ostringstream& ss, PlayerTaxi const& taxi) -{ - for (uint8 i = 0; i < TaxiMaskSize; ++i) - ss << taxi.m_taximask[i] << ' '; - return ss; -} - -//== TradeData ================================================= - -TradeData* TradeData::GetTraderData() const -{ - return m_trader->GetTradeData(); -} - -Item* TradeData::GetItem(TradeSlots slot) const -{ - return m_items[slot] ? m_player->GetItemByGuid(m_items[slot]) : nullptr; -} - -bool TradeData::HasItem(ObjectGuid itemGuid) const -{ - for (uint8 i = 0; i < TRADE_SLOT_COUNT; ++i) - if (m_items[i] == itemGuid) - return true; - - return false; -} - -TradeSlots TradeData::GetTradeSlotForItem(ObjectGuid itemGuid) const -{ - for (uint8 i = 0; i < TRADE_SLOT_COUNT; ++i) - if (m_items[i] == itemGuid) - return TradeSlots(i); - - return TRADE_SLOT_INVALID; -} - -Item* TradeData::GetSpellCastItem() const -{ - return m_spellCastItem ? m_player->GetItemByGuid(m_spellCastItem) : nullptr; -} - -void TradeData::SetItem(TradeSlots slot, Item* item) -{ - ObjectGuid itemGuid = item ? item->GetGUID() : ObjectGuid::Empty; - - if (m_items[slot] == itemGuid) - return; - - m_items[slot] = itemGuid; - - SetAccepted(false); - GetTraderData()->SetAccepted(false); - - Update(); - - // need remove possible trader spell applied to changed item - if (slot == TRADE_SLOT_NONTRADED) - GetTraderData()->SetSpell(0); - - // need remove possible player spell applied (possible move reagent) - SetSpell(0); -} - -void TradeData::SetSpell(uint32 spell_id, Item* castItem /*= nullptr*/) -{ - ObjectGuid itemGuid = castItem ? castItem->GetGUID() : ObjectGuid::Empty; - - if (m_spell == spell_id && m_spellCastItem == itemGuid) - return; - - m_spell = spell_id; - m_spellCastItem = itemGuid; - - SetAccepted(false); - GetTraderData()->SetAccepted(false); - - Update(true); // send spell info to item owner - Update(false); // send spell info to caster self -} - -void TradeData::SetMoney(uint32 money) -{ - if (m_money == money) - return; - - if (!m_player->HasEnoughMoney(money)) - { - m_player->GetSession()->SendTradeStatus(TRADE_STATUS_BUSY); - return; - } - - m_money = money; - - SetAccepted(false); - GetTraderData()->SetAccepted(false); - - Update(true); -} - -void TradeData::Update(bool forTarget /*= true*/) -{ - if (forTarget) - m_trader->GetSession()->SendUpdateTrade(true); // player state for trader - else - m_player->GetSession()->SendUpdateTrade(false); // player state for player -} - -void TradeData::SetAccepted(bool state, bool crosssend /*= false*/) -{ - m_accepted = state; - - if (!state) - { - if (crosssend) - m_trader->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); - else - m_player->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); - } -} - -// == 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. _aliveSumLevel - sum of levels of alive group members within reward distance; -// 2.3. _sumLevel - sum of levels of group members within reward distance; -// 2.4. _maxLevel - maximum level of alive group member within reward distance; -// 2.5. _maxNotGrayMember - maximum level of alive group member within reward distance, -// for whom victim is not gray; -// 2.6. _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), _group(killer->GetGroup()), - _groupRate(1.0f), _maxNotGrayMember(nullptr), _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 - 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(); - - _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 != nullptr; itr = itr->next()) - if (Player* member = itr->GetSource()) - if ((_killer == member || member->IsAtGroupRewardDistance(_victim))) - { - const uint8 lvl = member->getLevel(); - if (member->IsAlive()) - { - // 2.1. _count - number of alive group members within reward distance; - ++_count; - // 2.2. _aliveSumLevel - sum of levels of alive group members within reward distance; - _aliveSumLevel += 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 = Acore::XP::GetGrayLevel(lvl); - if (_victim->getLevel() > grayLevel && (!_maxNotGrayMember || _maxNotGrayMember->getLevel() < lvl)) - { - _maxNotGrayMember = member; - } - } - // 2.5. _sumLevel - sum of levels of group members within reward distance; - _sumLevel += lvl; - } - // 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()); - } - 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 (_victim && (_isBattleGround || (!_isPvP && !_killer->GetVehicle()))) - _xp = Acore::XP::Gain(player, _victim, _isBattleGround); - - if (_xp && !_isBattleGround && _victim) // pussywizard: npcs with relatively low hp give lower exp - if (_victim->GetTypeId() == TYPEID_UNIT) - if (const CreatureTemplate* ct = _victim->ToCreature()->GetCreatureTemplate()) - if (ct->ModHealth <= 0.75f && ct->ModHealth >= 0.0f) - _xp = uint32(_xp * ct->ModHealth); -} - -inline void KillRewarder::_RewardHonor(Player* player) -{ - // Rewarded player must be alive. - if (player->IsAlive()) - player->RewardHonor(_victim, _count, -1); -} - -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) - AddPct(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 (Creature* target = _victim->ToCreature()) - { - player->KilledMonster(target->GetCreatureTemplate(), target->GetGUID()); - player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE_TYPE, target->GetCreatureType(), 1, 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(); - } - // Give XP only in PvE or in battlegrounds. - // 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. - float reputationRate = _group ? _groupRate * float(player->getLevel()) / _sumLevel : /*Personal rate is 100%.*/ 1.0f; // Group rate depends on the sum of levels. - sScriptMgr->OnRewardKillRewarder(player, isDungeon, xpRate); // Personal rate is 100%. - - if (_xp) - { - // 4.2. Give XP. - _RewardXP(player, xpRate); - } - if (!_isBattleGround) - { - // If killer is in dungeon then all members receive full reputation at kill. - _RewardReputation(player, isDungeon ? 1.0f : reputationRate); - _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 = Acore::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 != nullptr; itr = itr->next()) - { - if (Player* member = itr->GetSource()) - { - if (_killer == member || member->IsAtGroupRewardDistance(_victim)) - { - _RewardPlayer(member, isDungeon); - // Xinef: only count players - //if (_victim->GetTypeId() == TYPEID_PLAYER) - // member->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_SPECIAL_PVP_KILL, 1, 0, _victim); - } - } - } - } - } -} - -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. - if (_killer->IsInMap(_victim)) // pussywizard: killer may be on other map (crashfix), when killing in a group same map is required, so its not a problem - _RewardPlayer(_killer, false); - } - - // 5. Credit instance encounter. - if (Creature* victim = _victim->ToCreature()) - if (victim->IsDungeonBoss()) - if (Map* map = _victim->FindMap()) - map->UpdateEncounterState(ENCOUNTER_CREDIT_KILL_CREATURE, _victim->GetEntry(), _victim); -} - -// == Player ==================================================== - // we can disable this warning for this since it only // causes undefined behavior when passed to the base class constructor #ifdef _MSC_VER diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index f4ab58f7a..061dc7c31 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -7,18 +7,21 @@ #ifndef _PLAYER_H #define _PLAYER_H +#include "ArenaTeam.h" #include "Battleground.h" #include "DatabaseEnvFwd.h" #include "DBCStores.h" #include "GroupReference.h" #include "InstanceSaveMgr.h" -#include "ArenaTeam.h" #include "Item.h" +#include "KillerRewarder.h" #include "MapReference.h" #include "ObjectMgr.h" #include "PetDefines.h" +#include "PlayerTaxi.h" #include "QuestDef.h" #include "SpellMgr.h" +#include "TradeData.h" #include "Unit.h" #include "WorldSession.h" #include @@ -676,14 +679,6 @@ struct ItemPosCount }; typedef std::vector ItemPosCountVec; -enum TradeSlots -{ - TRADE_SLOT_COUNT = 7, - TRADE_SLOT_TRADED_COUNT = 6, - TRADE_SLOT_NONTRADED = 6, - TRADE_SLOT_INVALID = -1, -}; - enum TransferAbortReason { TRANSFER_ABORT_NONE = 0x00, @@ -917,64 +912,6 @@ enum EmoteBroadcastTextID EMOTE_BROADCAST_TEXT_ID_STRANGE_GESTURES = 91243 }; -class PlayerTaxi -{ -public: - PlayerTaxi(); - ~PlayerTaxi() = default; - // Nodes - void InitTaxiNodesForLevel(uint32 race, uint32 chrClass, uint8 level); - void LoadTaxiMask(std::string const& data); - - [[nodiscard]] bool IsTaximaskNodeKnown(uint32 nodeidx) const - { - uint8 field = uint8((nodeidx - 1) / 32); - uint32 submask = 1 << ((nodeidx - 1) % 32); - return (m_taximask[field] & submask) == submask; - } - bool SetTaximaskNode(uint32 nodeidx) - { - uint8 field = uint8((nodeidx - 1) / 32); - uint32 submask = 1 << ((nodeidx - 1) % 32); - if ((m_taximask[field] & submask) != submask) - { - m_taximask[field] |= submask; - return true; - } - else - return false; - } - void AppendTaximaskTo(ByteBuffer& data, bool all); - - // Destinations - bool LoadTaxiDestinationsFromString(std::string const& values, TeamId teamId); - std::string SaveTaxiDestinationsToString(); - - void ClearTaxiDestinations() { m_TaxiDestinations.clear(); _taxiSegment = 0; } - void AddTaxiDestination(uint32 dest) { m_TaxiDestinations.push_back(dest); } - [[nodiscard]] uint32 GetTaxiSource() const { return m_TaxiDestinations.size() <= _taxiSegment + 1 ? 0 : m_TaxiDestinations[_taxiSegment]; } - [[nodiscard]] uint32 GetTaxiDestination() const { return m_TaxiDestinations.size() <= _taxiSegment + 1 ? 0 : m_TaxiDestinations[_taxiSegment + 1]; } - [[nodiscard]] uint32 GetCurrentTaxiPath() const; - uint32 NextTaxiDestination() - { - ++_taxiSegment; - return GetTaxiDestination(); - } - - // xinef: - void SetTaxiSegment(uint32 segment) { _taxiSegment = segment; } - [[nodiscard]] uint32 GetTaxiSegment() const { return _taxiSegment; } - - [[nodiscard]] std::vector const& GetPath() const { return m_TaxiDestinations; } - [[nodiscard]] bool empty() const { return m_TaxiDestinations.empty(); } - - friend std::ostringstream& operator<< (std::ostringstream& ss, PlayerTaxi const& taxi); -private: - TaxiMask m_taximask; - std::vector m_TaxiDestinations; - uint32 _taxiSegment; -}; - std::ostringstream& operator<< (std::ostringstream& ss, PlayerTaxi const& taxi); class Player; @@ -1012,87 +949,6 @@ struct EntryPointData [[nodiscard]] bool HasTaxiPath() const { return !taxiPath.empty(); } }; -class TradeData -{ -public: // constructors - TradeData(Player* player, Player* trader) : m_player(player), m_trader(trader), m_accepted(false), m_acceptProccess(false), m_money(0), m_spell(0) - { - } - - [[nodiscard]] Player* GetTrader() const { return m_trader; } - [[nodiscard]] TradeData* GetTraderData() const; - - [[nodiscard]] Item* GetItem(TradeSlots slot) const; - [[nodiscard]] bool HasItem(ObjectGuid itemGuid) const; - [[nodiscard]] TradeSlots GetTradeSlotForItem(ObjectGuid itemGuid) const; - void SetItem(TradeSlots slot, Item* item); - - [[nodiscard]] uint32 GetSpell() const { return m_spell; } - void SetSpell(uint32 spell_id, Item* castItem = nullptr); - - [[nodiscard]] Item* GetSpellCastItem() const; - [[nodiscard]] bool HasSpellCastItem() const { return m_spellCastItem; } - - [[nodiscard]] uint32 GetMoney() const { return m_money; } - void SetMoney(uint32 money); - - [[nodiscard]] bool IsAccepted() const { return m_accepted; } - void SetAccepted(bool state, bool crosssend = false); - - [[nodiscard]] bool IsInAcceptProcess() const { return m_acceptProccess; } - void SetInAcceptProcess(bool state) { m_acceptProccess = state; } - -private: // internal functions - void Update(bool for_trader = true); - -private: // fields - Player* m_player; // Player who own of this TradeData - Player* m_trader; // Player who trade with m_player - - bool m_accepted; // m_player press accept for trade list - bool m_acceptProccess; // one from player/trader press accept and this processed - - uint32 m_money; // m_player place money to trade - - uint32 m_spell; // m_player apply spell to non-traded slot item - ObjectGuid m_spellCastItem; // applied spell casted by item use - - ObjectGuid 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; - Group* _group; - float _groupRate; - Player* _maxNotGrayMember; - uint32 _count; - uint32 _aliveSumLevel; - uint32 _sumLevel; - uint32 _xp; - bool _isFullXP; - uint8 _maxLevel; - bool _isBattleGround; - bool _isPvP; -}; - class Player : public Unit, public GridObject { friend class WorldSession; diff --git a/src/server/game/Entities/Player/PlayerTaxi.cpp b/src/server/game/Entities/Player/PlayerTaxi.cpp new file mode 100644 index 000000000..c2231bfb5 --- /dev/null +++ b/src/server/game/Entities/Player/PlayerTaxi.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3 + */ + +#include "Player.h" + +PlayerTaxi::PlayerTaxi() : _taxiSegment(0) +{ + memset(m_taximask, 0, sizeof(m_taximask)); +} + +void PlayerTaxi::InitTaxiNodesForLevel(uint32 race, uint32 chrClass, uint8 level) +{ + // class specific initial known nodes + switch (chrClass) + { + case CLASS_DEATH_KNIGHT: + { + for (uint8 i = 0; i < TaxiMaskSize; ++i) + m_taximask[i] |= sOldContinentsNodesMask[i]; + break; + } + } + + // race specific initial known nodes: capital and taxi hub masks + switch (race) + { + case RACE_HUMAN: + SetTaximaskNode(2); + break; // Human + case RACE_ORC: + SetTaximaskNode(23); + break; // Orc + case RACE_DWARF: + SetTaximaskNode(6); + break; // Dwarf + case RACE_NIGHTELF: + SetTaximaskNode(26); + SetTaximaskNode(27); + break; // Night Elf + case RACE_UNDEAD_PLAYER: + SetTaximaskNode(11); + break;// Undead + case RACE_TAUREN: + SetTaximaskNode(22); + break; // Tauren + case RACE_GNOME: + SetTaximaskNode(6); + break; // Gnome + case RACE_TROLL: + SetTaximaskNode(23); + break; // Troll + case RACE_BLOODELF: + SetTaximaskNode(82); + break; // Blood Elf + case RACE_DRAENEI: + SetTaximaskNode(94); + break; // Draenei + } + + // new continent starting masks (It will be accessible only at new map) + switch (Player::TeamIdForRace(race)) + { + case TEAM_ALLIANCE: + SetTaximaskNode(100); + break; + case TEAM_HORDE: + SetTaximaskNode(99); + break; + default: + break; + } + // level dependent taxi hubs + if (level >= 68) + SetTaximaskNode(213); //Shattered Sun Staging Area +} + +void PlayerTaxi::LoadTaxiMask(std::string const& data) +{ + Tokenizer tokens(data, ' '); + + uint8 index; + Tokenizer::const_iterator iter; + for (iter = tokens.begin(), index = 0; + (index < TaxiMaskSize) && (iter != tokens.end()); ++iter, ++index) + { + // load and set bits only for existed taxi nodes + m_taximask[index] = sTaxiNodesMask[index] & uint32(atol(*iter)); + } +} + +void PlayerTaxi::AppendTaximaskTo(ByteBuffer& data, bool all) +{ + if (all) + { + for (uint8 i = 0; i < TaxiMaskSize; i++) + data << uint32(sTaxiNodesMask[i]); // all existed nodes + } + else + { + for (uint8 i = 0; i < TaxiMaskSize; i++) + data << uint32(m_taximask[i]); // known nodes + } +} + +bool PlayerTaxi::LoadTaxiDestinationsFromString(const std::string& values, TeamId teamId) +{ + ClearTaxiDestinations(); + + Tokenizer tokens(values, ' '); + + for (Tokenizer::const_iterator iter = tokens.begin(); iter != tokens.end(); ++iter) + { + uint32 node = uint32(atol(*iter)); + AddTaxiDestination(node); + } + + // Check integrity + if (m_TaxiDestinations.size() < 3) + return false; + + // xinef: current segment is saved as last destination in db + _taxiSegment = m_TaxiDestinations[m_TaxiDestinations.size() - 1]; + m_TaxiDestinations.pop_back(); + + for (size_t i = 0; i < m_TaxiDestinations.size() - 1; ++i) + { + uint32 cost; + uint32 path; + sObjectMgr->GetTaxiPath(m_TaxiDestinations[i], m_TaxiDestinations[i + 1], path, cost); + if (!path) + return false; + } + + // can't load taxi path without mount set (quest taxi path?) + if (!sObjectMgr->GetTaxiMountDisplayId(GetTaxiSource(), teamId, true)) + return false; + + return true; +} + +std::string PlayerTaxi::SaveTaxiDestinationsToString() +{ + if (m_TaxiDestinations.empty()) + return ""; + + std::ostringstream ss; + + for (size_t i = 0; i < m_TaxiDestinations.size(); ++i) + ss << m_TaxiDestinations[i] << ' '; + + ss << _taxiSegment << ' '; + return ss.str(); +} + +uint32 PlayerTaxi::GetCurrentTaxiPath() const +{ + if (m_TaxiDestinations.size() < 2 || m_TaxiDestinations.size() <= _taxiSegment + 1) + return 0; + + uint32 path; + uint32 cost; + + sObjectMgr->GetTaxiPath(m_TaxiDestinations[_taxiSegment], m_TaxiDestinations[_taxiSegment + 1], path, cost); + + return path; +} + +std::ostringstream& operator<< (std::ostringstream& ss, PlayerTaxi const& taxi) +{ + for (uint8 i = 0; i < TaxiMaskSize; ++i) + ss << taxi.m_taximask[i] << ' '; + return ss; +} \ No newline at end of file diff --git a/src/server/game/Entities/Player/PlayerTaxi.h b/src/server/game/Entities/Player/PlayerTaxi.h new file mode 100644 index 000000000..3c6c7cf2f --- /dev/null +++ b/src/server/game/Entities/Player/PlayerTaxi.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3 + */ + +class PlayerTaxi +{ +public: + PlayerTaxi(); + ~PlayerTaxi() = default; + // Nodes + void InitTaxiNodesForLevel(uint32 race, uint32 chrClass, uint8 level); + void LoadTaxiMask(std::string const& data); + + [[nodiscard]] bool IsTaximaskNodeKnown(uint32 nodeidx) const + { + uint8 field = uint8((nodeidx - 1) / 32); + uint32 submask = 1 << ((nodeidx - 1) % 32); + return (m_taximask[field] & submask) == submask; + } + bool SetTaximaskNode(uint32 nodeidx) + { + uint8 field = uint8((nodeidx - 1) / 32); + uint32 submask = 1 << ((nodeidx - 1) % 32); + if ((m_taximask[field] & submask) != submask) + { + m_taximask[field] |= submask; + return true; + } + else + return false; + } + void AppendTaximaskTo(ByteBuffer& data, bool all); + + // Destinations + bool LoadTaxiDestinationsFromString(std::string const& values, TeamId teamId); + std::string SaveTaxiDestinationsToString(); + + void ClearTaxiDestinations() { m_TaxiDestinations.clear(); _taxiSegment = 0; } + void AddTaxiDestination(uint32 dest) { m_TaxiDestinations.push_back(dest); } + [[nodiscard]] uint32 GetTaxiSource() const { return m_TaxiDestinations.size() <= _taxiSegment + 1 ? 0 : m_TaxiDestinations[_taxiSegment]; } + [[nodiscard]] uint32 GetTaxiDestination() const { return m_TaxiDestinations.size() <= _taxiSegment + 1 ? 0 : m_TaxiDestinations[_taxiSegment + 1]; } + [[nodiscard]] uint32 GetCurrentTaxiPath() const; + uint32 NextTaxiDestination() + { + ++_taxiSegment; + return GetTaxiDestination(); + } + + // xinef: + void SetTaxiSegment(uint32 segment) { _taxiSegment = segment; } + [[nodiscard]] uint32 GetTaxiSegment() const { return _taxiSegment; } + + [[nodiscard]] std::vector const& GetPath() const { return m_TaxiDestinations; } + [[nodiscard]] bool empty() const { return m_TaxiDestinations.empty(); } + + friend std::ostringstream& operator<< (std::ostringstream& ss, PlayerTaxi const& taxi); +private: + TaxiMask m_taximask; + std::vector m_TaxiDestinations; + uint32 _taxiSegment; +}; diff --git a/src/server/game/Entities/Player/TradeData.cpp b/src/server/game/Entities/Player/TradeData.cpp new file mode 100644 index 000000000..83122715e --- /dev/null +++ b/src/server/game/Entities/Player/TradeData.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3 + */ + +#include "Player.h" + +TradeData* TradeData::GetTraderData() const +{ + return m_trader->GetTradeData(); +} + +Item* TradeData::GetItem(TradeSlots slot) const +{ + return m_items[slot] ? m_player->GetItemByGuid(m_items[slot]) : nullptr; +} + +bool TradeData::HasItem(ObjectGuid itemGuid) const +{ + for (uint8 i = 0; i < TRADE_SLOT_COUNT; ++i) + if (m_items[i] == itemGuid) + return true; + + return false; +} + +TradeSlots TradeData::GetTradeSlotForItem(ObjectGuid itemGuid) const +{ + for (uint8 i = 0; i < TRADE_SLOT_COUNT; ++i) + if (m_items[i] == itemGuid) + return TradeSlots(i); + + return TRADE_SLOT_INVALID; +} + +Item* TradeData::GetSpellCastItem() const +{ + return m_spellCastItem ? m_player->GetItemByGuid(m_spellCastItem) : nullptr; +} + +void TradeData::SetItem(TradeSlots slot, Item* item) +{ + ObjectGuid itemGuid = item ? item->GetGUID() : ObjectGuid::Empty; + + if (m_items[slot] == itemGuid) + return; + + m_items[slot] = itemGuid; + + SetAccepted(false); + GetTraderData()->SetAccepted(false); + + Update(); + + // need remove possible trader spell applied to changed item + if (slot == TRADE_SLOT_NONTRADED) + GetTraderData()->SetSpell(0); + + // need remove possible player spell applied (possible move reagent) + SetSpell(0); +} + +void TradeData::SetSpell(uint32 spell_id, Item* castItem /*= nullptr*/) +{ + ObjectGuid itemGuid = castItem ? castItem->GetGUID() : ObjectGuid::Empty; + + if (m_spell == spell_id && m_spellCastItem == itemGuid) + return; + + m_spell = spell_id; + m_spellCastItem = itemGuid; + + SetAccepted(false); + GetTraderData()->SetAccepted(false); + + Update(true); // send spell info to item owner + Update(false); // send spell info to caster self +} + +void TradeData::SetMoney(uint32 money) +{ + if (m_money == money) + return; + + if (!m_player->HasEnoughMoney(money)) + { + m_player->GetSession()->SendTradeStatus(TRADE_STATUS_BUSY); + return; + } + + m_money = money; + + SetAccepted(false); + GetTraderData()->SetAccepted(false); + + Update(true); +} + +void TradeData::Update(bool forTarget /*= true*/) +{ + if (forTarget) + m_trader->GetSession()->SendUpdateTrade(true); // player state for trader + else + m_player->GetSession()->SendUpdateTrade(false); // player state for player +} + +void TradeData::SetAccepted(bool state, bool crosssend /*= false*/) +{ + m_accepted = state; + + if (!state) + { + if (crosssend) + m_trader->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); + else + m_player->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); + } +} diff --git a/src/server/game/Entities/Player/TradeData.h b/src/server/game/Entities/Player/TradeData.h new file mode 100644 index 000000000..39303b146 --- /dev/null +++ b/src/server/game/Entities/Player/TradeData.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3 + */ + +enum TradeSlots +{ + TRADE_SLOT_COUNT = 7, + TRADE_SLOT_TRADED_COUNT = 6, + TRADE_SLOT_NONTRADED = 6, + TRADE_SLOT_INVALID = -1, +}; + +class TradeData +{ +public: // constructors + TradeData(Player* player, Player* trader) : m_player(player), m_trader(trader), m_accepted(false), m_acceptProccess(false), m_money(0), m_spell(0) + { + } + + [[nodiscard]] Player* GetTrader() const { return m_trader; } + [[nodiscard]] TradeData* GetTraderData() const; + + [[nodiscard]] Item* GetItem(TradeSlots slot) const; + [[nodiscard]] bool HasItem(ObjectGuid itemGuid) const; + [[nodiscard]] TradeSlots GetTradeSlotForItem(ObjectGuid itemGuid) const; + void SetItem(TradeSlots slot, Item* item); + + [[nodiscard]] uint32 GetSpell() const { return m_spell; } + void SetSpell(uint32 spell_id, Item* castItem = nullptr); + + [[nodiscard]] Item* GetSpellCastItem() const; + [[nodiscard]] bool HasSpellCastItem() const { return m_spellCastItem; } + + [[nodiscard]] uint32 GetMoney() const { return m_money; } + void SetMoney(uint32 money); + + [[nodiscard]] bool IsAccepted() const { return m_accepted; } + void SetAccepted(bool state, bool crosssend = false); + + [[nodiscard]] bool IsInAcceptProcess() const { return m_acceptProccess; } + void SetInAcceptProcess(bool state) { m_acceptProccess = state; } + +private: // internal functions + void Update(bool for_trader = true); + +private: // fields + Player* m_player; // Player who own of this TradeData + Player* m_trader; // Player who trade with m_player + + bool m_accepted; // m_player press accept for trade list + bool m_acceptProccess; // one from player/trader press accept and this processed + + uint32 m_money; // m_player place money to trade + + uint32 m_spell; // m_player apply spell to non-traded slot item + ObjectGuid m_spellCastItem; // applied spell casted by item use + + ObjectGuid m_items[TRADE_SLOT_COUNT]; // traded itmes from m_player side including non-traded slot +}; \ No newline at end of file