diff --git a/src/game/Calendar.cpp b/src/game/Calendar.cpp new file mode 100644 index 000000000..2a0630240 --- /dev/null +++ b/src/game/Calendar.cpp @@ -0,0 +1,724 @@ +/* + * This code is part of MaNGOS. Contributor & Copyright details are in AUTHORS/THANKS. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "Calendar.h" +#include "Guild.h" +#include "Mail.h" +#include "ObjectMgr.h" +#include "ProgressBar.h" + +INSTANTIATE_SINGLETON_1(CalendarMgr); + +////////////////////////////////////////////////////////////////////////// +// CalendarEvent Class to store single event informations +////////////////////////////////////////////////////////////////////////// + +CalendarEvent::~CalendarEvent() +{ + RemoveAllInvite(); +} + +// Add an invite to internal invite map return true if success +bool CalendarEvent::AddInvite(CalendarInvite* invite) +{ + if (!invite) + return false; + + return m_Invitee.insert(CalendarInviteMap::value_type(invite->InviteId, invite)).second; +} + +CalendarInvite* CalendarEvent::GetInviteById(uint64 inviteId) +{ + CalendarInviteMap::iterator itr = m_Invitee.find(inviteId); + if (itr != m_Invitee.end()) + return itr->second; + return NULL; +} + +CalendarInvite* CalendarEvent::GetInviteByGuid(ObjectGuid const& guid) +{ + CalendarInviteMap::const_iterator inviteItr = m_Invitee.begin(); + while (inviteItr != m_Invitee.end()) + { + if (inviteItr->second->InviteeGuid == guid) + return inviteItr->second; + ++inviteItr; + } + + return NULL; +} + +// remove invite by its iterator +void CalendarEvent::RemoveInviteByItr(CalendarInviteMap::iterator inviteItr) +{ + MANGOS_ASSERT(inviteItr != m_Invitee.end()); // iterator must be valid + + if (!IsGuildEvent()) + sCalendarMgr.SendCalendarEventInviteRemoveAlert(sObjectMgr.GetPlayer(inviteItr->second->InviteeGuid), this, CALENDAR_STATUS_REMOVED); + + sCalendarMgr.SendCalendarEventInviteRemove(inviteItr->second, Flags); + + CharacterDatabase.PExecute("DELETE FROM calendar_invites WHERE inviteId=" UI64FMTD, inviteItr->second->InviteId); + + delete inviteItr->second; + m_Invitee.erase(inviteItr); +} + +// remove invite by ObjectGuid of the player (may not be found so nothing removed) +void CalendarEvent::RemoveInviteByGuid(ObjectGuid const& playerGuid) +{ + CalendarInviteMap::iterator itr = m_Invitee.begin(); + while (itr != m_Invitee.end()) + { + if (itr->second->InviteeGuid == playerGuid) + RemoveInviteByItr(itr++); + else + ++itr; + } +} + +// remove invite by invite ID (some check done before and if requirement not complete +// operation is aborted and raison is sent to client +bool CalendarEvent::RemoveInviteById(uint64 inviteId, Player* remover) +{ + CalendarInviteMap::iterator inviteItr = m_Invitee.find(inviteId); + if (inviteItr == m_Invitee.end()) + { + // invite not found + sCalendarMgr.SendCalendarCommandResult(remover, CALENDAR_ERROR_NO_INVITE); + return false; + } + + // assign a pointer to CalendarInvite class to make read more easy + CalendarInvite* invite = inviteItr->second; + + if (invite->InviteeGuid != remover->GetObjectGuid()) + { + // check if remover is an invitee + CalendarInvite* removerInvite = GetInviteByGuid(remover->GetObjectGuid()); + if (removerInvite == NULL) + { + // remover is not invitee cheat??? + sCalendarMgr.SendCalendarCommandResult(remover, CALENDAR_ERROR_NOT_INVITED); + return false; + } + + if (removerInvite->Rank != CALENDAR_RANK_MODERATOR && removerInvite->Rank != CALENDAR_RANK_OWNER) + { + // remover have not enough right to remove invite + sCalendarMgr.SendCalendarCommandResult(remover, CALENDAR_ERROR_PERMISSIONS); + return false; + } + } + + if (CreatorGuid == invite->InviteeGuid) + { + sCalendarMgr.SendCalendarCommandResult(remover, CALENDAR_ERROR_DELETE_CREATOR_FAILED); + return false; + } + + // TODO: Send mail to invitee if needed + + RemoveInviteByItr(inviteItr); + return true; +} + +// remove all invite without sending ingame mail +void CalendarEvent::RemoveAllInvite() +{ + CalendarInviteMap::iterator itr = m_Invitee.begin(); + while (itr != m_Invitee.end()) + RemoveInviteByItr(itr++); +} + +// remove all invite sending ingame mail +void CalendarEvent::RemoveAllInvite(ObjectGuid const& removerGuid) +{ + // build mail title + std::ostringstream title; + title << removerGuid << ':' << Title; + + // build mail body + std::ostringstream body; + body << secsToTimeBitFields(time(NULL)); + + // creating mail draft + MailDraft draft(title.str(), body.str()); + + CalendarInviteMap::iterator itr = m_Invitee.begin(); + while (itr != m_Invitee.end()) + { + if (removerGuid != itr->second->InviteeGuid) + draft.SendMailTo(MailReceiver(itr->second->InviteeGuid), this, MAIL_CHECK_MASK_COPIED); + RemoveInviteByItr(itr++); + } +} + +////////////////////////////////////////////////////////////////////////// +// CalendarInvite Classes store single invite information +////////////////////////////////////////////////////////////////////////// + +CalendarInvite::CalendarInvite(CalendarEvent* event, uint64 inviteId, ObjectGuid senderGuid, ObjectGuid inviteeGuid, time_t statusTime, CalendarInviteStatus status, CalendarModerationRank rank, std::string text) : + m_calendarEvent(event), InviteId(inviteId), SenderGuid(senderGuid), InviteeGuid(inviteeGuid), LastUpdateTime(statusTime), Status(status), Rank(rank), Text(text) +{ + // only for pre invite case + if (!event) + InviteId = 0; +} + +////////////////////////////////////////////////////////////////////////// +// CalendarMgr Classes handle all events and invites. +////////////////////////////////////////////////////////////////////////// + +// fill all player events in provided CalendarEventsList +void CalendarMgr::GetPlayerEventsList(ObjectGuid const& guid, CalendarEventsList& calEventList) +{ + uint32 guildId = 0; + Player* player = sObjectMgr.GetPlayer(guid); + if (player) + guildId = player->GetGuildId(); + else + guildId = Player::GetGuildIdFromDB(guid); + + for (CalendarEventStore::iterator itr = m_EventStore.begin(); itr != m_EventStore.end(); ++itr) + { + CalendarEvent* event = &itr->second; + + // add own event and same guild event or announcement + if ((event->CreatorGuid == guid) || ((event->IsGuildAnnouncement() || event->IsGuildEvent()) && event->GuildId == guildId)) + { + calEventList.push_back(event); + continue; + } + + // add all event where player is invited + if (event->GetInviteByGuid(guid)) + calEventList.push_back(event); + } +} + +// fill all player invites in provided CalendarInvitesList +void CalendarMgr::GetPlayerInvitesList(ObjectGuid const& guid, CalendarInvitesList& calInvList) +{ + for (CalendarEventStore::iterator itr = m_EventStore.begin(); itr != m_EventStore.end(); ++itr) + { + CalendarEvent* event = &itr->second; + + if (event->IsGuildAnnouncement()) + continue; + + CalendarInviteMap const* cInvMap = event->GetInviteMap(); + CalendarInviteMap::const_iterator ci_itr = cInvMap->begin(); + while (ci_itr != cInvMap->end()) + { + if (ci_itr->second->InviteeGuid == guid) + { + calInvList.push_back(ci_itr->second); + break; + } + ++ci_itr; + } + } +} + +// add single event to main events store +// some check done before so it may fail and raison is sent to client +// return value is the CalendarEvent pointer on success +CalendarEvent* CalendarMgr::AddEvent(ObjectGuid const& guid, std::string title, std::string description, uint32 type, uint32 repeatable, + uint32 maxInvites, int32 dungeonId, time_t eventTime, time_t unkTime, uint32 flags) +{ + Player* player = sObjectMgr.GetPlayer(guid); + if (!player) + return NULL; + + if (title.empty()) + { + SendCalendarCommandResult(player, CALENDAR_ERROR_NEEDS_TITLE); + return NULL; + } + + if (eventTime < time(NULL)) + { + SendCalendarCommandResult(player, CALENDAR_ERROR_INVALID_DATE); + return NULL; + } + + uint32 guildId = 0; + + if ((flags & CALENDAR_FLAG_GUILD_EVENT) || (flags & CALENDAR_FLAG_GUILD_ANNOUNCEMENT)) + { + guildId = player->GetGuildId(); + if (!guildId) + { + SendCalendarCommandResult(player, CALENDAR_ERROR_GUILD_PLAYER_NOT_IN_GUILD); + return NULL; + } + + if (!CanAddGuildEvent(guildId)) + { + SendCalendarCommandResult(player, CALENDAR_ERROR_GUILD_EVENTS_EXCEEDED); + return NULL; + } + } + else + { + if (!CanAddEvent(guid)) + { + SendCalendarCommandResult(player, CALENDAR_ERROR_EVENTS_EXCEEDED); + return NULL; + } + } + + uint64 nId = GetNewEventId(); + + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "CalendarMgr::AddEvent> ID(" UI64FMTD "), '%s', Desc > '%s', type=%u, repeat=%u, maxInvites=%u, dungeonId=%d, flags=%u", + nId, title.c_str(), description.c_str(), type, repeatable, maxInvites, dungeonId, flags); + + CalendarEvent& newEvent = m_EventStore[nId]; + + newEvent.EventId = nId; + newEvent.CreatorGuid = guid; + newEvent.Title = title; + newEvent.Description = description; + newEvent.Type = (CalendarEventType) type; + newEvent.Repeatable = (CalendarRepeatType) repeatable; + newEvent.DungeonId = dungeonId; + newEvent.EventTime = eventTime; + newEvent.Flags = flags; + newEvent.GuildId = guildId; + + CharacterDatabase.escape_string(title); + CharacterDatabase.escape_string(description); + CharacterDatabase.PExecute("INSERT INTO calendar_events VALUES (" UI64FMTD ", %u, %u, %u, %u, %d, %u, '%s', '%s')", + nId, + guid.GetCounter(), + guildId, + type, + flags, + dungeonId, + uint32(eventTime), + title.c_str(), + description.c_str()); + return &newEvent; +} + +// remove event by its id +// some check done before so it may fail and raison is sent to client +void CalendarMgr::RemoveEvent(uint64 eventId, Player* remover) +{ + CalendarEventStore::iterator citr = m_EventStore.find(eventId); + if (citr == m_EventStore.end()) + { + SendCalendarCommandResult(remover, CALENDAR_ERROR_EVENT_INVALID); + return; + } + + if (remover->GetObjectGuid() != citr->second.CreatorGuid) + { + // only creator can remove his event + SendCalendarCommandResult(remover, CALENDAR_ERROR_PERMISSIONS); + return; + } + + SendCalendarEventRemovedAlert(&citr->second); + + CharacterDatabase.PExecute("DELETE FROM calendar_events WHERE eventId=" UI64FMTD, eventId); + + // explicitly remove all invite and send mail to all invitee + citr->second.RemoveAllInvite(remover->GetObjectGuid()); + m_EventStore.erase(citr); +} + +// Add invit to an event and inform client +// some check done before so it may fail and raison is sent to client +// return value is the CalendarInvite pointer on success +CalendarInvite* CalendarMgr::AddInvite(CalendarEvent* event, ObjectGuid const& senderGuid, ObjectGuid const& inviteeGuid, CalendarInviteStatus status, CalendarModerationRank rank, std::string text, time_t statusTime) +{ + Player* sender = sObjectMgr.GetPlayer(senderGuid); + if (!event || !sender) + return NULL; + + std::string name; + sObjectMgr.GetPlayerNameByGUID(inviteeGuid, name); + + // check if invitee is not already invited + if (event->GetInviteByGuid(inviteeGuid)) + { + SendCalendarCommandResult(sender, CALENDAR_ERROR_ALREADY_INVITED_TO_EVENT_S, name.c_str()); + return NULL; + } + + // check if player can still have new invite (except for event creator) + if (!event->IsGuildAnnouncement() && event->CreatorGuid != inviteeGuid) + { + if (!CanAddInviteTo(inviteeGuid)) + { + SendCalendarCommandResult(sender, CALENDAR_ERROR_OTHER_INVITES_EXCEEDED_S, name.c_str()); + return NULL; + } + } + + CalendarInvite* invite = new CalendarInvite(event, GetNewInviteId(), senderGuid, inviteeGuid, statusTime, status, rank, text); + + if (!event->IsGuildAnnouncement()) + SendCalendarEventInvite(invite); + + if (!event->IsGuildEvent() || invite->InviteeGuid == event->CreatorGuid) + SendCalendarEventInviteAlert(invite); + + if (event->IsGuildAnnouncement()) + { + // no need to realy add invite for announcements + delete invite; + return NULL; + } + + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "Add Invite> eventId[" UI64FMTD "], senderGuid[%s], inviteGuid[%s], Status[%u], rank[%u], text[%s], time[%u]", + event->EventId, senderGuid.GetString().c_str(), inviteeGuid.GetString().c_str(), uint32(status), uint32(rank), text.c_str(), uint32(statusTime)); + + if (!event->AddInvite(invite)) + { + sLog.outError("CalendarEvent::AddInvite > Fail adding invite!"); + delete invite; + return NULL; + } + + CharacterDatabase.PExecute("INSERT INTO calendar_invites VALUES (" UI64FMTD ", " UI64FMTD ", %u, %u, %u, %u, %u)", + invite->InviteId, + event->EventId, + inviteeGuid.GetCounter(), + senderGuid.GetCounter(), + uint32(status), + uint32(statusTime), + uint32(rank)); + + return invite; +} + +// remove invit from an event and inform client +// some check done before so it may fail and raison is sent to client +// require valid eventId/inviteId and correct right for the remover. +bool CalendarMgr::RemoveInvite(uint64 eventId, uint64 inviteId, ObjectGuid const& removerGuid) +{ + Player* remover = sObjectMgr.GetPlayer(removerGuid); + CalendarEventStore::iterator citr = m_EventStore.find(eventId); + if (citr == m_EventStore.end()) + { + SendCalendarCommandResult(remover, CALENDAR_ERROR_EVENT_INVALID); + return false; + } + + CalendarEvent& event = citr->second; + + return event.RemoveInviteById(inviteId, remover); +} + +// return how many events still require some pending action +uint32 CalendarMgr::GetPlayerNumPending(ObjectGuid const& guid) +{ + CalendarInvitesList inviteList; + GetPlayerInvitesList(guid, inviteList); + + uint32 pendingNum = 0; + time_t currTime = time(NULL); + for (CalendarInvitesList::const_iterator itr = inviteList.begin(); itr != inviteList.end(); ++itr) + { + if (CalendarEvent const* event = (*itr)->GetCalendarEvent()) + { + // pass all passed events + if (event->EventTime < currTime) + continue; + + // pass all locked events + if (event->Flags & CALENDAR_FLAG_INVITES_LOCKED) + continue; + } + + // add only invite that require some action + if ((*itr)->Status == CALENDAR_STATUS_INVITED || (*itr)->Status == CALENDAR_STATUS_TENTATIVE || (*itr)->Status == CALENDAR_STATUS_NOT_SIGNED_UP) + ++pendingNum; + } + + return pendingNum; +} + +// copy event to another date (all invitee is copied too but their status are reseted) +void CalendarMgr::CopyEvent(uint64 eventId, time_t newTime, ObjectGuid const& guid) +{ + Player* player = sObjectMgr.GetPlayer(guid); + CalendarEvent* event = GetEventById(eventId); + if (!event) + { + SendCalendarCommandResult(player, CALENDAR_ERROR_EVENT_INVALID); + return; + } + + CalendarEvent* newEvent = AddEvent(guid, event->Title, event->Description, event->Type, event->Repeatable, + CALENDAR_MAX_INVITES, event->DungeonId, newTime, event->UnknownTime, event->Flags); + + if (!newEvent) + return; + + if (newEvent->IsGuildAnnouncement()) + AddInvite(newEvent, guid, guid, CALENDAR_STATUS_CONFIRMED, CALENDAR_RANK_OWNER, "", time(NULL)); + else + { + // copy all invitees, set new owner as the one who make the copy, set invitees status to invited + CalendarInviteMap const* cInvMap = event->GetInviteMap(); + CalendarInviteMap::const_iterator ci_itr = cInvMap->begin(); + + while (ci_itr != cInvMap->end()) + { + if (ci_itr->second->InviteeGuid == guid) + { + AddInvite(newEvent, guid, ci_itr->second->InviteeGuid, CALENDAR_STATUS_CONFIRMED, CALENDAR_RANK_OWNER, "", time(NULL)); + } + else + { + CalendarModerationRank rank = CALENDAR_RANK_PLAYER; + // copy moderator rank + if (ci_itr->second->Rank == CALENDAR_RANK_MODERATOR) + rank = CALENDAR_RANK_MODERATOR; + + AddInvite(newEvent, guid, ci_itr->second->InviteeGuid, CALENDAR_STATUS_INVITED, rank, "", time(NULL)); + } + ++ci_itr; + } + } + + SendCalendarEvent(player, newEvent, CALENDAR_SENDTYPE_COPY); +} + +// remove all events and invite of player +// used when player is deleted +void CalendarMgr::RemovePlayerCalendar(ObjectGuid const& playerGuid) +{ + CalendarEventStore::iterator itr = m_EventStore.begin(); + + while (itr != m_EventStore.end()) + { + if (itr->second.CreatorGuid == playerGuid) + { + // all invite will be automaticaly deleted + m_EventStore.erase(itr++); + // itr already incremented so go recheck event owner + continue; + } + // event not owned by playerGuid but an invite can still be found + CalendarEvent* event = &itr->second; + event->RemoveInviteByGuid(playerGuid); + ++itr; + } +} + +// remove all events and invite of player related to a specific guild +// used when player quit a guild +void CalendarMgr::RemoveGuildCalendar(ObjectGuid const& playerGuid, uint32 GuildId) +{ + CalendarEventStore::iterator itr = m_EventStore.begin(); + + while (itr != m_EventStore.end()) + { + CalendarEvent* event = &itr->second; + if (event->CreatorGuid == playerGuid && (event->IsGuildEvent() || event->IsGuildAnnouncement())) + { + // all invite will be automaticaly deleted + m_EventStore.erase(itr++); + // itr already incremented so go recheck event owner + continue; + } + + // event not owned by playerGuid but an guild invite can still be found + if (event->GuildId != GuildId || !(event->IsGuildEvent() || event->IsGuildAnnouncement())) + { + ++itr; + continue; + } + + event->RemoveInviteByGuid(playerGuid); + ++itr; + } +} + +// load all events and their related invites from invite +void CalendarMgr::LoadCalendarsFromDB() +{ + // in case of reload (not yet implemented) + m_MaxInviteId = 0; + m_MaxEventId = 0; + m_EventStore.clear(); + + sLog.outString("Loading Calendar Events..."); + + // 0 1 2 3 4 5 6 7 8 + QueryResult* eventsQuery = CharacterDatabase.Query("SELECT eventId, creatorGuid, guildId, type, flags, dungeonId, eventTime, title, description FROM calendar_events ORDER BY eventId"); + if (!eventsQuery) + { + BarGoLink bar(1); + bar.step(); + sLog.outString(); + sLog.outString(">> calendar_events table is empty!"); + } + else + { + BarGoLink bar(eventsQuery->GetRowCount()); + do + { + Field* field = eventsQuery->Fetch(); + bar.step(); + + uint64 eventId = field[0].GetUInt64(); + + CalendarEvent& newEvent = m_EventStore[eventId]; + + newEvent.EventId = eventId; + newEvent.CreatorGuid = ObjectGuid(HIGHGUID_PLAYER, field[1].GetUInt32()); + newEvent.GuildId = field[2].GetUInt32(); + newEvent.Type = CalendarEventType(field[3].GetUInt8()); + newEvent.Flags = field[4].GetUInt32(); + newEvent.DungeonId = field[5].GetInt32(); + newEvent.EventTime = time_t(field[6].GetUInt32()); + newEvent.Title = field[7].GetCppString(); + newEvent.Description = field[8].GetCppString(); + + m_MaxEventId = std::max(eventId, m_MaxEventId); + } + while (eventsQuery->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u events!", uint32(eventsQuery->GetRowCount())); + delete eventsQuery; + } + + sLog.outString("Loading Calendar invites..."); + // 0 1 2 3 4 5 6 + QueryResult* invitesQuery = CharacterDatabase.Query("SELECT inviteId, eventId, inviteeGuid, senderGuid, status, lastUpdateTime, rank FROM calendar_invites ORDER BY inviteId"); + if (!invitesQuery) + { + BarGoLink bar(1); + bar.step(); + sLog.outString(); + + if (m_MaxEventId) // An Event was loaded before + { + // delete all events (no event exist without at least one invite) + m_EventStore.clear(); + m_MaxEventId = 0; + CharacterDatabase.DirectExecute("TRUNCATE TABLE calendar_events"); + sLog.outString(">> calendar_invites table is empty, cleared calendar_events table!"); + } + else + sLog.outString(">> calendar_invite table is empty!"); + } + else + { + if (m_MaxEventId) + { + uint64 totalInvites = 0; + uint32 deletedInvites = 0; + BarGoLink bar(invitesQuery->GetRowCount()); + do + { + Field* field = invitesQuery->Fetch(); + + uint64 inviteId = field[0].GetUInt64(); + uint64 eventId = field[1].GetUInt64(); + ObjectGuid inviteeGuid = ObjectGuid(HIGHGUID_PLAYER, field[2].GetUInt32()); + ObjectGuid senderGuid = ObjectGuid(HIGHGUID_PLAYER, field[3].GetUInt32()); + CalendarInviteStatus status = CalendarInviteStatus(field[4].GetUInt8()); + time_t lastUpdateTime = time_t(field[5].GetUInt32()); + CalendarModerationRank rank = CalendarModerationRank(field[6].GetUInt8()); + + CalendarEvent* event = GetEventById(eventId); + if (!event) + { + // delete invite + CharacterDatabase.PExecute("DELETE FROM calendar_invites WHERE inviteId =" UI64FMTD, field[0].GetUInt64()); + ++deletedInvites; + continue; + } + + CalendarInvite* invite = new CalendarInvite(event, inviteId, senderGuid, inviteeGuid, lastUpdateTime, status, rank, ""); + event->AddInvite(invite); + ++totalInvites; + m_MaxInviteId = std::max(inviteId, m_MaxInviteId); + } + while (invitesQuery->NextRow()); + sLog.outString(); + sLog.outString(">> Loaded " UI64FMTD " invites! %s", totalInvites, (deletedInvites != 0) ? "(deleted some invites without corresponding event!)" : ""); + } + else + { + // delete all invites (no invites exist without events) + CharacterDatabase.DirectExecute("TRUNCATE TABLE calendar_invites"); + sLog.outString(">> calendar_invites table is cleared! (invites without events found)"); + } + delete invitesQuery; + } + sLog.outString(); +} + +// check if player have not reached event limit +bool CalendarMgr::CanAddEvent(ObjectGuid const& guid) +{ + uint32 totalEvents = 0; + // count all event created by guid + for (CalendarEventStore::iterator itr = m_EventStore.begin(); itr != m_EventStore.end(); ++itr) + if ((itr->second.CreatorGuid == guid) && (++totalEvents >= CALENDAR_MAX_EVENTS)) + return false; + return true; +} + +// check if guild have not reached event limit +bool CalendarMgr::CanAddGuildEvent(uint32 guildId) +{ + if (!guildId) + return false; + + uint32 totalEvents = 0; + // count all guild events in a guild + for (CalendarEventStore::iterator itr = m_EventStore.begin(); itr != m_EventStore.end(); ++itr) + if ((itr->second.GuildId == guildId) && (++totalEvents >= CALENDAR_MAX_GUILD_EVENTS)) + return false; + return true; +} + +// check if an invitee have not reached invite limit +bool CalendarMgr::CanAddInviteTo(ObjectGuid const& guid) +{ + uint32 totalInvites = 0; + + for (CalendarEventStore::iterator itr = m_EventStore.begin(); itr != m_EventStore.end(); ++itr) + { + CalendarEvent* event = &itr->second; + + if (event->IsGuildAnnouncement()) + continue; + + CalendarInviteMap const* cInvMap = event->GetInviteMap(); + CalendarInviteMap::const_iterator ci_itr = cInvMap->begin(); + while (ci_itr != cInvMap->end()) + { + if ((ci_itr->second->InviteeGuid == guid) && (++totalInvites >= CALENDAR_MAX_INVITES)) + return false; + ++ci_itr; + } + } + + return true; +} diff --git a/src/game/CalendarHandler.cpp b/src/game/CalendarHandler.cpp new file mode 100644 index 000000000..a5e62e434 --- /dev/null +++ b/src/game/CalendarHandler.cpp @@ -0,0 +1,1044 @@ +/* + * This code is part of MaNGOS. Contributor & Copyright details are in AUTHORS/THANKS. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "Common.h" +#include "Log.h" +#include "Player.h" +#include "WorldPacket.h" +#include "WorldSession.h" +#include "Opcodes.h" +#include "MapPersistentStateMgr.h" +#include "Calendar.h" +#include "ObjectMgr.h" +#include "SocialMgr.h" +#include "World.h" +#include "Guild.h" +#include "GuildMgr.h" +#include "ArenaTeam.h" + +void WorldSession::HandleCalendarGetCalendar(WorldPacket& /*recv_data*/) +{ + ObjectGuid guid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_GET_CALENDAR [%s]", guid.GetString().c_str()); + + time_t currTime = time(NULL); + + WorldPacket data(SMSG_CALENDAR_SEND_CALENDAR); + + CalendarInvitesList invites; + sCalendarMgr.GetPlayerInvitesList(guid, invites); + + data << uint32(invites.size()); + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "Sending > %u invites", invites.size()); + + for (CalendarInvitesList::const_iterator itr = invites.begin(); itr != invites.end(); ++itr) + { + CalendarEvent const* event = (*itr)->GetCalendarEvent(); + MANGOS_ASSERT(event); // TODO: be sure no way to have a null event + + data << uint64(event->EventId); + data << uint64((*itr)->InviteId); + data << uint8((*itr)->Status); + data << uint8((*itr)->Rank); + + data << uint8(event->IsGuildEvent()); + data << event->CreatorGuid.WriteAsPacked(); + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "invite> EventId[" UI64FMTD "], InviteId[" UI64FMTD "], status[%u], rank[%u]", + event->EventId, (*itr)->InviteId, uint32((*itr)->Status), uint32((*itr)->Rank)); + } + + CalendarEventsList events; + sCalendarMgr.GetPlayerEventsList(guid, events); + + data << uint32(events.size()); + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "Sending > %u events", events.size()); + + for (CalendarEventsList::const_iterator itr = events.begin(); itr != events.end(); ++itr) + { + CalendarEvent const* event = *itr; + + data << uint64(event->EventId); + data << event->Title; + data << uint32(event->Type); + data << secsToTimeBitFields(event->EventTime); + data << uint32(event->Flags); + data << int32(event->DungeonId); + data << event->CreatorGuid.WriteAsPacked(); + + std::string timeStr = TimeToTimestampStr(event->EventTime); + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "Events> EventId[" UI64FMTD "], Title[%s], Time[%s], Type[%u], Flag[%u], DungeonId[%d], CreatorGuid[%s]", + event->EventId, event->Title.c_str(), timeStr.c_str(), uint32(event->Type), + uint32(event->Flags), event->DungeonId, event->CreatorGuid.GetString().c_str()); + } + + data << uint32(currTime); // server time + data << secsToTimeBitFields(currTime); // zone time ?? + + ByteBuffer dataBuffer; + uint32 boundCounter = 0; + for (uint8 i = 0; i < MAX_DIFFICULTY; ++i) + { + Player::BoundInstancesMap boundInstances = _player->GetBoundInstances(Difficulty(i)); + for (Player::BoundInstancesMap::const_iterator itr = boundInstances.begin(); itr != boundInstances.end(); ++itr) + { + if (itr->second.perm) + { + DungeonPersistentState const* state = itr->second.state; + dataBuffer << uint32(state->GetMapId()); + dataBuffer << uint32(state->GetDifficulty()); + dataBuffer << uint32(state->GetResetTime() - currTime); + dataBuffer << uint64(state->GetInstanceId()); // instance save id as unique instance copy id + ++boundCounter; + } + } + } + + data << uint32(boundCounter); + data.append(dataBuffer); + + data << uint32(1135753200); // Constant date, unk (28.12.2005 07:00) + + // Reuse variables + boundCounter = 0; + std::set sentMaps; + dataBuffer.clear(); + + for (MapDifficultyMap::const_iterator itr = sMapDifficultyMap.begin(); itr != sMapDifficultyMap.end(); ++itr) + { + uint32 map_diff_pair = itr->first; + uint32 mapId = PAIR32_LOPART(map_diff_pair); + Difficulty difficulty = Difficulty(PAIR32_HIPART(map_diff_pair)); + MapDifficultyEntry const* mapDiff = itr->second; + + // skip mapDiff without global reset time + if (!mapDiff->resetTime) + continue; + + // skip non raid map + MapEntry const* mapEntry = sMapStore.LookupEntry(mapId); + if (!mapEntry || !mapEntry->IsRaid()) + continue; + + // skip already sent map (not same difficulty?) + if (sentMaps.find(mapId) != sentMaps.end()) + continue; + + uint32 resetTime = sMapPersistentStateMgr.GetScheduler().GetMaxResetTimeFor(mapDiff); + + sentMaps.insert(mapId); + dataBuffer << mapId; + dataBuffer << resetTime; + + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "MapId [%u] -> Reset Time: %u", mapId, resetTime); + dataBuffer << int32(0); // showed 68400 on map 509 must investigate more + ++boundCounter; + } + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "Map sent [%u]", boundCounter); + + data << uint32(boundCounter); + data.append(dataBuffer); + + // TODO: Fix this, how we do know how many and what holidays to send? + uint32 holidayCount = 0; + data << uint32(holidayCount); + /*for (uint32 i = 0; i < holidayCount; ++i) + { + HolidaysEntry const* holiday = sHolidaysStore.LookupEntry(666); + + data << uint32(holiday->Id); // m_ID + data << uint32(holiday->Region); // m_region, might be looping + data << uint32(holiday->Looping); // m_looping, might be region + data << uint32(holiday->Priority); // m_priority + data << uint32(holiday->CalendarFilterType); // m_calendarFilterType + + for (uint8 j = 0; j < MAX_HOLIDAY_DATES; ++j) + data << uint32(holiday->Date[j]); // 26 * m_date -- WritePackedTime ? + + for (uint8 j = 0; j < MAX_HOLIDAY_DURATIONS; ++j) + data << uint32(holiday->Duration[j]); // 10 * m_duration + + for (uint8 j = 0; j < MAX_HOLIDAY_FLAGS; ++j) + data << uint32(holiday->CalendarFlags[j]); // 10 * m_calendarFlags + + data << holiday->TextureFilename; // m_textureFilename (holiday name) + }*/ + + SendPacket(&data); +} + +void WorldSession::HandleCalendarGetEvent(WorldPacket& recv_data) +{ + ObjectGuid guid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_GET_EVENT [%s]", guid.GetString().c_str()); + + uint64 eventId; + recv_data >> eventId; + + if (CalendarEvent* event = sCalendarMgr.GetEventById(eventId)) + sCalendarMgr.SendCalendarEvent(_player, event, CALENDAR_SENDTYPE_GET); + else + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_EVENT_INVALID); +} + +void WorldSession::HandleCalendarGuildFilter(WorldPacket& recv_data) +{ + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_GUILD_FILTER [%s]", _player->GetGuidStr().c_str()); + + uint32 minLevel; + uint32 maxLevel; + uint32 minRank; + + recv_data >> minLevel >> maxLevel >> minRank; + + if (Guild* guild = sGuildMgr.GetGuildById(_player->GetGuildId())) + guild->MassInviteToEvent(this, minLevel, maxLevel, minRank); + + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "Min level [%u], Max level [%u], Min rank [%u]", minLevel, maxLevel, minRank); +} + +void WorldSession::HandleCalendarEventSignup(WorldPacket& recv_data) +{ + ObjectGuid guid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_EVENT_SIGNUP [%s]", guid.GetString().c_str()); + + uint64 eventId; + bool tentative; + + recv_data >> eventId; + recv_data >> tentative; // uint8 == bool size in all compilator??? + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "EventId [" UI64FMTD "] Tentative %u", eventId, uint32(tentative)); + + if (CalendarEvent* event = sCalendarMgr.GetEventById(eventId)) + { + if (event->IsGuildEvent() && event->GuildId != _player->GetGuildId()) + { + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_GUILD_PLAYER_NOT_IN_GUILD); + return; + } + + CalendarInviteStatus status = tentative ? CALENDAR_STATUS_TENTATIVE : CALENDAR_STATUS_SIGNED_UP; + sCalendarMgr.AddInvite(event, guid, guid, CalendarInviteStatus(status), CALENDAR_RANK_PLAYER, "", time(NULL)); + sCalendarMgr.SendCalendarClearPendingAction(_player); + } + else + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_EVENT_INVALID); +} + +void WorldSession::HandleCalendarArenaTeam(WorldPacket& recv_data) +{ + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_ARENA_TEAM [%s]", _player->GetGuidStr().c_str()); + uint32 areanTeamId; + recv_data >> areanTeamId; + + if (ArenaTeam* team = sObjectMgr.GetArenaTeamById(areanTeamId)) + team->MassInviteToEvent(this); +} + +void WorldSession::HandleCalendarAddEvent(WorldPacket& recv_data) +{ + ObjectGuid guid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_ADD_EVENT [%s]", guid.GetString().c_str()); + + std::string title; + std::string description; + uint8 type; + uint8 repeatable; + uint32 maxInvites; + int32 dungeonId; + uint32 eventPackedTime; + uint32 unkPackedTime; + uint32 flags; + + recv_data >> title; + recv_data >> description; + recv_data >> type; + recv_data >> repeatable; + recv_data >> maxInvites; + recv_data >> dungeonId; + recv_data >> eventPackedTime; + recv_data >> unkPackedTime; + recv_data >> flags; + + // 946684800 is 01/01/2000 00:00:00 - default response time + CalendarEvent* cal = sCalendarMgr.AddEvent(_player->GetObjectGuid(), title, description, type, repeatable, maxInvites, dungeonId, timeBitFieldsToSecs(eventPackedTime), timeBitFieldsToSecs(unkPackedTime), flags); + + if (cal) + { + if (cal->IsGuildAnnouncement()) + { + sCalendarMgr.AddInvite(cal, guid, ObjectGuid(uint64(0)), CALENDAR_STATUS_NOT_SIGNED_UP, CALENDAR_RANK_PLAYER, "", time(NULL)); + } + else + { + uint32 inviteCount; + recv_data >> inviteCount; + + for (uint32 i = 0; i < inviteCount; ++i) + { + ObjectGuid invitee; + uint8 status = 0; + uint8 rank = 0; + recv_data >> invitee.ReadAsPacked(); + recv_data >> status; + recv_data >> rank; + + sCalendarMgr.AddInvite(cal, guid, invitee, CalendarInviteStatus(status), CalendarModerationRank(rank), "", time(NULL)); + } + } + sCalendarMgr.SendCalendarEvent(_player, cal, CALENDAR_SENDTYPE_ADD); + } +} + +void WorldSession::HandleCalendarUpdateEvent(WorldPacket& recv_data) +{ + ObjectGuid guid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_UPDATE_EVENT [%s]", guid.GetString().c_str()); + + time_t oldEventTime; + uint64 eventId; + uint64 inviteId; + std::string title; + std::string description; + uint8 type; + uint8 repetitionType; + uint32 maxInvites; + int32 dungeonId; + uint32 eventPackedTime; + uint32 UnknownPackedTime; + uint32 flags; + + recv_data >> eventId >> inviteId >> title >> description >> type >> repetitionType >> maxInvites >> dungeonId; + recv_data >> eventPackedTime; + recv_data >> UnknownPackedTime; + recv_data >> flags; + + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "EventId [" UI64FMTD "], InviteId [" UI64FMTD "] Title %s, Description %s, type %u " + "Repeatable %u, MaxInvites %u, Dungeon ID %d, Flags %u", eventId, inviteId, title.c_str(), + description.c_str(), uint32(type), uint32(repetitionType), maxInvites, dungeonId, flags); + + if (CalendarEvent* event = sCalendarMgr.GetEventById(eventId)) + { + if (guid != event->CreatorGuid) + { + CalendarInvite* updaterInvite = event->GetInviteByGuid(guid); + if (updaterInvite == NULL) + { + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_NOT_INVITED); + return ; + } + + if (updaterInvite->Rank != CALENDAR_RANK_MODERATOR) + { + // remover have not enough right to change invite status + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_PERMISSIONS); + return; + } + } + + oldEventTime = event->EventTime; + + event->Type = CalendarEventType(type); + event->Flags = flags; + event->EventTime = timeBitFieldsToSecs(eventPackedTime); + event->UnknownTime = timeBitFieldsToSecs(UnknownPackedTime); + event->DungeonId = dungeonId; + event->Title = title; + event->Description = description; + + sCalendarMgr.SendCalendarEventUpdateAlert(event, oldEventTime); + + // query construction + CharacterDatabase.escape_string(title); + CharacterDatabase.escape_string(description); + CharacterDatabase.PExecute("UPDATE calendar_events SET " + "type=%hu, flags=%u, dungeonId=%d, eventTime=%u, title='%s', description='%s'" + "WHERE eventid=" UI64FMTD, + type, flags, dungeonId, event->EventTime, title.c_str(), description.c_str(), eventId); + } + else + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_EVENT_INVALID); +} + +void WorldSession::HandleCalendarRemoveEvent(WorldPacket& recv_data) +{ + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_REMOVE_EVENT [%s]", _player->GetGuidStr().c_str()); + + uint64 eventId; + uint64 inviteId; + uint32 Flags; + + recv_data >> eventId; + recv_data >> inviteId; + recv_data >> Flags; + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "Remove event (eventId=" UI64FMTD ", remover inviteId =" UI64FMTD ")", eventId, inviteId); + sCalendarMgr.RemoveEvent(eventId, _player); +} + +void WorldSession::HandleCalendarCopyEvent(WorldPacket& recv_data) +{ + ObjectGuid guid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_COPY_EVENT [%s]", guid.GetString().c_str()); + + uint64 eventId; + uint64 inviteId; + uint32 packedTime; + + recv_data >> eventId >> inviteId; + recv_data >> packedTime; + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "EventId [" UI64FMTD "] inviteId [" UI64FMTD "]", + eventId, inviteId); + + sCalendarMgr.CopyEvent(eventId, timeBitFieldsToSecs(packedTime), guid); +} + +void WorldSession::HandleCalendarEventInvite(WorldPacket& recv_data) +{ + ObjectGuid playerGuid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_EVENT_INVITE [%s]", playerGuid.GetString().c_str()); + + uint64 eventId; + + // TODO it seem its not inviteID but event->CreatorGuid + uint64 inviteId; + std::string name; + bool isPreInvite; + bool isGuildEvent; + + ObjectGuid inviteeGuid; + uint32 inviteeTeam = 0; + uint32 inviteeGuildId = 0; + bool isIgnored = false; + + recv_data >> eventId >> inviteId >> name >> isPreInvite >> isGuildEvent; + + if (Player* player = sObjectAccessor.FindPlayerByName(name.c_str())) + { + // Invitee is online + inviteeGuid = player->GetObjectGuid(); + inviteeTeam = player->GetTeam(); + inviteeGuildId = player->GetGuildId(); + if (player->GetSocial()->HasIgnore(playerGuid)) + isIgnored = true; + } + else + { + // Invitee offline, get data from database + CharacterDatabase.escape_string(name); + QueryResult* result = CharacterDatabase.PQuery("SELECT guid,race FROM characters WHERE name ='%s'", name.c_str()); + if (result) + { + Field* fields = result->Fetch(); + inviteeGuid = ObjectGuid(HIGHGUID_PLAYER, fields[0].GetUInt32()); + inviteeTeam = Player::TeamForRace(fields[1].GetUInt8()); + inviteeGuildId = Player::GetGuildIdFromDB(inviteeGuid); + delete result; + + result = CharacterDatabase.PQuery("SELECT flags FROM character_social WHERE guid = %u AND friend = %u", inviteeGuid.GetCounter(), playerGuid.GetCounter()); + if (result) + { + Field* fields = result->Fetch(); + if (fields[0].GetUInt8() & SOCIAL_FLAG_IGNORED) + isIgnored = true; + delete result; + } + } + } + + if (inviteeGuid.IsEmpty()) + { + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_PLAYER_NOT_FOUND); + return; + } + + if (isIgnored) + { + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_IGNORING_YOU_S, name.c_str()); + return; + } + + if (_player->GetTeam() != inviteeTeam && !sWorld.getConfig(CONFIG_BOOL_ALLOW_TWO_SIDE_INTERACTION_CALENDAR)) + { + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_NOT_ALLIED); + return; + } + + if (!isPreInvite) + { + if (CalendarEvent* event = sCalendarMgr.GetEventById(eventId)) + { + if (event->IsGuildEvent() && event->GuildId == inviteeGuildId) + { + // we can't invite guild members to guild events + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_NO_GUILD_INVITES); + return; + } + + sCalendarMgr.AddInvite(event, playerGuid, inviteeGuid, CALENDAR_STATUS_INVITED, CALENDAR_RANK_PLAYER, "", time(NULL)); + } + else + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_EVENT_INVALID); + } + else + { + if (isGuildEvent && inviteeGuildId == _player->GetGuildId()) + { + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_NO_GUILD_INVITES); + return; + } + + // create a temp invite to send it back to client + CalendarInvite invite; + invite.SenderGuid = playerGuid; + invite.InviteeGuid = inviteeGuid; + invite.Status = CALENDAR_STATUS_INVITED; + invite.Rank = CALENDAR_RANK_PLAYER; + invite.LastUpdateTime = time(NULL); + + sCalendarMgr.SendCalendarEventInvite(&invite); + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "PREINVITE> sender[%s], Invitee[%u]", playerGuid.GetString().c_str(), inviteeGuid.GetString().c_str()); + } +} + +void WorldSession::HandleCalendarEventRsvp(WorldPacket& recv_data) +{ + ObjectGuid guid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_EVENT_RSVP [%s]", guid.GetString().c_str()); + + uint64 eventId; + uint64 inviteId; + uint32 status; + + recv_data >> eventId >> inviteId >> status; + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "EventId ["UI64FMTD "], InviteId [" UI64FMTD "], status %u", + eventId, inviteId, status); + + if (CalendarEvent* event = sCalendarMgr.GetEventById(eventId)) + { + // i think we still should be able to remove self from locked events + if (status != CALENDAR_STATUS_REMOVED && event->Flags & CALENDAR_FLAG_INVITES_LOCKED) + { + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_EVENT_LOCKED); + return; + } + + if (CalendarInvite* invite = event->GetInviteById(inviteId)) + { + if (invite->InviteeGuid != guid) + { + CalendarInvite* updaterInvite = event->GetInviteByGuid(guid); + if (updaterInvite == NULL) + { + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_NOT_INVITED); + return ; + } + + if (updaterInvite->Rank != CALENDAR_RANK_MODERATOR && updaterInvite->Rank != CALENDAR_RANK_OWNER) + { + // remover have not enough right to change invite status + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_PERMISSIONS); + return; + } + } + invite->Status = CalendarInviteStatus(status); + invite->LastUpdateTime = time(NULL); + + CharacterDatabase.PExecute("UPDATE calendar_invites SET status=%u, lastUpdateTime=%u WHERE inviteId = "UI64FMTD, status, uint32(invite->LastUpdateTime), invite->InviteId); + sCalendarMgr.SendCalendarEventStatus(invite); + sCalendarMgr.SendCalendarClearPendingAction(_player); + } + else + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_NO_INVITE); // correct? + } + else + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_EVENT_INVALID); +} + +void WorldSession::HandleCalendarEventRemoveInvite(WorldPacket& recv_data) +{ + ObjectGuid guid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_EVENT_REMOVE_INVITE [%s]", guid.GetString().c_str()); + + ObjectGuid invitee; + uint64 eventId; + uint64 ownerInviteId; // isn't it sender's inviteId? TODO: need more test to see what we can do with that value + uint64 inviteId; + + recv_data >> invitee.ReadAsPacked(); + recv_data >> inviteId >> ownerInviteId >> eventId; + + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "EventId [" UI64FMTD "], ownerInviteId [" UI64FMTD "], Invitee ([%s] id: [" UI64FMTD "])", + eventId, ownerInviteId, invitee.GetString().c_str(), inviteId); + + if (CalendarEvent* event = sCalendarMgr.GetEventById(eventId)) + sCalendarMgr.RemoveInvite(eventId, inviteId, guid); + else + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_EVENT_INVALID); +} + +void WorldSession::HandleCalendarEventStatus(WorldPacket& recv_data) +{ + ObjectGuid updaterGuid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_EVENT_STATUS [%s]", updaterGuid.GetString().c_str()); + recv_data.hexlike(); + + ObjectGuid invitee; + uint64 eventId; + uint64 inviteId; + uint64 ownerInviteId; // isn't it sender's inviteId? + uint32 status; + + recv_data >> invitee.ReadAsPacked(); + recv_data >> eventId >> inviteId >> ownerInviteId >> status; + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "EventId [" UI64FMTD "] ownerInviteId [" UI64FMTD "], Invitee ([%s] id: [" UI64FMTD "], status %u", + eventId, ownerInviteId, invitee.GetString().c_str(), inviteId, status); + + if (CalendarEvent* event = sCalendarMgr.GetEventById(eventId)) + { + if (CalendarInvite* invite = event->GetInviteById(inviteId)) + { + if (invite->InviteeGuid != updaterGuid) + { + CalendarInvite* updaterInvite = event->GetInviteByGuid(updaterGuid); + if (updaterInvite == NULL) + { + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_NOT_INVITED); + return ; + } + + if (updaterInvite->Rank != CALENDAR_RANK_MODERATOR && updaterInvite->Rank != CALENDAR_RANK_OWNER) + { + // remover have not enough right to change invite status + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_PERMISSIONS); + return; + } + } + invite->Status = (CalendarInviteStatus)status; + invite->LastUpdateTime = time(NULL); // not sure if we should set response time when moderator changes invite status + + CharacterDatabase.PExecute("UPDATE calendar_invites SET status=%u, lastUpdateTime=%u WHERE inviteId=" UI64FMTD, status, uint32(invite->LastUpdateTime), invite->InviteId); + sCalendarMgr.SendCalendarEventStatus(invite); + sCalendarMgr.SendCalendarClearPendingAction(sObjectMgr.GetPlayer(invitee)); + } + else + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_NO_INVITE); + } + else + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_EVENT_INVALID); +} + +void WorldSession::HandleCalendarEventModeratorStatus(WorldPacket& recv_data) +{ + ObjectGuid guid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_EVENT_MODERATOR_STATUS [%s]", guid.GetString().c_str()); + + ObjectGuid invitee; + uint64 eventId; + uint64 inviteId; + uint64 ownerInviteId; // isn't it sender's inviteId? + uint32 rank; + + recv_data >> invitee.ReadAsPacked(); + recv_data >> eventId >> inviteId >> ownerInviteId >> rank; + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "EventId [" UI64FMTD "] ownerInviteId [" UI64FMTD "], Invitee ([%s] id: [" UI64FMTD "], rank %u", + eventId, ownerInviteId, invitee.GetString().c_str(), inviteId, rank); + + if (CalendarEvent* event = sCalendarMgr.GetEventById(eventId)) + { + if (CalendarInvite* invite = event->GetInviteById(inviteId)) + { + if (invite->InviteeGuid != guid) + { + CalendarInvite* updaterInvite = event->GetInviteByGuid(guid); + if (updaterInvite == NULL) + { + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_NOT_INVITED); + return ; + } + if (updaterInvite->Rank != CALENDAR_RANK_MODERATOR && updaterInvite->Rank != CALENDAR_RANK_OWNER) + { + // remover have not enough right to change invite status + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_PERMISSIONS); + return; + } + } + + if (CalendarModerationRank(rank) == CALENDAR_RANK_OWNER) + { + // cannot set owner + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_PERMISSIONS); + return; + } + + CharacterDatabase.PExecute("UPDATE calendar_invites SET rank = %u WHERE inviteId=" UI64FMTD, rank, invite->InviteId); + invite->Rank = CalendarModerationRank(rank); + sCalendarMgr.SendCalendarEventModeratorStatusAlert(invite); + } + else + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_NO_INVITE); + } + else + sCalendarMgr.SendCalendarCommandResult(_player, CALENDAR_ERROR_EVENT_INVALID); +} + +void WorldSession::HandleCalendarComplain(WorldPacket& recv_data) +{ + ObjectGuid guid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_COMPLAIN [%s]", guid.GetString().c_str()); + + ObjectGuid badGuyGuid; + uint64 eventId; + uint64 inviteId; + + recv_data >> badGuyGuid; + recv_data >> eventId >> inviteId; + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "EventId [" UI64FMTD "], BadGuyGuid ([%s] inviteId: [" UI64FMTD "])", + eventId, badGuyGuid.GetString().c_str(), inviteId); + + // Remove the invite + if (sCalendarMgr.RemoveInvite(eventId, inviteId, guid)) + { + WorldPacket data(SMSG_COMPLAIN_RESULT, 1 + 1); + data << uint8(0); + data << uint8(0); // show complain saved. We can send 0x0C to show windows with ok button + SendPacket(&data); + } +} + +void WorldSession::HandleCalendarGetNumPending(WorldPacket& /*recv_data*/) +{ + ObjectGuid guid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_GET_NUM_PENDING [%s]", guid.GetString().c_str()); + + uint32 pending = sCalendarMgr.GetPlayerNumPending(guid); + + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "Pending: %u", pending); + + WorldPacket data(SMSG_CALENDAR_SEND_NUM_PENDING, 4); + data << uint32(pending); + SendPacket(&data); +} + +////////////////////////////////////////////////////////////////////////// +// Send function +////////////////////////////////////////////////////////////////////////// + +void CalendarMgr::SendCalendarEventInviteAlert(CalendarInvite const* invite) +{ + DEBUG_LOG("WORLD: SMSG_CALENDAR_EVENT_INVITE_ALERT"); + + CalendarEvent const* event = invite->GetCalendarEvent(); + if (!event) + return; + + WorldPacket data(SMSG_CALENDAR_EVENT_INVITE_ALERT); + data << uint64(event->EventId); + data << event->Title; + data << secsToTimeBitFields(event->EventTime); + data << uint32(event->Flags); + data << uint32(event->Type); + data << int32(event->DungeonId); + data << uint64(invite->InviteId); + data << uint8(invite->Status); + data << uint8(invite->Rank); + data << event->CreatorGuid.WriteAsPacked(); + data << invite->SenderGuid.WriteAsPacked(); + //data.hexlike(); + + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "SendCalendarInviteAlert> senderGuid[%s], inviteeGuid[%s], EventId[" UI64FMTD "], Status[%u], InviteId[" UI64FMTD "]", + invite->SenderGuid.GetString().c_str(), invite->InviteeGuid.GetString().c_str(), event->EventId, uint32(invite->Status), invite->InviteId); + + if (event->IsGuildEvent() || event->IsGuildAnnouncement()) + { + if (Guild* guild = sGuildMgr.GetGuildById(event->GuildId)) + guild->BroadcastPacket(&data); + } + else if (Player* player = sObjectMgr.GetPlayer(invite->InviteeGuid)) + player->SendDirectMessage(&data); +} + +void CalendarMgr::SendCalendarEventInvite(CalendarInvite const* invite) +{ + CalendarEvent const* event = invite->GetCalendarEvent(); + + time_t statusTime = invite->LastUpdateTime; + bool preInvite = true; + uint64 eventId = 0; + if (event != NULL) + { + preInvite = false; + eventId = event->EventId; + } + + Player* player = sObjectMgr.GetPlayer(invite->InviteeGuid); + + uint8 level = player ? player->getLevel() : Player::GetLevelFromDB(invite->InviteeGuid); + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "SMSG_CALENDAR_EVENT_INVITE"); + WorldPacket data(SMSG_CALENDAR_EVENT_INVITE, 8 + 8 + 8 + 1 + 1 + 1 + (preInvite ? 0 : 4) + 1); + data << invite->InviteeGuid.WriteAsPacked(); + data << uint64(eventId); + data << uint64(invite->InviteId); + data << uint8(level); + data << uint8(invite->Status); + data << uint8(!preInvite); + if (!preInvite) + data << secsToTimeBitFields(statusTime); + data << uint8(invite->SenderGuid != invite->InviteeGuid); // false only if the invite is sign-up (invitee create himself his invite) + + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "SendCalendarInvit> %s senderGuid[%s], inviteeGuid[%s], EventId[" UI64FMTD "], Status[%u], InviteId[" UI64FMTD "]", + preInvite ? "is PreInvite," : "", invite->SenderGuid.GetString().c_str(), invite->InviteeGuid.GetString().c_str(), eventId, uint32(invite->Status), invite->InviteId); + + //data.hexlike(); + if (preInvite) + { + if (Player* sender = sObjectMgr.GetPlayer(invite->SenderGuid)) + sender->SendDirectMessage(&data); + } + else + SendPacketToAllEventRelatives(data, event); +} + +void CalendarMgr::SendCalendarCommandResult(Player* player, CalendarError err, char const* param /*= NULL*/) +{ + if (!player) + return; + + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "SMSG_CALENDAR_COMMAND_RESULT (%u)", err); + WorldPacket data(SMSG_CALENDAR_COMMAND_RESULT, 0); + data << uint32(0); + data << uint8(0); + switch (err) + { + case CALENDAR_ERROR_OTHER_INVITES_EXCEEDED_S: + case CALENDAR_ERROR_ALREADY_INVITED_TO_EVENT_S: + case CALENDAR_ERROR_IGNORING_YOU_S: + data << param; + break; + default: + data << uint8(0); + break; + } + + data << uint32(err); + //data.hexlike(); + player->SendDirectMessage(&data); +} + +void CalendarMgr::SendCalendarEventRemovedAlert(CalendarEvent const* event) +{ + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "SMSG_CALENDAR_EVENT_REMOVED_ALERT"); + WorldPacket data(SMSG_CALENDAR_EVENT_REMOVED_ALERT, 1 + 8 + 1); + data << uint8(1); // show pending alert? + data << uint64(event->EventId); + data << secsToTimeBitFields(event->EventTime); + //data.hexlike(); + SendPacketToAllEventRelatives(data, event); +} + +void CalendarMgr::SendCalendarEvent(Player* player, CalendarEvent const* event, uint32 sendType) +{ + if (!player || !event) + return; + + std::string timeStr = TimeToTimestampStr(event->EventTime); + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "SendCalendarEvent> sendType[%u], CreatorGuid[%s], EventId[" UI64FMTD "], Type[%u], Flags[%u], Title[%s]", + sendType, event->CreatorGuid.GetString().c_str(), event->EventId, uint32(event->Type), event->Flags, event->Title.c_str()); + + WorldPacket data(SMSG_CALENDAR_SEND_EVENT); + data << uint8(sendType); + data << event->CreatorGuid.WriteAsPacked(); + data << uint64(event->EventId); + data << event->Title; + data << event->Description; + data << uint8(event->Type); + data << uint8(event->Repeatable); + data << uint32(CALENDAR_MAX_INVITES); + data << event->DungeonId; + data << event->Flags; + data << secsToTimeBitFields(event->EventTime); + data << secsToTimeBitFields(event->UnknownTime); + data << event->GuildId; + + CalendarInviteMap const* cInvMap = event->GetInviteMap(); + data << (uint32)cInvMap->size(); + for (CalendarInviteMap::const_iterator itr = cInvMap->begin(); itr != cInvMap->end(); ++itr) + { + CalendarInvite const* invite = itr->second; + ObjectGuid inviteeGuid = invite->InviteeGuid; + Player* invitee = sObjectMgr.GetPlayer(inviteeGuid); + + uint8 inviteeLevel = invitee ? invitee->getLevel() : Player::GetLevelFromDB(inviteeGuid); + uint32 inviteeGuildId = invitee ? invitee->GetGuildId() : Player::GetGuildIdFromDB(inviteeGuid); + + data << inviteeGuid.WriteAsPacked(); + data << uint8(inviteeLevel); + data << uint8(invite->Status); + data << uint8(invite->Rank); + data << uint8(event->IsGuildEvent() && event->GuildId == inviteeGuildId); + data << uint64(itr->first); + data << secsToTimeBitFields(invite->LastUpdateTime); + data << invite->Text; + + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "Invite> InviteId[" UI64FMTD "], InviteLvl[%u], Status[%u], Rank[%u], GuildEvent[%s], Text[%s]", + invite->InviteId, uint32(inviteeLevel), uint32(invite->Status), uint32(invite->Rank), + (event->IsGuildEvent() && event->GuildId == inviteeGuildId) ? "true" : "false", invite->Text.c_str()); + } + //data.hexlike(); + player->SendDirectMessage(&data); +} + +void CalendarMgr::SendCalendarEventInviteRemove(CalendarInvite const* invite, uint32 flags) +{ + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "SMSG_CALENDAR_EVENT_INVITE_REMOVED"); + + CalendarEvent const* event = invite->GetCalendarEvent(); + + WorldPacket data(SMSG_CALENDAR_EVENT_INVITE_REMOVED, 8 + 4 + 4 + 1); + data.appendPackGUID(invite->InviteeGuid); + data << uint64(event->EventId); + data << uint32(flags); + data << uint8(1); // show pending alert? + //data.hexlike(); + SendPacketToAllEventRelatives(data, event); +} + +void CalendarMgr::SendCalendarEventInviteRemoveAlert(Player* player, CalendarEvent const* event, CalendarInviteStatus status) +{ + if (player) + { + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "SMSG_CALENDAR_EVENT_INVITE_REMOVED_ALERT"); + WorldPacket data(SMSG_CALENDAR_EVENT_INVITE_REMOVED_ALERT, 8 + 4 + 4 + 1); + data << uint64(event->EventId); + data << secsToTimeBitFields(event->EventTime); + data << uint32(event->Flags); + data << uint8(status); + //data.hexlike(); + player->SendDirectMessage(&data); + } +} + +void CalendarMgr::SendCalendarEventStatus(CalendarInvite const* invite) +{ + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "SMSG_CALENDAR_EVENT_STATUS"); + WorldPacket data(SMSG_CALENDAR_EVENT_STATUS, 8 + 8 + 4 + 4 + 1 + 1 + 4); + CalendarEvent const* event = invite->GetCalendarEvent(); + + data << invite->InviteeGuid.WriteAsPacked(); + data << uint64(event->EventId); + data << secsToTimeBitFields(event->EventTime); + data << uint32(event->Flags); + data << uint8(invite->Status); + data << uint8(invite->Rank); + data << secsToTimeBitFields(invite->LastUpdateTime); + //data.hexlike(); + SendPacketToAllEventRelatives(data, event); +} + +void CalendarMgr::SendCalendarClearPendingAction(Player* player) +{ + if (player) + { + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "SMSG_CALENDAR_CLEAR_PENDING_ACTION TO [%s]", player->GetObjectGuid().GetString().c_str()); + WorldPacket data(SMSG_CALENDAR_CLEAR_PENDING_ACTION, 0); + player->SendDirectMessage(&data); + } +} + +void CalendarMgr::SendCalendarEventModeratorStatusAlert(CalendarInvite const* invite) +{ + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "SMSG_CALENDAR_EVENT_MODERATOR_STATUS_ALERT"); + CalendarEvent const* event = invite->GetCalendarEvent(); + WorldPacket data(SMSG_CALENDAR_EVENT_MODERATOR_STATUS_ALERT, 8 + 8 + 1 + 1); + data << invite->InviteeGuid.WriteAsPacked(); + data << uint64(event->EventId); + data << uint8(invite->Rank); + data << uint8(1); // Display pending action to client? + //data.hexlike(); + SendPacketToAllEventRelatives(data, event); +} + +void CalendarMgr::SendCalendarEventUpdateAlert(CalendarEvent const* event, time_t oldEventTime) +{ + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "SMSG_CALENDAR_EVENT_UPDATED_ALERT"); + WorldPacket data(SMSG_CALENDAR_EVENT_UPDATED_ALERT, 1 + 8 + 4 + 4 + 4 + 1 + 4 + + event->Title.size() + event->Description.size() + 1 + 4 + 4); + data << uint8(1); // show pending alert? + data << uint64(event->EventId); + data << secsToTimeBitFields(oldEventTime); + data << uint32(event->Flags); + data << secsToTimeBitFields(event->EventTime); + data << uint8(event->Type); + data << int32(event->DungeonId); + data << event->Title; + data << event->Description; + data << uint8(event->Repeatable); + data << uint32(CALENDAR_MAX_INVITES); + data << secsToTimeBitFields(event->UnknownTime); + //data.hexlike(); + + SendPacketToAllEventRelatives(data, event); +} + +void CalendarMgr::SendPacketToAllEventRelatives(WorldPacket packet, CalendarEvent const* event) +{ + // Send packet to all guild members + if (event->IsGuildEvent() || event->IsGuildAnnouncement()) + if (Guild* guild = sGuildMgr.GetGuildById(event->GuildId)) + guild->BroadcastPacket(&packet); + + // Send packet to all invitees if event is non-guild, in other case only to non-guild invitees (packet was broadcasted for them) + CalendarInviteMap const* cInvMap = event->GetInviteMap(); + for (CalendarInviteMap::const_iterator itr = cInvMap->begin(); itr != cInvMap->end(); ++itr) + if (Player* player = sObjectMgr.GetPlayer(itr->second->InviteeGuid)) + if (!event->IsGuildEvent() || (event->IsGuildEvent() && player->GetGuildId() != event->GuildId)) + player->SendDirectMessage(&packet); +} + +void CalendarMgr::SendCalendarRaidLockoutRemove(Player* player, DungeonPersistentState const* save) +{ + if (!save || !player) + return; + + DEBUG_LOG("SMSG_CALENDAR_RAID_LOCKOUT_REMOVED [%s]", player->GetObjectGuid().GetString().c_str()); + time_t currTime = time(NULL); + + WorldPacket data(SMSG_CALENDAR_RAID_LOCKOUT_REMOVED, 4 + 4 + 4 + 8); + data << uint32(save->GetMapId()); + data << uint32(save->GetDifficulty()); + data << uint32(save->GetResetTime() - currTime); + data << uint64(save->GetInstanceId()); + //data.hexlike(); + player->SendDirectMessage(&data); +} + +void CalendarMgr::SendCalendarRaidLockoutAdd(Player* player, DungeonPersistentState const* save) +{ + if (!save || !player) + return; + + DEBUG_LOG("SMSG_CALENDAR_RAID_LOCKOUT_ADDED [%s]", player->GetObjectGuid().GetString().c_str()); + time_t currTime = time(NULL); + + WorldPacket data(SMSG_CALENDAR_RAID_LOCKOUT_ADDED, 4 + 4 + 4 + 4 + 8); + data << secsToTimeBitFields(currTime); + data << uint32(save->GetMapId()); + data << uint32(save->GetDifficulty()); + data << uint32(save->GetResetTime() - currTime); + data << uint64(save->GetInstanceId()); + //data.hexlike(); + player->SendDirectMessage(&data); +}