Merge branch 'master' into Playerbot

This commit is contained in:
Yunfan Li
2025-03-14 21:37:02 +08:00
45 changed files with 2300 additions and 269 deletions
+44 -20
View File
@@ -154,25 +154,26 @@ void CalendarMgr::AddInvite(CalendarEvent* calendarEvent, CalendarInvite* invite
}
}
void CalendarMgr::RemoveEvent(uint64 eventId, ObjectGuid remover)
CalendarEventStore::iterator CalendarMgr::RemoveEvent(uint64 eventId, ObjectGuid remover)
{
CalendarEvent* calendarEvent = GetEvent(eventId);
CalendarEventStore::iterator current;
CalendarEvent* calendarEvent = GetEvent(eventId, &current);
if (!calendarEvent)
{
SendCalendarCommandResult(remover, CALENDAR_ERROR_EVENT_INVALID);
return;
return _events.end();
}
RemoveEvent(calendarEvent, remover);
CalendarEventStore::const_iterator constItr(current);
return RemoveEvent(calendarEvent, remover, &constItr);
}
void CalendarMgr::RemoveEvent(CalendarEvent* calendarEvent, ObjectGuid remover)
{
CalendarEventStore::iterator CalendarMgr::RemoveEvent(CalendarEvent* calendarEvent, ObjectGuid remover, CalendarEventStore::const_iterator* currIt) {
if (!calendarEvent)
{
SendCalendarCommandResult(remover, CALENDAR_ERROR_EVENT_INVALID);
return;
return _events.end();
}
SendCalendarEventRemovedAlert(*calendarEvent);
@@ -204,9 +205,20 @@ void CalendarMgr::RemoveEvent(CalendarEvent* calendarEvent, ObjectGuid remover)
trans->Append(stmt);
CharacterDatabase.CommitTransaction(trans);
_events.erase(calendarEvent);
if (currIt)
{
delete calendarEvent;
return _events.erase(*currIt);
}
if (auto it = _events.find(calendarEvent); it != _events.end())
{
delete calendarEvent;
return _events.erase(it);
}
delete calendarEvent;
return;
return _events.end();
}
void CalendarMgr::RemoveInvite(uint64 inviteId, uint64 eventId, ObjectGuid /*remover*/)
@@ -277,13 +289,12 @@ void CalendarMgr::RemoveAllPlayerEventsAndInvites(ObjectGuid guid)
{
for (CalendarEventStore::const_iterator itr = _events.begin(); itr != _events.end();)
{
CalendarEvent* event = *itr;
++itr;
if (event->GetCreatorGUID() == guid)
if (CalendarEvent* event = *itr; event->GetCreatorGUID() == guid)
{
RemoveEvent(event, ObjectGuid::Empty);
itr = RemoveEvent(event, ObjectGuid::Empty, &itr);
continue;
}
++itr;
}
CalendarInviteStore playerInvites = GetPlayerInvites(guid);
@@ -293,22 +304,33 @@ void CalendarMgr::RemoveAllPlayerEventsAndInvites(ObjectGuid guid)
void CalendarMgr::RemovePlayerGuildEventsAndSignups(ObjectGuid guid, uint32 guildId)
{
for (CalendarEventStore::const_iterator itr = _events.begin(); itr != _events.end(); ++itr)
for (CalendarEventStore::const_iterator itr = _events.begin(); itr != _events.end();)
{
if ((*itr)->GetCreatorGUID() == guid && ((*itr)->IsGuildEvent() || (*itr)->IsGuildAnnouncement()))
RemoveEvent((*itr)->GetEventId(), guid);
{
itr = RemoveEvent((*itr)->GetEventId(), guid);
continue;
}
++itr;
}
CalendarInviteStore playerInvites = GetPlayerInvites(guid);
for (CalendarInviteStore::const_iterator itr = playerInvites.begin(); itr != playerInvites.end(); ++itr)
if (CalendarEvent* calendarEvent = GetEvent((*itr)->GetEventId()))
if (CalendarEvent* calendarEvent = GetEvent((*itr)->GetEventId(), nullptr))
if (calendarEvent->IsGuildEvent() && calendarEvent->GetGuildId() == guildId)
RemoveInvite((*itr)->GetInviteId(), (*itr)->GetEventId(), guid);
}
CalendarEvent* CalendarMgr::GetEvent(uint64 eventId)
CalendarEvent* CalendarMgr::GetEvent(uint64 eventId, CalendarEventStore::iterator* it)
{
for (CalendarEventStore::iterator itr = _events.begin(); itr != _events.end(); ++itr)
if ((*itr)->GetEventId() == eventId)
{
if (it)
*it = itr;
return *itr;
}
return nullptr;
}
@@ -366,10 +388,12 @@ void CalendarMgr::DeleteOldEvents()
for (CalendarEventStore::const_iterator itr = _events.begin(); itr != _events.end();)
{
CalendarEvent* event = *itr;
if (CalendarEvent* event = *itr; event->GetEventTime() < oldEventsTime)
{
itr = RemoveEvent(event, ObjectGuid::Empty, &itr);
continue;
}
++itr;
if (event->GetEventTime() < oldEventsTime)
RemoveEvent(event, ObjectGuid::Empty);
}
}
+3 -3
View File
@@ -293,7 +293,7 @@ public:
void LoadFromDB();
CalendarEvent* GetEvent(uint64 eventId);
CalendarEvent* GetEvent(uint64 eventId, CalendarEventStore::iterator* it = nullptr);
CalendarEventStore const& GetEvents() const { return _events; }
CalendarEventStore GetEventsCreatedBy(ObjectGuid guid, bool includeGuildEvents = false);
CalendarEventStore GetPlayerEvents(ObjectGuid guid);
@@ -314,8 +314,8 @@ public:
uint32 GetPlayerNumPending(ObjectGuid guid);
void AddEvent(CalendarEvent* calendarEvent, CalendarSendEventType sendType);
void RemoveEvent(uint64 eventId, ObjectGuid remover);
void RemoveEvent(CalendarEvent* calendarEvent, ObjectGuid remover);
CalendarEventStore::iterator RemoveEvent(uint64 eventId, ObjectGuid remover);
CalendarEventStore::iterator RemoveEvent(CalendarEvent* calendarEvent, ObjectGuid remover, CalendarEventStore::const_iterator* currIt = nullptr);
void UpdateEvent(CalendarEvent* calendarEvent);
void AddInvite(CalendarEvent* calendarEvent, CalendarInvite* invite, CharacterDatabaseTransaction trans = nullptr);
@@ -2476,6 +2476,9 @@ void Player::GiveLevel(uint8 level)
if (level == oldLevel)
return;
if (!sScriptMgr->OnPlayerCanGiveLevel(this, level))
return;
if (Guild* guild = GetGuild())
guild->UpdateMemberData(this, GUILD_MEMBER_DATA_LEVEL, level);
+12 -3
View File
@@ -3928,7 +3928,7 @@ void Unit::_UpdateAutoRepeatSpell()
static uint32 const HUNTER_AUTOSHOOT = 75;
// Check "realtime" interrupts
if ((IsPlayer() && ToPlayer()->isMoving()) || IsNonMeleeSpellCast(false, false, true, spellProto->Id == HUNTER_AUTOSHOOT))
if ((IsPlayer() && ToPlayer()->isMoving() && spellProto->Id != HUNTER_AUTOSHOOT) || IsNonMeleeSpellCast(false, false, true, spellProto->Id == HUNTER_AUTOSHOOT))
{
// cancel wand shoot
if (spellProto->Id != HUNTER_AUTOSHOOT)
@@ -3937,7 +3937,8 @@ void Unit::_UpdateAutoRepeatSpell()
return;
}
if (m_AutoRepeatFirstCast && getAttackTimer(RANGED_ATTACK) < 500)
// Apply delay (Hunter's autoshoot not affected)
if (m_AutoRepeatFirstCast && getAttackTimer(RANGED_ATTACK) < 500 && spellProto->Id != HUNTER_AUTOSHOOT)
{
setAttackTimer(RANGED_ATTACK, 500);
}
@@ -8412,7 +8413,15 @@ bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggere
if (!procSpell || !IsPlayer() || !victim)
return false;
uint32 spell = procSpell->SpellFamilyFlags[0] & 0x2 ? 45297 : 45284;
uint32 spell = 45284;
// chain lightning only procs 1/3 of the time
if (procSpell->SpellFamilyFlags[0] & 0x2)
{
if (!roll_chance_i(33))
return false;
spell = 45297;
}
if (procEx & PROC_EX_CRITICAL_HIT)
damage /= 2;
-113
View File
@@ -10297,116 +10297,3 @@ uint32 ObjectMgr::GetQuestMoneyReward(uint8 level, uint32 questMoneyDifficulty)
return 0;
}
void ObjectMgr::SendServerMail(Player* player, uint32 id, uint32 reqLevel, uint32 reqPlayTime, uint32 rewardMoneyA, uint32 rewardMoneyH, uint32 rewardItemA, uint32 rewardItemCountA, uint32 rewardItemH, uint32 rewardItemCountH, std::string subject, std::string body, uint8 active) const
{
if (active)
{
if (player->GetLevel() < reqLevel)
return;
if (player->GetTotalPlayedTime() < reqPlayTime)
return;
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
MailSender sender(MAIL_NORMAL, player->GetGUID().GetCounter(), MAIL_STATIONERY_GM);
MailDraft draft(subject, body);
draft.AddMoney(player->GetTeamId() == TEAM_ALLIANCE ? rewardMoneyA : rewardMoneyH);
if (Item* mailItem = Item::CreateItem(player->GetTeamId() == TEAM_ALLIANCE ? rewardItemA : rewardItemH, player->GetTeamId() == TEAM_ALLIANCE ? rewardItemCountA : rewardItemCountH))
{
mailItem->SaveToDB(trans);
draft.AddItem(mailItem);
}
draft.SendMailTo(trans, MailReceiver(player), sender);
CharacterDatabase.CommitTransaction(trans);
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_MAIL_SERVER_CHARACTER);
stmt->SetData(0, player->GetGUID().GetCounter());
stmt->SetData(1, id);
CharacterDatabase.Execute(stmt);
LOG_DEBUG("entities.player", "ObjectMgr::SendServerMail() Sent mail id {} to {}", id, player->GetGUID().ToString());
}
}
void ObjectMgr::LoadMailServerTemplates()
{
uint32 oldMSTime = getMSTime();
_serverMailStore.clear(); // for reload case
// 0 1 2 3 4 5 6 7 8 9 10 11
QueryResult result = CharacterDatabase.Query("SELECT `id`, `reqLevel`, `reqPlayTime`, `moneyA`, `moneyH`, `itemA`, `itemCountA`, `itemH`,`itemCountH`, `subject`, `body`, `active` FROM `mail_server_template`");
if (!result)
{
LOG_INFO("sql.sql", ">> Loaded 0 server mail rewards. DB table `mail_server_template` is empty.");
LOG_INFO("server.loading", " ");
return;
}
_serverMailStore.rehash(result->GetRowCount());
do
{
Field* fields = result->Fetch();
uint32 id = fields[0].Get<uint32>();
ServerMail& servMail = _serverMailStore[id];
servMail.id = id;
servMail.reqLevel = fields[1].Get<uint8>();
servMail.reqPlayTime = fields[2].Get<uint32>();
servMail.moneyA = fields[3].Get<uint32>();
servMail.moneyH = fields[4].Get<uint32>();
servMail.itemA = fields[5].Get<uint32>();
servMail.itemCountA = fields[6].Get<uint32>();
servMail.itemH = fields[7].Get<uint32>();
servMail.itemCountH = fields[8].Get<uint32>();
servMail.subject = fields[9].Get<std::string>();
servMail.body = fields[10].Get<std::string>();
servMail.active = fields[11].Get<uint8>();
if (servMail.reqLevel > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
{
LOG_ERROR("sql.sql", "Table `mail_server_template` has reqLevel {} but max level is {} for id {}, skipped.", servMail.reqLevel, sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL), servMail.id);
return;
}
if (servMail.moneyA > MAX_MONEY_AMOUNT || servMail.moneyH > MAX_MONEY_AMOUNT)
{
LOG_ERROR("sql.sql", "Table `mail_server_template` has moneyA {} or moneyH {} larger than MAX_MONEY_AMOUNT {} for id {}, skipped.", servMail.moneyA, servMail.moneyH, MAX_MONEY_AMOUNT, servMail.id);
return;
}
ItemTemplate const* itemTemplateA = sObjectMgr->GetItemTemplate(servMail.itemA);
if (!itemTemplateA && servMail.itemA)
{
LOG_ERROR("sql.sql", "Table `mail_server_template` has invalid item in itemA {} for id {}, skipped.", servMail.itemA, servMail.id);
return;
}
ItemTemplate const* itemTemplateH = sObjectMgr->GetItemTemplate(servMail.itemH);
if (!itemTemplateH && servMail.itemH)
{
LOG_ERROR("sql.sql", "Table `mail_server_template` has invalid item in itemH {} for id {}, skipped.", servMail.itemH, servMail.id);
return;
}
if (!servMail.itemA && servMail.itemCountA)
{
LOG_ERROR("sql.sql", "Table `mail_server_template` has itemCountA {} with no ItemA, set to 0", servMail.itemCountA);
servMail.itemCountA = 0;
}
if (!servMail.itemH && servMail.itemCountH)
{
LOG_ERROR("sql.sql", "Table `mail_server_template` has itemCountH {} with no ItemH, set to 0", servMail.itemCountH);
servMail.itemCountH = 0;
}
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} Mail Server Template in {} ms", _serverMailStore.size(), GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
-7
View File
@@ -665,7 +665,6 @@ typedef std::map<std::pair<uint32, uint8>, QuestGreeting> QuestGreetingContainer
typedef std::unordered_map<uint32, VendorItemData> CacheVendorItemContainer;
typedef std::unordered_map<uint32, TrainerSpellData> CacheTrainerSpellContainer;
typedef std::unordered_map<uint32, ServerMail> ServerMailContainer;
typedef std::vector<uint32> CreatureCustomIDsContainer;
@@ -1056,7 +1055,6 @@ public:
void LoadInstanceTemplate();
void LoadInstanceEncounters();
void LoadMailLevelRewards();
void LoadMailServerTemplates();
void LoadVehicleTemplateAccessories();
void LoadVehicleAccessories();
void LoadVehicleSeatAddon();
@@ -1189,8 +1187,6 @@ public:
return nullptr;
}
[[nodiscard]] ServerMailContainer const& GetAllServerMailStore() const { return _serverMailStore; }
[[nodiscard]] BroadcastText const* GetBroadcastText(uint32 id) const
{
BroadcastTextContainer::const_iterator itr = _broadcastTextStore.find(id);
@@ -1450,7 +1446,6 @@ public:
}
[[nodiscard]] uint32 GetQuestMoneyReward(uint8 level, uint32 questMoneyDifficulty) const;
void SendServerMail(Player* player, uint32 id, uint32 reqLevel, uint32 reqPlayTime, uint32 rewardMoneyA, uint32 rewardMoneyH, uint32 rewardItemA, uint32 rewardItemCountA, uint32 rewardItemH, uint32 rewardItemCountH, std::string subject, std::string body, uint8 active) const;
private:
// first free id for selected id type
uint32 _auctionId; // pussywizard: accessed by a single thread
@@ -1615,8 +1610,6 @@ private:
CacheVendorItemContainer _cacheVendorItemStore;
CacheTrainerSpellContainer _cacheTrainerSpellStore;
ServerMailContainer _serverMailStore;
std::set<uint32> _difficultyEntries[MAX_DIFFICULTY - 1]; // already loaded difficulty 1 value in creatures, used in CheckCreatureTemplate
std::set<uint32> _hasDifficultyEntries[MAX_DIFFICULTY - 1]; // already loaded creatures with difficulty 1 values, used in CheckCreatureTemplate
+2
View File
@@ -1116,6 +1116,8 @@ void Group::GroupLoot(Loot* loot, WorldObject* pLootedObject)
if (member->IsAtLootRewardDistance(pLootedObject))
{
r->totalPlayersRolling++;
RollVote vote = NOT_EMITED_YET;
if (!CanRollOnItem(*i, member, loot))
{
+2
View File
@@ -1036,6 +1036,8 @@ void WorldSession::SendListInventory(ObjectGuid vendorGuid, uint32 vendorEntry)
{
LOG_DEBUG("network", "WORLD: Sent SMSG_LIST_INVENTORY");
sScriptMgr->OnPlayerSendListInventory(GetPlayer(), vendorGuid, vendorEntry);
Creature* vendor = GetPlayer()->GetNPCIfCanInteractWith(vendorGuid, UNIT_NPC_FLAG_VENDOR);
if (!vendor)
{
-17
View File
@@ -210,21 +210,4 @@ struct Mail
[[nodiscard]] bool IsReturnedMail() const { return checked & MAIL_CHECK_MASK_RETURNED; }
};
struct ServerMail
{
ServerMail() = default;
uint32 id{ 0 };
uint8 reqLevel{ 0 };
uint32 reqPlayTime{ 0 };
uint32 moneyA{ 0 };
uint32 moneyH{ 0 };
uint32 itemA{ 0 };
uint32 itemCountA{ 0 };
uint32 itemH{ 0 };
uint32 itemCountH{ 0 };
std::string subject;
std::string body;
uint8 active{ 0 };
};
#endif
+351
View File
@@ -0,0 +1,351 @@
/*
* This file is part of the AzerothCore 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 Affero General Public License as published by the
* Free Software Foundation; either version 3 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 Affero 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 <http://www.gnu.org/licenses/>.
*/
#include "ServerMailMgr.h"
#include "AchievementMgr.h"
#include "DatabaseEnv.h"
#include "Item.h"
#include "Log.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "QuestDef.h"
#include "SharedDefines.h"
#include "Timer.h"
ServerMailMgr* ServerMailMgr::instance()
{
static ServerMailMgr instance;
return &instance;
}
void ServerMailMgr::LoadMailServerTemplates()
{
uint32 oldMSTime = getMSTime();
_serverMailStore.clear(); // for reload case
// 0 1 2 3 4 5
QueryResult result = CharacterDatabase.Query("SELECT `id`, `moneyA`, `moneyH`, `subject`, `body`, `active` FROM `mail_server_template`");
if (!result)
{
LOG_INFO("server.loading", ">> Loaded 0 server mail rewards. DB table `mail_server_template` is empty.");
LOG_INFO("server.loading", " ");
return;
}
_serverMailStore.reserve(result->GetRowCount());
do
{
Field* fields = result->Fetch();
uint32 id = fields[0].Get<uint32>();
ServerMail& servMail = _serverMailStore[id];
servMail.id = id;
servMail.moneyA = fields[1].Get<uint32>();
servMail.moneyH = fields[2].Get<uint32>();
servMail.subject = fields[3].Get<std::string>();
servMail.body = fields[4].Get<std::string>();
servMail.active = fields[5].Get<uint8>();
// Skip non-activated entries
if (!servMail.active)
continue;
if (servMail.moneyA > MAX_MONEY_AMOUNT || servMail.moneyH > MAX_MONEY_AMOUNT)
{
LOG_ERROR("sql.sql", "Table `mail_server_template` has moneyA {} or moneyH {} larger than MAX_MONEY_AMOUNT {} for id {}, skipped.", servMail.moneyA, servMail.moneyH, MAX_MONEY_AMOUNT, servMail.id);
continue;
}
} while (result->NextRow());
LoadMailServerTemplatesItems();
LoadMailServerTemplatesConditions();
LOG_INFO("server.loading", ">> Loaded {} Mail Server definitions in {} ms", _serverMailStore.size(), GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
void ServerMailMgr::LoadMailServerTemplatesItems()
{
// 0 1 2 3
QueryResult result = CharacterDatabase.Query("SELECT `templateID`, `faction`, `item`, `itemCount` FROM `mail_server_template_items`");
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 server mail items. DB table `mail_server_template_items` is empty.");
return;
}
do
{
Field* fields = result->Fetch();
uint32 templateID = fields[0].Get<uint32>();
std::string_view faction = fields[1].Get<std::string_view>();
uint32 item = fields[2].Get<uint32>();
uint32 itemCount = fields[3].Get<uint32>();
if (_serverMailStore.find(templateID) == _serverMailStore.end())
{
LOG_ERROR("sql.sql", "Table `mail_server_template_items` has an invalid templateID {}, skipped.", templateID);
continue;
}
ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(item);
if (!itemTemplate)
{
LOG_ERROR("sql.sql", "Table `mail_server_template_items` has an invalid item {} for templateID {}, skipped.", item, templateID);
continue;
}
if (!itemCount)
{
LOG_ERROR("sql.sql", "Table `mail_server_template_items` has itemCount 0 for item {}, skipped.", item);
continue;
}
uint32 stackable = itemTemplate->Stackable;
if (itemCount > stackable)
{
LOG_ERROR("sql.sql", "Table `mail_server_template_items` has itemCount {} exceeding item_template.Stackable {} for item {}, skipped.", itemCount, stackable, item);
continue;
}
uint32 maxCount = itemTemplate->MaxCount;
if (maxCount && itemCount > maxCount)
{
LOG_ERROR("sql.sql", "Table `mail_server_template_items` has itemCount {} exceeding item_template.MaxCount {} for item {}, skipped", itemCount, maxCount, item);
continue;
}
ServerMailItems mailItem;
mailItem.item = item;
mailItem.itemCount = itemCount;
if (faction == "Alliance")
_serverMailStore[templateID].itemsA.push_back(mailItem);
else if (faction == "Horde")
_serverMailStore[templateID].itemsH.push_back(mailItem);
else [[unlikely]]
{
LOG_ERROR("sql.sql", "Table `mail_server_template_items` has invalid faction value '{}' for templateID {}, skipped.", faction, templateID);
continue;
}
} while (result->NextRow());
}
void ServerMailMgr::LoadMailServerTemplatesConditions()
{
// 0 1 2 3
QueryResult result = CharacterDatabase.Query("SELECT `templateID`, `conditionType`, `conditionValue`, `conditionState` FROM `mail_server_template_conditions`");
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 server mail conditions. DB table `mail_server_template_conditions` is empty.");
return;
}
do
{
Field* fields = result->Fetch();
uint32 templateID = fields[0].Get<uint32>();
std::string_view conditionTypeStr = fields[1].Get<std::string_view>();
uint32 conditionValue = fields[2].Get<uint32>();
uint32 conditionState = fields[3].Get<uint32>();
if (_serverMailStore.find(templateID) == _serverMailStore.end())
{
LOG_ERROR("sql.sql", "Table `mail_server_template_conditions` has an invalid templateID {}, skipped.", templateID);
continue;
}
// Get conditiontype from ServerMailConditionTypePairs
ServerMailConditionType conditionType;
conditionType = GetServerMailConditionType(conditionTypeStr);
if (conditionType == ServerMailConditionType::Invalid) [[unlikely]]
{
LOG_ERROR("sql.sql", "Table `mail_server_template_conditions` has unknown conditionType '{}', skipped.", conditionTypeStr);
continue;
}
if (conditionState && !ConditionTypeUsesConditionState(conditionType))
LOG_WARN("sql.sql", "Table `mail_server_template_conditions` has conditionState value ({}) for conditionType ({}) which does not use conditionState.", conditionState, conditionTypeStr);
switch (conditionType)
{
case ServerMailConditionType::Level:
if (conditionValue > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
{
LOG_ERROR("sql.sql", "Table `mail_server_template_conditions` has conditionType 'Level' with invalid conditionValue ({}), max level is ({}) for templateID {}, skipped.", conditionValue, sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL), templateID);
continue;
}
break;
case ServerMailConditionType::Quest:
{
Quest const* qInfo = sObjectMgr->GetQuestTemplate(conditionValue);
if (!qInfo)
{
LOG_ERROR("sql.sql", "Table `mail_server_template_conditions` has conditionType 'Quest' with invalid quest in conditionValue ({}) for templateID {}, skipped.", conditionValue, templateID);
continue;
}
if (conditionState < QUEST_STATUS_NONE || conditionState >= MAX_QUEST_STATUS ||
/*2 and 4 not defined and should not be used*/ conditionState == 2 || conditionState == 4)
{
LOG_ERROR("sql.sql", "Table `mail_server_template_conditions` has conditionType 'Quest' with invalid conditionState ({}) for templateID {}, skipped.", conditionState, templateID);
continue;
}
break;
}
case ServerMailConditionType::Achievement:
{
AchievementEntry const* achievement = sAchievementStore.LookupEntry(conditionValue);
if (!achievement)
{
LOG_ERROR("sql.sql", "Table `mail_server_template_conditions` has conditionType 'Achievement' with invalid achievement in conditionValue ({}) for templateID {}, skipped.", conditionValue, templateID);
continue;
}
break;
}
case ServerMailConditionType::Reputation:
{
FactionEntry const* faction = sFactionStore.LookupEntry(conditionValue);
if (!faction)
{
LOG_ERROR("sql.sql", "Table `mail_server_template_conditions` has conditionType 'Reputation' with invalid faction in conditionValue ({}) for templateID {}, skipped.", conditionValue, templateID);
continue;
}
if (conditionState < REP_HATED || conditionState > REP_EXALTED)
{
LOG_ERROR("sql.sql", "Table `mail_server_template_conditions` has conditionType 'Reputation' with invalid conditionState ({}) for templateID {}, skipped.", conditionState, templateID);
continue;
}
break;
}
case ServerMailConditionType::Faction:
if (conditionValue < TEAM_ALLIANCE || conditionValue > TEAM_HORDE)
{
LOG_ERROR("sql.sql", "Table `mail_server_template_conditions` has conditionType 'Faction' with invalid conditionValue ({}) for templateID {}, skipped.", conditionState, templateID);
continue;
}
break;
case ServerMailConditionType::Race:
if (conditionValue & ~RACEMASK_ALL_PLAYABLE)
{
LOG_ERROR("sql.sql", "Table `mail_server_template_conditions` has conditionType 'Race' with invalid conditionValue ({}) for templateID {}, skipped.", conditionState, templateID);
continue;
}
break;
case ServerMailConditionType::Class:
if (conditionValue & ~CLASSMASK_ALL_PLAYABLE)
{
LOG_ERROR("sql.sql", "Table `mail_server_template_conditions` has conditionType 'Class' with invalid conditionValue ({}) for templateID {}, skipped.", conditionState, templateID);
continue;
}
break;
default:
break;
}
ServerMailCondition condition;
condition.type = conditionType;
condition.value = conditionValue;
condition.state = conditionState;
_serverMailStore[templateID].conditions.push_back(condition);
} while (result->NextRow());
}
void ServerMailMgr::SendServerMail(Player* player, uint32 id, uint32 money,
std::vector<ServerMailItems> const& items,
std::vector<ServerMailCondition> const& conditions,
std::string const& subject, std::string const& body) const
{
for (ServerMailCondition const& condition : conditions)
if (!condition.CheckCondition(player))
return;
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
MailSender sender(MAIL_NORMAL, player->GetGUID().GetCounter(), MAIL_STATIONERY_GM);
MailDraft draft(subject, body);
draft.AddMoney(money);
// Loop through all items and attach them to the mail
for (auto const& mailItem : items)
if (Item* newItem = Item::CreateItem(mailItem.item, mailItem.itemCount))
{
newItem->SaveToDB(trans);
draft.AddItem(newItem);
}
draft.SendMailTo(trans, MailReceiver(player), sender);
CharacterDatabase.CommitTransaction(trans);
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_MAIL_SERVER_CHARACTER);
stmt->SetData(0, player->GetGUID().GetCounter());
stmt->SetData(1, id);
CharacterDatabase.Execute(stmt);
}
ServerMailConditionType ServerMailMgr::GetServerMailConditionType(std::string_view conditionTypeStr) const
{
for (auto const& pair : ServerMailConditionTypePairs)
if (pair.first == conditionTypeStr)
return pair.second;
return ServerMailConditionType::Invalid;
}
bool ServerMailMgr::ConditionTypeUsesConditionState(ServerMailConditionType type) const
{
switch (type)
{
case ServerMailConditionType::Quest:
case ServerMailConditionType::Reputation:
return true;
default:
return false;
}
}
bool ServerMailCondition::CheckCondition(Player* player) const
{
switch (type)
{
case ServerMailConditionType::Level:
return player->GetLevel() >= value;
case ServerMailConditionType::PlayTime:
return player->GetTotalPlayedTime() >= value;
case ServerMailConditionType::Quest:
return player->GetQuestStatus(value) == state;
case ServerMailConditionType::Achievement:
return player->HasAchieved(value);
case ServerMailConditionType::Reputation:
return player->GetReputationRank(value) >= state;
case ServerMailConditionType::Faction:
return player->GetTeamId() == value;
case ServerMailConditionType::Race:
return (player->getRaceMask() & value) != 0;
case ServerMailConditionType::Class:
return (player->getClassMask() & value) != 0;
default:
[[unlikely]] LOG_ERROR("server.mail", "Unknown server mail condition type '{}'", static_cast<uint32>(type));
return false;
}
}
+231
View File
@@ -0,0 +1,231 @@
/*
* This file is part of the AzerothCore 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 Affero General Public License as published by the
* Free Software Foundation; either version 3 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 Affero 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 <http://www.gnu.org/licenses/>.
*/
/**
* @file ServerMailMgr.h
* @brief Manages the ServerMail operations, including template loading, condition checking, and mail delivery.
*
* This class handles the loading of server mail templates, associated items, and conditions from the database.
* It also provides functionality to check player eligibility for receiving server mails based on configured conditions.
*
* Key features:
* - Supports multi-item mails via `mail_server_template_items`
* - Supports flexible mail conditions (level, playtime, quest, achievement) via `mail_server_template_conditions`
* - Ensures all related data is loaded and validated on startup
*/
#ifndef _SERVERMAILMGR_H
#define _SERVERMAILMGR_H
#include "Define.h"
#include <string>
#include <unordered_map>
#include <vector>
class Player;
/**
* @enum ServerMailConditionType
* @brief Represents the type of conditions that can be applied to server mail.
*/
enum class ServerMailConditionType : uint8
{
Invalid = 0, ///< Internal use, not used in DB.
Level = 1, ///< Requires the player to be at least a specific level.
PlayTime = 2, ///< Requires the player to have played for a minimum amount of time (in milliseconds).
Quest = 3, ///< Requires the player to have completed a specific quest.
Achievement = 4, ///< Requires the player to have earned a specific achievement.
Reputation = 5, ///< Requires the player to have earned reputation with a specific faction.
Faction = 6, ///< Requires the player to be a part of a specific faction. Horde/Alliance.
Race = 7, ///< Requires the player to be a specific race.
Class = 8, ///< Requires the player to be a specific class.
};
/**
* @brief Assign string condition to corresponding @ref ServerMailConditionType enum value
*/
constexpr std::pair<std::string_view, ServerMailConditionType> ServerMailConditionTypePairs[] =
{
{ "Level", ServerMailConditionType::Level },
{ "PlayTime", ServerMailConditionType::PlayTime },
{ "Quest", ServerMailConditionType::Quest },
{ "Achievement", ServerMailConditionType::Achievement },
{ "Reputation", ServerMailConditionType::Reputation },
{ "Faction", ServerMailConditionType::Faction },
{ "Race", ServerMailConditionType::Race },
{ "Class", ServerMailConditionType::Class }
};
/**
* @struct ServerMailCondition
* @brief Represents a condition that must be met for a player to receive a server mail.
*
* Each condition has a type (see @ref ServerMailConditionType) and a value associated with the type.
* For example, for a level condition, the value represents the required player level.
*
* Some condition also have a state associated with the value.
* For example, for a reputation condition, the state represents the current reputation state, like Exalted.
*
* Conditions are attached to server mail templates and are evaluated when players log in.
*/
struct ServerMailCondition
{
ServerMailCondition() = default;
ServerMailConditionType type = ServerMailConditionType::Invalid;
uint32 value{ 0 };
uint32 state{ 0 };
/**
* @brief Checks if a player meets this condition.
*
* Evaluates the condition type and compares the player's attributes to the required value.
*
* @param player The player to check.
* @return True if the player meets the condition, otherwise false.
*/
bool CheckCondition(Player* player) const;
};
/**
* @struct ServerMailItems
* @brief Represents an item reward associated with a server mail template.
*
* Server mail templates can have multiple item rewards, stored separately for each faction.
* This struct tracks the item ID and item count.
*/
struct ServerMailItems
{
ServerMailItems() = default;
uint32 item{ 0 };
uint32 itemCount{ 0 };
};
/**
* @struct ServerMail
* @brief Represents a server mail template, including rewards, conditions, and metadata.
*
* This structure defines a mail template that can be sent to players upon login,
* provided they meet the associated conditions.
*/
struct ServerMail
{
ServerMail() = default;
uint32 id{ 0 };
uint32 moneyA{ 0 };
uint32 moneyH{ 0 };
std::string subject;
std::string body;
uint8 active{ 0 };
// Conditions from mail_server_template_conditions
std::vector<ServerMailCondition> conditions;
// Items from mail_server_template_items
std::vector<ServerMailItems> itemsA;
std::vector<ServerMailItems> itemsH;
};
typedef std::unordered_map<uint32, ServerMail> ServerMailContainer;
class ServerMailMgr
{
private:
ServerMailMgr() = default;
~ServerMailMgr() = default;
public:
static ServerMailMgr* instance();
/**
* @brief Loads all server mail templates from the database into memory.
*
* Queries the `mail_server_template` table and loads all rows into memory.
* This method is intended to be called during server startup.
*/
void LoadMailServerTemplates();
/**
* @brief Loads all items associated with server mail templates.
*
* Queries the `mail_server_template_items` table and loads all items into memory,
* linking them to their corresponding templates by template ID.
* This method is intended to be called during server startup.
*/
void LoadMailServerTemplatesItems();
/**
* @brief Loads all conditions associated with server mail templates.
*
* Queries the `mail_server_template_conditions` table and loads all conditions into memory,
* linking them to their corresponding templates by template ID.
* This method is intended to be called during server startup.
*/
void LoadMailServerTemplatesConditions();
/**
* @brief Convert DB value of conditionType to ServerMailConditionType.
*
* Lookup the corresponding SeverMailConditionType enum for the provided
* string by DB. If the string is not found we return internal default value
* ServerMailConditionType::Invalid
*
* @param conditionTypeStr string value from DB of conditionType
* @return ServerMailConditionType The corresponding value (see @ref ServerMailConditionType) or default to ServerMailConditionType::Invalid
*/
ServerMailConditionType GetServerMailConditionType(std::string_view conditionTypeStr) const;
/**
* @brief Check if ConditionType should use ConditionState
*
* @return True if the ConditionType is allowed to use ConditionState, otherwise False.
*/
bool ConditionTypeUsesConditionState(ServerMailConditionType type) const;
/**
* @brief Sends a server mail to a player if the template is active and the player is eligible.
*
* This method handles the creation of the mail, adding money and items, and saving the mail to the database.
* It also records that the player received the mail to prevent duplicate delivery.
*
* @param player The recipient player.
* @param id The template ID.
* @param money Money reward.
* @param items List of items to include in the mail.
* @param conditions List of the conditions for the mail.
* @param subject Mail subject.
* @param body Mail body.
* @param active Whether the mail template is active.
*/
void SendServerMail(Player* player, uint32 id, uint32 money, std::vector<ServerMailItems> const& items, std::vector<ServerMailCondition> const& conditions, std::string const& subject, std::string const& body) const;
/**
* @brief Retrieves the entire server mail store.
*
* This function returns a constant reference to the internal
* `_serverMailStore` container, which holds all server mail data.
*
* @return A constant reference to the `ServerMailContainer` containing all stored server mail.
*/
[[nodiscard]] ServerMailContainer const& GetAllServerMailStore() const { return _serverMailStore; }
private:
ServerMailContainer _serverMailStore;
};
#define sServerMailMgr ServerMailMgr::instance()
#endif // _SERVERMAILMGR_H
@@ -921,6 +921,16 @@ bool ScriptMgr::OnPlayerCanResurrect(Player* player)
CALL_ENABLED_BOOLEAN_HOOKS(PlayerScript, PLAYERHOOK_CAN_RESURRECT, !script->OnPlayerCanResurrect(player));
}
bool ScriptMgr::OnPlayerCanGiveLevel(Player* player, uint8 newLevel)
{
CALL_ENABLED_BOOLEAN_HOOKS(PlayerScript, PLAYERHOOK_ON_CAN_GIVE_LEVEL, !script->OnPlayerCanGiveLevel(player, newLevel));
}
void ScriptMgr::OnPlayerSendListInventory(Player* player, ObjectGuid vendorGuid, uint32& vendorEntry)
{
CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_SEND_LIST_INVENTORY, script->OnPlayerSendListInventory(player, vendorGuid, vendorEntry));
}
PlayerScript::PlayerScript(const char* name, std::vector<uint16> enabledHooks)
: ScriptObject(name, PLAYERHOOK_END)
{
@@ -209,6 +209,8 @@ enum PlayerHook
PLAYERHOOK_ON_BEFORE_UPDATE_SKILL,
PLAYERHOOK_ON_UPDATE_SKILL,
PLAYERHOOK_CAN_RESURRECT,
PLAYERHOOK_ON_CAN_GIVE_LEVEL,
PLAYERHOOK_ON_SEND_LIST_INVENTORY,
PLAYERHOOK_END
};
@@ -784,6 +786,25 @@ public:
* @return true if player is authorized to resurect
*/
virtual bool OnPlayerCanResurrect(Player* /*player*/) { return true; }
/**
* @brief This hook is called, to cancel the normal level up flow
*
* @param player Contains information about the Player
* @param newLevel The new level the player is about to be given
*
* @return true if player is allowed to gain the new level
*/
virtual bool OnPlayerCanGiveLevel(Player* /*player*/, uint8 /*newLevel*/) { return true; }
/**
* @brief This hook is called whenever a player interacts with a vendor, and is then shown the vendor list
*
* @param player Contains information about the Player
* @param vendorGuid Guid of the vendor player is interacting with
* @param vendorEntry Entry of the vendor player is interacting with
*/
virtual void OnPlayerSendListInventory(Player* /*player*/, ObjectGuid /*vendorGuid*/, uint32& /*vendorEntry*/) {}
};
#endif
+2
View File
@@ -484,6 +484,8 @@ public: /* PlayerScript */
void OnPlayerBeforeUpdateSkill(Player* player, uint32 skill_id, uint32& value, uint32 max, uint32 step);
void OnPlayerUpdateSkill(Player* player, uint32 skillId, uint32 value, uint32 max, uint32 step, uint32 newValue);
bool OnPlayerCanResurrect(Player* player);
bool OnPlayerCanGiveLevel(Player* player, uint8 newLevel);
void OnPlayerSendListInventory(Player* player, ObjectGuid vendorGuid, uint32& vendorEntry);
// Anti cheat
void AnticheatSetCanFlybyServer(Player* player, bool apply);
@@ -3678,12 +3678,6 @@ void SpellMgr::LoadSpellInfoCorrections()
spellInfo->AttributesEx5 |= SPELL_ATTR5_ALWAYS_AOE_LINE_OF_SIGHT;
});
//Crushing the Crown
ApplySpellFix({ 71024 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_DEST_DYNOBJ_NONE);
});
// Battle for the Undercity
ApplySpellFix({
59892 // Cyclone fall
@@ -3984,13 +3978,6 @@ void SpellMgr::LoadSpellInfoCorrections()
spellInfo->Effects[EFFECT_0].BasePoints = -25;
});
// Focused Eyebeam Summon Trigger
ApplySpellFix({ 63342 }, [](SpellInfo* spellInfo)
{
spellInfo->MaxAffectedTargets = 1;
spellInfo->Effects[EFFECT_0].TargetB = SpellImplicitTargetInfo();
});
// Eye of Kilrogg Passive (DND)
ApplySpellFix({ 2585 }, [](SpellInfo* spellInfo)
{
+3 -2
View File
@@ -74,6 +74,7 @@
#include "PoolMgr.h"
#include "Realm.h"
#include "ScriptMgr.h"
#include "ServerMailMgr.h"
#include "SkillDiscovery.h"
#include "SkillExtraItems.h"
#include "SmartAI.h"
@@ -1668,8 +1669,8 @@ void World::SetInitialWorldSettings()
LOG_INFO("server.loading", "Loading Player Level Dependent Mail Rewards...");
sObjectMgr->LoadMailLevelRewards();
LOG_INFO("server.loading", "Load Mail Server Template...");
sObjectMgr->LoadMailServerTemplates();
LOG_INFO("server.loading", "Load Mail Server definitions...");
sServerMailMgr->LoadMailServerTemplates();
// Loot tables
LoadLootTables();