Merge branch 'master' into Playerbot
This commit is contained in:
@@ -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, ¤t);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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", " ");
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -38,6 +38,7 @@ EndScriptData */
|
||||
#include "MotdMgr.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "ServerMailMgr.h"
|
||||
#include "SkillDiscovery.h"
|
||||
#include "SkillExtraItems.h"
|
||||
#include "SmartAI.h"
|
||||
@@ -1196,7 +1197,7 @@ public:
|
||||
static bool HandleReloadMailServerTemplateCommand(ChatHandler* handler)
|
||||
{
|
||||
LOG_INFO("server.loading", "Reloading `server_mail_template` table");
|
||||
sObjectMgr->LoadMailServerTemplates();
|
||||
sServerMailMgr->LoadMailServerTemplates();
|
||||
handler->SendGlobalGMSysMessage("DB table `server_mail_template` reloaded.");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleGMTicketCompleteCommand(ChatHandler* handler, uint32 ticketId)
|
||||
static bool HandleGMTicketCompleteCommand(ChatHandler* handler, uint32 ticketId, std::optional<std::string> response)
|
||||
{
|
||||
GmTicket* ticket = sTicketMgr->GetTicket(ticketId);
|
||||
if (!ticket || ticket->IsClosed() || ticket->IsCompleted())
|
||||
@@ -217,9 +217,8 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
char* response = strtok(nullptr, "\n");
|
||||
if (response)
|
||||
ticket->AppendResponse(response);
|
||||
ticket->AppendResponse(response.value());
|
||||
|
||||
if (Player* player2 = ticket->GetPlayer())
|
||||
{
|
||||
|
||||
@@ -84,7 +84,7 @@ struct boss_brutallus : public BossAI
|
||||
|
||||
ScheduleTimedEvent(11s, [&] {
|
||||
DoCastVictim(SPELL_METEOR_SLASH);
|
||||
}, 10s);
|
||||
}, 12s);
|
||||
|
||||
ScheduleTimedEvent(30s, [&] {
|
||||
DoCastVictim(SPELL_STOMP);
|
||||
@@ -137,6 +137,7 @@ enum eMadrigosa
|
||||
EVENT_MAD_6 = 6,
|
||||
EVENT_MAD_7 = 7,
|
||||
EVENT_MAD_8 = 8,
|
||||
EVENT_MAD_8_1 = 800,
|
||||
EVENT_MAD_9 = 9,
|
||||
EVENT_MAD_10 = 10,
|
||||
EVENT_MAD_11 = 11,
|
||||
@@ -166,7 +167,7 @@ enum eMadrigosa
|
||||
|
||||
SPELL_BRUTALLUS_CHARGE = 44884,
|
||||
SPELL_BRUTALLUS_FEL_FIREBALL = 44844,
|
||||
SPELL_BRUTALLUS_FLAME_RING = 44874,
|
||||
SPELL_BRUTALLUS_FLAME_RING = 44873,
|
||||
SPELL_BRUTALLUS_BREAK_ICE = 46637,
|
||||
};
|
||||
|
||||
@@ -261,6 +262,7 @@ struct npc_madrigosa : public NullCreatureAI
|
||||
me->CastSpell(me, SPELL_MADRIGOSA_FROST_BLAST, false);
|
||||
events.ScheduleEvent(EVENT_MAD_8, 3000);
|
||||
events.ScheduleEvent(EVENT_MAD_8, 5000);
|
||||
events.ScheduleEvent(EVENT_MAD_8_1, 6000);
|
||||
events.ScheduleEvent(EVENT_MAD_8, 6500);
|
||||
events.ScheduleEvent(EVENT_MAD_8, 7500);
|
||||
events.ScheduleEvent(EVENT_MAD_8, 8500);
|
||||
@@ -273,10 +275,13 @@ struct npc_madrigosa : public NullCreatureAI
|
||||
if (Creature* brutallus = instance->GetCreature(DATA_BRUTALLUS))
|
||||
me->CastSpell(brutallus, SPELL_MADRIGOSA_FROSTBOLT, false);
|
||||
break;
|
||||
case EVENT_MAD_8_1:
|
||||
if (Creature* brutallus = instance->GetCreature(DATA_BRUTALLUS))
|
||||
brutallus->CastSpell(brutallus, SPELL_BRUTALLUS_FLAME_RING, false);
|
||||
break;
|
||||
case EVENT_MAD_9:
|
||||
if (Creature* brutallus = instance->GetCreature(DATA_BRUTALLUS))
|
||||
{
|
||||
brutallus->CastSpell(brutallus, SPELL_BRUTALLUS_FLAME_RING, true);
|
||||
brutallus->RemoveAllAuras();
|
||||
brutallus->CastSpell(brutallus, SPELL_BRUTALLUS_FEL_FIREBALL, false);
|
||||
brutallus->AI()->Talk(YELL_INTRO_BREAK_ICE);
|
||||
|
||||
@@ -394,6 +394,7 @@ struct npc_demonic_vapor_trail : public NullCreatureAI
|
||||
void Reset() override
|
||||
{
|
||||
me->CastSpell(me, SPELL_DEMONIC_VAPOR_TRAIL_PERIODIC, true);
|
||||
me->DespawnOrUnsummon(20000);
|
||||
}
|
||||
|
||||
void SpellHitTarget(Unit*, SpellInfo const* spellInfo) override
|
||||
|
||||
@@ -224,7 +224,7 @@ struct boss_kalecgos : public BossAI
|
||||
{
|
||||
BossAI::JustEngagedWith(who);
|
||||
|
||||
ScheduleTimedEvent(6s, [&] {
|
||||
ScheduleTimedEvent(8s, [&] {
|
||||
DoCastAOE(SPELL_ARCANE_BUFFET);
|
||||
}, 8s);
|
||||
|
||||
@@ -232,17 +232,17 @@ struct boss_kalecgos : public BossAI
|
||||
DoCastVictim(SPELL_FROST_BREATH);
|
||||
}, 15s);
|
||||
|
||||
ScheduleTimedEvent(10s, [&] {
|
||||
ScheduleTimedEvent(6s, [&] {
|
||||
me->CastCustomSpell(RAND(44978, 45001, 45002, 45004, 45006, 45010), SPELLVALUE_MAX_TARGETS, 1, me, false);
|
||||
}, 20s);
|
||||
}, 6s, 7s);
|
||||
|
||||
ScheduleTimedEvent(25s, [&] {
|
||||
DoCastVictim(SPELL_TAIL_LASH);
|
||||
}, 15s);
|
||||
|
||||
ScheduleTimedEvent(20s, [&] {
|
||||
ScheduleTimedEvent(13s, [&] {
|
||||
DoCastAOE(SPELL_SPECTRAL_BLAST);
|
||||
}, 15s, 25s);
|
||||
}, 20s, 30s);
|
||||
|
||||
scheduler.Schedule(16s, [this](TaskContext)
|
||||
{
|
||||
@@ -345,9 +345,9 @@ struct boss_sathrovarr : public ScriptedAI
|
||||
DoCastVictim(SPELL_SHADOW_BOLT);
|
||||
}, 9s);
|
||||
|
||||
ScheduleTimedEvent(20s, [&] {
|
||||
ScheduleTimedEvent(40s, [&] {
|
||||
me->CastCustomSpell(SPELL_CURSE_OF_BOUNDLESS_AGONY, SPELLVALUE_MAX_TARGETS, 1, me, false);
|
||||
}, 30s);
|
||||
}, 40s);
|
||||
|
||||
ScheduleTimedEvent(20s, [&] {
|
||||
if (roll_chance_i(20))
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "CreatureScript.h"
|
||||
#include "InstanceMapScript.h"
|
||||
#include "InstanceScript.h"
|
||||
#include "Player.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "SpellScript.h"
|
||||
#include "SpellScriptLoader.h"
|
||||
#include "sunwell_plateau.h"
|
||||
@@ -148,8 +150,106 @@ class spell_cataclysm_breath : public SpellScript
|
||||
}
|
||||
};
|
||||
|
||||
enum SunbladeScout
|
||||
{
|
||||
NPC_SUNBLADE_PROTECTOR = 25507,
|
||||
SAY_AGGRO = 0, // Enemies spotted! Attack while I try to activate a Protector!
|
||||
SPELL_ACTIVATE_SUNBLADE_PROTECTOR = 46475,
|
||||
SPELL_COSMETIC_STUN_IMMUNE_PERMANENT = 59123,
|
||||
SPELL_FELBLOOD_CHANNEL = 46319,
|
||||
SPELL_SINISTER_STRIKE = 46558,
|
||||
};
|
||||
|
||||
struct npc_sunblade_scout : public ScriptedAI
|
||||
{
|
||||
npc_sunblade_scout(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
scheduler.CancelAll();
|
||||
ScheduleOOC();
|
||||
me->SetCombatMovement(false);
|
||||
me->SetReactState(REACT_AGGRESSIVE);
|
||||
_protectorGUID.Clear();
|
||||
}
|
||||
|
||||
void JustEngagedWith(Unit* /*who*/) override
|
||||
{
|
||||
scheduler.CancelAll();
|
||||
me->CallForHelp(30.0f);
|
||||
Talk(SAY_AGGRO);
|
||||
std::list<Creature*> protectors;
|
||||
GetCreatureListWithEntryInGrid(protectors, me, NPC_SUNBLADE_PROTECTOR, 100.0f); // range unknown
|
||||
// Skip already activated protectors
|
||||
protectors.remove_if([](Creature* trigger) {return !trigger->HasAura(SPELL_COSMETIC_STUN_IMMUNE_PERMANENT);});
|
||||
protectors.sort(Acore::ObjectDistanceOrderPred(me));
|
||||
if (protectors.empty())
|
||||
ScheduleCombat();
|
||||
Creature* closestProtector = protectors.front();
|
||||
me->GetMotionMaster()->MoveFollow(closestProtector, 0.0f, 0.0f);
|
||||
_protectorGUID = closestProtector->GetGUID();
|
||||
me->ClearTarget();
|
||||
me->SetReactState(REACT_PASSIVE);
|
||||
scheduler.Schedule(1s, [this](TaskContext context)
|
||||
{
|
||||
if (_protectorGUID)
|
||||
if (Creature* protector = ObjectAccessor::GetCreature(*me, _protectorGUID))
|
||||
{
|
||||
if (me->IsWithinRange(protector, 25.0f))
|
||||
{
|
||||
me->SetFacingToObject(protector);
|
||||
DoCastSelf(SPELL_ACTIVATE_SUNBLADE_PROTECTOR);
|
||||
scheduler.Schedule(5s, [this](TaskContext /*context*/)
|
||||
{
|
||||
ScheduleCombat();
|
||||
});
|
||||
return;
|
||||
}
|
||||
context.Repeat(1s);
|
||||
return;
|
||||
}
|
||||
ScheduleCombat();
|
||||
});
|
||||
}
|
||||
|
||||
void ScheduleCombat()
|
||||
{
|
||||
me->SetReactState(REACT_AGGRESSIVE);
|
||||
me->SetCombatMovement(true);
|
||||
if (Unit* victim = me->GetVictim())
|
||||
me->GetMotionMaster()->MoveChase(victim);
|
||||
scheduler.Schedule(2s, 5s, [this](TaskContext context)
|
||||
{
|
||||
DoCastVictim(SPELL_SINISTER_STRIKE);
|
||||
context.Repeat(7s, 8s);
|
||||
});
|
||||
}
|
||||
|
||||
void ScheduleOOC()
|
||||
{
|
||||
scheduler.Schedule(45s, [this](TaskContext context)
|
||||
{
|
||||
DoCastAOE(SPELL_FELBLOOD_CHANNEL);
|
||||
context.Repeat();
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
scheduler.Update(diff);
|
||||
|
||||
if (!me->IsCombatMovementAllowed() || !UpdateVictim())
|
||||
return;
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
private:
|
||||
ObjectGuid _protectorGUID;
|
||||
};
|
||||
|
||||
void AddSC_instance_sunwell_plateau()
|
||||
{
|
||||
new instance_sunwell_plateau();
|
||||
RegisterSpellScript(spell_cataclysm_breath);
|
||||
RegisterSunwellPlateauCreatureAI(npc_sunblade_scout);
|
||||
}
|
||||
|
||||
@@ -44,16 +44,12 @@ enum DataTypes
|
||||
DATA_SATHROVARR = 10,
|
||||
DATA_KJ_CONTROLLER = 11,
|
||||
DATA_ANVEENA = 12,
|
||||
DATA_KALECGOS_KJ = 13
|
||||
};
|
||||
|
||||
enum DataTypesObjects
|
||||
{
|
||||
DATA_ORB_OF_THE_BLUE_DRAGONFLIGHT_1 = 0,
|
||||
DATA_ORB_OF_THE_BLUE_DRAGONFLIGHT_2 = 1,
|
||||
DATA_ORB_OF_THE_BLUE_DRAGONFLIGHT_3 = 2,
|
||||
DATA_ORB_OF_THE_BLUE_DRAGONFLIGHT_4 = 3,
|
||||
DATA_ICEBARRIER = 4,
|
||||
DATA_KALECGOS_KJ = 13,
|
||||
DATA_ORB_OF_THE_BLUE_DRAGONFLIGHT_1 = 14,
|
||||
DATA_ORB_OF_THE_BLUE_DRAGONFLIGHT_2 = 15,
|
||||
DATA_ORB_OF_THE_BLUE_DRAGONFLIGHT_3 = 16,
|
||||
DATA_ORB_OF_THE_BLUE_DRAGONFLIGHT_4 = 17,
|
||||
DATA_ICEBARRIER = 18,
|
||||
};
|
||||
|
||||
enum CreatureIds
|
||||
|
||||
@@ -86,6 +86,12 @@ struct npc_forest_frog : public ScriptedAI
|
||||
if (eventTimer)
|
||||
{
|
||||
Player* player = ObjectAccessor::GetPlayer(me->GetMap(), PlayerGUID);
|
||||
if (!player)
|
||||
{
|
||||
events.CancelEvent(eventTimer);
|
||||
eventTimer = 0;
|
||||
return;
|
||||
}
|
||||
switch (events.ExecuteEvent())
|
||||
{
|
||||
case 1:
|
||||
@@ -141,7 +147,7 @@ struct npc_forest_frog : public ScriptedAI
|
||||
if (me->GetEntry() == NPC_ADARRAH)
|
||||
Talk(SAY_CHEST_TALK + 1, player);
|
||||
else
|
||||
Talk(SAY_CHEST_TALK);
|
||||
Talk(SAY_CHEST_TALK, player);
|
||||
|
||||
eventTimer = 4;
|
||||
if (me->GetEntry() == NPC_GUNTER || me->GetEntry() == NPC_KYREN)
|
||||
@@ -155,7 +161,7 @@ struct npc_forest_frog : public ScriptedAI
|
||||
if (me->GetEntry() == NPC_ADARRAH)
|
||||
Talk(SAY_GOODBYE + 1, player);
|
||||
else
|
||||
Talk(SAY_GOODBYE);
|
||||
Talk(SAY_GOODBYE, player);
|
||||
|
||||
eventTimer = 5;
|
||||
events.ScheduleEvent(eventTimer, 2000);
|
||||
@@ -201,6 +207,9 @@ struct npc_forest_frog : public ScriptedAI
|
||||
events.ScheduleEvent(eventTimer, 3000);
|
||||
|
||||
me->UpdateEntry(cEntry);
|
||||
|
||||
if (Player* player = ObjectAccessor::GetPlayer(me->GetMap(), PlayerGUID))
|
||||
me->SetFacingToObject(player);
|
||||
}
|
||||
|
||||
void SpellHit(Unit* caster, SpellInfo const* spell) override
|
||||
@@ -208,7 +217,6 @@ struct npc_forest_frog : public ScriptedAI
|
||||
if (spell->Id == SPELL_REMOVE_AMANI_CURSE && caster->IsPlayer() && me->GetEntry() == NPC_FOREST_FROG)
|
||||
{
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
me->SetFacingToObject(caster);
|
||||
PlayerGUID = caster->GetGUID();
|
||||
|
||||
if (roll_chance_i(2))
|
||||
@@ -223,7 +231,6 @@ struct npc_forest_frog : public ScriptedAI
|
||||
|
||||
private:
|
||||
InstanceScript* instance;
|
||||
EventMap events;
|
||||
uint8 eventTimer;
|
||||
ObjectGuid PlayerGUID;
|
||||
};
|
||||
|
||||
@@ -92,8 +92,9 @@ public:
|
||||
{
|
||||
_bossWave = TO_BE_DECIDED;
|
||||
_retreat = 0;
|
||||
trash = 0;
|
||||
_trash = 0;
|
||||
_currentWave = 0;
|
||||
_initialWaves = false;
|
||||
_encounterNPCs.clear();
|
||||
_summonedNPCs.clear();
|
||||
_baseAlliance.clear();
|
||||
@@ -190,9 +191,9 @@ public:
|
||||
|
||||
if (creature->IsSummon() && _bossWave != TO_BE_DECIDED)
|
||||
{
|
||||
if (_currentWave == 0)
|
||||
if (_currentWave == 0 && _initialWaves)
|
||||
creature->SetReputationRewardDisabled(true);
|
||||
DoUpdateWorldState(WORLD_STATE_ENEMYCOUNT, ++trash); // Update the instance wave count on new trash spawn
|
||||
DoUpdateWorldState(WORLD_STATE_ENEMYCOUNT, ++_trash); // Update the instance wave count on new trash spawn
|
||||
_encounterNPCs.insert(creature->GetGUID()); // Used for despawning on wipe
|
||||
}
|
||||
break;
|
||||
@@ -230,10 +231,10 @@ public:
|
||||
{
|
||||
if (_bossWave != TO_BE_DECIDED)
|
||||
{
|
||||
DoUpdateWorldState(WORLD_STATE_ENEMYCOUNT, --trash); // Update the instance wave count on new trash death
|
||||
DoUpdateWorldState(WORLD_STATE_ENEMYCOUNT, --_trash); // Update the instance wave count on new trash death
|
||||
_encounterNPCs.erase(creature->GetGUID()); // Used for despawning on wipe
|
||||
|
||||
if (trash == 0) // It can reach negatives if trash spawned after a retreat are killed, it shouldn't affect anything. Also happens on retail
|
||||
if (_trash == 0) // It can reach negatives if trash spawned after a retreat are killed, it shouldn't affect anything. Also happens on retail
|
||||
SetData(DATA_SPAWN_WAVES, 1);
|
||||
}
|
||||
}
|
||||
@@ -245,14 +246,12 @@ public:
|
||||
_summonedNPCs.erase(creature->GetGUID());
|
||||
break;
|
||||
case NPC_WINTERCHILL:
|
||||
_initialWaves = false;
|
||||
[[fallthrough]];
|
||||
case NPC_ANETHERON:
|
||||
case NPC_KAZROGAL:
|
||||
case NPC_AZGALOR:
|
||||
if (Creature* jaina = GetCreature(DATA_JAINA))
|
||||
jaina->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP);
|
||||
if (Creature* thrall = GetCreature(DATA_THRALL))
|
||||
thrall->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP);
|
||||
SetData(DATA_RESET_WAVES, 1);
|
||||
ResetWaves();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -364,6 +363,7 @@ public:
|
||||
_retreat = 0;
|
||||
if (GetBossState(DATA_WINTERCHILL) != DONE)
|
||||
{
|
||||
_initialWaves = true;
|
||||
if (_bossWave == TO_BE_DECIDED)
|
||||
for (ObjectGuid const& guid : _baseAlliance)
|
||||
if (Creature* creature = instance->GetCreature(guid))
|
||||
@@ -481,16 +481,19 @@ public:
|
||||
SetData(DATA_RESET_WAVES, 0);
|
||||
break;
|
||||
case DATA_RESET_WAVES:
|
||||
if (GetBossState(DATA_WINTERCHILL) != DONE)
|
||||
_initialWaves = true;
|
||||
|
||||
scheduler.CancelGroup(CONTEXT_GROUP_WAVES);
|
||||
_encounterNPCs.clear();
|
||||
_summonedNPCs.clear();
|
||||
_currentWave = 0;
|
||||
trash = 0;
|
||||
_trash = 0;
|
||||
_bossWave = TO_BE_DECIDED;
|
||||
_retreat = 0;
|
||||
DoUpdateWorldState(WORLD_STATE_WAVES, _currentWave);
|
||||
DoUpdateWorldState(WORLD_STATE_ENEMY, trash);
|
||||
DoUpdateWorldState(WORLD_STATE_ENEMYCOUNT, trash);
|
||||
DoUpdateWorldState(WORLD_STATE_ENEMY, _trash);
|
||||
DoUpdateWorldState(WORLD_STATE_ENEMYCOUNT, _trash);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -517,7 +520,7 @@ public:
|
||||
{
|
||||
// No overlapping!
|
||||
scheduler.CancelGroup(CONTEXT_GROUP_WAVES);
|
||||
trash = 0; // Reset counter here to avoid resetting the counter from scheduled waves. Required because creatures killed for RP events counts towards the kill counter as well, confirmed in Retail.
|
||||
_trash = 0; // Reset counter here to avoid resetting the counter from scheduled waves. Required because creatures killed for RP events counts towards the kill counter as well, confirmed in Retail.
|
||||
|
||||
scheduler.Schedule(1ms, [this, startWaves, maxWaves, timerptr](TaskContext context)
|
||||
{
|
||||
@@ -566,8 +569,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
int32 trash;
|
||||
private:
|
||||
int32 _trash;
|
||||
uint8 _currentWave;
|
||||
uint8 _bossWave;
|
||||
uint8 _retreat;
|
||||
@@ -581,6 +584,16 @@ public:
|
||||
GuidSet _ancientGemHorde;
|
||||
GuidSet _roaringFlameAlliance;
|
||||
GuidSet _roaringFlameHorde;
|
||||
bool _initialWaves;
|
||||
|
||||
void ResetWaves()
|
||||
{
|
||||
if (Creature* jaina = GetCreature(DATA_JAINA))
|
||||
jaina->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP);
|
||||
if (Creature* thrall = GetCreature(DATA_THRALL))
|
||||
thrall->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP);
|
||||
SetData(DATA_RESET_WAVES, 1);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -155,7 +155,6 @@ public:
|
||||
boss_kologarnAI(Creature* pCreature) : ScriptedAI(pCreature), vehicle(me->GetVehicleKit()), summons(me), breathReady(false)
|
||||
{
|
||||
m_pInstance = me->GetInstanceScript();
|
||||
eyebeamTarget = nullptr;
|
||||
assert(vehicle);
|
||||
me->SetStandState(UNIT_STAND_STATE_SUBMERGED);
|
||||
}
|
||||
@@ -167,8 +166,6 @@ public:
|
||||
EventMap events;
|
||||
SummonList summons;
|
||||
|
||||
Unit* eyebeamTarget;
|
||||
|
||||
bool _looksAchievement, breathReady;
|
||||
uint8 _rubbleAchievement;
|
||||
|
||||
@@ -236,6 +233,7 @@ public:
|
||||
_rubbleAchievement = 0;
|
||||
_looksAchievement = true;
|
||||
|
||||
me->GetMotionMaster()->MoveTargetedHome();
|
||||
me->SetDisableGravity(true);
|
||||
me->DisableRotate(true);
|
||||
|
||||
@@ -295,6 +293,24 @@ public:
|
||||
summons.Summon(cr);
|
||||
}
|
||||
|
||||
void SummonedCreatureDespawn(Creature* cr) override
|
||||
{
|
||||
if (m_pInstance->GetData(TYPE_KOLOGARN) > NOT_STARTED)
|
||||
return;
|
||||
|
||||
if (cr->GetEntry() == NPC_LEFT_ARM)
|
||||
{
|
||||
_left.Clear();
|
||||
AttachLeftArm();
|
||||
}
|
||||
|
||||
if (cr->GetEntry() == NPC_RIGHT_ARM)
|
||||
{
|
||||
_right.Clear();
|
||||
AttachRightArm();
|
||||
}
|
||||
}
|
||||
|
||||
void JustDied(Unit*) override
|
||||
{
|
||||
summons.DespawnAll();
|
||||
@@ -340,7 +356,7 @@ public:
|
||||
|
||||
void PassengerBoarded(Unit* who, int8 /*seatId*/, bool apply) override
|
||||
{
|
||||
if (!me->IsAlive())
|
||||
if (!me->IsAlive() || m_pInstance->GetData(TYPE_KOLOGARN) != IN_PROGRESS)
|
||||
return;
|
||||
|
||||
if (!apply)
|
||||
@@ -408,10 +424,7 @@ public:
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
{
|
||||
EnterEvadeMode(EVADE_REASON_OTHER);
|
||||
return;
|
||||
}
|
||||
|
||||
events.Update(diff);
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
@@ -465,12 +478,7 @@ public:
|
||||
case EVENT_FOCUSED_EYEBEAM:
|
||||
{
|
||||
events.ScheduleEvent(EVENT_FOCUSED_EYEBEAM, 20s);
|
||||
|
||||
if ((eyebeamTarget = SelectTarget(SelectTargetMethod::MinDistance, 0, 0, true)))
|
||||
{
|
||||
me->CastSpell(eyebeamTarget, SPELL_FOCUSED_EYEBEAM_SUMMON, false);
|
||||
}
|
||||
|
||||
me->CastSpell(me, SPELL_FOCUSED_EYEBEAM_SUMMON, false);
|
||||
Talk(EMOTE_EYES);
|
||||
return;
|
||||
}
|
||||
@@ -621,14 +629,14 @@ public:
|
||||
}
|
||||
struct boss_kologarn_eyebeamAI : public ScriptedAI
|
||||
{
|
||||
boss_kologarn_eyebeamAI(Creature* c) : ScriptedAI(c), _timer(1), _damaged(false), justSpawned(true)
|
||||
boss_kologarn_eyebeamAI(Creature* c) : ScriptedAI(c), _timer(1), _damaged(false)
|
||||
{
|
||||
m_pInstance = (InstanceScript*)c->GetInstanceScript();
|
||||
}
|
||||
|
||||
InstanceScript* m_pInstance;
|
||||
uint32 _timer;
|
||||
bool _damaged, justSpawned;
|
||||
bool _damaged;
|
||||
|
||||
void DamageDealt(Unit* /*victim*/, uint32& damage, DamageEffectType /*damageType*/, SpellSchoolMask /*damageSchoolMask*/) override
|
||||
{
|
||||
@@ -640,24 +648,34 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
void IsSummonedBy(WorldObject* summoner) override
|
||||
{
|
||||
if (justSpawned)
|
||||
if (!summoner)
|
||||
{
|
||||
me->DespawnOrUnsummon(10000);
|
||||
return;
|
||||
}
|
||||
|
||||
// Should only work on playable characters
|
||||
if (Player* player = summoner->ToPlayer())
|
||||
{
|
||||
me->Attack(player, false);
|
||||
me->GetMotionMaster()->MoveChase(player);
|
||||
|
||||
if (Creature* cr = ObjectAccessor::GetCreature(*me, m_pInstance->GetGuidData(TYPE_KOLOGARN)))
|
||||
{
|
||||
me->CastSpell(cr, me->GetEntry() == NPC_EYE_LEFT ? SPELL_FOCUSED_EYEBEAM_LEFT : SPELL_FOCUSED_EYEBEAM_RIGHT, true);
|
||||
}
|
||||
me->CastSpell(me, SPELL_FOCUSED_EYEBEAM, true);
|
||||
justSpawned = false;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (_timer)
|
||||
{
|
||||
_timer += diff;
|
||||
if (_timer >= 2000)
|
||||
{
|
||||
me->CastSpell(me, (me->GetMap()->Is25ManRaid() ? SPELL_FOCUSED_EYEBEAM_25 : SPELL_FOCUSED_EYEBEAM_10), true);
|
||||
me->CastSpell(me, SPELL_FOCUSED_EYEBEAM, true);
|
||||
_timer = 0;
|
||||
}
|
||||
}
|
||||
@@ -665,6 +683,52 @@ public:
|
||||
};
|
||||
};
|
||||
|
||||
class spell_kologarn_focused_eyebeam : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_kologarn_focused_eyebeam);
|
||||
|
||||
bool Load() override
|
||||
{
|
||||
return GetCaster()->IsCreature();
|
||||
}
|
||||
|
||||
void FilterTargetsInitial(std::list<WorldObject*>& targets)
|
||||
{
|
||||
std::list<Unit*> newTargets;
|
||||
Creature* creature = GetCaster()->ToCreature();
|
||||
// Select 3 most distant targets
|
||||
GetCaster()->GetAI()->SelectTargetList(newTargets, 3, SelectTargetMethod::MaxDistance, 0, NonTankTargetSelector(creature, true));
|
||||
|
||||
// If no distant targets available, get 1 target from original list
|
||||
if (newTargets.empty())
|
||||
{
|
||||
if (!targets.empty())
|
||||
{
|
||||
while (1 < targets.size())
|
||||
{
|
||||
std::list<WorldObject*>::iterator itr = targets.begin();
|
||||
advance(itr, urand(0, targets.size() - 1));
|
||||
targets.erase(itr);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear original targets
|
||||
targets.clear();
|
||||
|
||||
// Select a random target
|
||||
std::list<Unit*>::iterator head = newTargets.begin();
|
||||
advance(head, urand(0, newTargets.size() - 1));
|
||||
targets.push_back(*head);
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_kologarn_focused_eyebeam::FilterTargetsInitial, EFFECT_ALL, TARGET_UNIT_SRC_AREA_ENEMY);
|
||||
}
|
||||
};
|
||||
|
||||
struct boss_kologarn_pit_kill_bunny : public NullCreatureAI
|
||||
{
|
||||
boss_kologarn_pit_kill_bunny(Creature* creature) : NullCreatureAI(creature) { }
|
||||
@@ -724,18 +788,16 @@ class spell_ulduar_stone_grip_cast_target : public SpellScript
|
||||
|
||||
bool Load() override
|
||||
{
|
||||
if (!GetCaster()->IsCreature())
|
||||
return false;
|
||||
return true;
|
||||
return GetCaster()->IsCreature();
|
||||
}
|
||||
|
||||
void FilterTargetsInitial(std::list<WorldObject*>& targets)
|
||||
{
|
||||
// Remove "main tank" and non-player targets
|
||||
targets.remove_if (StoneGripTargetSelector(GetCaster()->ToCreature(), GetCaster()->GetVictim()));
|
||||
targets.remove_if(StoneGripTargetSelector(GetCaster()->ToCreature(), GetCaster()->GetVictim()));
|
||||
// Maximum affected targets per difficulty mode
|
||||
uint32 maxTargets = 1;
|
||||
if (GetSpellInfo()->Id == 63981)
|
||||
if (GetSpellInfo()->Id == SPELL_STONE_GRIP_25)
|
||||
maxTargets = 3;
|
||||
|
||||
// Return a random amount of targets based on maxTargets
|
||||
@@ -882,6 +944,7 @@ void AddSC_boss_kologarn()
|
||||
RegisterSpellScript(spell_ulduar_stone_grip_cast_target);
|
||||
RegisterSpellScript(spell_ulduar_stone_grip_aura);
|
||||
RegisterSpellScript(spell_ulduar_squeezed_lifeless);
|
||||
RegisterSpellScript(spell_kologarn_focused_eyebeam);
|
||||
RegisterSpellAndAuraScriptPair(spell_kologarn_stone_shout, spell_kologarn_stone_shout_aura);
|
||||
|
||||
// Achievements
|
||||
|
||||
@@ -338,8 +338,7 @@ void OPvPCapturePointZM_Graveyard::SetBeaconState(TeamId controlling_factionId)
|
||||
|
||||
bool OPvPCapturePointZM_Graveyard::CanTalkTo(Player* player, Creature* c, GossipMenuItems const& /*gso*/)
|
||||
{
|
||||
ObjectGuid guid = c->GetGUID();
|
||||
auto itr = _creatureTypes.find(guid.GetCounter());
|
||||
auto itr = _creatureTypes.find(c->GetSpawnId());
|
||||
if (itr != _creatureTypes.end())
|
||||
{
|
||||
if (itr->second == ZM_ALLIANCE_FIELD_SCOUT && player->GetTeamId() == TEAM_ALLIANCE && m_BothControllingFactionId == TEAM_ALLIANCE && !m_FlagCarrierGUID && m_GraveyardState != ZM_GRAVEYARD_A)
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
*/
|
||||
|
||||
#include "CreatureScript.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Player.h"
|
||||
#include "PlayerScript.h"
|
||||
#include "QueryResult.h"
|
||||
#include "ServerMailMgr.h"
|
||||
|
||||
class ServerMailReward : public PlayerScript
|
||||
{
|
||||
@@ -30,13 +30,14 @@ public:
|
||||
void OnPlayerLogin(Player* player) override
|
||||
{
|
||||
// Retrieve all server mail records and session only once
|
||||
auto const& serverMailStore = sObjectMgr->GetAllServerMailStore();
|
||||
auto const& serverMailStore = sServerMailMgr->GetAllServerMailStore();
|
||||
WorldSession* session = player->GetSession();
|
||||
// We should always have a session, just incase
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
uint32 playerGUID = player->GetGUID().GetCounter();
|
||||
bool isAlliance = player->GetTeamId() == TEAM_ALLIANCE;
|
||||
|
||||
for (auto const& [mailId, servMail] : serverMailStore)
|
||||
{
|
||||
@@ -45,26 +46,24 @@ public:
|
||||
stmt->SetData(1, mailId);
|
||||
|
||||
// Capture servMail by value
|
||||
auto callback = [session, servMailWrapper = std::reference_wrapper<ServerMail const>(servMail)](PreparedQueryResult result)
|
||||
auto callback = [session, servMailWrapper = std::reference_wrapper<ServerMail const>(servMail), isAlliance](PreparedQueryResult result)
|
||||
{
|
||||
ServerMail const& servMail = servMailWrapper.get(); // Dereference the wrapper to get the original object
|
||||
ServerMail const& servMail = servMailWrapper.get(); // Dereference the wrapper to get the original object
|
||||
|
||||
if (!result)
|
||||
{
|
||||
sObjectMgr->SendServerMail(
|
||||
uint32 money = isAlliance ? servMail.moneyA : servMail.moneyH;
|
||||
std::vector<ServerMailItems> const& items = isAlliance ? servMail.itemsA : servMail.itemsH;
|
||||
std::vector<ServerMailCondition> const& conditions = servMail.conditions;
|
||||
|
||||
sServerMailMgr->SendServerMail(
|
||||
session->GetPlayer(),
|
||||
servMail.id,
|
||||
servMail.reqLevel,
|
||||
servMail.reqPlayTime,
|
||||
servMail.moneyA,
|
||||
servMail.moneyH,
|
||||
servMail.itemA,
|
||||
servMail.itemCountA,
|
||||
servMail.itemH,
|
||||
servMail.itemCountH,
|
||||
money,
|
||||
items,
|
||||
conditions,
|
||||
servMail.subject,
|
||||
servMail.body,
|
||||
servMail.active
|
||||
servMail.body
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user