diff --git a/src/game/Calendar.cpp b/src/game/Calendar.cpp index fea496e32..c014e6695 100644 --- a/src/game/Calendar.cpp +++ b/src/game/Calendar.cpp @@ -15,3 +15,710 @@ * 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(); + + 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 index b784a5bd1..bbb8ecb81 100644 --- a/src/game/CalendarHandler.cpp +++ b/src/game/CalendarHandler.cpp @@ -23,254 +23,1022 @@ #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*/) { - DEBUG_LOG("WORLD: CMSG_CALENDAR_GET_CALENDAR"); // empty + ObjectGuid guid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_GET_CALENDAR [%s]", guid.GetString().c_str()); - time_t cur_time = time(NULL); + time_t currTime = time(NULL); - WorldPacket data(SMSG_CALENDAR_SEND_CALENDAR, 4 + 4 * 0 + 4 + 4 * 0 + 4 + 4); + WorldPacket data(SMSG_CALENDAR_SEND_CALENDAR); - // TODO: calendar invite event output - data << (uint32) 0; // invite node count - // TODO: calendar event output - data << (uint32) 0; // event count + CalendarInvitesList invites; + sCalendarMgr.GetPlayerInvitesList(guid, invites); - data << (uint32) 0; // Current Unix Time? - data << (uint32) secsToTimeBitFields(cur_time); // current packed time + data << uint32(invites.size()); + DEBUG_FILTER_LOG(LOG_FILTER_CALENDAR, "Sending > %u invites", invites.size()); - uint32 counter = 0; - size_t p_counter = data.wpos(); - data << uint32(counter); // instance state count - - for (int i = 0; i < MAX_DIFFICULTY; ++i) + for (CalendarInvitesList::const_iterator itr = invites.begin(); itr != invites.end(); ++itr) { - for (Player::BoundInstancesMap::const_iterator itr = _player->m_boundInstances[i].begin(); itr != _player->m_boundInstances[i].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* state = itr->second.state; - data << uint32(state->GetMapId()); - data << uint32(state->GetDifficulty()); - data << uint32(state->GetResetTime() - cur_time); - data << ObjectGuid(state->GetInstanceGuid()); - ++counter; + 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.put(p_counter, counter); - data << (uint32) 1135753200; // base date (28.12.2005 12:00) - data << (uint32) 0; // raid reset count - data << (uint32) 0; // holidays count - /* - for(uint32 i = 0; i < holidays_count; ++i) - { - data << uint32(0); // Holidays.dbc ID - data << uint32(0); // Holidays.dbc region - data << uint32(0); // Holidays.dbc looping - data << uint32(0); // Holidays.dbc priority - data << uint32(0); // Holidays.dbc calendarFilterType + data << uint32(boundCounter); + data.append(dataBuffer); - for(uint32 j = 0; j < 26; ++j) - data << uint32(0); // Holidays.dbc date + data << uint32(1135753200); // Constant date, unk (28.12.2005 07:00) - for(uint32 j = 0; j < 10; ++j) - data << uint32(0); // Holidays.dbc duration + // Reuse variables + boundCounter = 0; + std::set sentMaps; + dataBuffer.clear(); - for(uint32 j = 0; j < 10; ++j) - data << uint32(0); // Holidays.dbc calendarFlags + 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) + }*/ - data << ""; // Holidays.dbc textureFilename - } - */ - // DEBUG_LOG("Sending calendar"); - // data.hexlike(); SendPacket(&data); } void WorldSession::HandleCalendarGetEvent(WorldPacket& recv_data) { - DEBUG_LOG("WORLD: CMSG_CALENDAR_GET_EVENT"); - recv_data.hexlike(); - recv_data.read_skip(); // unk + 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: CMSG_CALENDAR_GUILD_FILTER"); - recv_data.hexlike(); - recv_data.read_skip(); // unk1 - recv_data.read_skip(); // unk2 - recv_data.read_skip(); // unk3 + 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: CMSG_CALENDAR_ARENA_TEAM"); - recv_data.hexlike(); - recv_data.read_skip(); // unk + 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) { - DEBUG_LOG("WORLD: CMSG_CALENDAR_ADD_EVENT"); - recv_data.hexlike(); - recv_data.rpos(recv_data.wpos()); // set to end to avoid warnings spam + ObjectGuid guid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_ADD_EVENT [%s]", guid.GetString().c_str()); - // std::string unk1, unk2; - // recv_data >> (std::string)unk1; - // recv_data >> (std::string)unk2; + std::string title; + std::string description; + uint8 type; + uint8 repeatable; + uint32 maxInvites; + int32 dungeonId; + uint32 eventPackedTime; + uint32 unkPackedTime; + uint32 flags; - // uint8 unk3, unk4; - // uint32 unk5, unk6, unk7, unk8, unk9, count = 0; - // recv_data >> (uint8)unk3; - // recv_data >> (uint8)unk4; - // recv_data >> (uint32)unk5; - // recv_data >> (uint32)unk6; - // recv_data >> (uint32)unk7; - // recv_data >> (uint32)unk8; - // recv_data >> (uint32)unk9; - // if (!((unk9 >> 6) & 1)) - //{ - // recv_data >> (uint32)count; - // if (count) - // { - // uint8 unk12,unk13; - // ObjectGuid guid; - // for (int i = 0; i < count; ++i) - // { - // recv_data >> guid.ReadAsPacked(); - // recv_data >> (uint8)unk12; - // recv_data >> (uint8)unk13; - // } - // } - //} + 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) { - DEBUG_LOG("WORLD: CMSG_CALENDAR_UPDATE_EVENT"); - recv_data.hexlike(); - recv_data.rpos(recv_data.wpos()); // set to end to avoid warnings spam + ObjectGuid guid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_UPDATE_EVENT [%s]", guid.GetString().c_str()); - // recv_data >> uint64 - // recv_data >> uint64 - // recv_data >> std::string - // recv_data >> std::string - // recv_data >> uint8 - // recv_data >> uint8 - // recv_data >> uint32 - // recv_data >> uint32 - // recv_data >> uint32 - // recv_data >> uint32 - // recv_data >> uint32 + 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: CMSG_CALENDAR_REMOVE_EVENT"); - recv_data.hexlike(); - recv_data.rpos(recv_data.wpos()); // set to end to avoid warnings spam + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_REMOVE_EVENT [%s]", _player->GetGuidStr().c_str()); - // recv_data >> uint64 - // recv_data >> uint64 - // recv_data >> uint32 + 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) { - DEBUG_LOG("WORLD: CMSG_CALENDAR_COPY_EVENT"); - recv_data.hexlike(); - recv_data.rpos(recv_data.wpos()); // set to end to avoid warnings spam + ObjectGuid guid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_COPY_EVENT [%s]", guid.GetString().c_str()); - // recv_data >> uint64 - // recv_data >> uint64 - // recv_data >> uint32 + 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) { - DEBUG_LOG("WORLD: CMSG_CALENDAR_EVENT_INVITE"); - recv_data.hexlike(); - recv_data.rpos(recv_data.wpos()); // set to end to avoid warnings spam + ObjectGuid playerGuid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_EVENT_INVITE [%s]", playerGuid.GetString().c_str()); - // recv_data >> uint64 - // recv_data >> uint64 - // recv_data >> std::string - // recv_data >> uint8 - // recv_data >> uint8 + 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) { - DEBUG_LOG("WORLD: CMSG_CALENDAR_EVENT_RSVP"); - recv_data.hexlike(); - recv_data.rpos(recv_data.wpos()); // set to end to avoid warnings spam + ObjectGuid guid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_EVENT_RSVP [%s]", guid.GetString().c_str()); - // recv_data >> uint64 - // recv_data >> uint64 - // recv_data >> uint32 + 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) { - DEBUG_LOG("WORLD: CMSG_CALENDAR_EVENT_REMOVE_INVITE"); - recv_data.hexlike(); - recv_data.rpos(recv_data.wpos()); // set to end to avoid warnings spam + ObjectGuid guid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_EVENT_REMOVE_INVITE [%s]", guid.GetString().c_str()); - // recv_data.readPackGUID(guid) - // recv_data >> uint64 - // recv_data >> uint64 - // recv_data >> uint64 + 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) { - DEBUG_LOG("WORLD: CMSG_CALENDAR_EVENT_STATUS"); + ObjectGuid updaterGuid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_EVENT_STATUS [%s]", updaterGuid.GetString().c_str()); recv_data.hexlike(); - recv_data.rpos(recv_data.wpos()); // set to end to avoid warnings spam - // recv_data.readPackGUID(guid) - // recv_data >> uint64 - // recv_data >> uint64 - // recv_data >> uint64 - // recv_data >> uint32 + 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) { - DEBUG_LOG("WORLD: CMSG_CALENDAR_EVENT_MODERATOR_STATUS"); - recv_data.hexlike(); - recv_data.rpos(recv_data.wpos()); // set to end to avoid warnings spam + ObjectGuid guid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_EVENT_MODERATOR_STATUS [%s]", guid.GetString().c_str()); - // recv_data.readPackGUID(guid) - // recv_data >> uint64 - // recv_data >> uint64 - // recv_data >> uint64 - // recv_data >> uint32 + 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) { - DEBUG_LOG("WORLD: CMSG_CALENDAR_COMPLAIN"); - recv_data.hexlike(); - recv_data.rpos(recv_data.wpos()); // set to end to avoid warnings spam + ObjectGuid guid = _player->GetObjectGuid(); + DEBUG_LOG("WORLD: Received opcode CMSG_CALENDAR_COMPLAIN [%s]", guid.GetString().c_str()); - // recv_data >> uint64 - // recv_data >> uint64 - // recv_data >> uint64 + 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*/) { - DEBUG_LOG("WORLD: CMSG_CALENDAR_GET_NUM_PENDING"); // empty + 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(0); // 0 - no pending invites, 1 - some pending invites + 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); +} diff --git a/src/game/CreatureEventAI.cpp b/src/game/CreatureEventAI.cpp index b1b810747..ca883e200 100644 --- a/src/game/CreatureEventAI.cpp +++ b/src/game/CreatureEventAI.cpp @@ -39,7 +39,7 @@ bool CreatureEventAIHolder::UpdateRepeatTimer(Creature* creature, uint32 repeatM Time = urand(repeatMin, repeatMax); else { - sLog.outErrorDb("CreatureEventAI: Creature %u using Event %u (Type = %u) has RandomMax < RandomMin. Event repeating disabled.", creature->GetEntry(), Event.event_id, Event.event_type); + sLog.outErrorEventAI("Creature %u using Event %u (Type = %u) has RandomMax < RandomMin. Event repeating disabled.", creature->GetEntry(), Event.event_id, Event.event_type); Enabled = false; return false; } @@ -57,10 +57,17 @@ int CreatureEventAI::Permissible(const Creature* creature) void CreatureEventAI::GetAIInformation(ChatHandler& reader) { reader.PSendSysMessage(LANG_NPC_EVENTAI_PHASE, (uint32)m_Phase); - reader.PSendSysMessage(LANG_NPC_EVENTAI_MOVE, reader.GetOnOffStr(m_CombatMovementEnabled)); + reader.PSendSysMessage(LANG_NPC_EVENTAI_MOVE, reader.GetOnOffStr(m_isCombatMovement)); reader.PSendSysMessage(LANG_NPC_EVENTAI_COMBAT, reader.GetOnOffStr(m_MeleeEnabled)); } +// For Non Dungeon map only allow non-difficulty flags or EFLAG_DIFFICULTY_0 mode +inline bool IsEventFlagsFitForNormalMap(uint8 eFlags) +{ + return !(eFlags & (EFLAG_DIFFICULTY_0 | EFLAG_DIFFICULTY_1 | EFLAG_DIFFICULTY_2 | EFLAG_DIFFICULTY_3)) || + (eFlags & EFLAG_DIFFICULTY_0); +} + CreatureEventAI::CreatureEventAI(Creature* c) : CreatureAI(c) { // Need make copy for filter unneeded steps and safe in case table reload @@ -68,21 +75,21 @@ CreatureEventAI::CreatureEventAI(Creature* c) : CreatureAI(c) if (creatureEventsItr != sEventAIMgr.GetCreatureEventAIMap().end()) { uint32 events_count = 0; - for (CreatureEventAI_Event_Vec::const_iterator i = (*creatureEventsItr).second.begin(); i != (*creatureEventsItr).second.end(); ++i) + for (CreatureEventAI_Event_Vec::const_iterator i = creatureEventsItr->second.begin(); i != creatureEventsItr->second.end(); ++i) { // Debug check #ifndef MANGOS_DEBUG - if ((*i).event_flags & EFLAG_DEBUG_ONLY) + if (i->event_flags & EFLAG_DEBUG_ONLY) continue; #endif if (m_creature->GetMap()->IsDungeon()) { - if ((1 << (m_creature->GetMap()->GetSpawnMode() + 1)) & (*i).event_flags) + if ((1 << (m_creature->GetMap()->GetSpawnMode() + 1)) & i->event_flags) { ++events_count; } } - else + else if (IsEventFlagsFitForNormalMap(i->event_flags)) ++events_count; } // EventMap had events but they were not added because they must be for instance @@ -91,47 +98,65 @@ CreatureEventAI::CreatureEventAI(Creature* c) : CreatureAI(c) else { m_CreatureEventAIList.reserve(events_count); - for (CreatureEventAI_Event_Vec::const_iterator i = (*creatureEventsItr).second.begin(); i != (*creatureEventsItr).second.end(); ++i) + for (CreatureEventAI_Event_Vec::const_iterator i = creatureEventsItr->second.begin(); i != creatureEventsItr->second.end(); ++i) { // Debug check #ifndef MANGOS_DEBUG - if ((*i).event_flags & EFLAG_DEBUG_ONLY) + if (i->event_flags & EFLAG_DEBUG_ONLY) continue; #endif if (m_creature->GetMap()->IsDungeon()) { - if ((1 << (m_creature->GetMap()->GetSpawnMode() + 1)) & (*i).event_flags) + if ((1 << (m_creature->GetMap()->GetSpawnMode() + 1)) & i->event_flags) { // event flagged for instance mode m_CreatureEventAIList.push_back(CreatureEventAIHolder(*i)); } } - else + else if (IsEventFlagsFitForNormalMap(i->event_flags)) m_CreatureEventAIList.push_back(CreatureEventAIHolder(*i)); } } } else - sLog.outError("CreatureEventAI: EventMap for Creature %u is empty but creature is using CreatureEventAI.", m_creature->GetEntry()); + sLog.outErrorEventAI("EventMap for Creature %u is empty but creature is using CreatureEventAI.", m_creature->GetEntry()); m_bEmptyList = m_CreatureEventAIList.empty(); m_Phase = 0; - m_CombatMovementEnabled = true; m_MeleeEnabled = true; - m_AttackDistance = 0.0f; - m_AttackAngle = 0.0f; m_InvinceabilityHpLevel = 0; - // Handle Spawned Events - if (!m_bEmptyList) + // Handle Spawned Events, also calls Reset() + JustRespawned(); +} + +#define LOG_PROCESS_EVENT \ + DEBUG_FILTER_LOG(LOG_FILTER_EVENT_AI_DEV, "CreatureEventAI: Event type %u (script %u) triggered for %s (invoked by %s)", \ + pHolder.Event.event_type, pHolder.Event.event_id, m_creature->GetGuidStr().c_str(), pActionInvoker ? pActionInvoker->GetGuidStr().c_str() : "") + +inline bool IsTimerBasedEvent(EventAI_Type type) +{ + switch (type) { - for (CreatureEventAIList::iterator i = m_CreatureEventAIList.begin(); i != m_CreatureEventAIList.end(); ++i) - if (SpawnedEventConditionsCheck((*i).Event)) - ProcessEvent(*i); + case EVENT_T_TIMER_IN_COMBAT: + case EVENT_T_TIMER_OOC: + case EVENT_T_TIMER_GENERIC: + case EVENT_T_MANA: + case EVENT_T_HP: + case EVENT_T_TARGET_HP: + case EVENT_T_TARGET_CASTING: + case EVENT_T_FRIENDLY_HP: + case EVENT_T_AURA: + case EVENT_T_TARGET_AURA: + case EVENT_T_MISSING_AURA: + case EVENT_T_TARGET_MISSING_AURA: + case EVENT_T_RANGE: + return true; + default: + return false; } - Reset(); } bool CreatureEventAI::ProcessEvent(CreatureEventAIHolder& pHolder, Unit* pActionInvoker) @@ -141,17 +166,25 @@ bool CreatureEventAI::ProcessEvent(CreatureEventAIHolder& pHolder, Unit* pAction // Check the inverse phase mask (event doesn't trigger if current phase bit is set in mask) if (pHolder.Event.event_inverse_phase_mask & (1 << m_Phase)) + { + if (!IsTimerBasedEvent(pHolder.Event.event_type)) + DEBUG_FILTER_LOG(LOG_FILTER_EVENT_AI_DEV, "CreatureEventAI: Event %u skipped because of phasemask %u. Current phase %u", pHolder.Event.event_id, pHolder.Event.event_inverse_phase_mask, m_Phase); return false; + } + + if (!IsTimerBasedEvent(pHolder.Event.event_type)) + LOG_PROCESS_EVENT; CreatureEventAI_Event const& event = pHolder.Event; // Check event conditions based on the event type, also reset events switch (event.event_type) { - case EVENT_T_TIMER: + case EVENT_T_TIMER_IN_COMBAT: if (!m_creature->isInCombat()) return false; + LOG_PROCESS_EVENT; // Repeat Timers pHolder.UpdateRepeatTimer(m_creature, event.timer.repeatMin, event.timer.repeatMax); break; @@ -159,6 +192,12 @@ bool CreatureEventAI::ProcessEvent(CreatureEventAIHolder& pHolder, Unit* pAction if (m_creature->isInCombat() || m_creature->IsInEvadeMode()) return false; + LOG_PROCESS_EVENT; + // Repeat Timers + pHolder.UpdateRepeatTimer(m_creature, event.timer.repeatMin, event.timer.repeatMax); + break; + case EVENT_T_TIMER_GENERIC: + LOG_PROCESS_EVENT; // Repeat Timers pHolder.UpdateRepeatTimer(m_creature, event.timer.repeatMin, event.timer.repeatMax); break; @@ -172,6 +211,7 @@ bool CreatureEventAI::ProcessEvent(CreatureEventAIHolder& pHolder, Unit* pAction if (perc > event.percent_range.percentMax || perc < event.percent_range.percentMin) return false; + LOG_PROCESS_EVENT; // Repeat Timers pHolder.UpdateRepeatTimer(m_creature, event.percent_range.repeatMin, event.percent_range.repeatMax); break; @@ -186,6 +226,7 @@ bool CreatureEventAI::ProcessEvent(CreatureEventAIHolder& pHolder, Unit* pAction if (perc > event.percent_range.percentMax || perc < event.percent_range.percentMin) return false; + LOG_PROCESS_EVENT; // Repeat Timers pHolder.UpdateRepeatTimer(m_creature, event.percent_range.repeatMin, event.percent_range.repeatMax); break; @@ -225,6 +266,7 @@ bool CreatureEventAI::ProcessEvent(CreatureEventAIHolder& pHolder, Unit* pAction if (perc > event.percent_range.percentMax || perc < event.percent_range.percentMin) return false; + LOG_PROCESS_EVENT; // Repeat Timers pHolder.UpdateRepeatTimer(m_creature, event.percent_range.repeatMin, event.percent_range.repeatMax); break; @@ -233,6 +275,7 @@ bool CreatureEventAI::ProcessEvent(CreatureEventAIHolder& pHolder, Unit* pAction if (!m_creature->isInCombat() || !m_creature->getVictim() || !m_creature->getVictim()->IsNonMeleeSpellCasted(false, false, true)) return false; + LOG_PROCESS_EVENT; // Repeat Timers pHolder.UpdateRepeatTimer(m_creature, event.target_casting.repeatMin, event.target_casting.repeatMax); break; @@ -247,6 +290,7 @@ bool CreatureEventAI::ProcessEvent(CreatureEventAIHolder& pHolder, Unit* pAction pActionInvoker = pUnit; + LOG_PROCESS_EVENT; // Repeat Timers pHolder.UpdateRepeatTimer(m_creature, event.friendly_hp.repeatMin, event.friendly_hp.repeatMax); break; @@ -325,6 +369,7 @@ bool CreatureEventAI::ProcessEvent(CreatureEventAIHolder& pHolder, Unit* pAction if (!holder || holder->GetStackAmount() < event.buffed.amount) return false; + LOG_PROCESS_EVENT; // Repeat Timers pHolder.UpdateRepeatTimer(m_creature, event.buffed.repeatMin, event.buffed.repeatMax); break; @@ -338,6 +383,7 @@ bool CreatureEventAI::ProcessEvent(CreatureEventAIHolder& pHolder, Unit* pAction if (!holder || holder->GetStackAmount() < event.buffed.amount) return false; + LOG_PROCESS_EVENT; // Repeat Timers pHolder.UpdateRepeatTimer(m_creature, event.buffed.repeatMin, event.buffed.repeatMax); break; @@ -348,6 +394,7 @@ bool CreatureEventAI::ProcessEvent(CreatureEventAIHolder& pHolder, Unit* pAction if (holder && holder->GetStackAmount() >= event.buffed.amount) return false; + LOG_PROCESS_EVENT; // Repeat Timers pHolder.UpdateRepeatTimer(m_creature, event.buffed.repeatMin, event.buffed.repeatMax); break; @@ -361,12 +408,13 @@ bool CreatureEventAI::ProcessEvent(CreatureEventAIHolder& pHolder, Unit* pAction if (holder && holder->GetStackAmount() >= event.buffed.amount) return false; + LOG_PROCESS_EVENT; // Repeat Timers pHolder.UpdateRepeatTimer(m_creature, event.buffed.repeatMin, event.buffed.repeatMax); break; } default: - sLog.outErrorDb("CreatureEventAI: Creature %u using Event %u has invalid Event Type(%u), missing from ProcessEvent() Switch.", m_creature->GetEntry(), pHolder.Event.event_id, pHolder.Event.event_type); + sLog.outErrorEventAI("Creature %u using Event %u has invalid Event Type(%u), missing from ProcessEvent() Switch.", m_creature->GetEntry(), pHolder.Event.event_id, pHolder.Event.event_type); break; } @@ -421,6 +469,12 @@ bool CreatureEventAI::ProcessEvent(CreatureEventAIHolder& pHolder, Unit* pAction void CreatureEventAI::ProcessAction(CreatureEventAI_Action const& action, uint32 rnd, uint32 EventId, Unit* pActionInvoker) { + if (action.type == ACTION_T_NONE) + return; + + DEBUG_FILTER_LOG(LOG_FILTER_EVENT_AI_DEV, "CreatureEventAI: Process action %u (script %u) triggered for %s (invoked by %s)", + action.type, EventId, m_creature->GetGuidStr().c_str(), pActionInvoker ? pActionInvoker->GetGuidStr().c_str() : ""); + switch (action.type) { case ACTION_T_TEXT: @@ -527,8 +581,15 @@ void CreatureEventAI::ProcessAction(CreatureEventAI_Action const& action, uint32 } case ACTION_T_CAST: { - Unit* target = GetTargetByType(action.cast.target, pActionInvoker); + uint32 selectFlags = 0; + uint32 spellId = 0; + if (!(action.cast.castFlags & (CAST_TRIGGERED | CAST_FORCE_CAST | CAST_FORCE_TARGET_SELF))) + { + spellId = action.cast.spellId; + selectFlags = SELECT_FLAG_IN_LOS; + } + Unit* target = GetTargetByType(action.cast.target, pActionInvoker, spellId, selectFlags); if (!target) { sLog.outDebug("CreatureEventAI: NULL target for ACTION_T_CAST creature entry %u casting spell id %u", m_creature->GetEntry(), action.cast.spellId); @@ -549,11 +610,11 @@ void CreatureEventAI::ProcessAction(CreatureEventAI_Action const& action, uint32 { case CHASE_MOTION_TYPE: case FOLLOW_MOTION_TYPE: - m_AttackDistance = 0.0f; - m_AttackAngle = 0.0f; + m_attackDistance = 0.0f; + m_attackAngle = 0.0f; m_creature->GetMotionMaster()->Clear(false); - m_creature->GetMotionMaster()->MoveChase(m_creature->getVictim(), m_AttackDistance, m_AttackAngle); + m_creature->GetMotionMaster()->MoveChase(m_creature->getVictim(), m_attackDistance, m_attackAngle); break; default: break; @@ -574,12 +635,12 @@ void CreatureEventAI::ProcessAction(CreatureEventAI_Action const& action, uint32 Creature* pCreature = NULL; if (action.summon.duration) - pCreature = m_creature->SummonCreature(action.summon.creatureId, 0.0f, 0.0f, 0.0f, 0.0f, TEMPSUMMON_TIMED_OR_DEAD_DESPAWN, action.summon.duration); + pCreature = m_creature->SummonCreature(action.summon.creatureId, 0.0f, 0.0f, 0.0f, 0.0f, TEMPSUMMON_TIMED_OOC_OR_DEAD_DESPAWN, action.summon.duration); else - pCreature = m_creature->SummonCreature(action.summon.creatureId, 0.0f, 0.0f, 0.0f, 0.0f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 0); + pCreature = m_creature->SummonCreature(action.summon.creatureId, 0.0f, 0.0f, 0.0f, 0.0f, TEMPSUMMON_TIMED_OOC_DESPAWN, 0); if (!pCreature) - sLog.outErrorDb("CreatureEventAI: failed to spawn creature %u. Spawn event %d is on creature %d", action.summon.creatureId, EventId, m_creature->GetEntry()); + sLog.outErrorEventAI("failed to spawn creature %u. Spawn event %d is on creature %d", action.summon.creatureId, EventId, m_creature->GetEntry()); else if (action.summon.target != TARGET_T_SELF && target) pCreature->AI()->AttackStart(target); break; @@ -602,7 +663,7 @@ void CreatureEventAI::ProcessAction(CreatureEventAI_Action const& action, uint32 ((Player*)target)->AreaExploredOrEventHappens(action.quest_event.questId); break; case ACTION_T_CAST_EVENT: - if (Unit* target = GetTargetByType(action.cast_event.target, pActionInvoker)) + if (Unit* target = GetTargetByType(action.cast_event.target, pActionInvoker, 0, SELECT_FLAG_PLAYER)) if (target->GetTypeId() == TYPEID_PLAYER) ((Player*)target)->CastedCreatureOrGO(action.cast_event.creatureId, m_creature->GetObjectGuid(), action.cast_event.spellId); break; @@ -632,57 +693,37 @@ void CreatureEventAI::ProcessAction(CreatureEventAI_Action const& action, uint32 break; case ACTION_T_COMBAT_MOVEMENT: // ignore no affect case - if (m_CombatMovementEnabled == (action.combat_movement.state != 0)) + if (m_isCombatMovement == (action.combat_movement.state != 0)) return; - m_CombatMovementEnabled = action.combat_movement.state != 0; + SetCombatMovement(action.combat_movement.state != 0, true); - // Allow movement (create new targeted movement gen only if idle) - if (m_CombatMovementEnabled) - { - if (action.combat_movement.melee && m_creature->isInCombat()) - if (Unit* victim = m_creature->getVictim()) - m_creature->SendMeleeAttackStart(victim); - - if (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == IDLE_MOTION_TYPE) - { - m_creature->GetMotionMaster()->Clear(false); - m_creature->GetMotionMaster()->MoveChase(m_creature->getVictim(), m_AttackDistance, m_AttackAngle); - } - } - else - { - if (action.combat_movement.melee && m_creature->isInCombat()) - if (Unit* victim = m_creature->getVictim()) - m_creature->SendMeleeAttackStop(victim); - - if (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE) - { - m_creature->GetMotionMaster()->Clear(false); - m_creature->GetMotionMaster()->MoveIdle(); - m_creature->StopMoving(); - } - } + if (m_isCombatMovement && action.combat_movement.melee && m_creature->isInCombat() && m_creature->getVictim()) + m_creature->SendMeleeAttackStart(m_creature->getVictim()); + else if (action.combat_movement.melee && m_creature->isInCombat() && m_creature->getVictim()) + m_creature->SendMeleeAttackStop(m_creature->getVictim()); break; case ACTION_T_SET_PHASE: m_Phase = action.set_phase.phase; + DEBUG_FILTER_LOG(LOG_FILTER_EVENT_AI_DEV, "CreatureEventAI: ACTION_T_SET_PHASE - script %u for %s, phase is now %u", EventId, m_creature->GetGuidStr().c_str(), m_Phase); break; case ACTION_T_INC_PHASE: { int32 new_phase = int32(m_Phase) + action.set_inc_phase.step; if (new_phase < 0) { - sLog.outErrorDb("CreatureEventAI: Event %d decrease Phase under 0. CreatureEntry = %d", EventId, m_creature->GetEntry()); + sLog.outErrorEventAI("Event %d decrease Phase under 0. CreatureEntry = %d", EventId, m_creature->GetEntry()); m_Phase = 0; } else if (new_phase >= MAX_PHASE) { - sLog.outErrorDb("CreatureEventAI: Event %d incremented Phase above %u. Phase mask cannot be used with phases past %u. CreatureEntry = %d", EventId, MAX_PHASE - 1, MAX_PHASE - 1, m_creature->GetEntry()); + sLog.outErrorEventAI("Event %d incremented Phase above %u. Phase mask cannot be used with phases past %u. CreatureEntry = %d", EventId, MAX_PHASE - 1, MAX_PHASE - 1, m_creature->GetEntry()); m_Phase = MAX_PHASE - 1; } else m_Phase = new_phase; + DEBUG_FILTER_LOG(LOG_FILTER_EVENT_AI_DEV, "CreatureEventAI: ACTION_T_INC_PHASE - script %u for %s, phase is now %u", EventId, m_creature->GetGuidStr().c_str(), m_Phase); break; } case ACTION_T_EVADE: @@ -708,27 +749,28 @@ void CreatureEventAI::ProcessAction(CreatureEventAI_Action const& action, uint32 target->RemoveAurasDueToSpell(action.remove_aura.spellId); break; case ACTION_T_RANGED_MOVEMENT: - m_AttackDistance = (float)action.ranged_movement.distance; - m_AttackAngle = action.ranged_movement.angle / 180.0f * M_PI_F; + m_attackDistance = (float)action.ranged_movement.distance; + m_attackAngle = action.ranged_movement.angle / 180.0f * M_PI_F; - if (m_CombatMovementEnabled) + if (m_isCombatMovement) { if (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE) { // Drop current movement gen m_creature->GetMotionMaster()->Clear(false); - m_creature->GetMotionMaster()->MoveChase(m_creature->getVictim(), m_AttackDistance, m_AttackAngle); + m_creature->GetMotionMaster()->MoveChase(m_creature->getVictim(), m_attackDistance, m_attackAngle); } } break; case ACTION_T_RANDOM_PHASE: m_Phase = GetRandActionParam(rnd, action.random_phase.phase1, action.random_phase.phase2, action.random_phase.phase3); + DEBUG_FILTER_LOG(LOG_FILTER_EVENT_AI_DEV, "CreatureEventAI: ACTION_T_RANDOM_PHASE - script %u for %s, phase is now %u", EventId, m_creature->GetGuidStr().c_str(), m_Phase); break; case ACTION_T_RANDOM_PHASE_RANGE: if (action.random_phase_range.phaseMax > action.random_phase_range.phaseMin) m_Phase = action.random_phase_range.phaseMin + (rnd % (action.random_phase_range.phaseMax - action.random_phase_range.phaseMin)); else - sLog.outErrorDb("CreatureEventAI: ACTION_T_RANDOM_PHASE_RANGE cannot have Param2 <= Param1. Divide by Zero. Event = %d. CreatureEntry = %d", EventId, m_creature->GetEntry()); + sLog.outErrorEventAI("ACTION_T_RANDOM_PHASE_RANGE cannot have Param2 <= Param1. Divide by Zero. Event = %d. CreatureEntry = %d", EventId, m_creature->GetEntry()); break; case ACTION_T_SUMMON_ID: { @@ -737,18 +779,18 @@ void CreatureEventAI::ProcessAction(CreatureEventAI_Action const& action, uint32 CreatureEventAI_Summon_Map::const_iterator i = sEventAIMgr.GetCreatureEventAISummonMap().find(action.summon_id.spawnId); if (i == sEventAIMgr.GetCreatureEventAISummonMap().end()) { - sLog.outErrorDb("CreatureEventAI: failed to spawn creature %u. Summon map index %u does not exist. EventID %d. CreatureID %d", action.summon_id.creatureId, action.summon_id.spawnId, EventId, m_creature->GetEntry()); + sLog.outErrorEventAI("failed to spawn creature %u. Summon map index %u does not exist. EventID %d. CreatureID %d", action.summon_id.creatureId, action.summon_id.spawnId, EventId, m_creature->GetEntry()); return; } Creature* pCreature = NULL; if ((*i).second.SpawnTimeSecs) - pCreature = m_creature->SummonCreature(action.summon_id.creatureId, (*i).second.position_x, (*i).second.position_y, (*i).second.position_z, (*i).second.orientation, TEMPSUMMON_TIMED_OR_DEAD_DESPAWN, (*i).second.SpawnTimeSecs); + pCreature = m_creature->SummonCreature(action.summon_id.creatureId, (*i).second.position_x, (*i).second.position_y, (*i).second.position_z, (*i).second.orientation, TEMPSUMMON_TIMED_OOC_OR_DEAD_DESPAWN, (*i).second.SpawnTimeSecs); else - pCreature = m_creature->SummonCreature(action.summon_id.creatureId, (*i).second.position_x, (*i).second.position_y, (*i).second.position_z, (*i).second.orientation, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 0); + pCreature = m_creature->SummonCreature(action.summon_id.creatureId, (*i).second.position_x, (*i).second.position_y, (*i).second.position_z, (*i).second.orientation, TEMPSUMMON_TIMED_OOC_DESPAWN, 0); if (!pCreature) - sLog.outErrorDb("CreatureEventAI: failed to spawn creature %u. EventId %d.Creature %d", action.summon_id.creatureId, EventId, m_creature->GetEntry()); + sLog.outErrorEventAI("failed to spawn creature %u. EventId %d.Creature %d", action.summon_id.creatureId, EventId, m_creature->GetEntry()); else if (action.summon_id.target != TARGET_T_SELF && target) pCreature->AI()->AttackStart(target); @@ -761,7 +803,7 @@ void CreatureEventAI::ProcessAction(CreatureEventAI_Action const& action, uint32 else { // if not available, use pActionInvoker - if (Unit* pTarget = GetTargetByType(action.killed_monster.target, pActionInvoker)) + if (Unit* pTarget = GetTargetByType(action.killed_monster.target, pActionInvoker, 0, SELECT_FLAG_PLAYER)) if (Player* pPlayer2 = pTarget->GetCharmerOrOwnerPlayerOrPlayerItself()) pPlayer2->RewardPlayerAndGroupAtEvent(action.killed_monster.creatureId, m_creature); } @@ -771,7 +813,7 @@ void CreatureEventAI::ProcessAction(CreatureEventAI_Action const& action, uint32 InstanceData* pInst = m_creature->GetInstanceData(); if (!pInst) { - sLog.outErrorDb("CreatureEventAI: Event %d attempt to set instance data without instance script. Creature %d", EventId, m_creature->GetEntry()); + sLog.outErrorEventAI("Event %d attempt to set instance data without instance script. Creature %d", EventId, m_creature->GetEntry()); return; } @@ -783,14 +825,14 @@ void CreatureEventAI::ProcessAction(CreatureEventAI_Action const& action, uint32 Unit* target = GetTargetByType(action.set_inst_data64.target, pActionInvoker); if (!target) { - sLog.outErrorDb("CreatureEventAI: Event %d attempt to set instance data64 but Target == NULL. Creature %d", EventId, m_creature->GetEntry()); + sLog.outErrorEventAI("Event %d attempt to set instance data64 but Target == NULL. Creature %d", EventId, m_creature->GetEntry()); return; } InstanceData* pInst = m_creature->GetInstanceData(); if (!pInst) { - sLog.outErrorDb("CreatureEventAI: Event %d attempt to set instance data64 without instance script. Creature %d", EventId, m_creature->GetEntry()); + sLog.outErrorEventAI("Event %d attempt to set instance data64 without instance script. Creature %d", EventId, m_creature->GetEntry()); return; } @@ -801,7 +843,7 @@ void CreatureEventAI::ProcessAction(CreatureEventAI_Action const& action, uint32 if (m_creature->GetEntry() == action.update_template.creatureId) { - sLog.outErrorDb("CreatureEventAI: Event %d ACTION_T_UPDATE_TEMPLATE call with param1 == current entry. Creature %d", EventId, m_creature->GetEntry()); + sLog.outErrorEventAI("Event %d ACTION_T_UPDATE_TEMPLATE call with param1 == current entry. Creature %d", EventId, m_creature->GetEntry()); return; } @@ -811,7 +853,7 @@ void CreatureEventAI::ProcessAction(CreatureEventAI_Action const& action, uint32 if (m_creature->isDead()) { - sLog.outErrorDb("CreatureEventAI: Event %d ACTION_T_DIE on dead creature. Creature %d", EventId, m_creature->GetEntry()); + sLog.outErrorEventAI("Event %d ACTION_T_DIE on dead creature. Creature %d", EventId, m_creature->GetEntry()); return; } m_creature->DealDamage(m_creature, m_creature->GetMaxHealth(), NULL, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, NULL, false); @@ -869,17 +911,25 @@ void CreatureEventAI::ProcessAction(CreatureEventAI_Action const& action, uint32 } } -void CreatureEventAI::JustRespawned() +void CreatureEventAI::JustRespawned() // NOTE that this is called from the AI's constructor as well { Reset(); if (m_bEmptyList) return; - // Handle Spawned Events for (CreatureEventAIList::iterator i = m_CreatureEventAIList.begin(); i != m_CreatureEventAIList.end(); ++i) - if (SpawnedEventConditionsCheck((*i).Event)) + { + // Reset generic timer + if (i->Event.event_type == EVENT_T_TIMER_GENERIC) + { + if (i->UpdateRepeatTimer(m_creature, i->Event.timer.initialMin, i->Event.timer.initialMax)) + i->Enabled = true; + } + // Handle Spawned Events + else if (SpawnedEventConditionsCheck((*i).Event)) ProcessEvent(*i); + } } void CreatureEventAI::Reset() @@ -928,7 +978,7 @@ void CreatureEventAI::JustReachedHome() void CreatureEventAI::EnterEvadeMode() { - m_creature->RemoveAllAuras(); + m_creature->RemoveAllAurasOnEvade(); m_creature->DeleteThreatList(); m_creature->CombatStop(true); @@ -1036,7 +1086,7 @@ void CreatureEventAI::EnterCombat(Unit* enemy) ProcessEvent(*i, enemy); break; // Reset all in combat timers - case EVENT_T_TIMER: + case EVENT_T_TIMER_IN_COMBAT: if ((*i).UpdateRepeatTimer(m_creature, event.timer.initialMin, event.timer.initialMax)) (*i).Enabled = true; break; @@ -1064,15 +1114,7 @@ void CreatureEventAI::AttackStart(Unit* who) m_creature->SetInCombatWith(who); who->SetInCombatWith(m_creature); - if (m_CombatMovementEnabled) - { - m_creature->GetMotionMaster()->MoveChase(who, m_AttackDistance, m_AttackAngle); - } - else - { - m_creature->GetMotionMaster()->MoveIdle(); - m_creature->StopMoving(); - } + HandleMovementOnAttackStart(who); } } @@ -1177,9 +1219,10 @@ void CreatureEventAI::UpdateAI(const uint32 diff) switch ((*i).Event.event_type) { case EVENT_T_TIMER_OOC: + case EVENT_T_TIMER_GENERIC: ProcessEvent(*i); break; - case EVENT_T_TIMER: + case EVENT_T_TIMER_IN_COMBAT: case EVENT_T_MANA: case EVENT_T_HP: case EVENT_T_TARGET_HP: @@ -1246,7 +1289,7 @@ inline int32 CreatureEventAI::GetRandActionParam(uint32 rnd, int32 param1, int32 return 0; } -inline Unit* CreatureEventAI::GetTargetByType(uint32 Target, Unit* pActionInvoker) +inline Unit* CreatureEventAI::GetTargetByType(uint32 Target, Unit* pActionInvoker, uint32 forSpellId, uint32 selectFlags) { switch (Target) { @@ -1255,15 +1298,21 @@ inline Unit* CreatureEventAI::GetTargetByType(uint32 Target, Unit* pActionInvoke case TARGET_T_HOSTILE: return m_creature->getVictim(); case TARGET_T_HOSTILE_SECOND_AGGRO: - return m_creature->SelectAttackingTarget(ATTACKING_TARGET_TOPAGGRO, 1); + return m_creature->SelectAttackingTarget(ATTACKING_TARGET_TOPAGGRO, 1, forSpellId, selectFlags); case TARGET_T_HOSTILE_LAST_AGGRO: - return m_creature->SelectAttackingTarget(ATTACKING_TARGET_BOTTOMAGGRO, 0); + return m_creature->SelectAttackingTarget(ATTACKING_TARGET_BOTTOMAGGRO, 0, forSpellId, selectFlags); case TARGET_T_HOSTILE_RANDOM: - return m_creature->SelectAttackingTarget(ATTACKING_TARGET_RANDOM, 0); + return m_creature->SelectAttackingTarget(ATTACKING_TARGET_RANDOM, 0, forSpellId, selectFlags); case TARGET_T_HOSTILE_RANDOM_NOT_TOP: - return m_creature->SelectAttackingTarget(ATTACKING_TARGET_RANDOM, 1); + return m_creature->SelectAttackingTarget(ATTACKING_TARGET_RANDOM, 1, forSpellId, selectFlags); + case TARGET_T_HOSTILE_RANDOM_PLAYER: + return m_creature->SelectAttackingTarget(ATTACKING_TARGET_RANDOM, 0, forSpellId, SELECT_FLAG_PLAYER | selectFlags); + case TARGET_T_HOSTILE_RANDOM_NOT_TOP_PLAYER: + return m_creature->SelectAttackingTarget(ATTACKING_TARGET_RANDOM, 1, forSpellId, SELECT_FLAG_PLAYER | selectFlags); case TARGET_T_ACTION_INVOKER: return pActionInvoker; + case TARGET_T_ACTION_INVOKER_OWNER: + return pActionInvoker ? pActionInvoker->GetCharmerOrOwnerOrSelf() : NULL; default: return NULL; }; @@ -1305,13 +1354,13 @@ void CreatureEventAI::DoScriptText(int32 textEntry, WorldObject* pSource, Unit* { if (!pSource) { - sLog.outErrorDb("CreatureEventAI: DoScriptText entry %i, invalid Source pointer.", textEntry); + sLog.outErrorEventAI("DoScriptText entry %i, invalid Source pointer.", textEntry); return; } if (textEntry >= 0) { - sLog.outErrorDb("CreatureEventAI: DoScriptText with source entry %u (TypeId=%u, guid=%u) attempts to process text entry %i, but text entry must be negative.", pSource->GetEntry(), pSource->GetTypeId(), pSource->GetGUIDLow(), textEntry); + sLog.outErrorEventAI("DoScriptText with source entry %u (TypeId=%u, guid=%u) attempts to process text entry %i, but text entry must be negative.", pSource->GetEntry(), pSource->GetTypeId(), pSource->GetGUIDLow(), textEntry); return; } @@ -1319,7 +1368,7 @@ void CreatureEventAI::DoScriptText(int32 textEntry, WorldObject* pSource, Unit* if (i == sEventAIMgr.GetCreatureEventAITextMap().end()) { - sLog.outErrorDb("CreatureEventAI: DoScriptText with source entry %u (TypeId=%u, guid=%u) could not find text entry %i.", pSource->GetEntry(), pSource->GetTypeId(), pSource->GetGUIDLow(), textEntry); + sLog.outErrorEventAI("DoScriptText with source entry %u (TypeId=%u, guid=%u) could not find text entry %i.", pSource->GetEntry(), pSource->GetTypeId(), pSource->GetGUIDLow(), textEntry); return; } @@ -1330,7 +1379,7 @@ void CreatureEventAI::DoScriptText(int32 textEntry, WorldObject* pSource, Unit* if (GetSoundEntriesStore()->LookupEntry((*i).second.SoundId)) pSource->PlayDirectSound((*i).second.SoundId); else - sLog.outErrorDb("CreatureEventAI: DoScriptText entry %i tried to process invalid sound id %u.", textEntry, (*i).second.SoundId); + sLog.outErrorEventAI("DoScriptText entry %i tried to process invalid sound id %u.", textEntry, (*i).second.SoundId); } if ((*i).second.Emote) @@ -1340,7 +1389,7 @@ void CreatureEventAI::DoScriptText(int32 textEntry, WorldObject* pSource, Unit* ((Unit*)pSource)->HandleEmote((*i).second.Emote); } else - sLog.outErrorDb("CreatureEventAI: DoScriptText entry %i tried to process emote for invalid TypeId (%u).", textEntry, pSource->GetTypeId()); + sLog.outErrorEventAI("DoScriptText entry %i tried to process emote for invalid TypeId (%u).", textEntry, pSource->GetTypeId()); } switch ((*i).second.Type) @@ -1361,13 +1410,13 @@ void CreatureEventAI::DoScriptText(int32 textEntry, WorldObject* pSource, Unit* { if (target && target->GetTypeId() == TYPEID_PLAYER) pSource->MonsterWhisper(textEntry, target); - else sLog.outErrorDb("CreatureEventAI: DoScriptText entry %i cannot whisper without target unit (TYPEID_PLAYER).", textEntry); + else sLog.outErrorEventAI("DoScriptText entry %i cannot whisper without target unit (TYPEID_PLAYER).", textEntry); } break; case CHAT_TYPE_BOSS_WHISPER: { if (target && target->GetTypeId() == TYPEID_PLAYER) pSource->MonsterWhisper(textEntry, target, true); - else sLog.outErrorDb("CreatureEventAI: DoScriptText entry %i cannot whisper without target unit (TYPEID_PLAYER).", textEntry); + else sLog.outErrorEventAI("DoScriptText entry %i cannot whisper without target unit (TYPEID_PLAYER).", textEntry); } break; case CHAT_TYPE_ZONE_YELL: pSource->MonsterYellToZone(textEntry, (*i).second.Language, target); @@ -1375,36 +1424,6 @@ void CreatureEventAI::DoScriptText(int32 textEntry, WorldObject* pSource, Unit* } } -bool CreatureEventAI::CanCast(Unit* Target, SpellEntry const* Spell, bool Triggered) -{ - // No target so we can't cast - if (!Target || !Spell) - return false; - - // Silenced so we can't cast - if (!Triggered && (m_creature->hasUnitState(UNIT_STAT_CAN_NOT_REACT_OR_LOST_CONTROL) || - m_creature->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED))) - return false; - - // Check for power - if (!Triggered && m_creature->GetPower((Powers)Spell->powerType) < Spell::CalculatePowerCost(Spell, m_creature)) - return false; - - SpellRangeEntry const* TempRange = NULL; - - TempRange = GetSpellRangeStore()->LookupEntry(Spell->rangeIndex); - - // Spell has invalid range store so we can't use it - if (!TempRange) - return false; - - // Unit is out of range of this spell - if (!m_creature->IsInRange(Target, TempRange->minRange, TempRange->maxRange)) - return false; - - return true; -} - void CreatureEventAI::ReceiveEmote(Player* pPlayer, uint32 text_emote) { if (m_bEmptyList) @@ -1418,7 +1437,7 @@ void CreatureEventAI::ReceiveEmote(Player* pPlayer, uint32 text_emote) return; PlayerCondition pcon(0, (*itr).Event.receive_emote.condition, (*itr).Event.receive_emote.conditionValue1, (*itr).Event.receive_emote.conditionValue2); - if (pcon.Meets(pPlayer)) + if (pcon.Meets(pPlayer, m_creature->GetMap(), m_creature, CONDITION_FROM_EVENTAI)) { DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "CreatureEventAI: ReceiveEmote CreatureEventAI: Condition ok, processing"); ProcessEvent(*itr, pPlayer); diff --git a/src/game/CreatureEventAIMgr.cpp b/src/game/CreatureEventAIMgr.cpp index 4027bc7fe..c7d97d603 100644 --- a/src/game/CreatureEventAIMgr.cpp +++ b/src/game/CreatureEventAIMgr.cpp @@ -23,9 +23,10 @@ #include "CreatureEventAIMgr.h" #include "ObjectMgr.h" #include "ProgressBar.h" -#include "Policies/SingletonImp.h" +#include "Policies/Singleton.h" #include "ObjectGuid.h" #include "GridDefines.h" +#include "SpellMgr.h" INSTANTIATE_SINGLETON_1(CreatureEventAIMgr); @@ -62,33 +63,33 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Texts(bool check_entry_use) // range negative if (i > MIN_CREATURE_AI_TEXT_STRING_ID || i <= MAX_CREATURE_AI_TEXT_STRING_ID) { - sLog.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` is not in valid range(%d-%d)", i, MIN_CREATURE_AI_TEXT_STRING_ID, MAX_CREATURE_AI_TEXT_STRING_ID); + sLog.outErrorEventAI("CreatureEventAI: Entry %i in table `creature_ai_texts` is not in valid range(%d-%d)", i, MIN_CREATURE_AI_TEXT_STRING_ID, MAX_CREATURE_AI_TEXT_STRING_ID); continue; } // range negative (don't must be happen, loaded from same table) if (!sObjectMgr.GetMangosStringLocale(i)) { - sLog.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` not found", i); + sLog.outErrorEventAI("Entry %i in table `creature_ai_texts` not found", i); continue; } if (temp.SoundId) { if (!sSoundEntriesStore.LookupEntry(temp.SoundId)) - sLog.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` has Sound %u but sound does not exist.", i, temp.SoundId); + sLog.outErrorEventAI("Entry %i in table `creature_ai_texts` has Sound %u but sound does not exist.", i, temp.SoundId); } if (!GetLanguageDescByID(temp.Language)) - sLog.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` using Language %u but Language does not exist.", i, temp.Language); + sLog.outErrorEventAI("Entry %i in table `creature_ai_texts` using Language %u but Language does not exist.", i, temp.Language); if (temp.Type > CHAT_TYPE_ZONE_YELL) - sLog.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` has Type %u but this Chat Type does not exist.", i, temp.Type); + sLog.outErrorEventAI("Entry %i in table `creature_ai_texts` has Type %u but this Chat Type does not exist.", i, temp.Type); if (temp.Emote) { if (!sEmotesStore.LookupEntry(temp.Emote)) - sLog.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` has Emote %u but emote does not exist.", i, temp.Emote); + sLog.outErrorEventAI("Entry %i in table `creature_ai_texts` has Emote %u but emote does not exist.", i, temp.Emote); } m_CreatureEventAI_TextMap[i] = temp; @@ -143,13 +144,12 @@ void CreatureEventAIMgr::CheckUnusedAITexts() } default: break; } - } } } for (std::set::const_iterator itr = idx_set.begin(); itr != idx_set.end(); ++itr) - sLog.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` but not used in EventAI scripts.", *itr); + sLog.outErrorEventAI("Entry %i in table `creature_ai_texts` but not used in EventAI scripts.", *itr); } // ------------------- @@ -182,7 +182,7 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Summons(bool check_entry_use) if (!MaNGOS::IsValidMapCoord(temp.position_x, temp.position_y, temp.position_z, temp.orientation)) { - sLog.outErrorDb("CreatureEventAI: Summon id %u have wrong coordinates (%f, %f, %f, %f), skipping.", temp.id, temp.position_x, temp.position_y, temp.position_z, temp.orientation); + sLog.outErrorEventAI("Summon id %u have wrong coordinates (%f, %f, %f, %f), skipping.", temp.id, temp.position_x, temp.position_y, temp.position_z, temp.orientation); continue; } @@ -235,13 +235,12 @@ void CreatureEventAIMgr::CheckUnusedAISummons() } default: break; } - } } } for (std::set::const_iterator itr = idx_set.begin(); itr != idx_set.end(); ++itr) - sLog.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_summons` but not used in EventAI scripts.", *itr); + sLog.outErrorEventAI("Entry %i in table `creature_ai_summons` but not used in EventAI scripts.", *itr); } // ------------------- @@ -278,7 +277,7 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Scripts() // Report any errors in event if (e_type >= EVENT_T_END) { - sLog.outErrorDb("CreatureEventAI: Event %u have wrong type (%u), skipping.", i, e_type); + sLog.outErrorEventAI("Event %u have wrong type (%u), skipping.", i, e_type); continue; } temp.event_type = EventAI_Type(e_type); @@ -294,43 +293,44 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Scripts() // Creature does not exist in database if (!sCreatureStorage.LookupEntry(temp.creature_id)) { - sLog.outErrorDb("CreatureEventAI: Event %u has script for non-existing creature entry (%u), skipping.", i, temp.creature_id); + sLog.outErrorEventAI("Event %u has script for non-existing creature entry (%u), skipping.", i, temp.creature_id); continue; } // No chance of this event occuring if (temp.event_chance == 0) - sLog.outErrorDb("CreatureEventAI: Event %u has 0 percent chance. Event will never trigger!", i); + sLog.outErrorEventAI("Event %u has 0 percent chance. Event will never trigger!", i); // Chance above 100, force it to be 100 else if (temp.event_chance > 100) { - sLog.outErrorDb("CreatureEventAI: Creature %u are using event %u with more than 100 percent chance. Adjusting to 100 percent.", temp.creature_id, i); + sLog.outErrorEventAI("Creature %u are using event %u with more than 100 percent chance. Adjusting to 100 percent.", temp.creature_id, i); temp.event_chance = 100; } // Individual event checks switch (temp.event_type) { - case EVENT_T_TIMER: + case EVENT_T_TIMER_IN_COMBAT: case EVENT_T_TIMER_OOC: + case EVENT_T_TIMER_GENERIC: if (temp.timer.initialMax < temp.timer.initialMin) - sLog.outErrorDb("CreatureEventAI: Creature %u are using timed event(%u) with param2 < param1 (InitialMax < InitialMin). Event will never repeat.", temp.creature_id, i); + sLog.outErrorEventAI("Creature %u are using timed event(%u) with param2 < param1 (InitialMax < InitialMin). Event will never repeat.", temp.creature_id, i); if (temp.timer.repeatMax < temp.timer.repeatMin) - sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); + sLog.outErrorEventAI("Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); break; case EVENT_T_HP: case EVENT_T_MANA: case EVENT_T_TARGET_HP: case EVENT_T_TARGET_MANA: if (temp.percent_range.percentMax > 100) - sLog.outErrorDb("CreatureEventAI: Creature %u are using percentage event(%u) with param2 (MinPercent) > 100. Event will never trigger! ", temp.creature_id, i); + sLog.outErrorEventAI("Creature %u are using percentage event(%u) with param2 (MinPercent) > 100. Event will never trigger! ", temp.creature_id, i); if (temp.percent_range.percentMax <= temp.percent_range.percentMin) - sLog.outErrorDb("CreatureEventAI: Creature %u are using percentage event(%u) with param1 <= param2 (MaxPercent <= MinPercent). Event will never trigger! ", temp.creature_id, i); + sLog.outErrorEventAI("Creature %u are using percentage event(%u) with param1 <= param2 (MaxPercent <= MinPercent). Event will never trigger! ", temp.creature_id, i); if (temp.event_flags & EFLAG_REPEATABLE && !temp.percent_range.repeatMin && !temp.percent_range.repeatMax) { - sLog.outErrorDb("CreatureEventAI: Creature %u has param3 and param4=0 (RepeatMin/RepeatMax) but cannot be repeatable without timers. Removing EFLAG_REPEATABLE for event %u.", temp.creature_id, i); + sLog.outErrorEventAI("Creature %u has param3 and param4=0 (RepeatMin/RepeatMax) but cannot be repeatable without timers. Removing EFLAG_REPEATABLE for event %u.", temp.creature_id, i); temp.event_flags &= ~EFLAG_REPEATABLE; } break; @@ -340,29 +340,29 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Scripts() SpellEntry const* pSpell = sSpellStore.LookupEntry(temp.spell_hit.spellId); if (!pSpell) { - sLog.outErrorDb("CreatureEventAI: Creature %u has nonexistent SpellID(%u) defined in event %u.", temp.creature_id, temp.spell_hit.spellId, i); + sLog.outErrorEventAI("Creature %u has nonexistent SpellID(%u) defined in event %u.", temp.creature_id, temp.spell_hit.spellId, i); continue; } if ((temp.spell_hit.schoolMask & pSpell->SchoolMask) != pSpell->SchoolMask) - sLog.outErrorDb("CreatureEventAI: Creature %u has param1(spellId %u) but param2 is not -1 and not equal to spell's school mask. Event %u can never trigger.", temp.creature_id, temp.spell_hit.schoolMask, i); + sLog.outErrorEventAI("Creature %u has param1(spellId %u) but param2 is not -1 and not equal to spell's school mask. Event %u can never trigger.", temp.creature_id, temp.spell_hit.schoolMask, i); } if (!temp.spell_hit.schoolMask) - sLog.outErrorDb("CreatureEventAI: Creature %u is using invalid SpellSchoolMask(%u) defined in event %u.", temp.creature_id, temp.spell_hit.schoolMask, i); + sLog.outErrorEventAI("Creature %u is using invalid SpellSchoolMask(%u) defined in event %u.", temp.creature_id, temp.spell_hit.schoolMask, i); if (temp.spell_hit.repeatMax < temp.spell_hit.repeatMin) - sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); + sLog.outErrorEventAI("Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); break; case EVENT_T_RANGE: if (temp.range.maxDist < temp.range.minDist) - sLog.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with param2 < param1 (MaxDist < MinDist). Event will never repeat.", temp.creature_id, i); + sLog.outErrorEventAI("Creature %u are using event(%u) with param2 < param1 (MaxDist < MinDist). Event will never repeat.", temp.creature_id, i); if (temp.range.repeatMax < temp.range.repeatMin) - sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); + sLog.outErrorEventAI("Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); break; case EVENT_T_OOC_LOS: if (temp.ooc_los.repeatMax < temp.ooc_los.repeatMin) - sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); + sLog.outErrorEventAI("Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); break; case EVENT_T_SPAWNED: switch (temp.spawned.condition) @@ -371,63 +371,63 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Scripts() break; case SPAWNED_EVENT_MAP: if (!sMapStore.LookupEntry(temp.spawned.conditionValue1)) - sLog.outErrorDb("CreatureEventAI: Creature %u are using spawned event(%u) with param1 = %u 'map specific' but map (param2: %u) does not exist. Event will never repeat.", temp.creature_id, i, temp.spawned.condition, temp.spawned.conditionValue1); + sLog.outErrorEventAI("Creature %u are using spawned event(%u) with param1 = %u 'map specific' but map (param2: %u) does not exist. Event will never repeat.", temp.creature_id, i, temp.spawned.condition, temp.spawned.conditionValue1); break; case SPAWNED_EVENT_ZONE: if (!GetAreaEntryByAreaID(temp.spawned.conditionValue1)) - sLog.outErrorDb("CreatureEventAI: Creature %u are using spawned event(%u) with param1 = %u 'area specific' but area (param2: %u) does not exist. Event will never repeat.", temp.creature_id, i, temp.spawned.condition, temp.spawned.conditionValue1); + sLog.outErrorEventAI("Creature %u are using spawned event(%u) with param1 = %u 'area specific' but area (param2: %u) does not exist. Event will never repeat.", temp.creature_id, i, temp.spawned.condition, temp.spawned.conditionValue1); break; default: - sLog.outErrorDb("CreatureEventAI: Creature %u are using invalid spawned event %u mode (%u) in param1", temp.creature_id, i, temp.spawned.condition); + sLog.outErrorEventAI("Creature %u are using invalid spawned event %u mode (%u) in param1", temp.creature_id, i, temp.spawned.condition); break; } break; case EVENT_T_FRIENDLY_HP: if (temp.friendly_hp.repeatMax < temp.friendly_hp.repeatMin) - sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); + sLog.outErrorEventAI("Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); break; case EVENT_T_FRIENDLY_IS_CC: if (temp.friendly_is_cc.repeatMax < temp.friendly_is_cc.repeatMin) - sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); + sLog.outErrorEventAI("Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); break; case EVENT_T_FRIENDLY_MISSING_BUFF: { SpellEntry const* pSpell = sSpellStore.LookupEntry(temp.friendly_buff.spellId); if (!pSpell) { - sLog.outErrorDb("CreatureEventAI: Creature %u has nonexistent SpellID(%u) defined in event %u.", temp.creature_id, temp.friendly_buff.spellId, i); + sLog.outErrorEventAI("Creature %u has nonexistent SpellID(%u) defined in event %u.", temp.creature_id, temp.friendly_buff.spellId, i); continue; } if (temp.friendly_buff.radius <= 0) { - sLog.outErrorDb("CreatureEventAI: Creature %u has wrong radius (%u) for EVENT_T_FRIENDLY_MISSING_BUFF defined in event %u.", temp.creature_id, temp.friendly_buff.radius, i); + sLog.outErrorEventAI("Creature %u has wrong radius (%u) for EVENT_T_FRIENDLY_MISSING_BUFF defined in event %u.", temp.creature_id, temp.friendly_buff.radius, i); continue; } if (temp.friendly_buff.repeatMax < temp.friendly_buff.repeatMin) - sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); + sLog.outErrorEventAI("Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); break; } case EVENT_T_KILL: if (temp.kill.repeatMax < temp.kill.repeatMin) - sLog.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with param2 < param1 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); + sLog.outErrorEventAI("Creature %u are using event(%u) with param2 < param1 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); break; case EVENT_T_TARGET_CASTING: if (temp.target_casting.repeatMax < temp.target_casting.repeatMin) - sLog.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with param2 < param1 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); + sLog.outErrorEventAI("Creature %u are using event(%u) with param2 < param1 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); break; case EVENT_T_SUMMONED_UNIT: case EVENT_T_SUMMONED_JUST_DIED: case EVENT_T_SUMMONED_JUST_DESPAWN: if (!sCreatureStorage.LookupEntry(temp.summoned.creatureId)) - sLog.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with nonexistent creature template id (%u) in param1, skipped.", temp.creature_id, i, temp.summoned.creatureId); + sLog.outErrorEventAI("Creature %u are using event(%u) with nonexistent creature template id (%u) in param1, skipped.", temp.creature_id, i, temp.summoned.creatureId); if (temp.summoned.repeatMax < temp.summoned.repeatMin) - sLog.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with param2 < param1 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); + sLog.outErrorEventAI("Creature %u are using event(%u) with param2 < param1 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); break; case EVENT_T_QUEST_ACCEPT: case EVENT_T_QUEST_COMPLETE: if (!sObjectMgr.GetQuestTemplate(temp.quest.questId)) - sLog.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with nonexistent quest id (%u) in param1, skipped.", temp.creature_id, i, temp.quest.questId); - sLog.outErrorDb("CreatureEventAI: Creature %u using not implemented event (%u) in event %u.", temp.creature_id, temp.event_id, i); + sLog.outErrorEventAI("Creature %u are using event(%u) with nonexistent quest id (%u) in param1, skipped.", temp.creature_id, i, temp.quest.questId); + sLog.outErrorEventAI("Creature %u using not implemented event (%u) in event %u.", temp.creature_id, temp.event_id, i); continue; case EVENT_T_AGGRO: @@ -437,7 +437,7 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Scripts() { if (temp.event_flags & EFLAG_REPEATABLE) { - sLog.outErrorDb("CreatureEventAI: Creature %u has EFLAG_REPEATABLE set. Event can never be repeatable. Removing flag for event %u.", temp.creature_id, i); + sLog.outErrorEventAI("Creature %u has EFLAG_REPEATABLE set. Event can never be repeatable. Removing flag for event %u.", temp.creature_id, i); temp.event_flags &= ~EFLAG_REPEATABLE; } @@ -448,19 +448,19 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Scripts() { if (!sEmotesTextStore.LookupEntry(temp.receive_emote.emoteId)) { - sLog.outErrorDb("CreatureEventAI: Creature %u using event %u: param1 (EmoteTextId: %u) are not valid.", temp.creature_id, i, temp.receive_emote.emoteId); + sLog.outErrorEventAI("Creature %u using event %u: param1 (EmoteTextId: %u) are not valid.", temp.creature_id, i, temp.receive_emote.emoteId); continue; } if (!PlayerCondition::IsValid(0, ConditionType(temp.receive_emote.condition), temp.receive_emote.conditionValue1, temp.receive_emote.conditionValue2)) { - sLog.outErrorDb("CreatureEventAI: Creature %u using event %u: param2 (Condition: %u) are not valid.", temp.creature_id, i, temp.receive_emote.condition); + sLog.outErrorEventAI("Creature %u using event %u: param2 (Condition: %u) are not valid.", temp.creature_id, i, temp.receive_emote.condition); continue; } if (!(temp.event_flags & EFLAG_REPEATABLE)) { - sLog.outErrorDb("CreatureEventAI: Creature %u using event %u: EFLAG_REPEATABLE not set. Event must always be repeatable. Flag applied.", temp.creature_id, i); + sLog.outErrorEventAI("Creature %u using event %u: EFLAG_REPEATABLE not set. Event must always be repeatable. Flag applied.", temp.creature_id, i); temp.event_flags |= EFLAG_REPEATABLE; } @@ -475,21 +475,21 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Scripts() SpellEntry const* pSpell = sSpellStore.LookupEntry(temp.buffed.spellId); if (!pSpell) { - sLog.outErrorDb("CreatureEventAI: Creature %u has nonexistent SpellID(%u) defined in event %u.", temp.creature_id, temp.buffed.spellId, i); + sLog.outErrorEventAI("Creature %u has nonexistent SpellID(%u) defined in event %u.", temp.creature_id, temp.buffed.spellId, i); continue; } if (temp.buffed.amount < 1) { - sLog.outErrorDb("CreatureEventAI: Creature %u has wrong spell stack size (%u) defined in event %u.", temp.creature_id, temp.buffed.amount, i); + sLog.outErrorEventAI("Creature %u has wrong spell stack size (%u) defined in event %u.", temp.creature_id, temp.buffed.amount, i); continue; } if (temp.buffed.repeatMax < temp.buffed.repeatMin) - sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); + sLog.outErrorEventAI("Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i); break; } default: - sLog.outErrorDb("CreatureEventAI: Creature %u using not checked at load event (%u) in event %u. Need check code update?", temp.creature_id, temp.event_id, i); + sLog.outErrorEventAI("Creature %u using not checked at load event (%u) in event %u. Need check code update?", temp.creature_id, temp.event_id, i); break; } @@ -498,7 +498,7 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Scripts() uint16 action_type = fields[10 + (j * 4)].GetUInt16(); if (action_type >= ACTION_T_END) { - sLog.outErrorDb("CreatureEventAI: Event %u Action %u has incorrect action type (%u), replace by ACTION_T_NONE.", i, j + 1, action_type); + sLog.outErrorEventAI("Event %u Action %u has incorrect action type (%u), replace by ACTION_T_NONE.", i, j + 1, action_type); temp.action[j].type = ACTION_T_NONE; continue; } @@ -518,9 +518,9 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Scripts() case ACTION_T_CHANCED_TEXT: // Check first param as chance if (!action.chanced_text.chance) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u has not set chance param1. Text will not be displayed", i, j + 1); + sLog.outErrorEventAI("Event %u Action %u has not set chance param1. Text will not be displayed", i, j + 1); else if (action.chanced_text.chance >= 100) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u has set chance param1 >= 100. Text will always be displayed", i, j + 1); + sLog.outErrorEventAI("Event %u Action %u has set chance param1 >= 100. Text will always be displayed", i, j + 1); // no break here to check texts case ACTION_T_TEXT: { @@ -531,19 +531,19 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Scripts() if (action.text.TextId[k]) { if (k > firstTextParam && not_set) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u has param%d, but it follow after not set param. Required for randomized text.", i, j + 1, k + 1); + sLog.outErrorEventAI("Event %u Action %u has param%d, but it follow after not set param. Required for randomized text.", i, j + 1, k + 1); if (!action.text.TextId[k]) not_set = true; // range negative else if (action.text.TextId[k] > MIN_CREATURE_AI_TEXT_STRING_ID || action.text.TextId[k] <= MAX_CREATURE_AI_TEXT_STRING_ID) { - sLog.outErrorDb("CreatureEventAI: Event %u Action %u param%d references out-of-range entry (%i) in texts table.", i, j + 1, k + 1, action.text.TextId[k]); + sLog.outErrorEventAI("Event %u Action %u param%d references out-of-range entry (%i) in texts table.", i, j + 1, k + 1, action.text.TextId[k]); action.text.TextId[k] = 0; } else if (m_CreatureEventAI_TextMap.find(action.text.TextId[k]) == m_CreatureEventAI_TextMap.end()) { - sLog.outErrorDb("CreatureEventAI: Event %u Action %u param%d references non-existing entry (%i) in texts table.", i, j + 1, k + 1, action.text.TextId[k]); + sLog.outErrorEventAI("Event %u Action %u param%d references non-existing entry (%i) in texts table.", i, j + 1, k + 1, action.text.TextId[k]); action.text.TextId[k] = 0; } } @@ -553,7 +553,7 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Scripts() case ACTION_T_SET_FACTION: if (action.set_faction.factionId != 0 && !sFactionTemplateStore.LookupEntry(action.set_faction.factionId)) { - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses nonexistent FactionId %u.", i, j + 1, action.set_faction.factionId); + sLog.outErrorEventAI("Event %u Action %u uses nonexistent FactionId %u.", i, j + 1, action.set_faction.factionId); action.set_faction.factionId = 0; } break; @@ -562,7 +562,7 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Scripts() { if (action.morph.creatureId && !sCreatureStorage.LookupEntry(action.morph.creatureId)) { - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses nonexistent Creature entry %u.", i, j + 1, action.morph.creatureId); + sLog.outErrorEventAI("Event %u Action %u uses nonexistent Creature entry %u.", i, j + 1, action.morph.creatureId); action.morph.creatureId = 0; } @@ -570,12 +570,12 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Scripts() { if (action.morph.creatureId) { - sLog.outErrorDb("CreatureEventAI: Event %u Action %u have unused ModelId %u with also set creature id %u.", i, j + 1, action.morph.modelId, action.morph.creatureId); + sLog.outErrorEventAI("Event %u Action %u have unused ModelId %u with also set creature id %u.", i, j + 1, action.morph.modelId, action.morph.creatureId); action.morph.modelId = 0; } else if (!sCreatureDisplayInfoStore.LookupEntry(action.morph.modelId)) { - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses nonexistent ModelId %u.", i, j + 1, action.morph.modelId); + sLog.outErrorEventAI("Event %u Action %u uses nonexistent ModelId %u.", i, j + 1, action.morph.modelId); action.morph.modelId = 0; } } @@ -583,33 +583,33 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Scripts() break; case ACTION_T_SOUND: if (!sSoundEntriesStore.LookupEntry(action.sound.soundId)) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses nonexistent SoundID %u.", i, j + 1, action.sound.soundId); + sLog.outErrorEventAI("Event %u Action %u uses nonexistent SoundID %u.", i, j + 1, action.sound.soundId); break; case ACTION_T_EMOTE: if (!sEmotesStore.LookupEntry(action.emote.emoteId)) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u param1 (EmoteId: %u) are not valid.", i, j + 1, action.emote.emoteId); + sLog.outErrorEventAI("Event %u Action %u param1 (EmoteId: %u) are not valid.", i, j + 1, action.emote.emoteId); break; case ACTION_T_RANDOM_SOUND: if (!sSoundEntriesStore.LookupEntry(action.random_sound.soundId1)) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u param1 uses nonexistent SoundID %u.", i, j + 1, action.random_sound.soundId1); + sLog.outErrorEventAI("Event %u Action %u param1 uses nonexistent SoundID %u.", i, j + 1, action.random_sound.soundId1); if (action.random_sound.soundId2 >= 0 && !sSoundEntriesStore.LookupEntry(action.random_sound.soundId2)) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u param2 uses nonexistent SoundID %u.", i, j + 1, action.random_sound.soundId2); + sLog.outErrorEventAI("Event %u Action %u param2 uses nonexistent SoundID %u.", i, j + 1, action.random_sound.soundId2); if (action.random_sound.soundId3 >= 0 && !sSoundEntriesStore.LookupEntry(action.random_sound.soundId3)) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u param3 uses nonexistent SoundID %u.", i, j + 1, action.random_sound.soundId3); + sLog.outErrorEventAI("Event %u Action %u param3 uses nonexistent SoundID %u.", i, j + 1, action.random_sound.soundId3); break; case ACTION_T_RANDOM_EMOTE: if (!sEmotesStore.LookupEntry(action.random_emote.emoteId1)) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u param1 (EmoteId: %u) are not valid.", i, j + 1, action.random_emote.emoteId1); + sLog.outErrorEventAI("Event %u Action %u param1 (EmoteId: %u) are not valid.", i, j + 1, action.random_emote.emoteId1); if (action.random_emote.emoteId2 >= 0 && !sEmotesStore.LookupEntry(action.random_emote.emoteId2)) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u param2 (EmoteId: %u) are not valid.", i, j + 1, action.random_emote.emoteId2); + sLog.outErrorEventAI("Event %u Action %u param2 (EmoteId: %u) are not valid.", i, j + 1, action.random_emote.emoteId2); if (action.random_emote.emoteId3 >= 0 && !sEmotesStore.LookupEntry(action.random_emote.emoteId3)) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u param3 (EmoteId: %u) are not valid.", i, j + 1, action.random_emote.emoteId3); + sLog.outErrorEventAI("Event %u Action %u param3 (EmoteId: %u) are not valid.", i, j + 1, action.random_emote.emoteId3); break; case ACTION_T_CAST: { const SpellEntry* spell = sSpellStore.LookupEntry(action.cast.spellId); if (!spell) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses nonexistent SpellID %u.", i, j + 1, action.cast.spellId); + sLog.outErrorEventAI("Event %u Action %u uses nonexistent SpellID %u.", i, j + 1, action.cast.spellId); /* FIXME: temp.raw.param3 not have event tipes with recovery time in it.... else { @@ -627,143 +627,158 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Scripts() action.cast.castFlags |= CAST_TRIGGERED; if (action.cast.target >= TARGET_T_END) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j + 1); + sLog.outErrorEventAI("Event %u Action %u uses incorrect Target type", i, j + 1); + + // Some Advanced target type checks - Can have false positives + if (!sLog.HasLogFilter(LOG_FILTER_EVENT_AI_DEV) && spell) + { + // used TARGET_T_ACTION_INVOKER, but likely should be _INVOKER_OWNER instead + if (action.cast.target == TARGET_T_ACTION_INVOKER && + (IsSpellHaveEffect(spell, SPELL_EFFECT_QUEST_COMPLETE) || IsSpellHaveEffect(spell, SPELL_EFFECT_CREATE_RANDOM_ITEM) || IsSpellHaveEffect(spell, SPELL_EFFECT_DUMMY) + || IsSpellHaveEffect(spell, SPELL_EFFECT_KILL_CREDIT_PERSONAL) || IsSpellHaveEffect(spell, SPELL_EFFECT_KILL_CREDIT_GROUP))) + sLog.outErrorEventAI("Event %u Action %u has TARGET_T_ACTION_INVOKER(%u) target type, but should have TARGET_T_ACTION_INVOKER_OWNER(%u).", i, j + 1, TARGET_T_ACTION_INVOKER, TARGET_T_ACTION_INVOKER_OWNER); + + // Spell that should only target players, but could get any + if (spell->HasAttribute(SPELL_ATTR_EX3_TARGET_ONLY_PLAYER) && + (action.cast.target == TARGET_T_ACTION_INVOKER || action.cast.target == TARGET_T_HOSTILE_RANDOM || action.cast.target == TARGET_T_HOSTILE_RANDOM_NOT_TOP)) + sLog.outErrorEventAI("Event %u Action %u uses Target type %u for a spell (%u) that should only target players. This could be wrong.", i, j + 1, action.cast.target, action.cast.spellId); + } break; } case ACTION_T_SUMMON: if (!sCreatureStorage.LookupEntry(action.summon.creatureId)) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses nonexistent creature entry %u.", i, j + 1, action.summon.creatureId); + sLog.outErrorEventAI("Event %u Action %u uses nonexistent creature entry %u.", i, j + 1, action.summon.creatureId); if (action.summon.target >= TARGET_T_END) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j + 1); + sLog.outErrorEventAI("Event %u Action %u uses incorrect Target type", i, j + 1); break; case ACTION_T_THREAT_SINGLE_PCT: if (std::abs(action.threat_single_pct.percent) > 100) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses invalid percent value %u.", i, j + 1, action.threat_single_pct.percent); + sLog.outErrorEventAI("Event %u Action %u uses invalid percent value %u.", i, j + 1, action.threat_single_pct.percent); if (action.threat_single_pct.target >= TARGET_T_END) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j + 1); + sLog.outErrorEventAI("Event %u Action %u uses incorrect Target type", i, j + 1); break; case ACTION_T_THREAT_ALL_PCT: if (std::abs(action.threat_all_pct.percent) > 100) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses invalid percent value %u.", i, j + 1, action.threat_all_pct.percent); + sLog.outErrorEventAI("Event %u Action %u uses invalid percent value %u.", i, j + 1, action.threat_all_pct.percent); break; case ACTION_T_QUEST_EVENT: if (Quest const* qid = sObjectMgr.GetQuestTemplate(action.quest_event.questId)) { if (!qid->HasSpecialFlag(QUEST_SPECIAL_FLAG_EXPLORATION_OR_EVENT)) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u. SpecialFlags for quest entry %u does not include |2, Action will not have any effect.", i, j + 1, action.quest_event.questId); + sLog.outErrorEventAI("Event %u Action %u. SpecialFlags for quest entry %u does not include |2, Action will not have any effect.", i, j + 1, action.quest_event.questId); } else - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses nonexistent Quest entry %u.", i, j + 1, action.quest_event.questId); + sLog.outErrorEventAI("Event %u Action %u uses nonexistent Quest entry %u.", i, j + 1, action.quest_event.questId); if (action.quest_event.target >= TARGET_T_END) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j + 1); + sLog.outErrorEventAI("Event %u Action %u uses incorrect Target type", i, j + 1); break; case ACTION_T_CAST_EVENT: if (!sCreatureStorage.LookupEntry(action.cast_event.creatureId)) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses nonexistent creature entry %u.", i, j + 1, action.cast_event.creatureId); + sLog.outErrorEventAI("Event %u Action %u uses nonexistent creature entry %u.", i, j + 1, action.cast_event.creatureId); if (!sSpellStore.LookupEntry(action.cast_event.spellId)) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses nonexistent SpellID %u.", i, j + 1, action.cast_event.spellId); + sLog.outErrorEventAI("Event %u Action %u uses nonexistent SpellID %u.", i, j + 1, action.cast_event.spellId); if (action.cast_event.target >= TARGET_T_END) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j + 1); + sLog.outErrorEventAI("Event %u Action %u uses incorrect Target type", i, j + 1); break; case ACTION_T_SET_UNIT_FIELD: if (action.set_unit_field.field < OBJECT_END || action.set_unit_field.field >= UNIT_END) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u param1 (UNIT_FIELD*). Index out of range for intended use.", i, j + 1); + sLog.outErrorEventAI("Event %u Action %u param1 (UNIT_FIELD*). Index out of range for intended use.", i, j + 1); if (action.set_unit_field.target >= TARGET_T_END) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j + 1); + sLog.outErrorEventAI("Event %u Action %u uses incorrect Target type", i, j + 1); break; case ACTION_T_SET_UNIT_FLAG: case ACTION_T_REMOVE_UNIT_FLAG: if (action.unit_flag.target >= TARGET_T_END) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j + 1); + sLog.outErrorEventAI("Event %u Action %u uses incorrect Target type", i, j + 1); break; case ACTION_T_SET_PHASE: if (action.set_phase.phase >= MAX_PHASE) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phase >= %u. Phase mask cannot be used past phase %u.", i, j + 1, MAX_PHASE, MAX_PHASE - 1); + sLog.outErrorEventAI("Event %u Action %u attempts to set phase >= %u. Phase mask cannot be used past phase %u.", i, j + 1, MAX_PHASE, MAX_PHASE - 1); break; case ACTION_T_INC_PHASE: if (action.set_inc_phase.step == 0) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u is incrementing phase by 0. Was this intended?", i, j + 1); + sLog.outErrorEventAI("Event %u Action %u is incrementing phase by 0. Was this intended?", i, j + 1); else if (std::abs(action.set_inc_phase.step) > MAX_PHASE - 1) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u is change phase by too large for any use %i.", i, j + 1, action.set_inc_phase.step); + sLog.outErrorEventAI("Event %u Action %u is change phase by too large for any use %i.", i, j + 1, action.set_inc_phase.step); break; case ACTION_T_QUEST_EVENT_ALL: if (Quest const* qid = sObjectMgr.GetQuestTemplate(action.quest_event_all.questId)) { if (!qid->HasSpecialFlag(QUEST_SPECIAL_FLAG_EXPLORATION_OR_EVENT)) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u. SpecialFlags for quest entry %u does not include |2, Action will not have any effect.", i, j + 1, action.quest_event_all.questId); + sLog.outErrorEventAI("Event %u Action %u. SpecialFlags for quest entry %u does not include |2, Action will not have any effect.", i, j + 1, action.quest_event_all.questId); } else - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses nonexistent Quest entry %u.", i, j + 1, action.quest_event_all.questId); + sLog.outErrorEventAI("Event %u Action %u uses nonexistent Quest entry %u.", i, j + 1, action.quest_event_all.questId); break; case ACTION_T_CAST_EVENT_ALL: if (!sCreatureStorage.LookupEntry(action.cast_event_all.creatureId)) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses nonexistent creature entry %u.", i, j + 1, action.cast_event_all.creatureId); + sLog.outErrorEventAI("Event %u Action %u uses nonexistent creature entry %u.", i, j + 1, action.cast_event_all.creatureId); if (!sSpellStore.LookupEntry(action.cast_event_all.spellId)) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses nonexistent SpellID %u.", i, j + 1, action.cast_event_all.spellId); + sLog.outErrorEventAI("Event %u Action %u uses nonexistent SpellID %u.", i, j + 1, action.cast_event_all.spellId); break; case ACTION_T_REMOVEAURASFROMSPELL: if (!sSpellStore.LookupEntry(action.remove_aura.spellId)) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses nonexistent SpellID %u.", i, j + 1, action.remove_aura.spellId); + sLog.outErrorEventAI("Event %u Action %u uses nonexistent SpellID %u.", i, j + 1, action.remove_aura.spellId); if (action.remove_aura.target >= TARGET_T_END) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j + 1); + sLog.outErrorEventAI("Event %u Action %u uses incorrect Target type", i, j + 1); break; case ACTION_T_RANDOM_PHASE: // PhaseId1, PhaseId2, PhaseId3 if (action.random_phase.phase1 >= MAX_PHASE) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phase1 >= %u. Phase mask cannot be used past phase %u.", i, j + 1, MAX_PHASE, MAX_PHASE - 1); + sLog.outErrorEventAI("Event %u Action %u attempts to set phase1 >= %u. Phase mask cannot be used past phase %u.", i, j + 1, MAX_PHASE, MAX_PHASE - 1); if (action.random_phase.phase2 >= MAX_PHASE) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phase2 >= %u. Phase mask cannot be used past phase %u.", i, j + 1, MAX_PHASE, MAX_PHASE - 1); + sLog.outErrorEventAI("Event %u Action %u attempts to set phase2 >= %u. Phase mask cannot be used past phase %u.", i, j + 1, MAX_PHASE, MAX_PHASE - 1); if (action.random_phase.phase3 >= MAX_PHASE) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phase3 >= %u. Phase mask cannot be used past phase %u.", i, j + 1, MAX_PHASE, MAX_PHASE - 1); + sLog.outErrorEventAI("Event %u Action %u attempts to set phase3 >= %u. Phase mask cannot be used past phase %u.", i, j + 1, MAX_PHASE, MAX_PHASE - 1); break; case ACTION_T_RANDOM_PHASE_RANGE: // PhaseMin, PhaseMax if (action.random_phase_range.phaseMin >= MAX_PHASE) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phaseMin >= %u. Phase mask cannot be used past phase %u.", i, j + 1, MAX_PHASE, MAX_PHASE - 1); + sLog.outErrorEventAI("Event %u Action %u attempts to set phaseMin >= %u. Phase mask cannot be used past phase %u.", i, j + 1, MAX_PHASE, MAX_PHASE - 1); if (action.random_phase_range.phaseMin >= MAX_PHASE) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phaseMax >= %u. Phase mask cannot be used past phase %u.", i, j + 1, MAX_PHASE, MAX_PHASE - 1); + sLog.outErrorEventAI("Event %u Action %u attempts to set phaseMax >= %u. Phase mask cannot be used past phase %u.", i, j + 1, MAX_PHASE, MAX_PHASE - 1); if (action.random_phase_range.phaseMin >= action.random_phase_range.phaseMax) { - sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phaseMax <= phaseMin.", i, j + 1); + sLog.outErrorEventAI("Event %u Action %u attempts to set phaseMax <= phaseMin.", i, j + 1); std::swap(action.random_phase_range.phaseMin, action.random_phase_range.phaseMax); // equal case processed at call } break; case ACTION_T_SUMMON_ID: if (!sCreatureStorage.LookupEntry(action.summon_id.creatureId)) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses nonexistent creature entry %u.", i, j + 1, action.summon_id.creatureId); + sLog.outErrorEventAI("Event %u Action %u uses nonexistent creature entry %u.", i, j + 1, action.summon_id.creatureId); if (action.summon_id.target >= TARGET_T_END) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j + 1); + sLog.outErrorEventAI("Event %u Action %u uses incorrect Target type", i, j + 1); if (m_CreatureEventAI_Summon_Map.find(action.summon_id.spawnId) == m_CreatureEventAI_Summon_Map.end()) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u summons missing CreatureEventAI_Summon %u", i, j + 1, action.summon_id.spawnId); + sLog.outErrorEventAI("Event %u Action %u summons missing CreatureEventAI_Summon %u", i, j + 1, action.summon_id.spawnId); break; case ACTION_T_KILLED_MONSTER: if (!sCreatureStorage.LookupEntry(action.killed_monster.creatureId)) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses nonexistent creature entry %u.", i, j + 1, action.killed_monster.creatureId); + sLog.outErrorEventAI("Event %u Action %u uses nonexistent creature entry %u.", i, j + 1, action.killed_monster.creatureId); if (action.killed_monster.target >= TARGET_T_END) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j + 1); + sLog.outErrorEventAI("Event %u Action %u uses incorrect Target type", i, j + 1); break; case ACTION_T_SET_INST_DATA: if (!(temp.event_flags & EFLAG_DIFFICULTY_ALL)) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u. Cannot set instance data without difficulty event flags.", i, j + 1); + sLog.outErrorEventAI("Event %u Action %u. Cannot set instance data without difficulty event flags.", i, j + 1); if (action.set_inst_data.value > 4/*SPECIAL*/) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set instance data above encounter state 4. Custom case?", i, j + 1); + sLog.outErrorEventAI("Event %u Action %u attempts to set instance data above encounter state 4. Custom case?", i, j + 1); break; case ACTION_T_SET_INST_DATA64: if (!(temp.event_flags & EFLAG_DIFFICULTY_ALL)) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u. Cannot set instance data without difficulty event flags.", i, j + 1); + sLog.outErrorEventAI("Event %u Action %u. Cannot set instance data without difficulty event flags.", i, j + 1); if (action.set_inst_data64.target >= TARGET_T_END) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j + 1); + sLog.outErrorEventAI("Event %u Action %u uses incorrect Target type", i, j + 1); break; case ACTION_T_UPDATE_TEMPLATE: if (!sCreatureStorage.LookupEntry(action.update_template.creatureId)) - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses nonexistent creature entry %u.", i, j + 1, action.update_template.creatureId); + sLog.outErrorEventAI("Event %u Action %u uses nonexistent creature entry %u.", i, j + 1, action.update_template.creatureId); break; case ACTION_T_SET_SHEATH: if (action.set_sheath.sheath >= MAX_SHEATH_STATE) { - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses wrong sheath state %u.", i, j + 1, action.set_sheath.sheath); + sLog.outErrorEventAI("Event %u Action %u uses wrong sheath state %u.", i, j + 1, action.set_sheath.sheath); action.set_sheath.sheath = SHEATH_STATE_UNARMED; } break; @@ -772,7 +787,7 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Scripts() { if (action.invincibility_hp_level.hp_level > 100) { - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses wrong percent value %u.", i, j + 1, action.invincibility_hp_level.hp_level); + sLog.outErrorEventAI("Event %u Action %u uses wrong percent value %u.", i, j + 1, action.invincibility_hp_level.hp_level); action.invincibility_hp_level.hp_level = 100; } } @@ -782,7 +797,7 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Scripts() { if (action.mount.creatureId && !sCreatureStorage.LookupEntry(action.mount.creatureId)) { - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses nonexistent Creature entry %u.", i, j + 1, action.mount.creatureId); + sLog.outErrorEventAI("Event %u Action %u uses nonexistent Creature entry %u.", i, j + 1, action.mount.creatureId); action.morph.creatureId = 0; } @@ -790,12 +805,12 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Scripts() { if (action.mount.creatureId) { - sLog.outErrorDb("CreatureEventAI: Event %u Action %u have unused ModelId %u with also set creature id %u.", i, j + 1, action.mount.modelId, action.mount.creatureId); + sLog.outErrorEventAI("Event %u Action %u have unused ModelId %u with also set creature id %u.", i, j + 1, action.mount.modelId, action.mount.creatureId); action.mount.modelId = 0; } else if (!sCreatureDisplayInfoStore.LookupEntry(action.mount.modelId)) { - sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses nonexistent ModelId %u.", i, j + 1, action.mount.modelId); + sLog.outErrorEventAI("Event %u Action %u uses nonexistent ModelId %u.", i, j + 1, action.mount.modelId); action.mount.modelId = 0; } } @@ -815,10 +830,10 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Scripts() case ACTION_T_RANDOM_SAY: case ACTION_T_RANDOM_YELL: case ACTION_T_RANDOM_TEXTEMOTE: - sLog.outErrorDb("CreatureEventAI: Event %u Action %u currently unused ACTION type. Did you forget to update database?", i, j + 1); + sLog.outErrorEventAI("Event %u Action %u currently unused ACTION type. Did you forget to update database?", i, j + 1); break; default: - sLog.outErrorDb("CreatureEventAI: Event %u Action %u have currently not checked at load action type (%u). Need check code update?", i, j + 1, temp.action[j].type); + sLog.outErrorEventAI("Event %u Action %u have currently not checked at load action type (%u). Need check code update?", i, j + 1, temp.action[j].type); break; } } @@ -832,16 +847,16 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Scripts() delete result; // post check - for (uint32 i = 1; i < sCreatureStorage.MaxEntry; ++i) + for (uint32 i = 1; i < sCreatureStorage.GetMaxEntry(); ++i) { if (CreatureInfo const* cInfo = sCreatureStorage.LookupEntry(i)) { bool ainame = strcmp(cInfo->AIName, "EventAI") == 0; bool hasevent = m_CreatureEventAI_Event_Map.find(i) != m_CreatureEventAI_Event_Map.end(); if (ainame && !hasevent) - sLog.outErrorDb("CreatureEventAI: EventAI not has script for creature entry (%u), but AIName = '%s'.", i, cInfo->AIName); + sLog.outErrorEventAI("EventAI not has script for creature entry (%u), but AIName = '%s'.", i, cInfo->AIName); else if (!ainame && hasevent) - sLog.outErrorDb("CreatureEventAI: EventAI has script for creature entry (%u), but AIName = '%s' instead 'EventAI'.", i, cInfo->AIName); + sLog.outErrorEventAI("EventAI has script for creature entry (%u), but AIName = '%s' instead 'EventAI'.", i, cInfo->AIName); } } diff --git a/src/game/DBCStores.cpp b/src/game/DBCStores.cpp index 5f14c5b7e..15edec7b6 100644 --- a/src/game/DBCStores.cpp +++ b/src/game/DBCStores.cpp @@ -788,7 +788,7 @@ void LoadDBCStores(const std::string& dataPath) // fix DK node at Ebon Hold if (i == 315) - ((TaxiNodesEntry*)node)->MountCreatureID[1] = 32981; + (const_cast(node))->MountCreatureID[1] = node->MountCreatureID[0]; } } diff --git a/src/game/GameObject.cpp b/src/game/GameObject.cpp index 47ae2a2ae..1a0f5cc33 100644 --- a/src/game/GameObject.cpp +++ b/src/game/GameObject.cpp @@ -2209,7 +2209,7 @@ void GameObject::DealGameObjectDamage(uint32 damage, uint32 spell, Unit* caster) ForceGameObjectHealth(-int32(damage), caster); - WorldPacket data(SMSG_DESTRUCTIBLE_BUILDING_DAMAGE, 9+9+9+4+4); + WorldPacket data(SMSG_DESTRUCTIBLE_BUILDING_DAMAGE, 9 + 9 + 9 + 4 + 4); data << GetPackGUID(); data << caster->GetPackGUID(); data << caster->GetCharmerOrOwnerOrSelf()->GetPackGUID(); diff --git a/src/game/Level1.cpp b/src/game/Level1.cpp index 25f50ba30..8dea40e63 100644 --- a/src/game/Level1.cpp +++ b/src/game/Level1.cpp @@ -302,9 +302,9 @@ bool ChatHandler::HandleGPSCommand(char* args) zone_y = 0; } - TerrainInfo const* map = obj->GetTerrain(); - float ground_z = map->GetHeight(obj->GetPositionX(), obj->GetPositionY(), MAX_HEIGHT); - float floor_z = map->GetHeight(obj->GetPositionX(), obj->GetPositionY(), obj->GetPositionZ()); + Map const* map = obj->GetMap(); + float ground_z = map->GetHeight(obj->GetPhaseMask(), obj->GetPositionX(), obj->GetPositionY(), MAX_HEIGHT); + float floor_z = map->GetHeight(obj->GetPhaseMask(), obj->GetPositionX(), obj->GetPositionY(), obj->GetPositionZ()); GridPair p = MaNGOS::ComputeGridPair(obj->GetPositionX(), obj->GetPositionY()); @@ -314,9 +314,11 @@ bool ChatHandler::HandleGPSCommand(char* args) uint32 have_map = GridMap::ExistMap(obj->GetMapId(), gx, gy) ? 1 : 0; uint32 have_vmap = GridMap::ExistVMap(obj->GetMapId(), gx, gy) ? 1 : 0; + TerrainInfo const* terrain = obj->GetTerrain(); + if (have_vmap) { - if (map->IsOutdoors(obj->GetPositionX(), obj->GetPositionY(), obj->GetPositionZ())) + if (terrain->IsOutdoors(obj->GetPositionX(), obj->GetPositionY(), obj->GetPositionZ())) PSendSysMessage("You are OUTdoor"); else PSendSysMessage("You are INdoor"); @@ -347,11 +349,22 @@ bool ChatHandler::HandleGPSCommand(char* args) zone_x, zone_y, ground_z, floor_z, have_map, have_vmap); GridMapLiquidData liquid_status; - GridMapLiquidStatus res = map->getLiquidStatus(obj->GetPositionX(), obj->GetPositionY(), obj->GetPositionZ(), MAP_ALL_LIQUIDS, &liquid_status); + GridMapLiquidStatus res = terrain->getLiquidStatus(obj->GetPositionX(), obj->GetPositionY(), obj->GetPositionZ(), MAP_ALL_LIQUIDS, &liquid_status); if (res) { - PSendSysMessage(LANG_LIQUID_STATUS, liquid_status.level, liquid_status.depth_level, liquid_status.type, res); + PSendSysMessage(LANG_LIQUID_STATUS, liquid_status.level, liquid_status.depth_level, liquid_status.type_flags, res); } + + // Additional vmap debugging help +#ifdef _DEBUG_VMAPS + PSendSysMessage("Static terrain height (maps only): %f", obj->GetTerrain()->GetHeightStatic(obj->GetPositionX(), obj->GetPositionY(), obj->GetPositionZ(), false)); + + if (VMAP::IVMapManager* vmgr = VMAP::VMapFactory::createOrGetVMapManager()) + PSendSysMessage("Vmap Terrain Height %f", vmgr->getHeight(obj->GetMapId(), obj->GetPositionX(), obj->GetPositionY(), obj->GetPositionZ() + 2.0f, 10000.0f)); + + PSendSysMessage("Static map height (maps and vmaps): %f", obj->GetTerrain()->GetHeightStatic(obj->GetPositionX(), obj->GetPositionY(), obj->GetPositionZ())); +#endif + return true; } @@ -492,7 +505,6 @@ bool ChatHandler::HandleGonameCommand(char* args) return false; } - if (target) { // check online security @@ -642,6 +654,49 @@ bool ChatHandler::HandleRecallCommand(char* args) return HandleGoHelper(target, target->m_recallMap, target->m_recallX, target->m_recallY, &target->m_recallZ, &target->m_recallO); } +bool ChatHandler::HandleModifyHolyPowerCommand(char* args) +{ + if (!*args) + return false; + + int32 power = atoi(args); + + if (power < 0) + { + SendSysMessage(LANG_BAD_VALUE); + SetSentErrorMessage(true); + return false; + } + + Player* chr = getSelectedPlayer(); + if (!chr) + { + SendSysMessage(LANG_NO_CHAR_SELECTED); + SetSentErrorMessage(true); + return false; + } + + // check online security + if (HasLowerSecurity(chr)) + return false; + + int32 maxPower = int32(chr->GetMaxPower(POWER_HOLY_POWER)); + if (power > maxPower) + { + SendSysMessage(LANG_BAD_VALUE); + SetSentErrorMessage(true); + return false; + } + + PSendSysMessage(LANG_YOU_CHANGE_HOLY_POWER, GetNameLink(chr).c_str(), power, maxPower); + if (needReportToTarget(chr)) + ChatHandler(chr).PSendSysMessage(LANG_YOURS_HOLY_POWER_CHANGED, GetNameLink().c_str(), power, maxPower); + + chr->SetPower(POWER_HOLY_POWER, power); + + return true; +} + // Edit Player HP bool ChatHandler::HandleModifyHPCommand(char* args) { @@ -1519,15 +1574,20 @@ bool ChatHandler::HandleModifyMoneyCommand(char* args) if (HasLowerSecurity(chr)) return false; - int32 addmoney = atoi(args); + int64 addmoney; + if (!ExtractInt64(&args, addmoney)) + return false; - uint32 moneyuser = chr->GetMoney(); + uint64 moneyuser = chr->GetMoney(); + + std::stringstream absadd; absadd << abs(addmoney); + std::stringstream add; add << addmoney; if (addmoney < 0) { - int32 newmoney = int32(moneyuser) + addmoney; - - DETAIL_LOG(GetMangosString(LANG_CURRENT_MONEY), moneyuser, addmoney, newmoney); + int64 newmoney = int64(moneyuser) + addmoney; + DETAIL_LOG("USER1: %s, ADD: %s, DIF: %s", + MoneyToString(moneyuser).c_str(), MoneyToString(addmoney).c_str(), MoneyToString(newmoney).c_str()); if (newmoney <= 0) { PSendSysMessage(LANG_YOU_TAKE_ALL_MONEY, GetNameLink(chr).c_str()); @@ -1541,17 +1601,17 @@ bool ChatHandler::HandleModifyMoneyCommand(char* args) if (newmoney > MAX_MONEY_AMOUNT) newmoney = MAX_MONEY_AMOUNT; - PSendSysMessage(LANG_YOU_TAKE_MONEY, abs(addmoney), GetNameLink(chr).c_str()); + PSendSysMessage(LANG_YOU_TAKE_MONEY, MoneyToString(abs(addmoney)).c_str(), GetNameLink(chr).c_str()); if (needReportToTarget(chr)) - ChatHandler(chr).PSendSysMessage(LANG_YOURS_MONEY_TAKEN, GetNameLink().c_str(), abs(addmoney)); + ChatHandler(chr).PSendSysMessage(LANG_YOURS_MONEY_TAKEN, GetNameLink().c_str(), MoneyToString(abs(addmoney)).c_str()); chr->SetMoney(newmoney); } } else { - PSendSysMessage(LANG_YOU_GIVE_MONEY, addmoney, GetNameLink(chr).c_str()); + PSendSysMessage(LANG_YOU_GIVE_MONEY, MoneyToString(addmoney).c_str(), GetNameLink(chr).c_str()); if (needReportToTarget(chr)) - ChatHandler(chr).PSendSysMessage(LANG_YOURS_MONEY_GIVEN, GetNameLink().c_str(), addmoney); + ChatHandler(chr).PSendSysMessage(LANG_YOURS_MONEY_GIVEN, GetNameLink().c_str(), MoneyToString(addmoney).c_str()); if (addmoney >= MAX_MONEY_AMOUNT) chr->SetMoney(MAX_MONEY_AMOUNT); @@ -1559,7 +1619,8 @@ bool ChatHandler::HandleModifyMoneyCommand(char* args) chr->ModifyMoney(addmoney); } - DETAIL_LOG(GetMangosString(LANG_NEW_MONEY), moneyuser, addmoney, chr->GetMoney()); + DETAIL_LOG("USER2: %s, ADD: %s, RESULT: %s\n", + MoneyToString(moneyuser).c_str(), MoneyToString(addmoney).c_str(), MoneyToString(chr->GetMoney()).c_str()); return true; } @@ -2090,8 +2151,6 @@ bool ChatHandler::HandleGoCommand(char* args) return HandleGoHelper(_player, mapid, x, y, &z); } - - // teleport at coordinates bool ChatHandler::HandleGoXYCommand(char* args) { diff --git a/src/game/Map.cpp b/src/game/Map.cpp index e13c31731..f4c557a8b 100644 --- a/src/game/Map.cpp +++ b/src/game/Map.cpp @@ -37,6 +37,7 @@ #include "VMapFactory.h" #include "MoveMap.h" #include "BattleGround/BattleGroundMgr.h" +#include "Calendar.h" Map::~Map() { @@ -434,6 +435,8 @@ bool Map::loaded(const GridPair& p) const void Map::Update(const uint32& t_diff) { + m_dyn_tree.update(t_diff); + /// update worldsessions for existing players for (m_mapRefIter = m_mapRefManager.begin(); m_mapRefIter != m_mapRefManager.end(); ++m_mapRefIter) { @@ -1338,6 +1341,7 @@ bool DungeonMap::Add(Player* player) data << uint32(0); player->GetSession()->SendPacket(&data); player->BindToInstance(GetPersistanceState(), true); + sCalendarMgr.SendCalendarRaidLockoutAdd(player, GetPersistanceState()); } } } @@ -1444,6 +1448,7 @@ void DungeonMap::PermBindAllPlayers(Player* player) WorldPacket data(SMSG_INSTANCE_SAVE_CREATED, 4); data << uint32(0); plr->GetSession()->SendPacket(&data); + sCalendarMgr.SendCalendarRaidLockoutAdd(plr, GetPersistanceState()); } // if the leader is not in the instance the group will not get a perm bind @@ -1490,7 +1495,6 @@ DungeonPersistentState* DungeonMap::GetPersistanceState() const return (DungeonPersistentState*)Map::GetPersistentState(); } - /* ******* Battleground Instance Maps ******* */ BattleGroundMap::BattleGroundMap(uint32 id, time_t expiry, uint32 InstanceId, uint8 spawnMode) @@ -1516,7 +1520,6 @@ BattleGroundPersistentState* BattleGroundMap::GetPersistanceState() const return (BattleGroundPersistentState*)Map::GetPersistentState(); } - void BattleGroundMap::InitVisibilityDistance() { // init visibility distance for BG/Arenas @@ -1587,8 +1590,10 @@ bool Map::CanEnter(Player* player) } /// Put scripts in the execution queue -bool Map::ScriptsStart(ScriptMapMapName const& scripts, uint32 id, Object* source, Object* target) +bool Map::ScriptsStart(ScriptMapMapName const& scripts, uint32 id, Object* source, Object* target, ScriptExecutionParam execParams /*=SCRIPT_EXEC_PARAM_UNIQUE_BY_SOURCE_TARGET*/) { + MANGOS_ASSERT(source); + ///- Find the script map ScriptMapMap::const_iterator s = scripts.second.find(id); if (s == scripts.second.end()) @@ -1599,6 +1604,20 @@ bool Map::ScriptsStart(ScriptMapMapName const& scripts, uint32 id, Object* sourc ObjectGuid targetGuid = target ? target->GetObjectGuid() : ObjectGuid(); ObjectGuid ownerGuid = source->isType(TYPEMASK_ITEM) ? ((Item*)source)->GetOwnerGuid() : ObjectGuid(); + if (execParams) // Check if the execution should be uniquely + { + for (ScriptScheduleMap::const_iterator searchItr = m_scriptSchedule.begin(); searchItr != m_scriptSchedule.end(); ++searchItr) + { + if (searchItr->second.IsSameScript(scripts.first, id, + execParams & SCRIPT_EXEC_PARAM_UNIQUE_BY_SOURCE ? sourceGuid : ObjectGuid(), + execParams & SCRIPT_EXEC_PARAM_UNIQUE_BY_TARGET ? targetGuid : ObjectGuid(), ownerGuid)) + { + DEBUG_LOG("DB-SCRIPTS: Process table `%s` id %u. Skip script as script already started for source %s, target %s - ScriptsStartParams %u", scripts.first, id, sourceGuid.GetString().c_str(), targetGuid.GetString().c_str(), execParams); + return true; + } + } + } + ///- Schedule script execution for all scripts in the script map ScriptMap const* s2 = &(s->second); for (ScriptMap::const_iterator iter = s2->begin(); iter != s2->end(); ++iter) @@ -1640,12 +1659,33 @@ void Map::ScriptsProcess() // ok as multimap is a *sorted* associative container while (!m_scriptSchedule.empty() && (iter->first <= sWorld.GetGameTime())) { - iter->second.HandleScriptStep(); + if (iter->second.HandleScriptStep()) + { + // Terminate following script steps of this script + const char* tableName = iter->second.GetTableName(); + uint32 id = iter->second.GetId(); + ObjectGuid sourceGuid = iter->second.GetSourceGuid(); + ObjectGuid targetGuid = iter->second.GetTargetGuid(); + ObjectGuid ownerGuid = iter->second.GetOwnerGuid(); - m_scriptSchedule.erase(iter); + for (ScriptScheduleMap::iterator rmItr = m_scriptSchedule.begin(); rmItr != m_scriptSchedule.end();) + { + if (rmItr->second.IsSameScript(tableName, id, sourceGuid, targetGuid, ownerGuid)) + { + m_scriptSchedule.erase(rmItr++); + sScriptMgr.DecreaseScheduledScriptCount(); + } + else + ++rmItr; + } + } + else + { + m_scriptSchedule.erase(iter); + + sScriptMgr.DecreaseScheduledScriptCount(); + } iter = m_scriptSchedule.begin(); - - sScriptMgr.DecreaseScheduledScriptCount(); } } @@ -1850,7 +1890,6 @@ class StaticMonsterChatBuilder Unit* i_target; }; - /** * Function simulates yell of creature * @@ -1879,7 +1918,6 @@ void Map::MonsterYellToMap(ObjectGuid guid, int32 textId, uint32 language, Unit* } } - /** * Function simulates yell of creature * @@ -1921,18 +1959,60 @@ void Map::PlayDirectSoundToMap(uint32 soundId, uint32 zoneId /*=0*/) /** * Function to check if a point is in line of sight from an other point */ -bool Map::IsInLineOfSight(float srcX, float srcY, float srcZ, float destX, float destY, float destZ) +bool Map::IsInLineOfSight(float srcX, float srcY, float srcZ, float destX, float destY, float destZ, uint32 phasemask) const { - VMAP::IVMapManager* vMapManager = VMAP::VMapFactory::createOrGetVMapManager(); - return vMapManager->isInLineOfSight(GetId(), srcX, srcY, srcZ, destX, destY, destZ); + return VMAP::VMapFactory::createOrGetVMapManager()->isInLineOfSight(GetId(), srcX, srcY, srcZ, destX, destY, destZ) + && m_dyn_tree.isInLineOfSight(srcX, srcY, srcZ, destX, destY, destZ, phasemask); } /** - * get the hit position and return true if we hit something + * get the hit position and return true if we hit something (in this case the dest position will hold the hit-position) * otherwise the result pos will be the dest pos */ -bool Map::GetObjectHitPos(float srcX, float srcY, float srcZ, float destX, float destY, float destZ, float& resX, float& resY, float& resZ, float pModifyDist) +bool Map::GetHitPosition(float srcX, float srcY, float srcZ, float& destX, float& destY, float& destZ, uint32 phasemask, float modifyDist) const { - VMAP::IVMapManager* vMapManager = VMAP::VMapFactory::createOrGetVMapManager(); - return vMapManager->getObjectHitPos(GetId(), srcX, srcY, srcZ, destX, destY, destZ, resX, resY, resZ, pModifyDist); + // at first check all static objects + float tempX, tempY, tempZ = 0.0f; + bool result0 = VMAP::VMapFactory::createOrGetVMapManager()->getObjectHitPos(GetId(), srcX, srcY, srcZ, destX, destY, destZ, tempX, tempY, tempZ, modifyDist); + if (result0) + { + DEBUG_LOG("Map::GetHitPosition vmaps corrects gained with static objects! new dest coords are X:%f Y:%f Z:%f", destX, destY, destZ); + destX = tempX; + destY = tempY; + destZ = tempZ; + } + // at second all dynamic objects, if static check has an hit, then we can calculate only to this closer point + bool result1 = m_dyn_tree.getObjectHitPos(phasemask, srcX, srcY, srcZ, destX, destY, destZ, tempX, tempY, tempZ, modifyDist); + if (result1) + { + DEBUG_LOG("Map::GetHitPosition vmaps corrects gained with dynamic objects! new dest coords are X:%f Y:%f Z:%f", destX, destY, destZ); + destX = tempX; + destY = tempY; + destZ = tempZ; + } + return result0 || result1; +} + +float Map::GetHeight(uint32 phasemask, float x, float y, float z) const +{ + float staticHeight = m_TerrainData->GetHeightStatic(x, y, z); + + // Get Dynamic Height around static Height (if valid) + float dynSearchHeight = 2.0f + (z < staticHeight ? staticHeight : z); + return std::max(staticHeight, m_dyn_tree.getHeight(x, y, dynSearchHeight, dynSearchHeight - staticHeight, phasemask)); +} + +void Map::InsertGameObjectModel(const GameObjectModel& mdl) +{ + m_dyn_tree.insert(mdl); +} + +void Map::RemoveGameObjectModel(const GameObjectModel& mdl) +{ + m_dyn_tree.remove(mdl); +} + +bool Map::ContainsGameObjectModel(const GameObjectModel& mdl) const +{ + return m_dyn_tree.contains(mdl); } diff --git a/src/game/ObjectMgr.cpp b/src/game/ObjectMgr.cpp index 3ef80dd2a..efea81411 100755 --- a/src/game/ObjectMgr.cpp +++ b/src/game/ObjectMgr.cpp @@ -18,7 +18,7 @@ #include "ObjectMgr.h" #include "Database/DatabaseEnv.h" -#include "Policies/SingletonImp.h" +#include "Policies/Singleton.h" #include "SQLStorages.h" #include "Log.h" @@ -152,8 +152,6 @@ ObjectMgr::ObjectMgr() : m_FirstTemporaryCreatureGuid(1), m_FirstTemporaryGameObjectGuid(1) { - // Only zero condition left, others will be added while loading DB tables - mConditions.resize(1); } ObjectMgr::~ObjectMgr() @@ -440,7 +438,7 @@ void ObjectMgr::LoadPointOfInterestLocales() sLog.outString(">> Loaded " SIZEFMTD " points_of_interest locale strings", mPointOfInterestLocaleMap.size()); } -struct SQLCreatureLoader : public SQLStorageLoaderBase +struct SQLCreatureLoader : public SQLStorageLoaderBase { template void convert_from_str(uint32 /*field_pos*/, char const* src, D& dst) @@ -454,14 +452,14 @@ void ObjectMgr::LoadCreatureTemplates() SQLCreatureLoader loader; loader.Load(sCreatureStorage); - sLog.outString(">> Loaded %u creature definitions", sCreatureStorage.RecordCount); + sLog.outString(">> Loaded %u creature definitions", sCreatureStorage.GetRecordCount()); sLog.outString(); std::set difficultyEntries[MAX_DIFFICULTY - 1]; // already loaded difficulty 1 value in creatures std::set hasDifficultyEntries[MAX_DIFFICULTY - 1]; // already loaded creatures with difficulty 1 values // check data correctness - for (uint32 i = 1; i < sCreatureStorage.MaxEntry; ++i) + for (uint32 i = 1; i < sCreatureStorage.GetMaxEntry(); ++i) { CreatureInfo const* cInfo = sCreatureStorage.LookupEntry(i); if (!cInfo) @@ -612,6 +610,18 @@ void ObjectMgr::LoadCreatureTemplates() if (!displayScaleEntry) sLog.outErrorDb("Creature (Entry: %u) has nonexistent modelid in modelid_1/modelid_2/modelid_3/modelid_4", cInfo->Entry); + if (!cInfo->minlevel) + { + sLog.outErrorDb("Creature (Entry: %u) has invalid minlevel, set to 1", cInfo->Entry); + const_cast(cInfo)->minlevel = 1; + } + + if (cInfo->minlevel > cInfo->maxlevel) + { + sLog.outErrorDb("Creature (Entry: %u) has invalid maxlevel, set to minlevel", cInfo->Entry); + const_cast(cInfo)->maxlevel = cInfo->minlevel; + } + // use below code for 0-checks for unit_class if (!cInfo->unit_class) ERROR_DB_STRICT_LOG("Creature (Entry: %u) not has proper unit_class(%u) for creature_template", cInfo->Entry, cInfo->unit_class); @@ -632,7 +642,7 @@ void ObjectMgr::LoadCreatureTemplates() if (cInfo->npcflag & UNIT_NPC_FLAG_SPELLCLICK) { - sLog.outErrorDb("Creature (Entry: %u) has dynamic flag UNIT_NPC_FLAG_SPELLCLICK (%u) set, it expect to be set by code base at `npc_spellclick_spells` content.", cInfo->Entry, UNIT_NPC_FLAG_SPELLCLICK); + sLog.outDebug("Creature (Entry: %u) has dynamic flag UNIT_NPC_FLAG_SPELLCLICK (%u) set, but it is expected to be set in run-time based at `npc_spellclick_spells` contents.", cInfo->Entry, UNIT_NPC_FLAG_SPELLCLICK); const_cast(cInfo)->npcflag &= ~UNIT_NPC_FLAG_SPELLCLICK; } @@ -665,15 +675,6 @@ void ObjectMgr::LoadCreatureTemplates() sLog.outErrorDb("Creature (Entry: %u) has non-existing PetSpellDataId (%u)", cInfo->Entry, cInfo->PetSpellDataId); } - for (int j = 0; j < CREATURE_MAX_SPELLS; ++j) - { - if (cInfo->spells[j] && !sSpellStore.LookupEntry(cInfo->spells[j])) - { - sLog.outErrorDb("Creature (Entry: %u) has non-existing Spell%d (%u), set to 0", cInfo->Entry, j + 1, cInfo->spells[j]); - const_cast(cInfo)->spells[j] = 0; - } - } - if (cInfo->MovementType >= MAX_DB_MOTION_TYPE) { sLog.outErrorDb("Creature (Entry: %u) has wrong movement generator type (%u), ignore and set to IDLE.", cInfo->Entry, cInfo->MovementType); @@ -759,6 +760,26 @@ void ObjectMgr::ConvertCreatureAddonAuras(CreatureDataAddon* addon, char const* continue; } + // Must be Aura, but also allow dummy/script effect spells, as they are used sometimes to select a random aura or similar + if (!IsSpellAppliesAura(AdditionalSpellInfo) && !IsSpellHaveEffect(AdditionalSpellInfo, SPELL_EFFECT_DUMMY) && !IsSpellHaveEffect(AdditionalSpellInfo, SPELL_EFFECT_SCRIPT_EFFECT) && !IsSpellHaveEffect(AdditionalSpellInfo, SPELL_EFFECT_TRIGGER_SPELL)) + { + sLog.outErrorDb("Creature (%s: %u) has spell %u defined in `auras` field in `%s, but spell doesn't apply an aura`.", guidEntryStr, addon->guidOrEntry, cAura, table); + continue; + } + + // TODO: Remove LogFilter check after more research + if (!sLog.HasLogFilter(LOG_FILTER_DB_STRICTED_CHECK) && !IsOnlySelfTargeting(AdditionalSpellInfo)) + { + sLog.outErrorDb("Creature (%s: %u) has spell %u defined in `auras` field in `%s, but spell is no self-only spell`.", guidEntryStr, addon->guidOrEntry, cAura, table); + continue; + } + + if (IsSpellHaveAura(AdditionalSpellInfo, SPELL_AURA_CONTROL_VEHICLE)) + { + sLog.outErrorDb("Creature (%s: %u) has spell %u defined in `auras` field in `%s, but vehicle control spells are not suitable for _addon.auras handling`.", guidEntryStr, addon->guidOrEntry, cAura, table); + continue; + } + if (std::find(&addon->auras[0], &addon->auras[i], cAura) != &addon->auras[i]) { sLog.outErrorDb("Creature (%s: %u) has duplicate spell %u defined in `auras` field in `%s`.", guidEntryStr, addon->guidOrEntry, cAura, table); @@ -776,11 +797,11 @@ void ObjectMgr::LoadCreatureAddons(SQLStorage& creatureaddons, char const* entry { creatureaddons.Load(); - sLog.outString(">> Loaded %u %s", creatureaddons.RecordCount, comment); + sLog.outString(">> Loaded %u %s", creatureaddons.GetRecordCount(), comment); sLog.outString(); // check data correctness and convert 'auras' - for (uint32 i = 1; i < creatureaddons.MaxEntry; ++i) + for (uint32 i = 1; i < creatureaddons.GetMaxEntry(); ++i) { CreatureDataAddon const* addon = creatureaddons.LookupEntry(i); if (!addon) @@ -813,7 +834,7 @@ void ObjectMgr::LoadCreatureAddons() LoadCreatureAddons(sCreatureInfoAddonStorage, "Entry", "creature template addons"); // check entry ids - for (uint32 i = 1; i < sCreatureInfoAddonStorage.MaxEntry; ++i) + for (uint32 i = 1; i < sCreatureInfoAddonStorage.GetMaxEntry(); ++i) if (CreatureDataAddon const* addon = sCreatureInfoAddonStorage.LookupEntry(i)) if (!sCreatureStorage.LookupEntry(addon->guidOrEntry)) sLog.outErrorDb("Creature (Entry: %u) does not exist but has a record in `%s`", addon->guidOrEntry, sCreatureInfoAddonStorage.GetTableName()); @@ -821,7 +842,7 @@ void ObjectMgr::LoadCreatureAddons() LoadCreatureAddons(sCreatureDataAddonStorage, "GUID", "creature addons"); // check entry ids - for (uint32 i = 1; i < sCreatureDataAddonStorage.MaxEntry; ++i) + for (uint32 i = 1; i < sCreatureDataAddonStorage.GetMaxEntry(); ++i) if (CreatureDataAddon const* addon = sCreatureDataAddonStorage.LookupEntry(i)) if (mCreatureDataMap.find(addon->guidOrEntry) == mCreatureDataMap.end()) sLog.outErrorDb("Creature (GUID: %u) does not exist but has a record in `creature_addon`", addon->guidOrEntry); @@ -831,7 +852,7 @@ void ObjectMgr::LoadEquipmentTemplates() { sEquipmentStorage.Load(); - for (uint32 i = 0; i < sEquipmentStorage.MaxEntry; ++i) + for (uint32 i = 0; i < sEquipmentStorage.GetMaxEntry(); ++i) { EquipmentInfo const* eqInfo = sEquipmentStorage.LookupEntry(i); @@ -869,7 +890,7 @@ void ObjectMgr::LoadEquipmentTemplates() } } - sLog.outString(">> Loaded %u equipment template", sEquipmentStorage.RecordCount); + sLog.outString(">> Loaded %u equipment template", sEquipmentStorage.GetRecordCount()); sLog.outString(); } @@ -934,7 +955,7 @@ void ObjectMgr::LoadCreatureModelInfo() sCreatureModelStorage.Load(); // post processing - for (uint32 i = 1; i < sCreatureModelStorage.MaxEntry; ++i) + for (uint32 i = 1; i < sCreatureModelStorage.GetMaxEntry(); ++i) { CreatureModelInfo const* minfo = sCreatureModelStorage.LookupEntry(i); if (!minfo) @@ -1033,10 +1054,9 @@ void ObjectMgr::LoadCreatureModelInfo() } else sLog.outErrorDb("Table `creature_model_info` expect have data for character race %u male model id %u", race, raceEntry->model_m); - } - sLog.outString(">> Loaded %u creature model based info", sCreatureModelStorage.RecordCount); + sLog.outString(">> Loaded %u creature model based info", sCreatureModelStorage.GetRecordCount()); sLog.outString(); } @@ -1188,7 +1208,7 @@ void ObjectMgr::LoadCreatures() // build single time for check creature data std::set difficultyCreatures[MAX_DIFFICULTY - 1]; - for (uint32 i = 0; i < sCreatureStorage.MaxEntry; ++i) + for (uint32 i = 0; i < sCreatureStorage.GetMaxEntry(); ++i) if (CreatureInfo const* cInfo = sCreatureStorage.LookupEntry(i)) for (uint32 diff = 0; diff < MAX_DIFFICULTY - 1; ++diff) if (cInfo->DifficultyEntry[diff]) @@ -1340,7 +1360,6 @@ void ObjectMgr::LoadCreatures() AddCreatureToGrid(guid, &data); ++count; - } while (result->NextRow()); @@ -1537,7 +1556,6 @@ void ObjectMgr::LoadGameObjects() AddGameobjectToGrid(guid, &data); ++count; - } while (result->NextRow()); @@ -1551,10 +1569,10 @@ void ObjectMgr::LoadGameObjectAddon() { sGameObjectDataAddonStorage.Load(); - sLog.outString(">> Loaded %u gameobject addons", sGameObjectDataAddonStorage.RecordCount); + sLog.outString(">> Loaded %u gameobject addons", sGameObjectDataAddonStorage.GetRecordCount()); sLog.outString(); - for (uint32 i = 1; i < sGameObjectDataAddonStorage.MaxEntry; ++i) + for (uint32 i = 1; i < sGameObjectDataAddonStorage.GetMaxEntry(); ++i) { GameObjectDataAddon const* addon = sGameObjectDataAddonStorage.LookupEntry(i); if (!addon) @@ -1774,7 +1792,7 @@ void ObjectMgr::LoadItemLocales() sLog.outString(">> Loaded " SIZEFMTD " Item locale strings", mItemLocaleMap.size()); } -struct SQLItemLoader : public SQLStorageLoaderBase +struct SQLItemLoader : public SQLStorageLoaderBase { template void convert_from_str(uint32 /*field_pos*/, char const* src, D& dst) @@ -1787,11 +1805,11 @@ void ObjectMgr::LoadItemPrototypes() { SQLItemLoader loader; loader.Load(sItemStorage); - sLog.outString(">> Loaded %u item prototypes", sItemStorage.RecordCount); + sLog.outString(">> Loaded %u item prototypes", sItemStorage.GetRecordCount()); sLog.outString(); // check data correctness - for (uint32 i = 1; i < sItemStorage.MaxEntry; ++i) + for (uint32 i = 1; i < sItemStorage.GetMaxEntry(); ++i) { ItemPrototype const* proto = sItemStorage.LookupEntry(i); ItemEntry const *dbcitem = sItemStore.LookupEntry(i); @@ -2557,7 +2575,7 @@ void ObjectMgr::LoadItemRequiredTarget() { if (pItemProto->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_USE) { - SpellScriptTargetBounds bounds = sSpellMgr.GetSpellScriptTargetBounds(pSpellInfo->Id); + SQLMultiStorage::SQLMSIteratorBounds bounds = sSpellScriptTargetStorage.getBounds(pSpellInfo->Id); if (bounds.first != bounds.second) break; @@ -3113,15 +3131,15 @@ void ObjectMgr::LoadPlayerInfo() continue; // skip expansion races if not playing with expansion - if (sWorld.getConfig(CONFIG_UINT32_EXPANSION) < 1 && (race == RACE_BLOODELF || race == RACE_DRAENEI)) + if (sWorld.getConfig(CONFIG_UINT32_EXPANSION) < EXPANSION_TBC && (race == RACE_BLOODELF || race == RACE_DRAENEI)) continue; // skip expansion classes if not playing with expansion - if (sWorld.getConfig(CONFIG_UINT32_EXPANSION) < 2 && class_ == CLASS_DEATH_KNIGHT) + if (sWorld.getConfig(CONFIG_UINT32_EXPANSION) < EXPANSION_WOTLK && class_ == CLASS_DEATH_KNIGHT) continue; // skip expansion races if not playing with expansion - if (sWorld.getConfig(CONFIG_UINT32_EXPANSION) < 3 && (race == RACE_WORGEN || race == RACE_GOBLIN)) + if (sWorld.getConfig(CONFIG_UINT32_EXPANSION) < EXPANSION_CATA && (race == RACE_WORGEN || race == RACE_GOBLIN)) continue; // fatal error if no level 1 data @@ -3621,7 +3639,7 @@ void ObjectMgr::LoadQuests() // 139 140 "StartScript, CompleteScript, " // 141 142 143 144 145 146 147 - "ReqSpellLearned, PortraitGiver, PortraitTurnIn, PortraitGiverText, PortraitGiverName, PortraitTurnInText, PortraitTurnInName, " + "ReqSpellLearned, PortraitGiver, PortraitTurnIn, PortraitGiverName, PortraitGiverText, PortraitTurnInName, PortraitTurnInText, " // 148 149 150 151 152 153 154 155 "ReqCurrencyId1, ReqCurrencyId2, ReqCurrencyId3, ReqCurrencyId4, ReqCurrencyCount1, ReqCurrencyCount2, ReqCurrencyCount3, ReqCurrencyCount4, " // 156 157 158 159 160 161 162 163 @@ -4271,6 +4289,8 @@ void ObjectMgr::LoadQuests() { if (qinfo->ReqCurrencyId[j]) { + qinfo->SetSpecialFlag(QUEST_SPECIAL_FLAG_DELIVER); + CurrencyTypesEntry const * currencyEntry = sCurrencyTypesStore.LookupEntry(qinfo->ReqCurrencyId[j]); if (!currencyEntry) { @@ -4294,7 +4314,6 @@ void ObjectMgr::LoadQuests() qinfo->ReqCurrencyCount[j] = currencyEntry->TotalCap; } } - } else if (qinfo->ReqCurrencyCount[j]) { @@ -4411,14 +4430,15 @@ void ObjectMgr::LoadQuestLocales() mQuestLocaleMap.clear(); // need for reload case QueryResult* result = WorldDatabase.Query("SELECT entry," - "Title_loc1,Details_loc1,Objectives_loc1,OfferRewardText_loc1,RequestItemsText_loc1,EndText_loc1,CompletedText_loc1,ObjectiveText1_loc1,ObjectiveText2_loc1,ObjectiveText3_loc1,ObjectiveText4_loc1," - "Title_loc2,Details_loc2,Objectives_loc2,OfferRewardText_loc2,RequestItemsText_loc2,EndText_loc2,CompletedText_loc2,ObjectiveText1_loc2,ObjectiveText2_loc2,ObjectiveText3_loc2,ObjectiveText4_loc2," - "Title_loc3,Details_loc3,Objectives_loc3,OfferRewardText_loc3,RequestItemsText_loc3,EndText_loc3,CompletedText_loc3,ObjectiveText1_loc3,ObjectiveText2_loc3,ObjectiveText3_loc3,ObjectiveText4_loc3," - "Title_loc4,Details_loc4,Objectives_loc4,OfferRewardText_loc4,RequestItemsText_loc4,EndText_loc4,CompletedText_loc4,ObjectiveText1_loc4,ObjectiveText2_loc4,ObjectiveText3_loc4,ObjectiveText4_loc4," - "Title_loc5,Details_loc5,Objectives_loc5,OfferRewardText_loc5,RequestItemsText_loc5,EndText_loc5,CompletedText_loc5,ObjectiveText1_loc5,ObjectiveText2_loc5,ObjectiveText3_loc5,ObjectiveText4_loc5," - "Title_loc6,Details_loc6,Objectives_loc6,OfferRewardText_loc6,RequestItemsText_loc6,EndText_loc6,CompletedText_loc6,ObjectiveText1_loc6,ObjectiveText2_loc6,ObjectiveText3_loc6,ObjectiveText4_loc6," - "Title_loc7,Details_loc7,Objectives_loc7,OfferRewardText_loc7,RequestItemsText_loc7,EndText_loc7,CompletedText_loc7,ObjectiveText1_loc7,ObjectiveText2_loc7,ObjectiveText3_loc7,ObjectiveText4_loc7," - "Title_loc8,Details_loc8,Objectives_loc8,OfferRewardText_loc8,RequestItemsText_loc8,EndText_loc8,CompletedText_loc8,ObjectiveText1_loc8,ObjectiveText2_loc8,ObjectiveText3_loc8,ObjectiveText4_loc8" + // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + "Title_loc1,Details_loc1,Objectives_loc1,OfferRewardText_loc1,RequestItemsText_loc1,EndText_loc1,CompletedText_loc1,ObjectiveText1_loc1,ObjectiveText2_loc1,ObjectiveText3_loc1,ObjectiveText4_loc1,PortraitGiverName_loc1,PortraitGiverText_loc1,PortraitTurnInName_loc1,PortraitTurnInText_loc1," + "Title_loc2,Details_loc2,Objectives_loc2,OfferRewardText_loc2,RequestItemsText_loc2,EndText_loc2,CompletedText_loc2,ObjectiveText1_loc2,ObjectiveText2_loc2,ObjectiveText3_loc2,ObjectiveText4_loc2,PortraitGiverName_loc2,PortraitGiverText_loc2,PortraitTurnInName_loc2,PortraitTurnInText_loc2," + "Title_loc3,Details_loc3,Objectives_loc3,OfferRewardText_loc3,RequestItemsText_loc3,EndText_loc3,CompletedText_loc3,ObjectiveText1_loc3,ObjectiveText2_loc3,ObjectiveText3_loc3,ObjectiveText4_loc3,PortraitGiverName_loc3,PortraitGiverText_loc3,PortraitTurnInName_loc3,PortraitTurnInText_loc3," + "Title_loc4,Details_loc4,Objectives_loc4,OfferRewardText_loc4,RequestItemsText_loc4,EndText_loc4,CompletedText_loc4,ObjectiveText1_loc4,ObjectiveText2_loc4,ObjectiveText3_loc4,ObjectiveText4_loc4,PortraitGiverName_loc4,PortraitGiverText_loc4,PortraitTurnInName_loc4,PortraitTurnInText_loc4," + "Title_loc5,Details_loc5,Objectives_loc5,OfferRewardText_loc5,RequestItemsText_loc5,EndText_loc5,CompletedText_loc5,ObjectiveText1_loc5,ObjectiveText2_loc5,ObjectiveText3_loc5,ObjectiveText4_loc5,PortraitGiverName_loc5,PortraitGiverText_loc5,PortraitTurnInName_loc5,PortraitTurnInText_loc5," + "Title_loc6,Details_loc6,Objectives_loc6,OfferRewardText_loc6,RequestItemsText_loc6,EndText_loc6,CompletedText_loc6,ObjectiveText1_loc6,ObjectiveText2_loc6,ObjectiveText3_loc6,ObjectiveText4_loc6,PortraitGiverName_loc6,PortraitGiverText_loc6,PortraitTurnInName_loc6,PortraitTurnInText_loc6," + "Title_loc7,Details_loc7,Objectives_loc7,OfferRewardText_loc7,RequestItemsText_loc7,EndText_loc7,CompletedText_loc7,ObjectiveText1_loc7,ObjectiveText2_loc7,ObjectiveText3_loc7,ObjectiveText4_loc7,PortraitGiverName_loc7,PortraitGiverText_loc7,PortraitTurnInName_loc7,PortraitTurnInText_loc7," + "Title_loc8,Details_loc8,Objectives_loc8,OfferRewardText_loc8,RequestItemsText_loc8,EndText_loc8,CompletedText_loc8,ObjectiveText1_loc8,ObjectiveText2_loc8,ObjectiveText3_loc8,ObjectiveText4_loc8,PortraitGiverName_loc8,PortraitGiverText_loc8,PortraitTurnInName_loc8,PortraitTurnInText_loc8" " FROM locales_quest" ); @@ -4452,7 +4472,7 @@ void ObjectMgr::LoadQuestLocales() for (int i = 1; i < MAX_LOCALE; ++i) { - std::string str = fields[1 + 11 * (i - 1)].GetCppString(); + std::string str = fields[1 + 15 * (i - 1)].GetCppString(); if (!str.empty()) { int idx = GetOrNewIndexForLocale(LocaleConstant(i)); @@ -4464,7 +4484,7 @@ void ObjectMgr::LoadQuestLocales() data.Title[idx] = str; } } - str = fields[1 + 11 * (i - 1) + 1].GetCppString(); + str = fields[1 + 15 * (i - 1) + 1].GetCppString(); if (!str.empty()) { int idx = GetOrNewIndexForLocale(LocaleConstant(i)); @@ -4476,7 +4496,7 @@ void ObjectMgr::LoadQuestLocales() data.Details[idx] = str; } } - str = fields[1 + 11 * (i - 1) + 2].GetCppString(); + str = fields[1 + 15 * (i - 1) + 2].GetCppString(); if (!str.empty()) { int idx = GetOrNewIndexForLocale(LocaleConstant(i)); @@ -4488,7 +4508,7 @@ void ObjectMgr::LoadQuestLocales() data.Objectives[idx] = str; } } - str = fields[1 + 11 * (i - 1) + 3].GetCppString(); + str = fields[1 + 15 * (i - 1) + 3].GetCppString(); if (!str.empty()) { int idx = GetOrNewIndexForLocale(LocaleConstant(i)); @@ -4500,7 +4520,7 @@ void ObjectMgr::LoadQuestLocales() data.OfferRewardText[idx] = str; } } - str = fields[1 + 11 * (i - 1) + 4].GetCppString(); + str = fields[1 + 15 * (i - 1) + 4].GetCppString(); if (!str.empty()) { int idx = GetOrNewIndexForLocale(LocaleConstant(i)); @@ -4512,7 +4532,7 @@ void ObjectMgr::LoadQuestLocales() data.RequestItemsText[idx] = str; } } - str = fields[1 + 11 * (i - 1) + 5].GetCppString(); + str = fields[1 + 15 * (i - 1) + 5].GetCppString(); if (!str.empty()) { int idx = GetOrNewIndexForLocale(LocaleConstant(i)); @@ -4524,7 +4544,7 @@ void ObjectMgr::LoadQuestLocales() data.EndText[idx] = str; } } - str = fields[1 + 11 * (i - 1) + 6].GetCppString(); + str = fields[1 + 15 * (i - 1) + 6].GetCppString(); if (!str.empty()) { int idx = GetOrNewIndexForLocale(LocaleConstant(i)); @@ -4538,7 +4558,7 @@ void ObjectMgr::LoadQuestLocales() } for (int k = 0; k < 4; ++k) { - str = fields[1 + 11 * (i - 1) + 7 + k].GetCppString(); + str = fields[1 + 15 * (i - 1) + 7 + k].GetCppString(); if (!str.empty()) { int idx = GetOrNewIndexForLocale(LocaleConstant(i)); @@ -4551,6 +4571,54 @@ void ObjectMgr::LoadQuestLocales() } } } + str = fields[1 + 15 * (i - 1) + 11].GetCppString(); + if (!str.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if ((int32)data.PortraitGiverName.size() <= idx) + data.PortraitGiverName.resize(idx + 1); + + data.PortraitGiverName[idx] = str; + } + } + str = fields[1 + 15 * (i - 1) + 12].GetCppString(); + if (!str.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if ((int32)data.PortraitGiverText.size() <= idx) + data.PortraitGiverText.resize(idx + 1); + + data.PortraitGiverText[idx] = str; + } + } + str = fields[1 + 15 * (i - 1) + 13].GetCppString(); + if (!str.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if ((int32)data.PortraitTurnInName.size() <= idx) + data.PortraitTurnInName.resize(idx + 1); + + data.PortraitTurnInName[idx] = str; + } + } + str = fields[1 + 15 * (i - 1) + 14].GetCppString(); + if (!str.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if ((int32)data.PortraitTurnInText.size() <= idx) + data.PortraitTurnInText.resize(idx + 1); + + data.PortraitTurnInText[idx] = str; + } + } } } while (result->NextRow()); @@ -4563,13 +4631,11 @@ void ObjectMgr::LoadQuestLocales() void ObjectMgr::LoadPageTexts() { - sPageTextStore.Free(); // for reload case - sPageTextStore.Load(); - sLog.outString(">> Loaded %u page texts", sPageTextStore.RecordCount); + sLog.outString(">> Loaded %u page texts", sPageTextStore.GetRecordCount()); sLog.outString(); - for (uint32 i = 1; i < sPageTextStore.MaxEntry; ++i) + for (uint32 i = 1; i < sPageTextStore.GetMaxEntry(); ++i) { // check data correctness PageText const* page = sPageTextStore.LookupEntry(i); @@ -4654,7 +4720,6 @@ void ObjectMgr::LoadPageTextLocales() data.Text[idx] = str; } } - } while (result->NextRow()); @@ -4728,7 +4793,6 @@ void ObjectMgr::LoadInstanceEncounters() uint32 lastEncounterDungeon = fields[3].GetUInt32(); m_DungeonEncounters.insert(DungeonEncounterMap::value_type(creditEntry, new DungeonEncounter(dungeonEncounter, EncounterCreditType(creditType), creditEntry, lastEncounterDungeon))); - } while (result->NextRow()); @@ -4738,7 +4802,7 @@ void ObjectMgr::LoadInstanceEncounters() sLog.outString(">> Loaded " SIZEFMTD " Instance Encounters", m_DungeonEncounters.size()); } -struct SQLInstanceLoader : public SQLStorageLoaderBase +struct SQLInstanceLoader : public SQLStorageLoaderBase { template void convert_from_str(uint32 /*field_pos*/, char const* src, D& dst) @@ -4752,7 +4816,7 @@ void ObjectMgr::LoadInstanceTemplate() SQLInstanceLoader loader; loader.Load(sInstanceTemplate); - for (uint32 i = 0; i < sInstanceTemplate.MaxEntry; ++i) + for (uint32 i = 0; i < sInstanceTemplate.GetMaxEntry(); ++i) { InstanceTemplate const* temp = GetInstanceTemplate(i); if (!temp) @@ -4795,11 +4859,11 @@ void ObjectMgr::LoadInstanceTemplate() } } - sLog.outString(">> Loaded %u Instance Template definitions", sInstanceTemplate.RecordCount); + sLog.outString(">> Loaded %u Instance Template definitions", sInstanceTemplate.GetRecordCount()); sLog.outString(); } -struct SQLWorldLoader : public SQLStorageLoaderBase +struct SQLWorldLoader : public SQLStorageLoaderBase { template void convert_from_str(uint32 /*field_pos*/, char const* src, D& dst) @@ -4813,7 +4877,7 @@ void ObjectMgr::LoadWorldTemplate() SQLWorldLoader loader; loader.Load(sWorldTemplate, false); - for (uint32 i = 0; i < sWorldTemplate.MaxEntry; ++i) + for (uint32 i = 0; i < sWorldTemplate.GetMaxEntry(); ++i) { WorldTemplate const* temp = GetWorldTemplate(i); if (!temp) @@ -4835,16 +4899,16 @@ void ObjectMgr::LoadWorldTemplate() } } - sLog.outString(">> Loaded %u World Template definitions", sWorldTemplate.RecordCount); + sLog.outString(">> Loaded %u World Template definitions", sWorldTemplate.GetRecordCount()); sLog.outString(); } void ObjectMgr::LoadConditions() { SQLWorldLoader loader; - loader.Load(sConditionStorage, false); + loader.Load(sConditionStorage); - for (uint32 i = 0; i < sConditionStorage.MaxEntry; ++i) + for (uint32 i = 0; i < sConditionStorage.GetMaxEntry(); ++i) { const PlayerCondition* condition = sConditionStorage.LookupEntry(i); if (!condition) @@ -4858,7 +4922,7 @@ void ObjectMgr::LoadConditions() } } - sLog.outString(">> Loaded %u Condition definitions", sConditionStorage.RecordCount); + sLog.outString(">> Loaded %u Condition definitions", sConditionStorage.GetRecordCount()); sLog.outString(); } @@ -5176,7 +5240,6 @@ void ObjectMgr::LoadQuestAreaTriggers() } mQuestAreaTriggerMap[trigger_ID] = quest_ID; - } while (result->NextRow()); @@ -5695,7 +5758,6 @@ void ObjectMgr::LoadAreaTriggerTeleports() } mAreaTriggers[Trigger_ID] = at; - } while (result->NextRow()); @@ -6010,7 +6072,6 @@ void ObjectMgr::LoadGameObjectLocales() } } } - } while (result->NextRow()); @@ -6020,7 +6081,7 @@ void ObjectMgr::LoadGameObjectLocales() sLog.outString(">> Loaded " SIZEFMTD " gameobject locale strings", mGameObjectLocaleMap.size()); } -struct SQLGameObjectLoader : public SQLStorageLoaderBase +struct SQLGameObjectLoader : public SQLStorageLoaderBase { template void convert_from_str(uint32 /*field_pos*/, char const* src, D& dst) @@ -6111,12 +6172,9 @@ void ObjectMgr::LoadGameobjectInfo() loader.Load(sGOStorage); // some checks - for (uint32 id = 1; id < sGOStorage.MaxEntry; ++id) + for (SQLStorageBase::SQLSIterator itr = sGOStorage.getDataBegin(); itr < sGOStorage.getDataEnd(); ++itr) { - GameObjectInfo const* goInfo = sGOStorage.LookupEntry(id); - if (!goInfo) - continue; - + GameObjectInfo const* goInfo = itr.getValue(); if (goInfo->size <= 0.0f) // prevent use too small scales { @@ -6129,14 +6187,14 @@ void ObjectMgr::LoadGameobjectInfo() switch (goInfo->type) { - case GAMEOBJECT_TYPE_DOOR: //0 + case GAMEOBJECT_TYPE_DOOR: // 0 { if (goInfo->door.lockId) CheckGOLockId(goInfo, goInfo->door.lockId, 1); CheckGONoDamageImmuneId(goInfo, goInfo->door.noDamageImmune, 3); break; } - case GAMEOBJECT_TYPE_BUTTON: //1 + case GAMEOBJECT_TYPE_BUTTON: // 1 { if (goInfo->button.lockId) CheckGOLockId(goInfo, goInfo->button.lockId, 1); @@ -6145,14 +6203,14 @@ void ObjectMgr::LoadGameobjectInfo() CheckGONoDamageImmuneId(goInfo, goInfo->button.noDamageImmune, 4); break; } - case GAMEOBJECT_TYPE_QUESTGIVER: //2 + case GAMEOBJECT_TYPE_QUESTGIVER: // 2 { if (goInfo->questgiver.lockId) CheckGOLockId(goInfo, goInfo->questgiver.lockId, 0); CheckGONoDamageImmuneId(goInfo, goInfo->questgiver.noDamageImmune, 5); break; } - case GAMEOBJECT_TYPE_CHEST: //3 + case GAMEOBJECT_TYPE_CHEST: // 3 { if (goInfo->chest.lockId) CheckGOLockId(goInfo, goInfo->chest.lockId, 0); @@ -6163,7 +6221,7 @@ void ObjectMgr::LoadGameobjectInfo() CheckGOLinkedTrapId(goInfo, goInfo->chest.linkedTrapId, 7); break; } - case GAMEOBJECT_TYPE_TRAP: //6 + case GAMEOBJECT_TYPE_TRAP: // 6 { if (goInfo->trap.lockId) CheckGOLockId(goInfo, goInfo->trap.lockId, 0); @@ -6173,23 +6231,23 @@ void ObjectMgr::LoadGameobjectInfo() */ break; } - case GAMEOBJECT_TYPE_CHAIR: //7 + case GAMEOBJECT_TYPE_CHAIR: // 7 CheckAndFixGOChairHeightId(goInfo, goInfo->chair.height, 1); break; - case GAMEOBJECT_TYPE_SPELL_FOCUS: //8 + case GAMEOBJECT_TYPE_SPELL_FOCUS: // 8 { if (goInfo->spellFocus.focusId) { if (!sSpellFocusObjectStore.LookupEntry(goInfo->spellFocus.focusId)) sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data0=%u but SpellFocus (Id: %u) not exist.", - id, goInfo->type, goInfo->spellFocus.focusId, goInfo->spellFocus.focusId); + goInfo->id, goInfo->type, goInfo->spellFocus.focusId, goInfo->spellFocus.focusId); } if (goInfo->spellFocus.linkedTrapId) // linked trap CheckGOLinkedTrapId(goInfo, goInfo->spellFocus.linkedTrapId, 2); break; } - case GAMEOBJECT_TYPE_GOOBER: //10 + case GAMEOBJECT_TYPE_GOOBER: // 10 { if (goInfo->goober.lockId) CheckGOLockId(goInfo, goInfo->goober.lockId, 0); @@ -6200,7 +6258,7 @@ void ObjectMgr::LoadGameobjectInfo() { if (!sPageTextStore.LookupEntry(goInfo->goober.pageId)) sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data7=%u but PageText (Entry %u) not exist.", - id, goInfo->type, goInfo->goober.pageId, goInfo->goober.pageId); + goInfo->id, goInfo->type, goInfo->goober.pageId, goInfo->goober.pageId); } /* disable check for while, too many nonexistent spells if (goInfo->goober.spellId) // spell @@ -6211,29 +6269,29 @@ void ObjectMgr::LoadGameobjectInfo() CheckGOLinkedTrapId(goInfo, goInfo->goober.linkedTrapId, 12); break; } - case GAMEOBJECT_TYPE_AREADAMAGE: //12 + case GAMEOBJECT_TYPE_AREADAMAGE: // 12 { if (goInfo->areadamage.lockId) CheckGOLockId(goInfo, goInfo->areadamage.lockId, 0); break; } - case GAMEOBJECT_TYPE_CAMERA: //13 + case GAMEOBJECT_TYPE_CAMERA: // 13 { if (goInfo->camera.lockId) CheckGOLockId(goInfo, goInfo->camera.lockId, 0); break; } - case GAMEOBJECT_TYPE_MO_TRANSPORT: //15 + case GAMEOBJECT_TYPE_MO_TRANSPORT: // 15 { if (goInfo->moTransport.taxiPathId) { if (goInfo->moTransport.taxiPathId >= sTaxiPathNodesByPath.size() || sTaxiPathNodesByPath[goInfo->moTransport.taxiPathId].empty()) sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data0=%u but TaxiPath (Id: %u) not exist.", - id, goInfo->type, goInfo->moTransport.taxiPathId, goInfo->moTransport.taxiPathId); + goInfo->id, goInfo->type, goInfo->moTransport.taxiPathId, goInfo->moTransport.taxiPathId); } break; } - case GAMEOBJECT_TYPE_SUMMONING_RITUAL: //18 + case GAMEOBJECT_TYPE_SUMMONING_RITUAL: // 18 { /* disable check for while, too many nonexistent spells // always must have spell @@ -6241,44 +6299,44 @@ void ObjectMgr::LoadGameobjectInfo() */ break; } - case GAMEOBJECT_TYPE_SPELLCASTER: //22 + case GAMEOBJECT_TYPE_SPELLCASTER: // 22 { // always must have spell CheckGOSpellId(goInfo, goInfo->spellcaster.spellId, 0); break; } - case GAMEOBJECT_TYPE_FLAGSTAND: //24 + case GAMEOBJECT_TYPE_FLAGSTAND: // 24 { if (goInfo->flagstand.lockId) CheckGOLockId(goInfo, goInfo->flagstand.lockId, 0); CheckGONoDamageImmuneId(goInfo, goInfo->flagstand.noDamageImmune, 5); break; } - case GAMEOBJECT_TYPE_FISHINGHOLE: //25 + case GAMEOBJECT_TYPE_FISHINGHOLE: // 25 { if (goInfo->fishinghole.lockId) CheckGOLockId(goInfo, goInfo->fishinghole.lockId, 4); break; } - case GAMEOBJECT_TYPE_FLAGDROP: //26 + case GAMEOBJECT_TYPE_FLAGDROP: // 26 { if (goInfo->flagdrop.lockId) CheckGOLockId(goInfo, goInfo->flagdrop.lockId, 0); CheckGONoDamageImmuneId(goInfo, goInfo->flagdrop.noDamageImmune, 3); break; } - case GAMEOBJECT_TYPE_CAPTURE_POINT: //29 + case GAMEOBJECT_TYPE_CAPTURE_POINT: // 29 { CheckAndFixGOCaptureMinTime(goInfo, goInfo->capturePoint.minTime, 16); break; } - case GAMEOBJECT_TYPE_BARBER_CHAIR: //32 + case GAMEOBJECT_TYPE_BARBER_CHAIR: // 32 CheckAndFixGOChairHeightId(goInfo, goInfo->barberChair.chairheight, 0); break; } } - sLog.outString(">> Loaded %u game object templates", sGOStorage.RecordCount); + sLog.outString(">> Loaded %u game object templates", sGOStorage.GetRecordCount()); sLog.outString(); } @@ -6954,7 +7012,6 @@ void ObjectMgr::LoadNPCSpellClickSpells() sLog.outErrorDb("Table npc_spellclick_spells references unknown start quest %u. Skipping entry.", quest_start); continue; } - } bool quest_start_active = fields[3].GetBool(); @@ -6968,7 +7025,6 @@ void ObjectMgr::LoadNPCSpellClickSpells() sLog.outErrorDb("Table npc_spellclick_spells references unknown end quest %u. Skipping entry.", quest_end); continue; } - } uint8 castFlags = fields[5].GetUInt8(); @@ -6995,7 +7051,7 @@ void ObjectMgr::LoadNPCSpellClickSpells() static char* SERVER_SIDE_SPELL = "MaNGOS server-side spell"; -struct SQLSpellLoader : public SQLStorageLoaderBase +struct SQLSpellLoader : public SQLStorageLoaderBase { template void default_fill(uint32 field_pos, S src, D &dst) @@ -7025,10 +7081,10 @@ void ObjectMgr::LoadSpellTemplate() SQLSpellLoader loader; loader.Load(sSpellTemplate); - sLog.outString(">> Loaded %u spell definitions", sSpellTemplate.RecordCount); + sLog.outString(">> Loaded %u spell definitions", sSpellTemplate.GetRecordCount()); sLog.outString(); - for (uint32 i = 1; i < sSpellTemplate.MaxEntry; ++i) + for (uint32 i = 1; i < sSpellTemplate.GetMaxEntry(); ++i) { // check data correctness SpellEntry const* spellEntry = sSpellTemplate.LookupEntry(i); @@ -7473,7 +7529,7 @@ void ObjectMgr::LoadGameObjectForQuests() { mGameObjectForQuestSet.clear(); // need for reload case - if (!sGOStorage.MaxEntry) + if (!sGOStorage.GetMaxEntry()) { BarGoLink bar(1); bar.step(); @@ -7482,25 +7538,21 @@ void ObjectMgr::LoadGameObjectForQuests() return; } - BarGoLink bar(sGOStorage.MaxEntry - 1); + BarGoLink bar(sGOStorage.GetRecordCount()); uint32 count = 0; // collect GO entries for GO that must activated - for (uint32 go_entry = 1; go_entry < sGOStorage.MaxEntry; ++go_entry) + for (SQLStorageBase::SQLSIterator itr = sGOStorage.getDataBegin(); itr < sGOStorage.getDataEnd(); ++itr) { bar.step(); - GameObjectInfo const* goInfo = GetGameObjectInfo(go_entry); - if (!goInfo) - continue; - - switch (goInfo->type) + switch (itr->type) { case GAMEOBJECT_TYPE_QUESTGIVER: { - if (m_GOQuestRelations.find(go_entry) != m_GOQuestRelations.end() || - m_GOQuestInvolvedRelations.find(go_entry) != m_GOQuestInvolvedRelations.end()) + if (m_GOQuestRelations.find(itr->id) != m_GOQuestRelations.end() || + m_GOQuestInvolvedRelations.find(itr->id) != m_GOQuestInvolvedRelations.end()) { - mGameObjectForQuestSet.insert(go_entry); + mGameObjectForQuestSet.insert(itr->id); ++count; } @@ -7509,39 +7561,39 @@ void ObjectMgr::LoadGameObjectForQuests() case GAMEOBJECT_TYPE_CHEST: { // scan GO chest with loot including quest items - uint32 loot_id = goInfo->GetLootId(); + uint32 loot_id = itr->GetLootId(); // always activate to quest, GO may not have loot, OR find if GO has loot for quest. - if (goInfo->chest.questId || LootTemplates_Gameobject.HaveQuestLootFor(loot_id)) + if (itr->chest.questId || LootTemplates_Gameobject.HaveQuestLootFor(loot_id)) { - mGameObjectForQuestSet.insert(go_entry); + mGameObjectForQuestSet.insert(itr->id); ++count; } break; } case GAMEOBJECT_TYPE_GENERIC: { - if (goInfo->_generic.questID) // quest related objects, has visual effects + if (itr->_generic.questID) // quest related objects, has visual effects { - mGameObjectForQuestSet.insert(go_entry); + mGameObjectForQuestSet.insert(itr->id); ++count; } break; } case GAMEOBJECT_TYPE_SPELL_FOCUS: { - if (goInfo->spellFocus.questID) // quest related objects, has visual effect + if (itr->spellFocus.questID) // quest related objects, has visual effect { - mGameObjectForQuestSet.insert(go_entry); + mGameObjectForQuestSet.insert(itr->id); ++count; } break; } case GAMEOBJECT_TYPE_GOOBER: { - if (goInfo->goober.questId) // quests objects + if (itr->goober.questId) // quests objects { - mGameObjectForQuestSet.insert(go_entry); + mGameObjectForQuestSet.insert(itr->id); ++count; } break; @@ -7631,7 +7683,7 @@ bool ObjectMgr::LoadMangosStrings(DatabaseType& db, char const* table, int32 min MangosStringLocale& data = mMangosStringLocaleMap[entry]; - if (data.Content.size() > 0) + if (!data.Content.empty()) { sLog.outErrorDb("Table `%s` contain data for already loaded entry %i (from another table?), ignored.", table, entry); continue; @@ -7742,36 +7794,11 @@ void ObjectMgr::LoadFishingBaseSkillLevel() sLog.outString(">> Loaded %u areas for fishing base skill level", count); } -// Searches for the same condition already in Conditions store -// Returns Id if found, else adds it to Conditions and returns Id -uint16 ObjectMgr::GetConditionId(ConditionType condition, uint32 value1, uint32 value2) -{ - PlayerCondition lc = PlayerCondition(0, condition, value1, value2); - for (uint16 i = 0; i < mConditions.size(); ++i) - { - if (lc == mConditions[i]) - return i; - } - - mConditions.push_back(lc); - - if (mConditions.size() > 0xFFFF) - { - sLog.outError("Conditions store overflow! Current and later loaded conditions will ignored!"); - return 0; - } - - return mConditions.size() - 1; -} - // Check if a player meets condition conditionId -bool ObjectMgr::IsPlayerMeetToNEWCondition(Player const* pPlayer, uint16 conditionId) const +bool ObjectMgr::IsPlayerMeetToCondition(uint16 conditionId, Player const* pPlayer, Map const* map, WorldObject const* source, ConditionSource conditionSourceType) const { - if (!pPlayer) - return false; // player not present, return false - if (const PlayerCondition* condition = sConditionStorage.LookupEntry(conditionId)) - return condition->Meets(pPlayer); + return condition->Meets(pPlayer, map, source, conditionSourceType); return false; } @@ -7790,23 +7817,37 @@ bool ObjectMgr::CheckDeclinedNames(std::wstring mainpart, DeclinedName const& na return true; } -// Checks if player meets the condition -bool PlayerCondition::Meets(Player const* player) const +char const* conditionSourceToStr[] = { - if (!player) - return false; // player not present, return false + "loot system", + "referencing loot", + "gossip menu", + "gossip menu option", + "event AI", + "hardcoded", + "vendor's item check" +}; + +// Checks if player meets the condition +bool PlayerCondition::Meets(Player const* player, Map const* map, WorldObject const* source, ConditionSource conditionSourceType) const +{ + DEBUG_LOG("Condition-System: Check condition %u, type %i - called from %s with params plr: %s, map %i, src %s", + m_entry, m_condition, conditionSourceToStr[conditionSourceType], player ? player->GetGuidStr().c_str() : "", map ? map->GetId() : -1, source ? source->GetGuidStr().c_str() : ""); + + if (!CheckParamRequirements(player, map, source, conditionSourceType)) + return false; switch (m_condition) { case CONDITION_NOT: // Checked on load - return !sConditionStorage.LookupEntry(m_value1)->Meets(player); + return !sConditionStorage.LookupEntry(m_value1)->Meets(player, map, source, conditionSourceType); case CONDITION_OR: // Checked on load - return sConditionStorage.LookupEntry(m_value1)->Meets(player) || sConditionStorage.LookupEntry(m_value2)->Meets(player); + return sConditionStorage.LookupEntry(m_value1)->Meets(player, map, source, conditionSourceType) || sConditionStorage.LookupEntry(m_value2)->Meets(player, map, source, conditionSourceType); case CONDITION_AND: // Checked on load - return sConditionStorage.LookupEntry(m_value1)->Meets(player) && sConditionStorage.LookupEntry(m_value2)->Meets(player); + return sConditionStorage.LookupEntry(m_value1)->Meets(player, map, source, conditionSourceType) && sConditionStorage.LookupEntry(m_value2)->Meets(player, map, source, conditionSourceType); case CONDITION_NONE: return true; // empty condition, always met case CONDITION_AURA: @@ -7818,7 +7859,8 @@ bool PlayerCondition::Meets(Player const* player) const case CONDITION_AREAID: { uint32 zone, area; - player->GetZoneAndAreaId(zone, area); + WorldObject const* searcher = source ? source : player; + searcher->GetZoneAndAreaId(zone, area); return (zone == m_value1 || area == m_value1) == (m_value2 == 0); } case CONDITION_REPUTATION_RANK_MIN: @@ -7833,9 +7875,7 @@ bool PlayerCondition::Meets(Player const* player) const case CONDITION_QUESTREWARDED: return player->GetQuestRewardStatus(m_value1); case CONDITION_QUESTTAKEN: - { return player->IsCurrentQuest(m_value1, m_value2); - } case CONDITION_AD_COMMISSION_AURA: { Unit::SpellAuraHolderMap const& auras = player->GetSpellAuraHolderMap(); @@ -7850,7 +7890,8 @@ bool PlayerCondition::Meets(Player const* player) const return sGameEventMgr.IsActiveEvent(m_value1); case CONDITION_AREA_FLAG: { - if (AreaTableEntry const* pAreaEntry = GetAreaEntryByAreaID(player->GetAreaId())) + WorldObject const* searcher = source ? source : player; + if (AreaTableEntry const* pAreaEntry = GetAreaEntryByAreaID(searcher->GetAreaId())) { if ((!m_value1 || (pAreaEntry->flags & m_value1)) && (!m_value2 || !(pAreaEntry->flags & m_value2))) return true; @@ -7884,19 +7925,16 @@ bool PlayerCondition::Meets(Player const* player) const } case CONDITION_INSTANCE_SCRIPT: { - // have meaning only for specific map instance script so ignore other maps - if (player->GetMapId() != m_value1) - return false; - if (InstanceData* data = player->GetInstanceData()) - return data->CheckConditionCriteriaMeet(player, m_value1, m_value2); + if (!map) + map = player ? player->GetMap() : source->GetMap(); + + if (InstanceData* data = map->GetInstanceData()) + return data->CheckConditionCriteriaMeet(player, m_value1, source, conditionSourceType); return false; } case CONDITION_QUESTAVAILABLE: { - if (Quest const* quest = sObjectMgr.GetQuestTemplate(m_value1)) - return player->CanTakeQuest(quest, false); - else - return false; + return player->CanTakeQuest(sObjectMgr.GetQuestTemplate(m_value1), false); } case CONDITION_ACHIEVEMENT: { @@ -7986,10 +8024,12 @@ bool PlayerCondition::Meets(Player const* player) const return false; } case CONDITION_SKILL_BELOW: + { if (m_value2 == 1) return !player->HasSkill(m_value1); else return player->HasSkill(m_value1) && player->GetBaseSkillValue(m_value1) < m_value2; + } case CONDITION_REPUTATION_RANK_MAX: { FactionEntry const* faction = sFactionStore.LookupEntry(m_value1); @@ -7997,34 +8037,115 @@ bool PlayerCondition::Meets(Player const* player) const } case CONDITION_COMPLETED_ENCOUNTER: { - if (!player->GetMap()->IsDungeon()) + if (!map) + map = player ? player->GetMap() : source->GetMap(); + if (!map->IsDungeon()) { sLog.outErrorDb("CONDITION_COMPLETED_ENCOUNTER (entry %u) is used outside of a dungeon (on Map %u) by %s", m_entry, player->GetMapId(), player->GetGuidStr().c_str()); return false; } - uint32 completedEncounterMask = ((DungeonMap*)player->GetMap())->GetPersistanceState()->GetCompletedEncountersMask(); + uint32 completedEncounterMask = ((DungeonMap*)map)->GetPersistanceState()->GetCompletedEncountersMask(); DungeonEncounterEntry const* dbcEntry1 = sDungeonEncounterStore.LookupEntry(m_value1); DungeonEncounterEntry const* dbcEntry2 = sDungeonEncounterStore.LookupEntry(m_value2); // Check that on proper map - if (dbcEntry1->mapId != player->GetMapId()) + if (dbcEntry1->mapId != map->GetId()) { sLog.outErrorDb("CONDITION_COMPLETED_ENCOUNTER (entry %u, DungeonEncounterEntry %u) is used on wrong map (used on Map %u) by %s", m_entry, m_value1, player->GetMapId(), player->GetGuidStr().c_str()); return false; } // Select matching difficulties - if (player->GetDifficulty(player->GetMap()->IsRaid()) != Difficulty(dbcEntry1->Difficulty)) + if (map->GetDifficulty() != Difficulty(dbcEntry1->Difficulty)) dbcEntry1 = NULL; - if (dbcEntry2 && player->GetDifficulty(player->GetMap()->IsRaid()) != Difficulty(dbcEntry2->Difficulty)) + if (dbcEntry2 && map->GetDifficulty() != Difficulty(dbcEntry2->Difficulty)) dbcEntry2 = NULL; return completedEncounterMask & ((dbcEntry1 ? 1 << dbcEntry1->encounterIndex : 0) | (dbcEntry2 ? 1 << dbcEntry2->encounterIndex : 0)); } + case CONDITION_SOURCE_AURA: + { + if (!source->isType(TYPEMASK_UNIT)) + { + sLog.outErrorDb("CONDITION_SOURCE_AURA (entry %u) is used for non unit source (source %s) by %s", m_entry, source->GetGuidStr().c_str(), player->GetGuidStr().c_str()); + return false; + } + return ((Unit*)source)->HasAura(m_value1, SpellEffectIndex(m_value2)); + } + case CONDITION_LAST_WAYPOINT: + { + if (source->GetTypeId() != TYPEID_UNIT) + { + sLog.outErrorDb("CONDITION_LAST_WAYPOINT (entry %u) is used for non creature source (source %s) by %s", m_entry, source->GetGuidStr().c_str(), player->GetGuidStr().c_str()); + return false; + } + uint32 lastReachedWp = ((Creature*)source)->GetMotionMaster()->getLastReachedWaypoint(); + switch (m_value2) + { + case 0: return m_value1 == lastReachedWp; + case 1: return m_value1 <= lastReachedWp; + case 2: return m_value1 > lastReachedWp; + } + return false; + } default: return false; } } +// Which params must be provided to a Condition +bool PlayerCondition::CheckParamRequirements(Player const* pPlayer, Map const* map, WorldObject const* source, ConditionSource conditionSourceType) const +{ + switch (m_condition) + { + case CONDITION_NOT: + case CONDITION_AND: + case CONDITION_OR: + case CONDITION_NONE: + case CONDITION_ACTIVE_GAME_EVENT: + case CONDITION_ACHIEVEMENT_REALM: + case CONDITION_NOT_ACTIVE_GAME_EVENT: + case CONDITION_ACTIVE_HOLIDAY: + case CONDITION_NOT_ACTIVE_HOLIDAY: + break; + case CONDITION_AREAID: + case CONDITION_AREA_FLAG: + if (!pPlayer && !source) + { + sLog.outErrorDb("CONDITION %u type %u used with bad parameters, called from %s, used with plr: %s, map %i, src %s", + m_entry, m_condition, conditionSourceToStr[conditionSourceType], pPlayer ? pPlayer->GetGuidStr().c_str() : "NULL", map ? map->GetId() : -1, source ? source->GetGuidStr().c_str() : "NULL"); + return false; + } + break; + case CONDITION_INSTANCE_SCRIPT: + case CONDITION_COMPLETED_ENCOUNTER: + if (!pPlayer && !source && !map) + { + sLog.outErrorDb("CONDITION %u type %u used with bad parameters, called from %s, used with plr: %s, map %i, src %s", + m_entry, m_condition, conditionSourceToStr[conditionSourceType], pPlayer ? pPlayer->GetGuidStr().c_str() : "NULL", map ? map->GetId() : -1, source ? source->GetGuidStr().c_str() : "NULL"); + return false; + } + break; + case CONDITION_SOURCE_AURA: + case CONDITION_LAST_WAYPOINT: + if (!source) + { + sLog.outErrorDb("CONDITION %u type %u used with bad parameters, called from %s, used with plr: %s, map %i, src %s", + m_entry, m_condition, conditionSourceToStr[conditionSourceType], pPlayer ? pPlayer->GetGuidStr().c_str() : "NULL", map ? map->GetId() : -1, source ? source->GetGuidStr().c_str() : "NULL"); + return false; + } + break; + default: + if (!pPlayer) + { + sLog.outErrorDb("CONDITION %u type %u used with bad parameters, called from %s, used with plr: %s, map %i, src %s", + m_entry, m_condition, conditionSourceToStr[conditionSourceType], pPlayer ? pPlayer->GetGuidStr().c_str() : "NULL", map ? map->GetId() : -1, source ? source->GetGuidStr().c_str() : "NULL"); + return false; + } + break; + } + return true; +} + // Verification of condition values validity bool PlayerCondition::IsValid(uint16 entry, ConditionType condition, uint32 value1, uint32 value2) { @@ -8073,6 +8194,7 @@ bool PlayerCondition::IsValid(uint16 entry, ConditionType condition, uint32 valu break; } case CONDITION_AURA: + case CONDITION_SOURCE_AURA: { if (!sSpellStore.LookupEntry(value1)) { @@ -8284,16 +8406,7 @@ bool PlayerCondition::IsValid(uint16 entry, ConditionType condition, uint32 valu break; } case CONDITION_INSTANCE_SCRIPT: - { - MapEntry const* mapEntry = sMapStore.LookupEntry(value1); - if (!mapEntry || !mapEntry->IsDungeon()) - { - sLog.outErrorDb("Instance script condition (entry %u, type %u) has nonexistent map id %u as first arg, skipped", entry, condition, value1); - return false; - } - break; - } case CONDITION_ACHIEVEMENT: case CONDITION_ACHIEVEMENT_REALM: { @@ -8364,6 +8477,15 @@ bool PlayerCondition::IsValid(uint16 entry, ConditionType condition, uint32 valu } break; } + case CONDITION_LAST_WAYPOINT: + { + if (value2 > 2) + { + sLog.outErrorDb("Last Waypoint condition (entry %u, type %u) has an invalid value in value2. (Has %u, supported 0, 1, or 2), skipping.", entry, condition, value2); + return false; + } + break; + } case CONDITION_NONE: break; default: @@ -8373,6 +8495,38 @@ bool PlayerCondition::IsValid(uint16 entry, ConditionType condition, uint32 valu return true; } +// Check if a condition can be used without providing a player param +bool PlayerCondition::CanBeUsedWithoutPlayer(uint16 entry) +{ + PlayerCondition const* condition = sConditionStorage.LookupEntry(entry); + if (!condition) + return false; + + switch (condition->m_condition) + { + case CONDITION_NOT: + return CanBeUsedWithoutPlayer(condition->m_value1); + case CONDITION_AND: + case CONDITION_OR: + return CanBeUsedWithoutPlayer(condition->m_value1) && CanBeUsedWithoutPlayer(condition->m_value2); + case CONDITION_NONE: + case CONDITION_ACTIVE_GAME_EVENT: + case CONDITION_ACHIEVEMENT_REALM: + case CONDITION_NOT_ACTIVE_GAME_EVENT: + case CONDITION_ACTIVE_HOLIDAY: + case CONDITION_NOT_ACTIVE_HOLIDAY: + case CONDITION_AREAID: + case CONDITION_AREA_FLAG: + case CONDITION_INSTANCE_SCRIPT: + case CONDITION_COMPLETED_ENCOUNTER: + case CONDITION_SOURCE_AURA: + case CONDITION_LAST_WAYPOINT: + return true; + default: + return false; + } +} + SkillRangeType GetSkillRangeType(SkillLineEntry const* pSkill, bool racial) { switch (pSkill->categoryId) @@ -8759,9 +8913,9 @@ void ObjectMgr::LoadTrainers(char const* tableName, bool isTemplates) } ++count; - } while (result->NextRow()); + delete result; sLog.outString(); @@ -8778,7 +8932,7 @@ void ObjectMgr::LoadTrainerTemplates() for (CacheTrainerSpellMap::const_iterator tItr = m_mCacheTrainerTemplateSpellMap.begin(); tItr != m_mCacheTrainerTemplateSpellMap.end(); ++tItr) trainer_ids.insert(tItr->first); - for (uint32 i = 1; i < sCreatureStorage.MaxEntry; ++i) + for (uint32 i = 1; i < sCreatureStorage.GetMaxEntry(); ++i) { if (CreatureInfo const* cInfo = sCreatureStorage.LookupEntry(i)) { @@ -8807,7 +8961,7 @@ void ObjectMgr::LoadVendors(char const* tableName, bool isTemplates) std::set skip_vendors; - QueryResult* result = WorldDatabase.PQuery("SELECT entry, item, maxcount, incrtime, ExtendedCost FROM %s", tableName); + QueryResult* result = WorldDatabase.PQuery("SELECT entry, item, maxcount, incrtime, ExtendedCost, condition_id FROM %s", tableName); if (!result) { BarGoLink bar(1); @@ -8833,24 +8987,24 @@ void ObjectMgr::LoadVendors(char const* tableName, bool isTemplates) uint32 maxcount = fields[2].GetUInt32(); uint32 incrtime = fields[3].GetUInt32(); uint32 ExtendedCost = fields[4].GetUInt32(); + uint16 conditionId = fields[5].GetUInt16(); - if (!IsVendorItemValid(isTemplates, tableName, entry, item_id, type, maxcount, incrtime, ExtendedCost, NULL, &skip_vendors)) + if (!IsVendorItemValid(isTemplates, tableName, entry, item_id, type, maxcount, incrtime, ExtendedCost, conditionId, NULL, &skip_vendors)) continue; VendorItemData& vList = vendorList[entry]; - vList.AddItem(item_id, type, maxcount, incrtime, ExtendedCost); + vList.AddItem(item_id, type, maxcount, incrtime, ExtendedCost, conditionId); ++count; - } while (result->NextRow()); + delete result; sLog.outString(); sLog.outString(">> Loaded %u vendor %sitems", count, isTemplates ? "template " : ""); } - void ObjectMgr::LoadVendorTemplates() { LoadVendors("npc_vendor_template", true); @@ -8861,7 +9015,7 @@ void ObjectMgr::LoadVendorTemplates() for (CacheVendorItemMap::const_iterator vItr = m_mCacheVendorTemplateItemMap.begin(); vItr != m_mCacheVendorTemplateItemMap.end(); ++vItr) vendor_ids.insert(vItr->first); - for (uint32 i = 1; i < sCreatureStorage.MaxEntry; ++i) + for (uint32 i = 1; i < sCreatureStorage.GetMaxEntry(); ++i) { if (CreatureInfo const* cInfo = sCreatureStorage.LookupEntry(i)) { @@ -8922,9 +9076,9 @@ void ObjectMgr::LoadNpcGossips() m_mCacheNpcTextIdMap[guid] = textid ; ++count; - } while (result->NextRow()); + delete result; sLog.outString(); @@ -8936,8 +9090,8 @@ void ObjectMgr::LoadGossipMenu(std::set& gossipScriptSet) m_mGossipMenusMap.clear(); // 0 1 2 QueryResult* result = WorldDatabase.Query("SELECT entry, text_id, script_id, " - // 3 4 5 6 7 8 9 - "cond_1, cond_1_val_1, cond_1_val_2, cond_2, cond_2_val_1, cond_2_val_2, condition_id FROM gossip_menu"); + // 3 + "condition_id FROM gossip_menu"); if (!result) { @@ -8966,14 +9120,7 @@ void ObjectMgr::LoadGossipMenu(std::set& gossipScriptSet) gMenu.text_id = fields[1].GetUInt32(); gMenu.script_id = fields[2].GetUInt32(); - ConditionType cond_1 = (ConditionType)fields[3].GetUInt32(); - uint32 cond_1_val_1 = fields[4].GetUInt32(); - uint32 cond_1_val_2 = fields[5].GetUInt32(); - ConditionType cond_2 = (ConditionType)fields[6].GetUInt32(); - uint32 cond_2_val_1 = fields[7].GetUInt32(); - uint32 cond_2_val_2 = fields[8].GetUInt32(); - - gMenu.conditionId = fields[9].GetUInt16(); + gMenu.conditionId = fields[3].GetUInt16(); if (!GetGossipText(gMenu.text_id)) { @@ -8986,7 +9133,7 @@ void ObjectMgr::LoadGossipMenu(std::set& gossipScriptSet) { if (sGossipScripts.second.find(gMenu.script_id) == sGossipScripts.second.end()) { - sLog.outErrorDb("Table gossip_menu for menu %u, text-id %u have script_id %u that does not exist in `gossip_scripts`, ignoring", gMenu.entry, gMenu.text_id, gMenu.script_id); + sLog.outErrorDb("Table gossip_menu for menu %u, text-id %u have script_id %u that does not exist in `dbscripts_on_gossip`, ignoring", gMenu.entry, gMenu.text_id, gMenu.script_id); continue; } @@ -8994,21 +9141,6 @@ void ObjectMgr::LoadGossipMenu(std::set& gossipScriptSet) gossipScriptSet.erase(gMenu.script_id); } - if (!PlayerCondition::IsValid(0, cond_1, cond_1_val_1, cond_1_val_2)) - { - sLog.outErrorDb("Table gossip_menu entry %u, invalid condition 1 for id %u", gMenu.entry, gMenu.text_id); - continue; - } - - if (!PlayerCondition::IsValid(0, cond_2, cond_2_val_1, cond_2_val_2)) - { - sLog.outErrorDb("Table gossip_menu entry %u, invalid condition 2 for id %u", gMenu.entry, gMenu.text_id); - continue; - } - - gMenu.cond_1 = GetConditionId(cond_1, cond_1_val_1, cond_1_val_2); - gMenu.cond_2 = GetConditionId(cond_2, cond_2_val_1, cond_2_val_2); - if (gMenu.conditionId) { const PlayerCondition* condition = sConditionStorage.LookupEntry(gMenu.conditionId); @@ -9031,17 +9163,16 @@ void ObjectMgr::LoadGossipMenu(std::set& gossipScriptSet) sLog.outString(">> Loaded %u gossip_menu entries", count); // post loading tests - for (uint32 i = 1; i < sCreatureStorage.MaxEntry; ++i) + for (uint32 i = 1; i < sCreatureStorage.GetMaxEntry(); ++i) if (CreatureInfo const* cInfo = sCreatureStorage.LookupEntry(i)) if (cInfo->GossipMenuId) if (m_mGossipMenusMap.find(cInfo->GossipMenuId) == m_mGossipMenusMap.end()) sLog.outErrorDb("Creature (Entry: %u) has gossip_menu_id = %u for nonexistent menu", cInfo->Entry, cInfo->GossipMenuId); - for (uint32 i = 1; i < sGOStorage.MaxEntry; ++i) - if (GameObjectInfo const* gInfo = sGOStorage.LookupEntry(i)) - if (uint32 menuid = gInfo->GetGossipMenuId()) - if (m_mGossipMenusMap.find(menuid) == m_mGossipMenusMap.end()) - ERROR_DB_STRICT_LOG("Gameobject (Entry: %u) has gossip_menu_id = %u for nonexistent menu", gInfo->id, menuid); + for (SQLStorageBase::SQLSIterator itr = sGOStorage.getDataBegin(); itr < sGOStorage.getDataEnd(); ++itr) + if (uint32 menuid = itr->GetGossipMenuId()) + if (m_mGossipMenusMap.find(menuid) == m_mGossipMenusMap.end()) + ERROR_DB_STRICT_LOG("Gameobject (Entry: %u) has gossip_menu_id = %u for nonexistent menu", itr->id, menuid); } void ObjectMgr::LoadGossipMenuItems(std::set& gossipScriptSet) @@ -9051,9 +9182,7 @@ void ObjectMgr::LoadGossipMenuItems(std::set& gossipScriptSet) QueryResult* result = WorldDatabase.Query( "SELECT menu_id, id, option_icon, option_text, option_id, npc_option_npcflag, " "action_menu_id, action_poi_id, action_script_id, box_coded, box_money, box_text, " - "cond_1, cond_1_val_1, cond_1_val_2, " - "cond_2, cond_2_val_1, cond_2_val_2, " - "cond_3, cond_3_val_1, cond_3_val_2, condition_id " + "condition_id " "FROM gossip_menu_option"); if (!result) @@ -9075,10 +9204,9 @@ void ObjectMgr::LoadGossipMenuItems(std::set& gossipScriptSet) if (itr->first) menu_ids.insert(itr->first); - for (uint32 i = 1; i < sGOStorage.MaxEntry; ++i) - if (GameObjectInfo const* gInfo = sGOStorage.LookupEntry(i)) - if (uint32 menuid = gInfo->GetGossipMenuId()) - menu_ids.erase(menuid); + for (SQLStorageBase::SQLSIterator itr = sGOStorage.getDataBegin(); itr < sGOStorage.getDataEnd(); ++itr) + if (uint32 menuid = itr->GetGossipMenuId()) + menu_ids.erase(menuid); } // loading @@ -9089,7 +9217,7 @@ void ObjectMgr::LoadGossipMenuItems(std::set& gossipScriptSet) // prepare menuid -> CreatureInfo map for fast access typedef std::multimap Menu2CInfoMap; Menu2CInfoMap menu2CInfoMap; - for (uint32 i = 1; i < sCreatureStorage.MaxEntry; ++i) + for (uint32 i = 1; i < sCreatureStorage.GetMaxEntry(); ++i) if (CreatureInfo const* cInfo = sCreatureStorage.LookupEntry(i)) if (cInfo->GossipMenuId) { @@ -9121,16 +9249,7 @@ void ObjectMgr::LoadGossipMenuItems(std::set& gossipScriptSet) gMenuItem.box_money = fields[10].GetUInt32(); gMenuItem.box_text = fields[11].GetCppString(); - ConditionType cond_1 = (ConditionType)fields[12].GetUInt32(); - uint32 cond_1_val_1 = fields[13].GetUInt32(); - uint32 cond_1_val_2 = fields[14].GetUInt32(); - ConditionType cond_2 = (ConditionType)fields[15].GetUInt32(); - uint32 cond_2_val_1 = fields[16].GetUInt32(); - uint32 cond_2_val_2 = fields[17].GetUInt32(); - ConditionType cond_3 = (ConditionType)fields[18].GetUInt32(); - uint32 cond_3_val_1 = fields[19].GetUInt32(); - uint32 cond_3_val_2 = fields[20].GetUInt32(); - gMenuItem.conditionId = fields[21].GetUInt16(); + gMenuItem.conditionId = fields[12].GetUInt16(); if (gMenuItem.menu_id) // == 0 id is special and not have menu_id data { @@ -9141,22 +9260,6 @@ void ObjectMgr::LoadGossipMenuItems(std::set& gossipScriptSet) } } - if (!PlayerCondition::IsValid(0, cond_1, cond_1_val_1, cond_1_val_2)) - { - sLog.outErrorDb("Table gossip_menu_option menu %u, invalid condition 1 for id %u", gMenuItem.menu_id, gMenuItem.id); - continue; - } - if (!PlayerCondition::IsValid(0, cond_2, cond_2_val_1, cond_2_val_2)) - { - sLog.outErrorDb("Table gossip_menu_option menu %u, invalid condition 2 for id %u", gMenuItem.menu_id, gMenuItem.id); - continue; - } - if (!PlayerCondition::IsValid(0, cond_3, cond_3_val_1, cond_3_val_2)) - { - sLog.outErrorDb("Table gossip_menu_option menu %u, invalid condition 3 for id %u", gMenuItem.menu_id, gMenuItem.id); - continue; - } - if (gMenuItem.action_menu_id > 0) { if (m_mGossipMenusMap.find(gMenuItem.action_menu_id) == m_mGossipMenusMap.end()) @@ -9208,7 +9311,7 @@ void ObjectMgr::LoadGossipMenuItems(std::set& gossipScriptSet) { if (sGossipScripts.second.find(gMenuItem.action_script_id) == sGossipScripts.second.end()) { - sLog.outErrorDb("Table gossip_menu_option for menu %u, id %u have action_script_id %u that does not exist in `gossip_scripts`, ignoring", gMenuItem.menu_id, gMenuItem.id, gMenuItem.action_script_id); + sLog.outErrorDb("Table gossip_menu_option for menu %u, id %u have action_script_id %u that does not exist in `dbscripts_on_gossip`, ignoring", gMenuItem.menu_id, gMenuItem.id, gMenuItem.action_script_id); continue; } @@ -9216,10 +9319,6 @@ void ObjectMgr::LoadGossipMenuItems(std::set& gossipScriptSet) gossipScriptSet.erase(gMenuItem.action_script_id); } - gMenuItem.cond_1 = GetConditionId(cond_1, cond_1_val_1, cond_1_val_2); - gMenuItem.cond_2 = GetConditionId(cond_2, cond_2_val_1, cond_2_val_2); - gMenuItem.cond_3 = GetConditionId(cond_3, cond_3_val_1, cond_3_val_2); - if (gMenuItem.conditionId) { const PlayerCondition* condition = sConditionStorage.LookupEntry(gMenuItem.conditionId); @@ -9233,7 +9332,6 @@ void ObjectMgr::LoadGossipMenuItems(std::set& gossipScriptSet) m_mGossipMenuItemsMap.insert(GossipMenuItemsMap::value_type(gMenuItem.menu_id, gMenuItem)); ++count; - } while (result->NextRow()); @@ -9251,7 +9349,7 @@ void ObjectMgr::LoadGossipMenuItems(std::set& gossipScriptSet) void ObjectMgr::LoadGossipMenus() { - // Check which script-ids in gossip_scripts are not used + // Check which script-ids in dbscripts_on_gossip are not used std::set gossipScriptSet; for (ScriptMapMap::const_iterator itr = sGossipScripts.second.begin(); itr != sGossipScripts.second.end(); ++itr) gossipScriptSet.insert(itr->first); @@ -9263,13 +9361,13 @@ void ObjectMgr::LoadGossipMenus() LoadGossipMenuItems(gossipScriptSet); for (std::set::const_iterator itr = gossipScriptSet.begin(); itr != gossipScriptSet.end(); ++itr) - sLog.outErrorDb("Table `gossip_scripts` contains unused script, id %u.", *itr); + sLog.outErrorDb("Table `dbscripts_on_gossip` contains unused script, id %u.", *itr); } void ObjectMgr::AddVendorItem(uint32 entry, uint32 item, uint8 type, uint32 maxcount, uint32 incrtime, uint32 extendedcost) { VendorItemData& vList = m_mCacheVendorItemMap[entry]; - vList.AddItem(item, type, maxcount, incrtime, extendedcost); + vList.AddItem(item, type, maxcount, incrtime, extendedcost, 0); WorldDatabase.PExecuteLog("INSERT INTO npc_vendor (entry,item,maxcount,incrtime,extendedcost) VALUES('%u','%i','%u','%u','%u')", entry, type == VENDOR_ITEM_TYPE_CURRENCY ? -int32(item) : item, maxcount, incrtime, extendedcost); } @@ -9287,7 +9385,7 @@ bool ObjectMgr::RemoveVendorItem(uint32 entry, uint32 item, uint8 type) return true; } -bool ObjectMgr::IsVendorItemValid(bool isTemplate, char const* tableName, uint32 vendor_entry, uint32 item_id, uint8 type, uint32 maxcount, uint32 incrtime, uint32 ExtendedCost, Player* pl, std::set* skip_vendors) const +bool ObjectMgr::IsVendorItemValid(bool isTemplate, char const* tableName, uint32 vendor_entry, uint32 item_id, uint8 type, uint32 maxcount, uint32 incrtime, uint32 ExtendedCost, uint16 conditionId, Player* pl, std::set* skip_vendors) const { char const* idStr = isTemplate ? "vendor template" : "vendor"; char const* nameStr = type == VENDOR_ITEM_TYPE_CURRENCY ? "Currency" : "Item"; @@ -9348,7 +9446,7 @@ bool ObjectMgr::IsVendorItemValid(bool isTemplate, char const* tableName, uint32 } else { - if (currencyEntry->ID == CURRENCY_CONQUEST_ARENA_META || currencyEntry->ID == CURRENCY_CONQUEST_BG_META) + if (currencyEntry->Category == CURRENCY_CATEGORY_META) { if (pl) ChatHandler(pl).PSendSysMessage(LANG_VENDOR_META_CURRENCY_NOT_ALLOWED, item_id); @@ -9412,6 +9510,12 @@ bool ObjectMgr::IsVendorItemValid(bool isTemplate, char const* tableName, uint32 } } + if (conditionId && !sConditionStorage.LookupEntry(conditionId)) + { + sLog.outErrorDb("Table `%s` has `condition_id`=%u for item %u of %s %u but this condition is not valid, ignoring", tableName, conditionId, item_id, idStr, vendor_entry); + return false; + } + VendorItemData const* vItems = isTemplate ? GetNpcVendorTemplateItemList(vendor_entry) : GetNpcVendorItemList(vendor_entry); VendorItemData const* tItems = isTemplate ? NULL : GetNpcVendorTemplateItemList(vendor_entry); @@ -9483,7 +9587,6 @@ void ObjectMgr::RemoveArenaTeam(uint32 Id) mArenaTeamMap.erase(Id); } - void ObjectMgr::GetCreatureLocaleStrings(uint32 entry, int32 loc_idx, char const** namePtr, char const** subnamePtr) const { if (loc_idx >= 0) @@ -9576,6 +9679,31 @@ bool LoadMangosStrings(DatabaseType& db, char const* table, int32 start_value, i return sObjectMgr.LoadMangosStrings(db, table, start_value, end_value); } +void ObjectMgr::LoadCreatureTemplateSpells() +{ + sCreatureTemplateSpellsStorage.Load(); + + sLog.outString(">> Loaded %u creature_template_spells definitions", sCreatureTemplateSpellsStorage.GetRecordCount()); + sLog.outString(); + + for (SQLStorageBase::SQLSIterator itr = sCreatureTemplateSpellsStorage.getDataBegin(); itr < sCreatureTemplateSpellsStorage.getDataEnd(); ++itr) + { + if (!sCreatureStorage.LookupEntry(itr->entry)) + { + sLog.outErrorDb("LoadCreatureTemplateSpells: Spells found for creature entry %u, but creature does not exist, skipping", itr->entry); + sCreatureTemplateSpellsStorage.EraseEntry(itr->entry); + } + for (uint8 i = 0; i < CREATURE_MAX_SPELLS; ++i) + { + if (itr->spells[i] && !sSpellStore.LookupEntry(itr->spells[i])) + { + sLog.outErrorDb("LoadCreatureTemplateSpells: Spells found for creature entry %u, assigned spell %u does not exist, set to 0", itr->entry, itr->spells[i]); + const_cast(*itr)->spells[i] = 0; + } + } + } +} + CreatureInfo const* GetCreatureTemplateStore(uint32 entry) { return sCreatureStorage.LookupEntry(entry); diff --git a/src/game/ScriptMgr.cpp b/src/game/ScriptMgr.cpp index a4f84eab8..94cdff015 100644 --- a/src/game/ScriptMgr.cpp +++ b/src/game/ScriptMgr.cpp @@ -780,7 +780,7 @@ void ScriptMgr::LoadCreatureDeathScripts() LoadScripts(sCreatureDeathScripts, "dbscripts_on_creature_death"); // check ids - for(ScriptMapMap::const_iterator itr = sCreatureDeathScripts.second.begin(); itr != sCreatureDeathScripts.second.end(); ++itr) + for (ScriptMapMap::const_iterator itr = sCreatureDeathScripts.second.begin(); itr != sCreatureDeathScripts.second.end(); ++itr) { if (!sObjectMgr.GetCreatureTemplate(itr->first)) sLog.outErrorDb("Table `dbscripts_on_creature_death` has not existing creature (Entry: %u) as script id", itr->first); @@ -1667,12 +1667,12 @@ bool ScriptAction::HandleScriptStep() if (result) // Terminate further steps of this script { - if (m_script->textId[0] && !LogIfNotCreature(pSource)) - { - Creature* cSource = static_cast(pSource); - if (cSource->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE) - (static_cast* >(cSource->GetMotionMaster()->top()))->AddToWaypointPauseTime(m_script->textId[0]); - } + if (m_script->textId[0] && !LogIfNotCreature(pSource)) + { + Creature* cSource = static_cast(pSource); + if (cSource->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE) + (static_cast* >(cSource->GetMotionMaster()->top()))->AddToWaypointPauseTime(m_script->textId[0]); + } return true; } diff --git a/src/game/ScriptMgr.h b/src/game/ScriptMgr.h index 9871ac636..d8eaee3f3 100644 --- a/src/game/ScriptMgr.h +++ b/src/game/ScriptMgr.h @@ -397,9 +397,9 @@ class ScriptAction bool IsSameScript(const char* table, uint32 id, ObjectGuid sourceGuid, ObjectGuid targetGuid, ObjectGuid ownerGuid) const { return table == m_table && id == GetId() && - (sourceGuid == m_sourceGuid || !sourceGuid) && - (targetGuid == m_targetGuid || !targetGuid) && - (ownerGuid == m_ownerGuid || !ownerGuid); + (sourceGuid == m_sourceGuid || !sourceGuid) && + (targetGuid == m_targetGuid || !targetGuid) && + (ownerGuid == m_ownerGuid || !ownerGuid); } private: diff --git a/src/game/Spell.cpp b/src/game/Spell.cpp index 523205699..bb43da398 100644 --- a/src/game/Spell.cpp +++ b/src/game/Spell.cpp @@ -1687,7 +1687,7 @@ void Spell::SetTargetMap(SpellEffectIndex effIndex, uint32 targetMode, UnitList& // and no unitTarget (e.g. summon effects). As MaNGOS always needs a unitTarget we add just the caster here. // Logic: This is first target, and no second target => use m_caster -- This is second target: use m_caster if the spell is positive or a summon spell if ((spellEffect->EffectImplicitTargetA == targetMode && spellEffect->EffectImplicitTargetB == TARGET_NONE) || - (spellEffect->EffectImplicitTargetB == targetMode && (IsPositiveSpell(m_spellInfo) || spellEffect->Effect == SPELL_EFFECT_SUMMON))) + (spellEffect->EffectImplicitTargetB == targetMode && (IsPositiveSpell(m_spellInfo) || spellEffect->Effect == SPELL_EFFECT_SUMMON))) targetUnitMap.push_back(m_caster); break; } @@ -1724,7 +1724,7 @@ void Spell::SetTargetMap(SpellEffectIndex effIndex, uint32 targetMode, UnitList& // and no unitTarget (e.g. summon effects). As MaNGOS always needs a unitTarget we add just the caster here. // Logic: This is first target, and no second target => use m_caster -- This is second target: use m_caster if the spell is positive or a summon spell if ((spellEffect->EffectImplicitTargetA == targetMode && spellEffect->EffectImplicitTargetB == TARGET_NONE) || - (spellEffect->EffectImplicitTargetB == targetMode && (IsPositiveSpell(m_spellInfo) || spellEffect->Effect == SPELL_EFFECT_SUMMON))) + (spellEffect->EffectImplicitTargetB == targetMode && (IsPositiveSpell(m_spellInfo) || spellEffect->Effect == SPELL_EFFECT_SUMMON))) targetUnitMap.push_back(m_caster); break; diff --git a/src/game/SpellEffects.cpp b/src/game/SpellEffects.cpp index 49c80dda3..557a5d14f 100644 --- a/src/game/SpellEffects.cpp +++ b/src/game/SpellEffects.cpp @@ -5546,7 +5546,7 @@ bool Spell::DoSummonVehicle(CreatureSummonPositions& list, SummonPropertiesEntry } Creature* spawnCreature = m_caster->SummonCreature(creatureEntry, list[0].x, list[0].y, list[0].z, m_caster->GetOrientation(), - (m_duration == 0) ? TEMPSUMMON_CORPSE_DESPAWN : TEMPSUMMON_TIMED_OOC_OR_CORPSE_DESPAWN, m_duration); + (m_duration == 0) ? TEMPSUMMON_CORPSE_DESPAWN : TEMPSUMMON_TIMED_OOC_OR_CORPSE_DESPAWN, m_duration); if (!spawnCreature) { @@ -10039,7 +10039,7 @@ void Spell::EffectWMOChange(SpellEffectEntry const* effect) return; } - DEBUG_LOG("Spell::EffectWMOChange, spell Id %u, object %u, misc-value %u", m_spellInfo->Id,gameObjTarget->GetEntry(), effect->EffectMiscValue); + DEBUG_LOG("Spell::EffectWMOChange, spell Id %u, object %u, misc-value %u", m_spellInfo->Id, gameObjTarget->GetEntry(), effect->EffectMiscValue); Unit* caster = GetAffectiveCaster(); if (!caster) @@ -10060,7 +10060,7 @@ void Spell::EffectWMOChange(SpellEffectEntry const* effect) gameObjTarget->ForceGameObjectHealth(0, caster); break; default: - sLog.outError("Spell::EffectWMOChange, spell Id %u with undefined change value %u", m_spellInfo->Id,effect->EffectMiscValue); + sLog.outError("Spell::EffectWMOChange, spell Id %u with undefined change value %u", m_spellInfo->Id, effect->EffectMiscValue); break; } } diff --git a/src/game/SpellMgr.cpp b/src/game/SpellMgr.cpp index 0f4acac2c..d1a313e62 100644 --- a/src/game/SpellMgr.cpp +++ b/src/game/SpellMgr.cpp @@ -3429,7 +3429,7 @@ void SpellMgr::LoadSpellScriptTarget() if (bounds.first == bounds.second) { sLog.outErrorDb("Spell (ID: %u) has effect EffectImplicitTargetA/EffectImplicitTargetB = %u (TARGET_SCRIPT), but does not have record in `spell_script_target`", spellInfo->Id, TARGET_SCRIPT); - break; // effects of spell + break; // effects of spell } } } diff --git a/src/game/SpellMgr.h b/src/game/SpellMgr.h index b0970b8dc..c273c229e 100644 --- a/src/game/SpellMgr.h +++ b/src/game/SpellMgr.h @@ -527,7 +527,7 @@ inline bool IsNeedCastSpellAtFormApply(SpellEntry const* spellInfo, ShapeshiftFo // passive spells with SPELL_ATTR_EX2_NOT_NEED_SHAPESHIFT are already active without shapeshift, do no recast! return (shapeShift->Stances & (1<<(form-1)) && !(spellInfo->AttributesEx2 & SPELL_ATTR_EX2_NOT_NEED_SHAPESHIFT)); } - + inline bool IsNeedCastSpellAtOutdoor(SpellEntry const* spellInfo) { return (spellInfo->HasAttribute(SPELL_ATTR_OUTDOORS_ONLY) && spellInfo->HasAttribute(SPELL_ATTR_PASSIVE)); diff --git a/src/game/Unit.cpp b/src/game/Unit.cpp index 33de823e5..72b1034a6 100644 --- a/src/game/Unit.cpp +++ b/src/game/Unit.cpp @@ -623,9 +623,16 @@ void Unit::Update(uint32 update_diff, uint32 p_time) setAttackTimer(OFF_ATTACK, (update_diff >= base_att ? 0 : base_att - update_diff)); } - // Update passenger positions if we are the first vehicle - if (IsVehicle() && !IsBoarded()) - m_vehicleInfo->Update(update_diff); + if (IsVehicle()) + { + // Initialize vehicle if not done + if (isAlive() && !m_vehicleInfo->IsInitialized()) + m_vehicleInfo->Initialize(); + + // Update passenger positions if we are the first vehicle + if (!IsBoarded()) + m_vehicleInfo->Update(update_diff); + } // update abilities available only for fraction of time UpdateReactives(update_diff); @@ -653,7 +660,7 @@ bool Unit::UpdateMeleeAttackingState() setAttackTimer(OFF_ATTACK, 100); swingError = 1; } - //120 degrees of radiant range + // 120 degrees of radiant range else if (!HasInArc(2 * M_PI_F / 3, victim)) { setAttackTimer(BASE_ATTACK, 100); @@ -781,7 +788,7 @@ void Unit::RemoveSpellsCausingAura(AuraType auraType, ObjectGuid casterGuid) { if ((*iter)->GetCasterGuid() == casterGuid) { - RemoveAuraHolderFromStack((*iter)->GetId(), 1, casterGuid); + RemoveSpellAuraHolder((*iter)->GetHolder()); iter = m_modAuras[auraType].begin(); } else @@ -852,15 +859,12 @@ uint32 Unit::DealDamage(Unit* pVictim, uint32 damage, CleanDamage const* cleanDa // Critter may not die of damage taken, instead expect it to run away (no fighting back) // If (this) is TYPEID_PLAYER, (this) will enter combat w/victim, but after some time, automatically leave combat. // It is unclear how it should work for other cases. - DEBUG_FILTER_LOG(LOG_FILTER_DAMAGE, "DealDamage critter, critter dies"); - pVictim->SetDeathState(JUST_DIED); - pVictim->SetHealth(0); - ((Creature*)pVictim)->SetLootRecipient(this); - JustKilledCreature((Creature*)pVictim); + JustKilledCreature((Creature*)pVictim, NULL); + pVictim->SetHealth(0); return damage; } @@ -950,10 +954,12 @@ uint32 Unit::DealDamage(Unit* pVictim, uint32 damage, CleanDamage const* cleanDa if (health <= damage) { - DEBUG_FILTER_LOG(LOG_FILTER_DAMAGE, "DealDamage: victim just died"); + DEBUG_FILTER_LOG(LOG_FILTER_DAMAGE, "DealDamage %s Killed %s", GetGuidStr().c_str(), pVictim->GetGuidStr().c_str()); - // find player: owner of controlled `this` or `this` itself maybe - // for loot will be sued only if group_tap==NULL + /* + * Preparation: Who gets credit for killing whom, invoke SpiritOfRedemtion? + */ + // for loot will be used only if group_tap == NULL Player* player_tap = GetCharmerOrOwnerPlayerOrPlayerItself(); Group* group_tap = NULL; @@ -972,45 +978,8 @@ uint32 Unit::DealDamage(Unit* pVictim, uint32 damage, CleanDamage const* cleanDa group_tap = player_tap->GetGroup(); } - if (pVictim->GetTypeId() == TYPEID_PLAYER) - { - ((Player*)pVictim)->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_TOTAL_DAMAGE_RECEIVED, health); - if (player_tap) - player_tap->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_SPECIAL_PVP_KILL, 1, 0, pVictim); - } - - // call kill spell proc event (before real die and combat stop to triggering auras removed at death/combat stop) - if (player_tap && player_tap != pVictim) - { - player_tap->ProcDamageAndSpell(pVictim, PROC_FLAG_KILL, PROC_FLAG_KILLED, PROC_EX_NONE, 0); - - WorldPacket data(SMSG_PARTYKILLLOG, (8 + 8)); // send event PARTY_KILL - data << player_tap->GetObjectGuid(); // player with killing blow - data << pVictim->GetObjectGuid(); // victim - - if (group_tap) - group_tap->BroadcastPacket(&data, false, group_tap->GetMemberGroup(player_tap->GetObjectGuid()), player_tap->GetObjectGuid()); - - player_tap->SendDirectMessage(&data); - } - - // Reward player, his pets, and group/raid members - if (player_tap != pVictim) - { - if (group_tap) - group_tap->RewardGroupAtKill(pVictim, player_tap); - else if (player_tap) - player_tap->RewardSinglePlayerAtKill(pVictim); - } - - DEBUG_FILTER_LOG(LOG_FILTER_DAMAGE, "DealDamageAttackStop"); - - // stop combat - pVictim->CombatStop(); - pVictim->getHostileRefManager().deleteReferences(); - + // Spirit of Redemtion Talent bool damageFromSpiritOfRedemtionTalent = spellProto && spellProto->Id == 27795; - // if talent known but not triggered (check priest class for speedup check) Aura* spiritOfRedemtionTalentReady = NULL; if (!damageFromSpiritOfRedemtionTalent && // not called from SPELL_AURA_SPIRIT_OF_REDEMPTION @@ -1027,16 +996,45 @@ uint32 Unit::DealDamage(Unit* pVictim, uint32 damage, CleanDamage const* cleanDa } } - if (!spiritOfRedemtionTalentReady) + /* + * Generic Actions (ProcEvents, Combat-Log, Kill Rewards, Stop Combat) + */ + // call kill spell proc event (before real die and combat stop to triggering auras removed at death/combat stop) + if (player_tap && player_tap != pVictim) { - DEBUG_FILTER_LOG(LOG_FILTER_DAMAGE, "SET JUST_DIED"); - pVictim->SetDeathState(JUST_DIED); + player_tap->ProcDamageAndSpell(pVictim, PROC_FLAG_KILL, PROC_FLAG_KILLED, PROC_EX_NONE, 0); + + WorldPacket data(SMSG_PARTYKILLLOG, (8 + 8)); // send event PARTY_KILL + data << player_tap->GetObjectGuid(); // player with killing blow + data << pVictim->GetObjectGuid(); // victim + + if (group_tap) + group_tap->BroadcastPacket(&data, false, group_tap->GetMemberGroup(player_tap->GetObjectGuid()), player_tap->GetObjectGuid()); + + player_tap->SendDirectMessage(&data); } - DEBUG_FILTER_LOG(LOG_FILTER_DAMAGE, "DealDamageHealth1"); + // Reward player, his pets, and group/raid members + if (player_tap != pVictim) + { + if (group_tap) + group_tap->RewardGroupAtKill(pVictim, player_tap); + else if (player_tap) + player_tap->RewardSinglePlayerAtKill(pVictim); + } + // stop combat + DEBUG_FILTER_LOG(LOG_FILTER_DAMAGE, "DealDamageAttackStop"); + pVictim->CombatStop(); + pVictim->getHostileRefManager().deleteReferences(); + + /* + * Actions for the killer + */ if (spiritOfRedemtionTalentReady) { + DEBUG_FILTER_LOG(LOG_FILTER_DAMAGE, "DealDamage: Spirit of Redemtion ready"); + // save value before aura remove uint32 ressSpellId = pVictim->GetUInt32Value(PLAYER_SELF_RES_SPELL); if (!ressSpellId) @@ -1054,11 +1052,6 @@ uint32 Unit::DealDamage(Unit* pVictim, uint32 damage, CleanDamage const* cleanDa else pVictim->SetHealth(0); - // remember victim PvP death for corpse type and corpse reclaim delay - // at original death (not at SpiritOfRedemtionTalent timeout) - if (pVictim->GetTypeId() == TYPEID_PLAYER && !damageFromSpiritOfRedemtionTalent) - ((Player*)pVictim)->SetPvPDeath(player_tap != NULL); - // Call KilledUnit for creatures if (GetTypeId() == TYPEID_UNIT && ((Creature*)this)->AI()) ((Creature*)this)->AI()->KilledUnit(pVictim); @@ -1066,73 +1059,70 @@ uint32 Unit::DealDamage(Unit* pVictim, uint32 damage, CleanDamage const* cleanDa // Call AI OwnerKilledUnit (for any current summoned minipet/guardian/protector) PetOwnerKilledUnit(pVictim); - // achievement stuff - if (pVictim->GetTypeId() == TYPEID_PLAYER) + /* + * Actions for the victim + */ + if (pVictim->GetTypeId() == TYPEID_PLAYER) // Killed player { - if (GetTypeId() == TYPEID_UNIT) - ((Player*)pVictim)->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_CREATURE, GetEntry()); - else if (GetTypeId() == TYPEID_PLAYER && pVictim != this) - ((Player*)pVictim)->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_PLAYER, 1, ((Player*)this)->GetTeam()); - } + Player* playerVictim = (Player*)pVictim; - // 10% durability loss on death - // clean InHateListOf - if (pVictim->GetTypeId() == TYPEID_PLAYER) - { + // remember victim PvP death for corpse type and corpse reclaim delay + // at original death (not at SpiritOfRedemtionTalent timeout) + if (!damageFromSpiritOfRedemtionTalent) + playerVictim->SetPvPDeath(player_tap != NULL); + + // achievement stuff + playerVictim->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_TOTAL_DAMAGE_RECEIVED, health); + if (player_tap) + player_tap->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_SPECIAL_PVP_KILL, 1, 0, pVictim); + if (GetTypeId() == TYPEID_UNIT) + playerVictim->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_CREATURE, GetEntry()); + else if (GetTypeId() == TYPEID_PLAYER && pVictim != this) + playerVictim->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_PLAYER, 1, playerVictim->GetTeam()); + + // 10% durability loss on death // only if not player and not controlled by player pet. And not at BG - if (durabilityLoss && !player_tap && !((Player*)pVictim)->InBattleGround()) + if (durabilityLoss && !player_tap && !playerVictim->InBattleGround()) { - DEBUG_LOG("We are dead, loosing 10 percents durability"); - ((Player*)pVictim)->DurabilityLossAll(0.10f, false); + DEBUG_LOG("DealDamage: Killed %s, looing 10 percents durability", pVictim->GetGuidStr().c_str()); + playerVictim->DurabilityLossAll(0.10f, false); // durability lost message WorldPacket data(SMSG_DURABILITY_DAMAGE_DEATH, 0); - ((Player*)pVictim)->GetSession()->SendPacket(&data); + playerVictim->GetSession()->SendPacket(&data); } - } - else // creature died - { - DEBUG_FILTER_LOG(LOG_FILTER_DAMAGE, "DealDamage Killed NPC"); - JustKilledCreature((Creature*)pVictim); - } - // last damage from non duel opponent or opponent controlled creature - if (duel_hasEnded) - { - MANGOS_ASSERT(pVictim->GetTypeId() == TYPEID_PLAYER); - Player* he = (Player*)pVictim; - - MANGOS_ASSERT(he->duel); - - he->duel->opponent->CombatStopWithPets(true); - he->CombatStopWithPets(true); - - he->DuelComplete(DUEL_INTERUPTED); - } - - // handle player/npc kill in battleground or outdoor pvp script - if (player_tap) - { - if (pVictim->GetTypeId() == TYPEID_PLAYER) + if (!spiritOfRedemtionTalentReady) // Before informing Battleground { - Player* killed = (Player*)pVictim; - if (killed->InBattleGround()) + DEBUG_FILTER_LOG(LOG_FILTER_DAMAGE, "SET JUST_DIED"); + pVictim->SetDeathState(JUST_DIED); + } + + // playerVictim was in duel, duel must be interrupted + // last damage from non duel opponent or non opponent controlled creature + if (duel_hasEnded) + { + playerVictim->duel->opponent->CombatStopWithPets(true); + playerVictim->CombatStopWithPets(true); + + playerVictim->DuelComplete(DUEL_INTERRUPTED); + } + + if (player_tap) // PvP kill + { + if (BattleGround* bg = playerVictim->GetBattleGround()) { - if (BattleGround* bg = killed->GetBattleGround()) - bg->HandleKillPlayer(killed, player_tap); + bg->HandleKillPlayer(playerVictim, player_tap); } else if (pVictim != this) { // selfkills are not handled in outdoor pvp scripts - if (OutdoorPvP* outdoorPvP = sOutdoorPvPMgr.GetScript(player_tap->GetCachedZoneId())) - outdoorPvP->HandlePlayerKill(player_tap, killed); + if (OutdoorPvP* outdoorPvP = sOutdoorPvPMgr.GetScript(playerVictim->GetCachedZoneId())) + outdoorPvP->HandlePlayerKill(player_tap, playerVictim); } } - else if (pVictim->GetTypeId() == TYPEID_UNIT) - { - if (BattleGround* bg = player_tap->GetBattleGround()) - bg->HandleKillUnit((Creature*)pVictim, player_tap); - } } + else // Killed creature + JustKilledCreature((Creature*)pVictim, player_tap); } else // if (health <= damage) { @@ -1294,16 +1284,10 @@ struct PetOwnerKilledUnitHelper Unit* m_victim; }; -void Unit::JustKilledCreature(Creature* victim) +void Unit::JustKilledCreature(Creature* victim, Player* responsiblePlayer) { - if (!victim->IsPet()) // Prepare loot if can - { - victim->DeleteThreatList(); - // only lootable if it has loot or can drop gold - victim->PrepareBodyLootState(); - // may have no loot, so update death timer if allowed - victim->AllLootRemovedFromCorpse(); - } + victim->m_deathState = DEAD; // so that isAlive, isDead return expected results in the called hooks of JustKilledCreature + // must be used only shortly before SetDeathState(JUST_DIED) and only for Creatures or Pets // some critters required for quests (need normal entry instead possible heroic in any cases) if (victim->GetCreatureType() == CREATURE_TYPE_CRITTER && GetTypeId() == TYPEID_PLAYER) @@ -1350,10 +1334,16 @@ void Unit::JustKilledCreature(Creature* victim) if (InstanceData* mapInstance = victim->GetInstanceData()) mapInstance->OnCreatureDeath(victim); + if (responsiblePlayer) // killedby Player, inform BG + if (BattleGround* bg = responsiblePlayer->GetBattleGround()) + bg->HandleKillUnit(victim, responsiblePlayer); // Notify the outdoor pvp script - if (OutdoorPvP* outdoorPvP = sOutdoorPvPMgr.GetScript(GetZoneId())) + if (OutdoorPvP* outdoorPvP = sOutdoorPvPMgr.GetScript(responsiblePlayer ? responsiblePlayer->GetCachedZoneId() : GetZoneId())) outdoorPvP->HandleCreatureDeath(victim); + // Start creature death script + GetMap()->ScriptsStart(sCreatureDeathScripts, victim->GetEntry(), victim, responsiblePlayer ? responsiblePlayer : this); + if (victim->IsLinkingEventTrigger()) victim->GetMap()->GetCreatureLinkingHolder()->DoCreatureLinkingEvent(LINKING_EVENT_DIE, victim); @@ -1385,6 +1375,22 @@ void Unit::JustKilledCreature(Creature* victim) ((DungeonMap*)m)->GetPersistanceState()->UpdateEncounterState(ENCOUNTER_CREDIT_KILL_CREATURE, victim->GetEntry()); } } + + bool isPet = victim->IsPet(); + + /* ********************************* Set Death finally ************************************* */ + DEBUG_FILTER_LOG(LOG_FILTER_DAMAGE, "SET JUST_DIED"); + victim->SetDeathState(JUST_DIED); // if !spiritOfRedemtionTalentReady always true for unit + + if (isPet) + return; // Pets might have been unsummoned at this place, do not handle them further! + + /* ******************************** Prepare loot if can ************************************ */ + victim->DeleteThreatList(); + // only lootable if it has loot or can drop gold + victim->PrepareBodyLootState(); + // may have no loot, so update death timer if allowed, must be after SetDeathState(JUST_DIED) + victim->AllLootRemovedFromCorpse(); } void Unit::PetOwnerKilledUnit(Unit* pVictim) @@ -1437,6 +1443,15 @@ void Unit::CastSpell(Unit* Victim, SpellEntry const* spellInfo, bool triggered, triggeredBy = triggeredByAura->GetSpellProto(); } + else + { + triggeredByAura = GetTriggeredByClientAura(spellInfo->Id); + if (triggeredByAura) + { + triggered = true; + triggeredBy = triggeredByAura->GetSpellProto(); + } + } Spell* spell = new Spell(this, spellInfo, triggered, originalCaster, triggeredBy); @@ -1816,10 +1831,6 @@ void Unit::CalculateMeleeDamage(Unit* pVictim, uint32 damage, CalcDamageInfo* da mod += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_CRIT_DAMAGE_BONUS, SPELL_SCHOOL_MASK_NORMAL); - uint32 crTypeMask = damageInfo->target->GetCreatureTypeMask(); - - // Increase crit damage from SPELL_AURA_MOD_CRIT_PERCENT_VERSUS - mod += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_CRIT_PERCENT_VERSUS, crTypeMask); if (mod != 0) damageInfo->damage = int32((damageInfo->damage) * float((100.0f + mod) / 100.0f)); @@ -1961,7 +1972,6 @@ void Unit::CalculateMeleeDamage(Unit* pVictim, uint32 damage, CalcDamageInfo* da } if (damageInfo->resist) damageInfo->HitInfo |= HITINFO_RESIST; - } else // Umpossible get negative result but.... damageInfo->damage = 0; @@ -2071,13 +2081,13 @@ void Unit::DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss) uint32 damage = (*i)->GetModifier()->m_amount; SpellEntry const* i_spellProto = (*i)->GetSpellProto(); - uint32 absorb, resist; - CalculateDamageAbsorbAndResist(pVictim, GetSpellSchoolMask(i_spellProto), SPELL_DIRECT_DAMAGE, damage, &absorb, &resist); + //uint32 absorb, resist; + //CalculateDamageAbsorbAndResist(pVictim, GetSpellSchoolMask(i_spellProto), SPELL_DIRECT_DAMAGE, damage, &absorb, &resist); - if (damage >= absorb + resist) - damage -= absorb + resist; - else - damage = 0; + //if (damage >= absorb + resist) + // damage -= absorb + resist; + //else + // damage = 0; pVictim->DealDamageMods(this, damage, NULL); @@ -2091,7 +2101,7 @@ void Unit::DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss) data << uint32(damage); // Damage data << uint32(overkill); // Overkill data << uint32(i_spellProto->SchoolMask); - data << uint32(resist); // Resist + data << uint32(0); // FIXME: Resist pVictim->SendMessageToSet(&data, true); pVictim->DealDamage(this, damage, 0, SPELL_DIRECT_DAMAGE, GetSpellSchoolMask(i_spellProto), i_spellProto, true); @@ -2104,9 +2114,9 @@ void Unit::DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss) } } - void Unit::HandleEmoteCommand(uint32 emote_id) { + DEBUG_LOG("SMSG_EMOTE %u"); WorldPacket data(SMSG_EMOTE, 4 + 8); data << uint32(emote_id); data << GetObjectGuid(); @@ -2637,39 +2647,6 @@ void Unit::CalculateDamageAbsorbAndResist(Unit* pCaster, SpellSchoolMask schoolM // only split damage if not damaging yourself if (pCaster != this) { - AuraList const& vSplitDamageFlat = GetAurasByType(SPELL_AURA_SPLIT_DAMAGE_FLAT); - for (AuraList::const_iterator i = vSplitDamageFlat.begin(), next; i != vSplitDamageFlat.end() && RemainingDamage >= 0; i = next) - { - next = i; ++next; - - // check damage school mask - if (((*i)->GetModifier()->m_miscvalue & schoolMask) == 0) - continue; - - // Damage can be splitted only if aura has an alive caster - Unit* caster = (*i)->GetCaster(); - if (!caster || caster == this || !caster->IsInWorld() || !caster->isAlive()) - continue; - - int32 currentAbsorb; - if (RemainingDamage >= (*i)->GetModifier()->m_amount) - currentAbsorb = (*i)->GetModifier()->m_amount; - else - currentAbsorb = RemainingDamage; - - RemainingDamage -= currentAbsorb; - - - uint32 splitted = currentAbsorb; - uint32 splitted_absorb = 0; - pCaster->DealDamageMods(caster, splitted, &splitted_absorb); - - pCaster->SendSpellNonMeleeDamageLog(caster, (*i)->GetSpellProto()->Id, splitted, schoolMask, splitted_absorb, 0, false, 0, false); - - CleanDamage cleanDamage = CleanDamage(splitted, BASE_ATTACK, MELEE_HIT_NORMAL); - pCaster->DealDamage(caster, splitted, &cleanDamage, DIRECT_DAMAGE, schoolMask, (*i)->GetSpellProto(), false); - } - AuraList const& vSplitDamagePct = GetAurasByType(SPELL_AURA_SPLIT_DAMAGE_PCT); for (AuraList::const_iterator i = vSplitDamagePct.begin(), next; i != vSplitDamagePct.end() && RemainingDamage >= 0; i = next) { @@ -2760,7 +2737,7 @@ void Unit::CalculateAbsorbResistBlock(Unit* pCaster, SpellNonMeleeDamage* damage } uint32 absorb_affected_damage = pCaster->CalcNotIgnoreAbsorbDamage(damageInfo->damage, GetSpellSchoolMask(spellProto), spellProto); - CalculateDamageAbsorbAndResist(pCaster, GetSpellSchoolMask(spellProto), SPELL_DIRECT_DAMAGE, absorb_affected_damage, &damageInfo->absorb, &damageInfo->resist, !spellProto->HasAttribute(SPELL_ATTR_EX2_CANT_REFLECTED)); + CalculateDamageAbsorbAndResist(pCaster, GetSpellSchoolMask(spellProto), SPELL_DIRECT_DAMAGE, absorb_affected_damage, &damageInfo->absorb, &damageInfo->resist, !spellProto->HasAttribute(SPELL_ATTR_EX_CANT_REFLECTED)); damageInfo->damage -= damageInfo->absorb + damageInfo->resist; } @@ -2926,16 +2903,14 @@ MeleeHitOutcome Unit::RollMeleeOutcomeAgainst(const Unit* pVictim, WeaponAttackT int32 victimMaxSkillValueForLevel = pVictim->GetMaxSkillValueForLevel(this); // bonus from skills is 0.04% - int32 skillBonus = 4 * (attackerMaxSkillValueForLevel - victimMaxSkillValueForLevel); - int32 sum = 0, tmp = 0; - int32 roll = urand(0, 10000); + int32 skillBonus = 4 * (attackerMaxSkillValueForLevel - victimMaxSkillValueForLevel); + int32 sum = 0, tmp = 0; + int32 roll = urand(0, 10000); DEBUG_FILTER_LOG(LOG_FILTER_COMBAT, "RollMeleeOutcomeAgainst: skill bonus of %d for attacker", skillBonus); DEBUG_FILTER_LOG(LOG_FILTER_COMBAT, "RollMeleeOutcomeAgainst: rolled %d, miss %d, dodge %d, parry %d, block %d, crit %d", roll, miss_chance, dodge_chance, parry_chance, block_chance, crit_chance); - tmp = miss_chance; - if (tmp > 0 && roll < (sum += tmp)) { DEBUG_FILTER_LOG(LOG_FILTER_COMBAT, "RollMeleeOutcomeAgainst: MISS"); @@ -3119,7 +3094,7 @@ uint32 Unit::CalculateDamage(WeaponAttackType attType, bool normalized) float Unit::CalculateLevelPenalty(SpellEntry const* spellProto) const { uint32 spellLevel = spellProto->GetSpellLevel(); - if(spellLevel <= 0) + if (spellLevel <= 0 || spellLevel > spellProto->GetMaxLevel()) return 1.0f; float LvlPenalty = 0.0f; @@ -3407,13 +3382,8 @@ SpellMissInfo Unit::MagicSpellHitResult(Unit* pVictim, SpellEntry const* spell) // Spellmod from SPELLMOD_RESIST_MISS_CHANCE if (Player* modOwner = GetSpellModOwner()) modOwner->ApplySpellMod(spell->Id, SPELLMOD_RESIST_MISS_CHANCE, modHitChance); - // Increase from attacker SPELL_AURA_MOD_INCREASES_SPELL_PCT_TO_HIT auras - modHitChance += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_INCREASES_SPELL_PCT_TO_HIT, schoolMask); // Chance hit from victim SPELL_AURA_MOD_ATTACKER_SPELL_HIT_CHANCE auras modHitChance += pVictim->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_ATTACKER_SPELL_HIT_CHANCE, schoolMask); - // Reduce spell hit chance for Area of effect spells from victim SPELL_AURA_MOD_AOE_AVOIDANCE aura - if (IsAreaOfEffectSpell(spell)) - modHitChance -= pVictim->GetTotalAuraModifier(SPELL_AURA_MOD_AOE_AVOIDANCE); // Reduce spell hit chance for dispel mechanic spells from victim SPELL_AURA_MOD_DISPEL_RESIST if (IsDispelSpell(spell)) modHitChance -= pVictim->GetTotalAuraModifier(SPELL_AURA_MOD_DISPEL_RESIST); @@ -3433,9 +3403,6 @@ SpellMissInfo Unit::MagicSpellHitResult(Unit* pVictim, SpellEntry const* spell) // Apply mod modHitChance -= resist_mech; - // Chance resist debuff - modHitChance-=pVictim->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_DEBUFF_RESISTANCE, int32(spell->GetDispel())); - int32 HitChance = modHitChance * 100; // Increase hit chance from attacker SPELL_AURA_MOD_SPELL_HIT_CHANCE and attacker ratings HitChance += int32(m_modSpellHitChance * 100.0f); @@ -3484,7 +3451,7 @@ SpellMissInfo Unit::SpellHitResult(Unit* pVictim, SpellEntry const* spell, bool return SPELL_MISS_EVADE; // Check for immune - if (pVictim->IsImmuneToSpell(spell)) + if (pVictim->IsImmuneToSpell(spell, this == pVictim)) return SPELL_MISS_IMMUNE; // All positive spells can`t miss @@ -3768,7 +3735,8 @@ void Unit::_UpdateAutoRepeatSpell() bool isAutoShot = m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_spellInfo->Id == SPELL_ID_AUTOSHOT; // check movement - if (GetTypeId() == TYPEID_PLAYER && ((Player*)this)->isMoving()) + if (GetTypeId() == TYPEID_PLAYER && ((Player*)this)->isMoving() && + !HasAffectedAura(SPELL_AURA_ALLOW_CAST_WHILE_MOVING, m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_spellInfo)) { // cancel wand shoot if (!isAutoShot) @@ -4320,36 +4288,79 @@ bool Unit::AddSpellAuraHolder(SpellAuraHolder* holder) } } - // passive auras not stackable with other ranks - if (!IsPassiveSpellStackableWithRanks(aurSpellInfo)) + // normal spell or passive auras not stackable with other ranks + if (!IsPassiveSpell(aurSpellInfo) || !IsPassiveSpellStackableWithRanks(aurSpellInfo)) { - if (!RemoveNoStackAurasDueToAuraHolder(holder)) + // Hack exceptions for Vehicle/Linked auras + if (!IsSpellHaveAura(aurSpellInfo, SPELL_AURA_CONTROL_VEHICLE) && !IsSpellHaveAura(aurSpellInfo, SPELL_AURA_284) && + !RemoveNoStackAurasDueToAuraHolder(holder)) { delete holder; return false; // couldn't remove conflicting aura with higher rank } } - // update single target auras list (before aura add to aura list, to prevent unexpected remove recently added aura) - if (holder->IsSingleTarget()) + // update tracked aura targets list (before aura add to aura list, to prevent unexpected remove recently added aura) + if (TrackedAuraType trackedType = holder->GetTrackedAuraType()) { if (Unit* caster = holder->GetCaster()) // caster not in world { - SingleCastSpellTargetMap& scTargets = caster->GetSingleCastSpellTargets(); - for (SingleCastSpellTargetMap::iterator itr = scTargets.begin(); itr != scTargets.end();) + // Only compare TrackedAuras of same tracking type + TrackedAuraTargetMap& scTargets = caster->GetTrackedAuraTargets(trackedType); + for (TrackedAuraTargetMap::iterator itr = scTargets.begin(); itr != scTargets.end();) { SpellEntry const* itr_spellEntry = itr->first; - ObjectGuid itr_targetGuid = itr->second; + ObjectGuid itr_targetGuid = itr->second; // Target on whom the tracked aura is - if (itr_targetGuid != GetObjectGuid() && - IsSingleTargetSpells(itr_spellEntry, aurSpellInfo)) + if (itr_targetGuid == GetObjectGuid()) // Note: I don't understand this check (based on old aura concepts, kept when adding holders) { - scTargets.erase(itr); // remove for caster in any case + ++itr; + continue; + } - // remove from target if target found - if (Unit* itr_target = GetMap()->GetUnit(itr_targetGuid)) - itr_target->RemoveAurasDueToSpell(itr_spellEntry->Id); + bool removed = false; + switch (trackedType) + { + case TRACK_AURA_TYPE_SINGLE_TARGET: + if (IsSingleTargetSpells(itr_spellEntry, aurSpellInfo)) + { + removed = true; + // remove from target if target found + if (Unit* itr_target = GetMap()->GetUnit(itr_targetGuid)) + itr_target->RemoveAurasDueToSpell(itr_spellEntry->Id); // TODO AURA_REMOVE_BY_TRACKING (might require additional work elsewhere) + else // Normally the tracking will be removed by the AuraRemoval + scTargets.erase(itr); + } + break; + case TRACK_AURA_TYPE_CONTROL_VEHICLE: + { + // find minimal effect-index that applies an aura + uint8 i = EFFECT_INDEX_0; + for (; i < MAX_EFFECT_INDEX; ++i) + if (IsAuraApplyEffect(aurSpellInfo, SpellEffectIndex(i))) + break; + // Remove auras when first holder is applied + if ((1 << i) & holder->GetAuraFlags()) + { + removed = true; // each caster can only control one vehicle + + // remove from target if target found + if (Unit* itr_target = GetMap()->GetUnit(itr_targetGuid)) + itr_target->RemoveAurasByCasterSpell(itr_spellEntry->Id, caster->GetObjectGuid(), AURA_REMOVE_BY_TRACKING); + else // Normally the tracking will be removed by the AuraRemoval + scTargets.erase(itr); + } + break; + } + case TRACK_AURA_TYPE_NOT_TRACKED: // These two can never happen + case MAX_TRACKED_AURA_TYPES: + MANGOS_ASSERT(false); + break; + } + + if (removed) + { itr = scTargets.begin(); // list can be chnaged at remove aura continue; } @@ -4357,8 +4368,16 @@ bool Unit::AddSpellAuraHolder(SpellAuraHolder* holder) ++itr; } - // register spell holder single target - scTargets[aurSpellInfo] = GetObjectGuid(); + switch (trackedType) + { + case TRACK_AURA_TYPE_CONTROL_VEHICLE: // Only track the controlled vehicle, no secondary effects + if (!IsSpellHaveAura(aurSpellInfo, SPELL_AURA_CONTROL_VEHICLE, holder->GetAuraFlags())) + break; + // no break here, track other controlled + case TRACK_AURA_TYPE_SINGLE_TARGET: // Register spell holder single target + scTargets[aurSpellInfo] = GetObjectGuid(); + break; + } } } @@ -4370,7 +4389,7 @@ bool Unit::AddSpellAuraHolder(SpellAuraHolder* holder) if (Aura* aur = holder->GetAuraByEffectIndex(SpellEffectIndex(i))) AddAuraToModList(aur); - holder->ApplyAuraModifiers(true, true); + holder->ApplyAuraModifiers(true, true); // This is the place where auras are actually applied onto the target DEBUG_LOG("Holder of spell %u now is in use", holder->GetId()); // if aura deleted before boosts apply ignore @@ -4427,11 +4446,8 @@ bool Unit::RemoveNoStackAurasDueToAuraHolder(SpellAuraHolder* holder) uint32 spellId = holder->GetId(); // passive spell special case (only non stackable with ranks) - if (IsPassiveSpell(spellProto)) - { - if (IsPassiveSpellStackableWithRanks(spellProto)) - return true; - } + if (IsPassiveSpell(spellProto) && IsPassiveSpellStackableWithRanks(spellProto)) + return true; SpellSpecific spellId_spec = GetSpellSpecific(spellId); @@ -4603,7 +4619,7 @@ void Unit::RemoveAura(uint32 spellId, SpellEffectIndex effindex, Aura* except) ++iter; } } -void Unit::RemoveAurasByCasterSpell(uint32 spellId, ObjectGuid casterGuid) +void Unit::RemoveAurasByCasterSpell(uint32 spellId, ObjectGuid casterGuid, AuraRemoveMode mode /*=AURA_REMOVE_BY_DEFAULT*/) { SpellAuraHolderBounds spair = GetSpellAuraHolderBounds(spellId); for (SpellAuraHolderMap::iterator iter = spair.first; iter != spair.second;) @@ -4769,7 +4785,7 @@ void Unit::RemoveAurasDueToSpellBySteal(uint32 spellId, ObjectGuid casterGuid, U RemoveSpellAuraHolder(holder, AURA_REMOVE_BY_DISPEL); // strange but intended behaviour: Stolen single target auras won't be treated as single targeted - new_holder->SetIsSingleTarget(false); + new_holder->SetTrackedAuraType(TRACK_AURA_TYPE_NOT_TRACKED); stealer->AddSpellAuraHolder(new_holder); } @@ -4886,12 +4902,19 @@ void Unit::RemoveAurasWithAttribute(uint32 flags) } } -void Unit::RemoveNotOwnSingleTargetAuras(uint32 newPhase) +void Unit::RemoveNotOwnTrackedTargetAuras(uint32 newPhase) { - // single target auras from other casters + // tracked aura targets from other casters are removed if the phase does no more fit for (SpellAuraHolderMap::iterator iter = m_spellAuraHolders.begin(); iter != m_spellAuraHolders.end();) { - if (iter->second->GetCasterGuid() != GetObjectGuid() && iter->second->IsSingleTarget()) + TrackedAuraType trackedType = iter->second->GetTrackedAuraType(); + if (!trackedType) + { + ++iter; + continue; + } + + if (trackedType == TRACK_AURA_TYPE_CONTROL_VEHICLE || iter->second->GetCasterGuid() != GetObjectGuid()) { if (!newPhase) { @@ -4914,46 +4937,48 @@ void Unit::RemoveNotOwnSingleTargetAuras(uint32 newPhase) ++iter; } - // single target auras at other targets - SingleCastSpellTargetMap& scTargets = GetSingleCastSpellTargets(); - for (SingleCastSpellTargetMap::iterator itr = scTargets.begin(); itr != scTargets.end();) + // tracked aura targets at other targets + for (uint8 type = TRACK_AURA_TYPE_SINGLE_TARGET; type < MAX_TRACKED_AURA_TYPES; ++type) { - SpellEntry const* itr_spellEntry = itr->first; - ObjectGuid itr_targetGuid = itr->second; - - if (itr_targetGuid != GetObjectGuid()) + TrackedAuraTargetMap& scTargets = GetTrackedAuraTargets(TrackedAuraType(type)); + for (TrackedAuraTargetMap::iterator itr = scTargets.begin(); itr != scTargets.end();) { - if (!newPhase) - { - scTargets.erase(itr); // remove for caster in any case + SpellEntry const* itr_spellEntry = itr->first; + ObjectGuid itr_targetGuid = itr->second; - // remove from target if target found - if (Unit* itr_target = GetMap()->GetUnit(itr_targetGuid)) - itr_target->RemoveAurasByCasterSpell(itr_spellEntry->Id, GetObjectGuid()); - - itr = scTargets.begin(); // list can be changed at remove aura - continue; - } - else + if (itr_targetGuid != GetObjectGuid()) { - Unit* itr_target = GetMap()->GetUnit(itr_targetGuid); - if (!itr_target || !itr_target->InSamePhase(newPhase)) + if (!newPhase) { scTargets.erase(itr); // remove for caster in any case // remove from target if target found - if (itr_target) + if (Unit* itr_target = GetMap()->GetUnit(itr_targetGuid)) itr_target->RemoveAurasByCasterSpell(itr_spellEntry->Id, GetObjectGuid()); itr = scTargets.begin(); // list can be changed at remove aura continue; } + else + { + Unit* itr_target = GetMap()->GetUnit(itr_targetGuid); + if (!itr_target || !itr_target->InSamePhase(newPhase)) + { + scTargets.erase(itr); // remove for caster in any case + + // remove from target if target found + if (itr_target) + itr_target->RemoveAurasByCasterSpell(itr_spellEntry->Id, GetObjectGuid()); + + itr = scTargets.begin(); // list can be changed at remove aura + continue; + } + } } + + ++itr; } - - ++itr; } - } void Unit::RemoveSpellAuraHolder(SpellAuraHolder* holder, AuraRemoveMode mode) @@ -4980,7 +5005,7 @@ void Unit::RemoveSpellAuraHolder(SpellAuraHolder* holder, AuraRemoveMode mode) } holder->SetRemoveMode(mode); - holder->UnregisterSingleCastHolder(); + holder->UnregisterAndCleanupTrackedAuras(); for (int32 i = 0; i < MAX_EFFECT_INDEX; ++i) { @@ -5119,6 +5144,23 @@ void Unit::RemoveAllAurasOnDeath() } } +void Unit::RemoveAllAurasOnEvade() +{ + // used when evading to remove all auras except some special auras + // Vehicle control auras should not be removed on evade - neither should linked auras + for (SpellAuraHolderMap::iterator iter = m_spellAuraHolders.begin(); iter != m_spellAuraHolders.end();) + { + SpellEntry const* proto = iter->second->GetSpellProto(); + if (!IsSpellHaveAura(proto, SPELL_AURA_CONTROL_VEHICLE)) + { + RemoveSpellAuraHolder(iter->second, AURA_REMOVE_BY_DEFAULT); + iter = m_spellAuraHolders.begin(); + } + else + ++iter; + } +} + void Unit::DelaySpellAuraHolder(uint32 spellId, int32 delaytime, ObjectGuid casterGuid) { SpellAuraHolderBounds bounds = GetSpellAuraHolderBounds(spellId); @@ -5193,6 +5235,29 @@ Aura* Unit::GetAura(AuraType type, SpellFamily family, uint64 familyFlag, uint32 return NULL; } +Aura* Unit::GetTriggeredByClientAura(uint32 spellId) const +{ + MANGOS_ASSERT(spellId); + + AuraList const& auras = GetAurasByType(SPELL_AURA_PERIODIC_TRIGGER_BY_CLIENT); + for (AuraList::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + SpellAuraHolder const* holder = (*itr)->GetHolder(); + if (!holder || holder->IsDeleted()) + continue; + + SpellEffectEntry const* spellEffect = holder->GetSpellProto()->GetSpellEffect(SpellEffectIndex((*itr)->GetEffIndex())); + if(!spellEffect) + continue; + + // NOTE for further development: If there are more spells of this aura type, it might be required to check that this is the effect that applies SPELL_AURA_PERIODIC_TRIGGER_BY_CLIENT + if (holder->GetCasterGuid() == GetObjectGuid() && spellEffect->EffectTriggerSpell == spellId) + return *itr; + } + + return NULL; +} + bool Unit::HasAura(uint32 spellId, SpellEffectIndex effIndex) const { SpellAuraHolderConstBounds spair = GetSpellAuraHolderBounds(spellId); @@ -6583,6 +6648,20 @@ uint32 Unit::SpellDamageBonusDone(Unit* pVictim, SpellEntry const* spellProto, u DoneTotalMod += ((*i)->GetModifier()->m_amount + 100.0f) / 100.0f; } + if (getPowerType() == POWER_MANA) + { + Unit::AuraList const& doneFromManaPctAuras = GetAurasByType(SPELL_AURA_MOD_DAMAGE_DONE_FROM_PCT_POWER); + if (!doneFromManaPctAuras.empty()) + { + float powerPct = std::min(float(GetPower(POWER_MANA)) / GetMaxPower(POWER_MANA), 1.0f); + for (Unit::AuraList::const_iterator itr = doneFromManaPctAuras.begin(); itr != doneFromManaPctAuras.end(); ++itr) + { + if (GetSpellSchoolMask(spellProto) & (*itr)->GetModifier()->m_miscvalue) + DoneTotalMod *= (100.0f + (*itr)->GetModifier()->m_amount * powerPct) / 100.0f; + } + } + } + // done scripted mod (take it from owner) Unit* owner = GetOwner(); if (!owner) owner = this; @@ -6938,6 +7017,16 @@ int32 Unit::SpellBaseDamageBonusDone(SpellSchoolMask schoolMask) { int32 DoneAdvertisedBenefit = 0; + Unit::AuraList const& mOverrideSpellPowerAuras = GetAurasByType(SPELL_AURA_OVERRIDE_SPELL_POWER_BY_AP_PCT); + if (!mOverrideSpellPowerAuras.empty()) + { + for (Unit::AuraList::const_iterator itr = mOverrideSpellPowerAuras.begin(); itr != mOverrideSpellPowerAuras.end(); ++itr) + if (schoolMask & (*itr)->GetModifier()->m_miscvalue) + DoneAdvertisedBenefit += (*itr)->GetModifier()->m_amount; + + return int32(GetTotalAttackPowerValue(BASE_ATTACK) * (100.0f + DoneAdvertisedBenefit) / 100.0f); + } + // ..done AuraList const& mDamageDone = GetAurasByType(SPELL_AURA_MOD_DAMAGE_DONE); for (AuraList::const_iterator i = mDamageDone.begin(); i != mDamageDone.end(); ++i) @@ -6954,6 +7043,9 @@ int32 Unit::SpellBaseDamageBonusDone(SpellSchoolMask schoolMask) // Base value DoneAdvertisedBenefit += ((Player*)this)->GetBaseSpellPowerBonus(); + if (GetPowerIndex(POWER_MANA) != INVALID_POWER_INDEX) + DoneAdvertisedBenefit += std::max(0, int32(GetStat(STAT_INTELLECT)) - 10); // spellpower from intellect + // Damage bonus from stats AuraList const& mDamageDoneOfStatPercent = GetAurasByType(SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT); for (AuraList::const_iterator i = mDamageDoneOfStatPercent.begin(); i != mDamageDoneOfStatPercent.end(); ++i) @@ -6972,8 +7064,16 @@ int32 Unit::SpellBaseDamageBonusDone(SpellSchoolMask schoolMask) if ((*i)->GetModifier()->m_miscvalue & schoolMask) DoneAdvertisedBenefit += int32(GetTotalAttackPowerValue(BASE_ATTACK) * (*i)->GetModifier()->m_amount / 100.0f); } - } + + // pct spell power modifier + Unit::AuraList const& mSpellPowerPctAuras = GetAurasByType(SPELL_AURA_MOD_INCREASE_SPELL_POWER_PCT); + for (Unit::AuraList::const_iterator itr = mSpellPowerPctAuras.begin(); itr != mSpellPowerPctAuras.end(); ++itr) + { + if (!(*itr)->GetModifier()->m_miscvalue || (*itr)->GetModifier()->m_miscvalue & schoolMask) + DoneAdvertisedBenefit = int32(DoneAdvertisedBenefit * (100.0f + (*itr)->GetModifier()->m_amount) / 100.0f); + } + return DoneAdvertisedBenefit; } @@ -7201,9 +7301,6 @@ uint32 Unit::SpellCriticalDamageBonus(SpellEntry const* spellProto, uint32 damag critPctDamageMod += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_CRIT_DAMAGE_BONUS, GetSpellSchoolMask(spellProto)); - uint32 creatureTypeMask = pVictim->GetCreatureTypeMask(); - critPctDamageMod += GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_CRIT_PERCENT_VERSUS, creatureTypeMask); - if (critPctDamageMod != 0) crit_bonus = int32(crit_bonus * float((100.0f + critPctDamageMod) / 100.0f)); @@ -7218,12 +7315,6 @@ uint32 Unit::SpellCriticalHealingBonus(SpellEntry const* spellProto, uint32 dama // Calculate critical bonus int32 crit_bonus = damage; - if (pVictim) - { - uint32 creatureTypeMask = pVictim->GetCreatureTypeMask(); - crit_bonus = int32(crit_bonus * GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_CRIT_PERCENT_VERSUS, creatureTypeMask)); - } - if (crit_bonus > 0) damage += crit_bonus; @@ -7257,6 +7348,15 @@ uint32 Unit::SpellHealingBonusDone(Unit* pVictim, SpellEntry const* spellProto, for (AuraList::const_iterator i = mHealingDonePct.begin(); i != mHealingDonePct.end(); ++i) DoneTotalMod *= (100.0f + (*i)->GetModifier()->m_amount) / 100.0f; + AuraList const& mHealingFromHealthPct = GetAurasByType(SPELL_AURA_MOD_HEALING_DONE_FROM_PCT_HEALTH); + if (!mHealingFromHealthPct.empty()) + { + float healthPct = std::max(0.0f, 1.0f - float(pVictim->GetHealth()) / pVictim->GetMaxHealth()); + for (AuraList::const_iterator i = mHealingFromHealthPct.begin();i != mHealingFromHealthPct.end(); ++i) + if ((*i)->isAffectedOnSpell(spellProto)) + DoneTotalMod *= (100.0f + (*i)->GetModifier()->m_amount * healthPct) / 100.0f; + } + // done scripted mod (take it from owner) Unit* owner = GetOwner(); if (!owner) owner = this; @@ -7376,17 +7476,10 @@ uint32 Unit::SpellHealingBonusTaken(Unit* pCaster, SpellEntry const* spellProto, // Healing taken percent float minval = float(GetMaxNegativeAuraModifier(SPELL_AURA_MOD_HEALING_PCT)); - if (damagetype == DOT) - { - // overwrite max SPELL_AURA_MOD_HEALING_PCT if greater negative effect - float minDotVal = float(GetMaxNegativeAuraModifier(SPELL_AURA_MOD_PERIODIC_HEAL)); - minval = (minDotVal < minval) ? minDotVal : minval; - } if (minval) TakenTotalMod *= (100.0f + minval) / 100.0f; float maxval = float(GetMaxPositiveAuraModifier(SPELL_AURA_MOD_HEALING_PCT)); - // no SPELL_AURA_MOD_PERIODIC_HEAL positive cases if (maxval) TakenTotalMod *= (100.0f + maxval) / 100.0f; @@ -7422,6 +7515,16 @@ int32 Unit::SpellBaseHealingBonusDone(SpellSchoolMask schoolMask) { int32 AdvertisedBenefit = 0; + Unit::AuraList const& mOverrideSpellPowerAuras = GetAurasByType(SPELL_AURA_OVERRIDE_SPELL_POWER_BY_AP_PCT); + if (!mOverrideSpellPowerAuras.empty()) + { + for (Unit::AuraList::const_iterator itr = mOverrideSpellPowerAuras.begin(); itr != mOverrideSpellPowerAuras.end(); ++itr) + if (schoolMask & (*itr)->GetModifier()->m_miscvalue) + AdvertisedBenefit += (*itr)->GetModifier()->m_amount; + + return int32(GetTotalAttackPowerValue(BASE_ATTACK) * (100.0f + AdvertisedBenefit) / 100.0f); + } + AuraList const& mHealingDone = GetAurasByType(SPELL_AURA_MOD_HEALING_DONE); for (AuraList::const_iterator i = mHealingDone.begin(); i != mHealingDone.end(); ++i) if (!(*i)->GetModifier()->m_miscvalue || ((*i)->GetModifier()->m_miscvalue & schoolMask) != 0) @@ -7433,6 +7536,9 @@ int32 Unit::SpellBaseHealingBonusDone(SpellSchoolMask schoolMask) // Base value AdvertisedBenefit += ((Player*)this)->GetBaseSpellPowerBonus(); + if (GetPowerIndex(POWER_MANA) != INVALID_POWER_INDEX) + AdvertisedBenefit += std::max(0, int32(GetStat(STAT_INTELLECT)) - 10); // spellpower from intellect + // Healing bonus from stats AuraList const& mHealingDoneOfStatPercent = GetAurasByType(SPELL_AURA_MOD_SPELL_HEALING_OF_STAT_PERCENT); for (AuraList::const_iterator i = mHealingDoneOfStatPercent.begin(); i != mHealingDoneOfStatPercent.end(); ++i) @@ -7448,6 +7554,15 @@ int32 Unit::SpellBaseHealingBonusDone(SpellSchoolMask schoolMask) if ((*i)->GetModifier()->m_miscvalue & schoolMask) AdvertisedBenefit += int32(GetTotalAttackPowerValue(BASE_ATTACK) * (*i)->GetModifier()->m_amount / 100.0f); } + + // pct spell power modifier + Unit::AuraList const& mSpellPowerPctAuras = GetAurasByType(SPELL_AURA_MOD_INCREASE_SPELL_POWER_PCT); + for (Unit::AuraList::const_iterator itr = mSpellPowerPctAuras.begin(); itr != mSpellPowerPctAuras.end(); ++itr) + { + if (!(*itr)->GetModifier()->m_miscvalue || (*itr)->GetModifier()->m_miscvalue & schoolMask) + AdvertisedBenefit = int32(AdvertisedBenefit * (100.0f + (*itr)->GetModifier()->m_amount) / 100.0f); + } + return AdvertisedBenefit; } @@ -7479,7 +7594,7 @@ bool Unit::IsImmunedToDamage(SpellSchoolMask shoolMask) return false; } -bool Unit::IsImmuneToSpell(SpellEntry const* spellInfo) +bool Unit::IsImmuneToSpell(SpellEntry const* spellInfo, bool castOnSelf) { if (!spellInfo) return false; @@ -7492,8 +7607,8 @@ bool Unit::IsImmuneToSpell(SpellEntry const* spellInfo) if (itr->type == spellInfo->GetDispel()) return true; - if (!spellInfo->HasAttribute(SPELL_ATTR_EX_UNAFFECTED_BY_SCHOOL_IMMUNE) && // unaffected by school immunity - !spellInfo->HasAttribute(SPELL_ATTR_EX_DISPEL_AURAS_ON_IMMUNITY)) // can remove immune (by dispell or immune it) + if (!spellInfo->HasAttribute(SPELL_ATTR_EX_UNAFFECTED_BY_SCHOOL_IMMUNE) && // unaffected by school immunity + !spellInfo->HasAttribute(SPELL_ATTR_EX_DISPEL_AURAS_ON_IMMUNITY)) // can remove immune (by dispell or immune it) { SpellImmuneList const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; for (SpellImmuneList::const_iterator itr = schoolList.begin(); itr != schoolList.end(); ++itr) @@ -7518,7 +7633,7 @@ bool Unit::IsImmuneToSpell(SpellEntry const* spellInfo) return false; } -bool Unit::IsImmuneToSpellEffect(SpellEntry const* spellInfo, SpellEffectIndex index) const +bool Unit::IsImmuneToSpellEffect(SpellEntry const* spellInfo, SpellEffectIndex index, bool castOnSelf) const { //If m_immuneToEffect type contain this effect type, IMMUNE effect. SpellEffectEntry const* spellEffect = spellInfo->GetSpellEffect(index); @@ -8095,7 +8210,7 @@ void Unit::SetInCombatState(bool PvP, Unit* enemy) if (IsNonCombatSpell(spell->m_spellInfo)) InterruptSpell(CurrentSpellTypes(i), false); - if (getRace() == RACE_WORGEN && !IsInWorgenForm(true)) + if (getRace() == RACE_WORGEN && !IsInWorgenForm(true) && HasWorgenForm()) CastSpell(this, 97709, true); // cast Altered Form if (creatureNotInCombat) @@ -8133,9 +8248,10 @@ void Unit::ClearInCombat() RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT); // Player's state will be cleared in Player::UpdateContestedPvP - if (GetTypeId() != TYPEID_PLAYER) + if (GetTypeId() == TYPEID_UNIT) { - if (((Creature*)this)->GetCreatureInfo()->unit_flags & UNIT_FLAG_OOC_NOT_ATTACKABLE) + Creature* cThis = static_cast(this); + if (cThis->GetCreatureInfo()->unit_flags & UNIT_FLAG_OOC_NOT_ATTACKABLE && !(cThis->GetTemporaryFactionFlags() & TEMPFACTION_TOGGLE_OOC_NOT_ATTACK)) SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_OOC_NOT_ATTACKABLE); clearUnitState(UNIT_STAT_ATTACK_PLAYER); @@ -8248,8 +8364,8 @@ bool Unit::isVisibleForOrDetect(Unit const* u, WorldObject const* viewPoint, boo if (!at_same_transport && (!IsInWorld() || !u->IsInWorld())) return false; - // forbidden to seen (at GM respawn command) - if (m_Visibility == VISIBILITY_RESPAWN) + // forbidden to seen (while Removing corpse) + if (m_Visibility == VISIBILITY_REMOVE_CORPSE) return false; Map& _map = *u->GetMap(); @@ -8911,6 +9027,10 @@ void Unit::SetDeathState(DeathState s) i_motionMaster.MoveIdle(); StopMoving(); + // Unsummon vehicle accessories + if (IsVehicle()) + m_vehicleInfo->RemoveAccessoriesFromMap(); + ModifyAuraState(AURA_STATE_HEALTHLESS_20_PERCENT, false); ModifyAuraState(AURA_STATE_HEALTHLESS_35_PERCENT, false); // remove aurastates allowing special moves @@ -8935,14 +9055,14 @@ void Unit::SetDeathState(DeathState s) ######## ######## ########################################*/ -bool Unit::CanHaveThreatList() const +bool Unit::CanHaveThreatList(bool ignoreAliveState/*=false*/) const { // only creatures can have threat list if (GetTypeId() != TYPEID_UNIT) return false; // only alive units can have threat list - if (!isAlive()) + if (!isAlive() && !ignoreAliveState) return false; Creature const* creature = ((Creature const*)this); @@ -8990,7 +9110,7 @@ void Unit::AddThreat(Unit* pVictim, float threat /*= 0.0f*/, bool crit /*= false void Unit::DeleteThreatList() { - if (CanHaveThreatList() && !m_ThreatManager.isThreatListEmpty()) + if (CanHaveThreatList(true) && !m_ThreatManager.isThreatListEmpty()) SendThreatClear(); m_ThreatManager.clearReferences(); @@ -9044,6 +9164,8 @@ void Unit::TauntFadeOut(Unit* taunter) if (m_ThreatManager.isThreatListEmpty()) { + m_fixateTargetGuid.Clear(); + if (((Creature*)this)->AI()) ((Creature*)this)->AI()->EnterEvadeMode(); @@ -9068,6 +9190,19 @@ void Unit::TauntFadeOut(Unit* taunter) } } +//====================================================================== +/// if pVictim is given, the npc will fixate onto pVictim, if NULL it will remove current fixation +void Unit::FixateTarget(Unit* pVictim) +{ + if (!pVictim) // Remove Fixation + m_fixateTargetGuid.Clear(); + else if (pVictim->isTargetableForAttack()) // Apply Fixation + m_fixateTargetGuid = pVictim->GetObjectGuid(); + + // Start attacking the fixated target or the next proper one + SelectHostileTarget(); +} + //====================================================================== bool Unit::IsSecondChoiceTarget(Unit* pTarget, bool checkThreatArea) const @@ -9100,10 +9235,22 @@ bool Unit::SelectHostileTarget() Unit* target = NULL; Unit* oldTarget = getVictim(); - // First checking if we have some taunt on us - const AuraList& tauntAuras = GetAurasByType(SPELL_AURA_MOD_TAUNT); - if (!tauntAuras.empty()) + // first check if we should fixate a target + if (m_fixateTargetGuid) { + if (oldTarget && oldTarget->GetObjectGuid() == m_fixateTargetGuid) + target = oldTarget; + else + { + Unit* pFixateTarget = GetMap()->GetUnit(m_fixateTargetGuid); + if (pFixateTarget && pFixateTarget->isAlive() && !IsSecondChoiceTarget(pFixateTarget, true)) + target = pFixateTarget; + } + } + // then checking if we have some taunt on us + if (!target) + { + const AuraList& tauntAuras = GetAurasByType(SPELL_AURA_MOD_TAUNT); Unit* caster; // Find first available taunter target @@ -9120,7 +9267,7 @@ bool Unit::SelectHostileTarget() } } - // No taunt aura or taunt aura caster is dead, standard target selection + // No valid fixate target, taunt aura or taunt aura caster is dead, standard target selection if (!target && !m_ThreatManager.isThreatListEmpty()) target = m_ThreatManager.getHostileTarget(); @@ -9179,6 +9326,7 @@ bool Unit::SelectHostileTarget() } // enter in evade mode in other case + m_fixateTargetGuid.Clear(); ((Creature*)this)->AI()->EnterEvadeMode(); if (InstanceData* mapInstance = GetInstanceData()) @@ -9201,6 +9349,7 @@ int32 Unit::CalculateSpellDamage(Unit const* target, SpellEntry const* spellProt return 0; Player* unitPlayer = (GetTypeId() == TYPEID_PLAYER) ? (Player*)this : NULL; + uint32 level = getLevel(); // calculate basepoints dependent on mastery if (unitPlayer && spellProto->HasAttribute(SPELL_ATTR_EX8_MASTERY) && !spellProto->CalculateSimpleValue(effect_index)) @@ -9209,6 +9358,14 @@ int32 Unit::CalculateSpellDamage(Unit const* target, SpellEntry const* spellProt return int32(GetFloatValue(PLAYER_MASTERY) * masteryCoef / 100.0f); } + // calculate basepoints for armor specialization spells + if (unitPlayer && spellProto->HasAttribute(SPELL_ATTR_EX8_ARMOR_SPECIALIZATION)) + { + // check spells not valid for current talent tree or insufficient equipped items + if (!unitPlayer->FitArmorSpecializationRules(spellProto)) + return 0; + } + uint8 comboPoints = unitPlayer ? unitPlayer->GetComboPoints() : 0; int32 basePoints = 0; @@ -9217,28 +9374,36 @@ int32 Unit::CalculateSpellDamage(Unit const* target, SpellEntry const* spellProt SpellScalingEntry const* scalingEntry = spellProto->GetSpellScaling(); GtSpellScalingEntry const* gtScalingEntry = NULL; - if (scalingEntry) + if (scalingEntry && scalingEntry->IsScalableEffect(effect_index)) { - uint32 gtSpellScalingId = getLevel() - 1; + if (target && IsAuraApplyEffect(spellProto, effect_index) && IsPositiveEffect(spellProto, effect_index)) + level = target->getLevel(); + + uint32 gtSpellScalingId = level - 1; if (scalingEntry->playerClass == -1) - gtSpellScalingId += 11 * 100; + gtSpellScalingId += (MAX_CLASSES - 1) * GT_MAX_LEVEL; else - gtSpellScalingId += (scalingEntry->playerClass - 1) * 100; + gtSpellScalingId += (scalingEntry->playerClass - 1) * GT_MAX_LEVEL; gtScalingEntry = sGtSpellScalingStore.LookupEntry(gtSpellScalingId); } if (gtScalingEntry) { - basePoints = int32(scalingEntry->coeff1[effect_index] * gtScalingEntry->value); - int32 randomPoints = int32(scalingEntry->coeff1[effect_index] * gtScalingEntry->value * scalingEntry->coeff2[effect_index]); + float scale = gtScalingEntry->value; + if (scalingEntry->castTimeMax > 0 && scalingEntry->castScalingMaxLevel > level) + scale *= float(scalingEntry->castTimeMin + float(level - 1) * (scalingEntry->castTimeMax - scalingEntry->castTimeMin) / (scalingEntry->castScalingMaxLevel - 1)) / float(scalingEntry->castTimeMax); + if (scalingEntry->coefLevelBase > level) + scale *= (1.0f - scalingEntry->coefBase) * (level - 1) / (scalingEntry->coefLevelBase - 1) + scalingEntry->coefBase; + + basePoints = int32(scalingEntry->coeff1[effect_index] * scale); + int32 randomPoints = int32(scalingEntry->coeff1[effect_index] * scale * scalingEntry->coeff2[effect_index]); basePoints += irand(-randomPoints, randomPoints) / 2; - comboDamage = uint32(scalingEntry->coeff3[effect_index] * gtScalingEntry->value); + comboDamage = uint32(scalingEntry->coeff3[effect_index] * scale); } else { spellLevel = spellProto->GetSpellLevel(); - uint32 level = getLevel(); uint32 maxLevel = spellProto->GetMaxLevel(); uint32 baseLevel = spellProto->GetBaseLevel(); @@ -9299,12 +9464,12 @@ int32 Unit::CalculateSpellDamage(Unit const* target, SpellEntry const* spellProt spellEffect->Effect != SPELL_EFFECT_WEAPON_PERCENT_DAMAGE && spellEffect->Effect != SPELL_EFFECT_KNOCK_BACK && (spellEffect->Effect != SPELL_EFFECT_APPLY_AURA || spellEffect->EffectApplyAuraName != SPELL_AURA_MOD_DECREASE_SPEED)) - value = int32(value*0.25f*exp(getLevel()*(70-spellLevel)/1000.0f)); + value = int32(value * 0.25f * exp(level * (70 - spellLevel) / 1000.0f)); return value; } -int32 Unit::CalculateAuraDuration(SpellEntry const* spellProto, uint32 effectMask, int32 duration, Unit const* caster) +int32 Unit::CalculateAuraDuration(SpellEntry const* spellProto, uint32 effectMask, int32 duration, Unit const* caster, Spell const* spell /*=NULL*/) { if (duration <= 0) return duration; @@ -9370,6 +9535,12 @@ int32 Unit::CalculateAuraDuration(SpellEntry const* spellProto, uint32 effectMas if (Aura* aur = GetAura(57979, EFFECT_INDEX_0)) duration += aur->GetModifier()->m_amount * MINUTE * IN_MILLISECONDS; } + // Inquisition + else if (spellProto->Id == 84963) + { + if (spell && GetPowerIndex(POWER_HOLY_POWER) != INVALID_POWER_INDEX) + duration *= spell->GetUsedHolyPower(); + } break; default: break; @@ -9861,14 +10032,15 @@ void Unit::SetPowerByIndex(uint32 powerIndex, int32 val) MANGOS_ASSERT(powerIndex < MAX_STORED_POWERS); SetInt32Value(UNIT_FIELD_POWER1 + powerIndex, val); + Powers power = getPowerType(powerIndex); + MANGOS_ASSERT(power != INVALID_POWER); + if (IsInWorld()) { - Powers power = getPowerType(powerIndex); - MANGOS_ASSERT(power != INVALID_POWER); - WorldPacket data(SMSG_POWER_UPDATE); data << GetPackGUID(); data << uint32(1); // iteration count + // for (int i = 0; i < count; ++i) data << uint8(power); data << uint32(val); SendMessageToSet(&data, true); @@ -9890,6 +10062,10 @@ void Unit::SetPowerByIndex(uint32 powerIndex, int32 val) ((Player*)owner)->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_CUR_POWER); } } + + // modifying holy power resets it's fade timer + if (power == POWER_HOLY_POWER) + ResetHolyPowerRegenTimer(); } void Unit::SetMaxPower(Powers power, int32 val) @@ -9996,9 +10172,9 @@ uint32 Unit::GetCreatePowers(Powers power) const return 100; return (GetTypeId() == TYPEID_PLAYER || !((Creature const*)this)->IsPet() || ((Pet const*)this)->getPetType() != HUNTER_PET ? 0 : 100); case POWER_ENERGY: return 100; - case POWER_RUNE: return (GetTypeId() == TYPEID_PLAYER && ((Player const*)this)->getClass() == CLASS_DEATH_KNIGHT ? 8 : 0); - case POWER_RUNIC_POWER: return (GetTypeId() == TYPEID_PLAYER && ((Player const*)this)->getClass() == CLASS_DEATH_KNIGHT ? 1000 : 0); - case POWER_SOUL_SHARDS: return 0; // TODO: fix me + case POWER_RUNE: return GetTypeId() == TYPEID_PLAYER && ((Player const*)this)->getClass() == CLASS_DEATH_KNIGHT ? 8 : 0; + case POWER_RUNIC_POWER: return GetTypeId() == TYPEID_PLAYER && ((Player const*)this)->getClass() == CLASS_DEATH_KNIGHT ? 1000 : 0; + case POWER_SOUL_SHARDS: return 0; case POWER_ECLIPSE: return 0; // TODO: fix me case POWER_HOLY_POWER: return 0; } @@ -10006,6 +10182,21 @@ uint32 Unit::GetCreatePowers(Powers power) const return 0; } +uint32 Unit::GetCreateMaxPowers(Powers power) const +{ + switch (power) + { + case POWER_HOLY_POWER: + return GetTypeId() == TYPEID_PLAYER && ((Player const*)this)->getClass() == CLASS_PALADIN ? 3 : 0; + case POWER_SOUL_SHARDS: + return GetTypeId() == TYPEID_PLAYER && ((Player const*)this)->getClass() == CLASS_WARLOCK ? 3 : 0; + default: + return GetCreatePowers(power); + } + + return 0; +} + void Unit::AddToWorld() { Object::AddToWorld(); @@ -10018,7 +10209,7 @@ void Unit::RemoveFromWorld() if (IsInWorld()) { Uncharm(); - RemoveNotOwnSingleTargetAuras(); + RemoveNotOwnTrackedTargetAuras(); RemoveGuardians(); RemoveMiniPet(); UnsummonAllTotems(); @@ -10080,10 +10271,26 @@ void CharmInfo::InitPetActionBar() void CharmInfo::InitEmptyActionBar() { - for (uint32 x = ACTION_BAR_INDEX_START + 1; x < ACTION_BAR_INDEX_END; ++x) + for (uint32 x = ACTION_BAR_INDEX_START; x < ACTION_BAR_INDEX_END; ++x) SetActionBar(x, 0, ACT_PASSIVE); } +void CharmInfo::InitVehicleCreateSpells() +{ + InitEmptyActionBar(); + + if (m_unit->GetTypeId() == TYPEID_PLAYER) // player vehicles don't have spells, keep the action bar empty + return; + + for (uint32 x = 0; x < CREATURE_MAX_SPELLS; ++x) + { + if (IsPassiveSpell(((Creature*)m_unit)->m_spells[x])) + m_unit->CastSpell(m_unit, ((Creature*)m_unit)->m_spells[x], true); + else + AddSpellToActionBar(((Creature*)m_unit)->m_spells[x], ActiveStates(0x8 + x)); + } +} + void CharmInfo::InitPossessCreateSpells() { InitEmptyActionBar(); // charm action bar @@ -10443,7 +10650,6 @@ void Unit::ProcDamageAndSpellFor(bool isVictim, Unit* pTarget, uint32 procFlag, } else if (spellProcEvent->procEx == PROC_EX_NONE && procExtra == PROC_EX_CAST_END) continue; - } // don't check dbc FamilyFlags if schoolMask exists else if (!triggeredByAura->CanProcFrom(procSpell, procFlag, spellProcEvent->procEx, procExtra, damage != 0, !spellProcEvent->schoolMask)) @@ -10509,33 +10715,6 @@ Player* Unit::GetSpellModOwner() const } ///----------Pet responses methods----------------- -void Unit::SendPetCastFail(uint32 spellid, SpellCastResult msg) -{ - if (msg == SPELL_CAST_OK) - return; - - Unit* owner = GetCharmerOrOwner(); - if (!owner || owner->GetTypeId() != TYPEID_PLAYER) - return; - - WorldPacket data(SMSG_PET_CAST_FAILED, 1 + 4 + 1); - data << uint8(0); // cast count? - data << uint32(spellid); - data << uint8(msg); - - // More cases exist, see Spell::SendCastResult (can possibly be unified) - switch (msg) - { - case SPELL_FAILED_NOT_READY: - data << uint32(0); // unknown - break; - default: - break; - } - - ((Player*)owner)->GetSession()->SendPacket(&data); -} - void Unit::SendPetActionFeedback(uint8 msg) { Unit* owner = GetOwner(); @@ -10715,7 +10894,6 @@ void Unit::SetFeignDeath(bool apply, ObjectGuid casterGuid, uint32 /*spellID*/) else GetMotionMaster()->Initialize(); } - } } @@ -10856,7 +11034,7 @@ Unit* Unit::SelectRandomUnfriendlyTarget(Unit* except /*= NULL*/, float radius / { std::list targets; - MaNGOS::AnyUnfriendlyUnitInObjectRangeCheck u_check(this, this, radius); + MaNGOS::AnyUnfriendlyUnitInObjectRangeCheck u_check(this, radius); MaNGOS::UnitListSearcher searcher(targets, u_check); Cell::VisitAllObjects(this, searcher, radius); @@ -11104,7 +11282,7 @@ void Unit::SetPhaseMask(uint32 newPhaseMask, bool update) if (IsInWorld()) { - RemoveNotOwnSingleTargetAuras(newPhaseMask); // we can lost access to caster or target + RemoveNotOwnTrackedTargetAuras(newPhaseMask); // we can lost access to caster or target // all controlled except not owned charmed units CallForAllControlledUnits(SetPhaseMaskHelper(newPhaseMask), CONTROLLED_PET | CONTROLLED_GUARDIANS | CONTROLLED_MINIPET | CONTROLLED_TOTEMS); @@ -11205,8 +11383,8 @@ void Unit::KnockBackWithAngle(float angle, float horizontalSpeed, float vertical GetPosition(ox, oy, oz); float fx = ox + dis * vcos; float fy = oy + dis * vsin; - float fz = oz; - GetMap()->GetObjectHitPos(ox, oy, oz + 0.5f, fx, fy, oz + 0.5f, fx, fy, fz, -0.5f); + float fz = oz + 0.5f; + GetMap()->GetHitPosition(ox, oy, oz + 0.5f, fx, fy, fz, GetPhaseMask(), -0.5f); UpdateAllowedPositionZ(fx, fy, fz); GetMotionMaster()->MoveJump(fx, fy, fz, horizontalSpeed, max_height); } @@ -11478,7 +11656,11 @@ void Unit::OnRelocated() ScheduleAINotify(World::GetRelocationAINotifyDelay()); } -void Unit::SetVehicleId(uint32 entry) +/** + * @param entry entry of the vehicle kit + * @param overwriteNpcEntry use to select behaviour (like accessory) for this entry instead of GetEntry()'s result + */ +void Unit::SetVehicleId(uint32 entry, uint32 overwriteNpcEntry) { delete m_vehicleInfo; @@ -11487,7 +11669,7 @@ void Unit::SetVehicleId(uint32 entry) VehicleEntry const* ventry = sVehicleStore.LookupEntry(entry); MANGOS_ASSERT(ventry != NULL); - m_vehicleInfo = new VehicleInfo(this, ventry); + m_vehicleInfo = new VehicleInfo(this, ventry, overwriteNpcEntry); m_updateFlag |= UPDATEFLAG_VEHICLE; } else @@ -11527,7 +11709,9 @@ void Unit::UpdateSplineMovement(uint32 t_diff) m_movesplineTimer.Reset(POSITION_UPDATE_DELAY); Movement::Location loc = movespline->ComputePosition(); - if (GetTypeId() == TYPEID_PLAYER) + if (IsBoarded()) + GetTransportInfo()->SetLocalPosition(loc.x, loc.y, loc.z, loc.orientation); + else if (GetTypeId() == TYPEID_PLAYER) ((Player*)this)->SetPosition(loc.x, loc.y, loc.z, loc.orientation); else GetMap()->CreatureRelocation((Creature*)this, loc.x, loc.y, loc.z, loc.orientation); @@ -11555,6 +11739,11 @@ bool Unit::IsInWorgenForm(bool inPermanent) const return false; } +bool Unit::HasWorgenForm() const +{ + return HasAuraType(SPELL_AURA_ALLOW_WORGEN_TRANSFORM); +} + void Unit::BuildForceMoveRootPacket(WorldPacket* data, bool apply, uint32 value) { if (apply) @@ -11625,3 +11814,24 @@ void Unit::BuildMoveWaterWalkPacket(WorldPacket* data, bool apply, uint32 value) } } +void Unit::BuildMoveFeatherFallPacket(WorldPacket* data, bool apply, uint32 value) +{ + ObjectGuid guid = GetObjectGuid(); + + if (apply) + { + data->Initialize(SMSG_MOVE_FEATHER_FALL, 1 + 4 + 8); + data->WriteGuidMask<3, 1, 7, 0, 4, 2, 5, 6>(guid); + data->WriteGuidBytes<5, 7, 2>(guid); + *data << uint32(value); + data->WriteGuidBytes<0, 3, 4, 1, 6>(guid); + } + else + { + data->Initialize(SMSG_MOVE_NORMAL_FALL, 1 + 4 + 8); + *data << uint32(value); + data->WriteGuidMask<3, 0, 1, 5, 7, 4, 6, 2>(guid); + data->WriteGuidBytes<2, 7, 1, 4, 5, 0, 3, 6>(guid); + } +} + diff --git a/src/game/Vehicle.cpp b/src/game/Vehicle.cpp index 71251ff1c..d3dee37ff 100644 --- a/src/game/Vehicle.cpp +++ b/src/game/Vehicle.cpp @@ -23,39 +23,77 @@ * @file Vehicle.cpp * This file contains the code needed for CMaNGOS to support vehicles * Currently implemented - * - TODO Board - * - TODO Unboard - * - TODO Switch + * - Board to board a passenger onto a vehicle (includes checks) + * - Unboard to unboard a passenger from the vehicle + * - SwitchSeat to switch to another seat of the same vehicle * - CanBoard to check if a passenger can board a vehicle + * - Internal helper to set the controlling and spells for a vehicle's seat * - Internal helper to control the available seats of a vehicle */ +#include "Vehicle.h" #include "Common.h" #include "SharedDefines.h" #include "ObjectGuid.h" #include "Log.h" #include "Unit.h" #include "Creature.h" +#include "CreatureAI.h" #include "ObjectMgr.h" -#include "Vehicle.h" +#include "SQLStorages.h" #include "Util.h" #include "movement/MoveSplineInit.h" #include "movement/MoveSpline.h" #include "MapManager.h" +#include "TemporarySummon.h" + +void ObjectMgr::LoadVehicleAccessory() +{ + sVehicleAccessoryStorage.Load(); + + sLog.outString(">> Loaded %u vehicle accessories", sVehicleAccessoryStorage.GetRecordCount()); + sLog.outString(); + + // Check content + for (SQLMultiStorage::SQLSIterator itr = sVehicleAccessoryStorage.getDataBegin(); itr < sVehicleAccessoryStorage.getDataEnd(); ++itr) + { + if (!sCreatureStorage.LookupEntry(itr->vehicleEntry)) + { + sLog.outErrorDb("Table `vehicle_accessory` has entry (vehicle entry: %u, seat %u, passenger %u) where vehicle_entry is invalid, skip vehicle.", itr->vehicleEntry, itr->seatId, itr->passengerEntry); + sVehicleAccessoryStorage.EraseEntry(itr->vehicleEntry); + continue; + } + if (!sCreatureStorage.LookupEntry(itr->passengerEntry)) + { + sLog.outErrorDb("Table `vehicle_accessory` has entry (vehicle entry: %u, seat %u, passenger %u) where accessory_entry is invalid, skip vehicle.", itr->vehicleEntry, itr->seatId, itr->passengerEntry); + sVehicleAccessoryStorage.EraseEntry(itr->vehicleEntry); + continue; + } + if (itr->seatId >= MAX_VEHICLE_SEAT) + { + sLog.outErrorDb("Table `vehicle_accessory` has entry (vehicle entry: %u, seat %u, passenger %u) where seat is invalid (must be between 0 and %u), skip vehicle.", itr->vehicleEntry, itr->seatId, itr->passengerEntry, MAX_VEHICLE_SEAT - 1); + sVehicleAccessoryStorage.EraseEntry(itr->vehicleEntry); + continue; + } + } +} /** * Constructor of VehicleInfo * * @param owner MUST be provided owner of the vehicle (type Unit) * @param vehicleEntry MUST be provided dbc-entry of the vehicle + * @param overwriteNpcEntry Use to overwrite the GetEntry() result for selecting associated passengers * * This function will initialise the VehicleInfo of the vehicle owner * Also the seat-map is created here */ -VehicleInfo::VehicleInfo(Unit* owner, VehicleEntry const* vehicleEntry) : TransportBase(owner), +VehicleInfo::VehicleInfo(Unit* owner, VehicleEntry const* vehicleEntry, uint32 overwriteNpcEntry) : TransportBase(owner), m_vehicleEntry(vehicleEntry), m_creatureSeats(0), - m_playerSeats(0) + m_playerSeats(0), + m_overwriteNpcEntry(overwriteNpcEntry), + m_isInitialized(false) { MANGOS_ASSERT(vehicleEntry); @@ -78,6 +116,33 @@ VehicleInfo::VehicleInfo(Unit* owner, VehicleEntry const* vehicleEntry) : Transp } } +VehicleInfo::~VehicleInfo() +{ + ((Unit*)m_owner)->RemoveSpellsCausingAura(SPELL_AURA_CONTROL_VEHICLE); + + RemoveAccessoriesFromMap(); // Remove accessories (for example required with player vehicles) +} + +void VehicleInfo::Initialize() +{ + if (!m_overwriteNpcEntry) + m_overwriteNpcEntry = m_owner->GetEntry(); + + // Loading passengers (rough version only!) + SQLMultiStorage::SQLMSIteratorBounds bounds = sVehicleAccessoryStorage.getBounds(m_overwriteNpcEntry); + for (SQLMultiStorage::SQLMultiSIterator itr = bounds.first; itr != bounds.second; ++itr) + { + if (Creature* summoned = m_owner->SummonCreature(itr->passengerEntry, m_owner->GetPositionX(), m_owner->GetPositionY(), m_owner->GetPositionZ(), 2 * m_owner->GetOrientation(), TEMPSUMMON_DEAD_DESPAWN, 0)) + { + DEBUG_LOG("VehicleInfo(of %s)::Initialize: Load vehicle accessory %s onto seat %u", m_owner->GetGuidStr().c_str(), summoned->GetGuidStr().c_str(), itr->seatId); + m_accessoryGuids.insert(summoned->GetObjectGuid()); + int32 basepoint0 = itr->seatId + 1; + summoned->CastCustomSpell((Unit*)m_owner, SPELL_RIDE_VEHICLE_HARDCODED, &basepoint0, NULL, NULL, true); + } + } + m_isInitialized = true; +} + /** * This function will board a passenger onto a vehicle * @@ -88,11 +153,68 @@ void VehicleInfo::Board(Unit* passenger, uint8 seat) { MANGOS_ASSERT(passenger); - DEBUG_LOG("VehicleInfo::Board: Try to board passenger %s to seat %u", passenger->GetObjectGuid().GetString().c_str(), seat); + DEBUG_LOG("VehicleInfo(of %s)::Board: Try to board passenger %s to seat %u", m_owner->GetGuidStr().c_str(), passenger->GetGuidStr().c_str(), seat); + + // This check is also called in Spell::CheckCast() + if (!CanBoard(passenger)) + return; + + // Use the planned seat only if the seat is valid, possible to choose and empty + if (!IsSeatAvailableFor(passenger, seat)) + if (!GetUsableSeatFor(passenger, seat)) + return; + + VehicleSeatEntry const* seatEntry = GetSeatEntry(seat); + MANGOS_ASSERT(seatEntry); + + // ToDo: Unboard passenger from a MOTransport when they are properly implemented + /*if (TransportInfo* transportInfo = passenger->GetTransportInfo()) + { + WorldObject* transporter = transportInfo->GetTransport(); + + // Must be a MO transporter + MANGOS_ASSERT(transporter->GetObjectGuid().IsMOTransport()); + + ((Transport*)transporter)->UnBoardPassenger(passenger); + }*/ + + DEBUG_LOG("VehicleInfo::Board: Board passenger: %s to seat %u", passenger->GetGuidStr().c_str(), seat); + + // Calculate passengers local position + float lx, ly, lz, lo; + CalculateBoardingPositionOf(passenger->GetPositionX(), passenger->GetPositionY(), passenger->GetPositionZ(), passenger->GetOrientation(), lx, ly, lz, lo); + + BoardPassenger(passenger, lx, ly, lz, lo, seat); // Use TransportBase to store the passenger + + // Set data for createobject packets + passenger->m_movementInfo.SetTransportData(m_owner->GetObjectGuid(), lx, ly, lz, lo, 0, seat); + + if (passenger->GetTypeId() == TYPEID_PLAYER) + { + Player* pPlayer = (Player*)passenger; + pPlayer->RemovePet(PET_SAVE_AS_CURRENT); + + WorldPacket data(SMSG_ON_CANCEL_EXPECTED_RIDE_VEHICLE_AURA); + pPlayer->GetSession()->SendPacket(&data); + + // SMSG_BREAK_TARGET (?) + } + + if (!passenger->IsRooted()) + passenger->SetRoot(true); + + Movement::MoveSplineInit init(*passenger); + init.MoveTo(0.0f, 0.0f, 0.0f); // ToDo: Set correct local coords + init.SetFacing(0.0f); // local orientation ? ToDo: Set proper orientation! + init.SetBoardVehicle(); + init.Launch(); + + // Apply passenger modifications + ApplySeatMods(passenger, seatEntry->m_flags); } /** - * This function will switch the seat of a passenger + * This function will switch the seat of a passenger on the same vehicle * * @param passenger MUST be provided. This Unit will change its seat on the vehicle * @param seat Seat to which the passenger will be switched @@ -100,6 +222,50 @@ void VehicleInfo::Board(Unit* passenger, uint8 seat) void VehicleInfo::SwitchSeat(Unit* passenger, uint8 seat) { MANGOS_ASSERT(passenger); + + DEBUG_LOG("VehicleInfo::SwitchSeat: passenger: %s try to switch to seat %u", passenger->GetGuidStr().c_str(), seat); + + // Switching seats is not possible + if (m_vehicleEntry->m_flags & VEHICLE_FLAG_DISABLE_SWITCH) + return; + + PassengerMap::const_iterator itr = m_passengers.find(passenger); + MANGOS_ASSERT(itr != m_passengers.end()); + + // We are already boarded to this seat + if (itr->second->GetTransportSeat() == seat) + return; + + // Check if it's a valid seat + if (!IsSeatAvailableFor(passenger, seat)) + return; + + VehicleSeatEntry const* seatEntry = GetSeatEntry(itr->second->GetTransportSeat()); + MANGOS_ASSERT(seatEntry); + + // Switching seats is only allowed if this flag is set + if (~seatEntry->m_flags & SEAT_FLAG_CAN_SWITCH) + return; + + // Remove passenger modifications of the old seat + RemoveSeatMods(passenger, seatEntry->m_flags); + + // Set to new seat + itr->second->SetTransportSeat(seat); + + Movement::MoveSplineInit init(*passenger); + init.MoveTo(0.0f, 0.0f, 0.0f); // ToDo: Set correct local coords + //if (oldorientation != neworientation) (?) + //init.SetFacing(0.0f); // local orientation ? ToDo: Set proper orientation! + // It seems that Seat switching is sent without SplineFlag BoardVehicle + init.Launch(); + + // Get seatEntry of new seat + seatEntry = GetSeatEntry(seat); + MANGOS_ASSERT(seatEntry); + + // Apply passenger modifications of the new seat + ApplySeatMods(passenger, seatEntry->m_flags); } /** @@ -113,7 +279,63 @@ void VehicleInfo::UnBoard(Unit* passenger, bool changeVehicle) { MANGOS_ASSERT(passenger); - DEBUG_LOG("VehicleInfo::Unboard: passenger: %s", passenger->GetObjectGuid().GetString().c_str()); + DEBUG_LOG("VehicleInfo::Unboard: passenger: %s", passenger->GetGuidStr().c_str()); + + PassengerMap::const_iterator itr = m_passengers.find(passenger); + MANGOS_ASSERT(itr != m_passengers.end()); + + VehicleSeatEntry const* seatEntry = GetSeatEntry(itr->second->GetTransportSeat()); + MANGOS_ASSERT(seatEntry); + + UnBoardPassenger(passenger); // Use TransportBase to remove the passenger from storage list + + if (!changeVehicle) // Send expected unboarding packages + { + // Update movementInfo + passenger->m_movementInfo.ClearTransportData(); + + if (passenger->GetTypeId() == TYPEID_PLAYER) + { + Player* pPlayer = (Player*)passenger; + pPlayer->ResummonPetTemporaryUnSummonedIfAny(); + + // SMSG_PET_DISMISS_SOUND (?) + } + + if (passenger->IsRooted()) + passenger->SetRoot(false); + + Movement::MoveSplineInit init(*passenger); + // ToDo: Set proper unboard coordinates + init.MoveTo(m_owner->GetPositionX(), m_owner->GetPositionY(), m_owner->GetPositionZ()); + init.SetExitVehicle(); + init.Launch(); + + // Despawn if passenger was accessory + if (passenger->GetTypeId() == TYPEID_UNIT && m_accessoryGuids.find(passenger->GetObjectGuid()) != m_accessoryGuids.end()) + { + Creature* cPassenger = static_cast(passenger); + // TODO Same TODO as in VehicleInfo::RemoveAccessoriesFromMap + cPassenger->ForcedDespawn(5000); + m_accessoryGuids.erase(passenger->GetObjectGuid()); + } + } + + // Remove passenger modifications + RemoveSeatMods(passenger, seatEntry->m_flags); + + // Some creature vehicles get despawned after passenger unboarding + if (m_owner->GetTypeId() == TYPEID_UNIT) + { + // TODO: Guesswork, but seems to be fairly near correct + // Only if the passenger was on control seat? Also depending on some flags + if ((seatEntry->m_flags & SEAT_FLAG_CAN_CONTROL) && + !(m_vehicleEntry->m_flags & (VEHICLE_FLAG_UNK4 | VEHICLE_FLAG_UNK20))) + { + if (((Creature*)m_owner)->IsTemporarySummon()) + ((Creature*)m_owner)->ForcedDespawn(1000); + } + } } /** @@ -130,6 +352,14 @@ bool VehicleInfo::CanBoard(Unit* passenger) const if (passenger == m_owner) return false; + // Passenger is already on this vehicle (in this case switching seats is required) + if (passenger->IsBoarded() && passenger->GetTransportInfo()->GetTransport() == m_owner) + return false; + + // Prevent circular boarding: passenger (could only be vehicle) must not have m_owner on board + if (passenger->IsVehicle() && passenger->GetVehicleInfo()->HasOnBoard(m_owner)) + return false; + // Check if we have at least one empty seat if (!GetEmptySeats()) return false; @@ -146,8 +376,17 @@ bool VehicleInfo::CanBoard(Unit* passenger) const return GetEmptySeatsMask() & m_creatureSeats; } +Unit* VehicleInfo::GetPassenger(uint8 seat) const +{ + for (PassengerMap::const_iterator itr = m_passengers.begin(); itr != m_passengers.end(); ++itr) + if (itr->second->GetTransportSeat() == seat) + return (Unit*)itr->first; + + return NULL; +} + // Helper function to undo the turning of the vehicle to calculate a relative position of the passenger when boarding -void VehicleInfo::CalculateBoardingPositionOf(float gx, float gy, float gz, float go, float &lx, float &ly, float &lz, float &lo) +void VehicleInfo::CalculateBoardingPositionOf(float gx, float gy, float gz, float go, float& lx, float& ly, float& lz, float& lo) const { NormalizeRotatedPosition(gx - m_owner->GetPositionX(), gy - m_owner->GetPositionY(), lx, ly); @@ -155,6 +394,21 @@ void VehicleInfo::CalculateBoardingPositionOf(float gx, float gy, float gz, floa lo = NormalizeOrientation(go - m_owner->GetOrientation()); } +void VehicleInfo::RemoveAccessoriesFromMap() +{ + // Remove all accessories + for (GuidSet::const_iterator itr = m_accessoryGuids.begin(); itr != m_accessoryGuids.end(); ++itr) + { + if (Creature* pAccessory = m_owner->GetMap()->GetCreature(*itr)) + { + // TODO - unclear how long to despawn, also maybe some flag etc depending + pAccessory->ForcedDespawn(5000); + } + } + m_accessoryGuids.clear(); + m_isInitialized = false; +} + /* ************************************************************************************************ * Helper function for seat control * ***********************************************************************************************/ @@ -199,7 +453,7 @@ bool VehicleInfo::IsSeatAvailableFor(Unit* passenger, uint8 seat) const MANGOS_ASSERT(passenger); return seat < MAX_VEHICLE_SEAT && - (GetEmptySeatsMask() & (passenger->GetTypeId() == TYPEID_PLAYER ? m_playerSeats : m_creatureSeats) & (1 << seat)); + (GetEmptySeatsMask() & (passenger->GetTypeId() == TYPEID_PLAYER ? m_playerSeats : m_creatureSeats) & (1 << seat)); } /// Wrapper to collect all taken seats @@ -221,11 +475,102 @@ bool VehicleInfo:: IsUsableSeatForPlayer(uint32 seatFlags) const /// Add control and such modifiers to a passenger if required void VehicleInfo::ApplySeatMods(Unit* passenger, uint32 seatFlags) { + Unit* pVehicle = (Unit*)m_owner; // Vehicles are alawys Unit + + if (passenger->GetTypeId() == TYPEID_PLAYER) + { + Player* pPlayer = (Player*)passenger; + + if (seatFlags & SEAT_FLAG_CAN_CONTROL) + { + pPlayer->GetCamera().SetView(pVehicle); + + pPlayer->SetCharm(pVehicle); + pVehicle->SetCharmerGuid(pPlayer->GetObjectGuid()); + + pVehicle->addUnitState(UNIT_STAT_CONTROLLED); + pVehicle->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + + pPlayer->SetClientControl(pVehicle, 1); + pPlayer->SetMover(pVehicle); + + // Unconfirmed - default speed handling + if (pVehicle->GetTypeId() == TYPEID_UNIT) + { + if (!pPlayer->IsWalking() && pVehicle->IsWalking()) + { + ((Creature*)pVehicle)->SetWalk(false, true); + } + else if (pPlayer->IsWalking() && !pVehicle->IsWalking()) + { + ((Creature*)pVehicle)->SetWalk(true, true); + } + } + } + + if (seatFlags & (SEAT_FLAG_USABLE | SEAT_FLAG_CAN_CAST)) + { + CharmInfo* charmInfo = pVehicle->InitCharmInfo(pVehicle); + charmInfo->InitVehicleCreateSpells(); + + pPlayer->PossessSpellInitialize(); + } + } + else if (passenger->GetTypeId() == TYPEID_UNIT) + { + if (seatFlags & SEAT_FLAG_CAN_CONTROL) + { + passenger->SetCharm(pVehicle); + pVehicle->SetCharmerGuid(passenger->GetObjectGuid()); + } + + ((Creature*)passenger)->AI()->SetCombatMovement(false); + // Not entirely sure how this must be handled in relation to CONTROL + // But in any way this at least would require some changes in the movement system most likely + passenger->GetMotionMaster()->Clear(false, true); + passenger->GetMotionMaster()->MoveIdle(); + } } /// Remove control and such modifiers to a passenger if they were added void VehicleInfo::RemoveSeatMods(Unit* passenger, uint32 seatFlags) { + Unit* pVehicle = (Unit*)m_owner; + + if (passenger->GetTypeId() == TYPEID_PLAYER) + { + Player* pPlayer = (Player*)passenger; + + if (seatFlags & SEAT_FLAG_CAN_CONTROL) + { + pPlayer->SetCharm(NULL); + pVehicle->SetCharmerGuid(ObjectGuid()); + + pPlayer->SetClientControl(pVehicle, 0); + pPlayer->SetMover(NULL); + + pVehicle->clearUnitState(UNIT_STAT_CONTROLLED); + pVehicle->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + + // must be called after movement control unapplying + pPlayer->GetCamera().ResetView(); + } + + if (seatFlags & (SEAT_FLAG_USABLE | SEAT_FLAG_CAN_CAST)) + pPlayer->RemovePetActionBar(); + } + else if (passenger->GetTypeId() == TYPEID_UNIT) + { + if (seatFlags & SEAT_FLAG_CAN_CONTROL) + { + passenger->SetCharm(NULL); + pVehicle->SetCharmerGuid(ObjectGuid()); + } + // Reinitialize movement + ((Creature*)passenger)->AI()->SetCombatMovement(true, true); + if (!passenger->getVictim()) + passenger->GetMotionMaster()->Initialize(); + } } /*! @} */ diff --git a/src/shared/Util.cpp b/src/shared/Util.cpp index 38b77b297..ab304a8c7 100644 --- a/src/shared/Util.cpp +++ b/src/shared/Util.cpp @@ -260,6 +260,36 @@ std::string TimeToTimestampStr(time_t t) return std::string(buf); } +time_t timeBitFieldsToSecs(uint32 packedDate) +{ + tm lt; + memset(<, 0, sizeof(lt)); + + lt.tm_min = packedDate & 0x3F; + lt.tm_hour = (packedDate >> 6) & 0x1F; + lt.tm_wday = (packedDate >> 11) & 7; + lt.tm_mday = ((packedDate >> 14) & 0x3F) + 1; + lt.tm_mon = (packedDate >> 20) & 0xF; + lt.tm_year = ((packedDate >> 24) & 0x1F) + 100; + + return time_t(mktime(<)); +} + +std::string MoneyToString(uint64 money) +{ + uint32 gold = money / 10000; + uint32 silv = (money % 10000) / 100; + uint32 copp = (money % 10000) % 100; + std::stringstream ss; + if (gold) + ss << gold << "g"; + if (silv || gold) + ss << silv << "s"; + ss << copp << "c"; + + return ss.str(); +} + /// Check if the string is a valid ip address representation bool IsIPAddress(char const* ipaddress) { diff --git a/src/shared/revision_nr.h b/src/shared/revision_nr.h index 9e9890af4..8a212bcd6 100644 --- a/src/shared/revision_nr.h +++ b/src/shared/revision_nr.h @@ -1,4 +1,4 @@ #ifndef __REVISION_NR_H__ #define __REVISION_NR_H__ - #define REVISION_NR "12562" + #define REVISION_NR "12563" #endif // __REVISION_NR_H__