/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see .
*/
#include "WorldSession.h"
#include "Battleground.h"
#include "Common.h"
#include "Creature.h"
#include "CreatureAI.h"
#include "DatabaseEnv.h"
#include "GameObject.h"
#include "GameObjectAI.h"
#include "GossipDef.h"
#include "Group.h"
#include "Log.h"
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "QuestDef.h"
#include "QuestPackets.h"
#include "ScriptMgr.h"
#include "World.h"
#include "WorldPacket.h"
void WorldSession::HandleQuestgiverStatusQueryOpcode(WorldPacket& recvData)
{
ObjectGuid guid;
recvData >> guid;
uint32 questStatus = DIALOG_STATUS_NONE;
Object* questGiver = ObjectAccessor::GetObjectByTypeMask(*_player, guid, TYPEMASK_UNIT | TYPEMASK_GAMEOBJECT);
if (!questGiver)
{
TC_LOG_INFO("network", "Error in CMSG_QUESTGIVER_STATUS_QUERY, called for non-existing questgiver (%s)", guid.ToString().c_str());
return;
}
switch (questGiver->GetTypeId())
{
case TYPEID_UNIT:
{
TC_LOG_DEBUG("network", "WORLD: Received CMSG_QUESTGIVER_STATUS_QUERY for npc %s", questGiver->GetGUID().ToString().c_str());
if (!questGiver->ToCreature()->IsHostileTo(_player)) // do not show quest status to enemies
questStatus = _player->GetQuestDialogStatus(questGiver);
break;
}
case TYPEID_GAMEOBJECT:
{
TC_LOG_DEBUG("network", "WORLD: Received CMSG_QUESTGIVER_STATUS_QUERY for GameObject %s", questGiver->GetGUID().ToString().c_str());
questStatus = _player->GetQuestDialogStatus(questGiver);
break;
}
default:
TC_LOG_ERROR("network", "QuestGiver called for unexpected type %u", questGiver->GetTypeId());
break;
}
// inform client about status of quest
_player->PlayerTalkClass->SendQuestGiverStatus(uint8(questStatus), guid);
}
void WorldSession::HandleQuestgiverHelloOpcode(WorldPacket& recvData)
{
ObjectGuid guid;
recvData >> guid;
TC_LOG_DEBUG("network", "WORLD: Received CMSG_QUESTGIVER_HELLO %s", guid.ToString().c_str());
Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_QUESTGIVER);
if (!creature)
{
TC_LOG_DEBUG("network", "WORLD: HandleQuestgiverHelloOpcode - %s not found or you can't interact with him.",
guid.ToString().c_str());
return;
}
// remove fake death
if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
// Stop the npc if moving
if (uint32 pause = creature->GetMovementTemplate().GetInteractionPauseTimer())
creature->PauseMovement(pause);
creature->SetHomePosition(creature->GetPosition());
_player->PlayerTalkClass->ClearMenus();
if (creature->AI()->OnGossipHello(_player))
return;
_player->PrepareGossipMenu(creature, creature->GetCreatureTemplate()->GossipMenuId, true);
_player->SendPreparedGossip(creature);
}
void WorldSession::HandleQuestgiverAcceptQuestOpcode(WorldPacket& recvData)
{
ObjectGuid guid;
uint32 questId;
uint32 startCheat;
recvData >> guid >> questId >> startCheat;
TC_LOG_DEBUG("network", "WORLD: Received CMSG_QUESTGIVER_ACCEPT_QUEST %s, quest = %u, startCheat = %u", guid.ToString().c_str(), questId, startCheat);
Object* object;
if (!guid.IsPlayer())
object = ObjectAccessor::GetObjectByTypeMask(*_player, guid, TYPEMASK_UNIT | TYPEMASK_GAMEOBJECT | TYPEMASK_ITEM);
else
object = ObjectAccessor::FindPlayer(guid);
auto CLOSE_GOSSIP_CLEAR_SHARING_INFO = ([this]()
{
_player->PlayerTalkClass->SendCloseGossip();
_player->ClearQuestSharingInfo();
});
// no or incorrect quest giver
if (!object)
{
CLOSE_GOSSIP_CLEAR_SHARING_INFO();
return;
}
if (Player* playerQuestObject = object->ToPlayer())
{
if ((_player->GetPlayerSharingQuest() && _player->GetPlayerSharingQuest() != guid) || !playerQuestObject->CanShareQuest(questId))
{
CLOSE_GOSSIP_CLEAR_SHARING_INFO();
return;
}
if (!_player->IsInSameRaidWith(playerQuestObject))
{
CLOSE_GOSSIP_CLEAR_SHARING_INFO();
return;
}
}
else
{
if (!object->hasQuest(questId))
{
CLOSE_GOSSIP_CLEAR_SHARING_INFO();
return;
}
}
// some kind of WPE protection
if (!_player->CanInteractWithQuestGiver(object))
{
CLOSE_GOSSIP_CLEAR_SHARING_INFO();
return;
}
if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId))
{
// prevent cheating
if (!GetPlayer()->CanTakeQuest(quest, true))
{
CLOSE_GOSSIP_CLEAR_SHARING_INFO();
return;
}
if (!_player->GetPlayerSharingQuest().IsEmpty())
{
Player* player = ObjectAccessor::FindPlayer(_player->GetPlayerSharingQuest());
if (player)
{
player->SendPushToPartyResponse(_player, QUEST_PARTY_MSG_ACCEPT_QUEST);
_player->ClearQuestSharingInfo();
}
}
if (_player->CanAddQuest(quest, true))
{
_player->AddQuestAndCheckCompletion(quest, object);
if (quest->HasFlag(QUEST_FLAGS_PARTY_ACCEPT))
{
if (Group* group = _player->GetGroup())
{
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* player = itr->GetSource();
if (!player || player == _player) // not self
continue;
if (player->CanTakeQuest(quest, true))
{
player->SetQuestSharingInfo(_player->GetGUID(), questId);
// need confirmation that any gossip window will close
player->PlayerTalkClass->SendCloseGossip();
_player->SendQuestConfirmAccept(quest, player);
}
}
}
}
_player->PlayerTalkClass->SendCloseGossip();
if (quest->GetSrcSpell() > 0)
_player->CastSpell(_player, quest->GetSrcSpell(), true);
return;
}
}
CLOSE_GOSSIP_CLEAR_SHARING_INFO();
#undef CLOSE_GOSSIP_CLEAR_SHARING_INFO
}
void WorldSession::HandleQuestgiverQueryQuestOpcode(WorldPacket& recvData)
{
ObjectGuid guid;
uint32 questId;
uint8 unk1;
recvData >> guid >> questId >> unk1;
TC_LOG_DEBUG("network", "WORLD: Received CMSG_QUESTGIVER_QUERY_QUEST npc = %s, quest = %u, unk1 = %u", guid.ToString().c_str(), questId, unk1);
// Verify that the guid is valid and is a questgiver or involved in the requested quest
Object* object = ObjectAccessor::GetObjectByTypeMask(*_player, guid, TYPEMASK_UNIT | TYPEMASK_GAMEOBJECT | TYPEMASK_ITEM);
if (!object || (!object->hasQuest(questId) && !object->hasInvolvedQuest(questId)))
{
_player->PlayerTalkClass->SendCloseGossip();
return;
}
if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId))
{
// not sure here what should happen to quests with QUEST_FLAGS_AUTOCOMPLETE
// if this breaks them, add && object->GetTypeId() == TYPEID_ITEM to this check
// item-started quests never have that flag
if (!_player->CanTakeQuest(quest, true))
return;
if (quest->IsAutoAccept() && _player->CanAddQuest(quest, true))
_player->AddQuestAndCheckCompletion(quest, object);
if (quest->HasFlag(QUEST_FLAGS_AUTOCOMPLETE))
_player->PlayerTalkClass->SendQuestGiverRequestItems(quest, object->GetGUID(), _player->CanCompleteQuest(quest->GetQuestId()), true);
else
_player->PlayerTalkClass->SendQuestGiverQuestDetails(quest, object->GetGUID(), true);
}
}
void WorldSession::HandleQuestQueryOpcode(WorldPackets::Quest::QueryQuestInfo& query)
{
TC_LOG_DEBUG("network", "WORLD: Received CMSG_QUEST_QUERY quest = %u", query.QuestID);
if (Quest const* quest = sObjectMgr->GetQuestTemplate(query.QuestID))
_player->PlayerTalkClass->SendQuestQueryResponse(quest);
}
void WorldSession::HandleQuestgiverChooseRewardOpcode(WorldPacket& recvData)
{
uint32 questId, reward;
ObjectGuid guid;
recvData >> guid >> questId >> reward;
if (reward >= QUEST_REWARD_CHOICES_COUNT)
{
TC_LOG_ERROR("entities.player.cheat", "Error in CMSG_QUESTGIVER_CHOOSE_REWARD: player %s %s tried to get invalid reward (%u) (possible packet-hacking detected)", _player->GetName().c_str(), _player->GetGUID().ToString().c_str(), reward);
return;
}
TC_LOG_DEBUG("network", "WORLD: Received CMSG_QUESTGIVER_CHOOSE_REWARD npc = %s, quest = %u, reward = %u", guid.ToString().c_str(), questId, reward);
Object* object = ObjectAccessor::GetObjectByTypeMask(*_player, guid, TYPEMASK_UNIT | TYPEMASK_GAMEOBJECT);
if (!object || !object->hasInvolvedQuest(questId))
return;
// some kind of WPE protection
if (!_player->CanInteractWithQuestGiver(object))
return;
if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId))
{
if ((!_player->CanSeeStartQuest(quest) && _player->GetQuestStatus(questId) == QUEST_STATUS_NONE) ||
(_player->GetQuestStatus(questId) != QUEST_STATUS_COMPLETE && !quest->IsAutoComplete()))
{
TC_LOG_ERROR("entities.player.cheat", "Error in QUEST_STATUS_COMPLETE: player %s %s tried to complete quest %u, but is not allowed to do so (possible packet-hacking or high latency)",
_player->GetName().c_str(), _player->GetGUID().ToString().c_str(), questId);
return;
}
if (_player->CanRewardQuest(quest, true)) // First, check if player is allowed to turn the quest in (all objectives completed). If not, we send players to the offer reward screen
{
if (_player->CanRewardQuest(quest, reward, true)) // Then check if player can receive the reward item (if inventory is not full, if player doesn't have too many unique items, and so on). If not, the client will close the gossip window
{
_player->RewardQuest(quest, reward, object);
switch (object->GetTypeId())
{
case TYPEID_UNIT:
{
Creature* questgiver = object->ToCreature();
// Send next quest
if (Quest const* nextQuest = _player->GetNextQuest(guid, quest))
{
// Only send the quest to the player if the conditions are met
if (_player->CanTakeQuest(nextQuest, false))
{
if (nextQuest->IsAutoAccept() && _player->CanAddQuest(nextQuest, true))
_player->AddQuestAndCheckCompletion(nextQuest, object);
_player->PlayerTalkClass->SendQuestGiverQuestDetails(nextQuest, guid, true);
}
}
_player->PlayerTalkClass->ClearMenus();
questgiver->AI()->OnQuestReward(_player, quest, reward);
break;
}
case TYPEID_GAMEOBJECT:
{
GameObject* questGiver = object->ToGameObject();
// Send next quest
if (Quest const* nextQuest = _player->GetNextQuest(guid, quest))
{
// Only send the quest to the player if the conditions are met
if (_player->CanTakeQuest(nextQuest, false))
{
if (nextQuest->IsAutoAccept() && _player->CanAddQuest(nextQuest, true))
_player->AddQuestAndCheckCompletion(nextQuest, object);
_player->PlayerTalkClass->SendQuestGiverQuestDetails(nextQuest, guid, true);
}
}
_player->PlayerTalkClass->ClearMenus();
questGiver->AI()->OnQuestReward(_player, quest, reward);
break;
}
default:
break;
}
}
}
else
_player->PlayerTalkClass->SendQuestGiverOfferReward(quest, guid, true);
}
}
void WorldSession::HandleQuestgiverRequestRewardOpcode(WorldPacket& recvData)
{
uint32 questId;
ObjectGuid guid;
recvData >> guid >> questId;
TC_LOG_DEBUG("network", "WORLD: Received CMSG_QUESTGIVER_REQUEST_REWARD npc = %s, quest = %u", guid.ToString().c_str(), questId);
Object* object = ObjectAccessor::GetObjectByTypeMask(*_player, guid, TYPEMASK_UNIT | TYPEMASK_GAMEOBJECT);
if (!object || !object->hasInvolvedQuest(questId))
return;
// some kind of WPE protection
if (!_player->CanInteractWithQuestGiver(object))
return;
if (_player->CanCompleteQuest(questId))
_player->CompleteQuest(questId);
if (_player->GetQuestStatus(questId) != QUEST_STATUS_COMPLETE)
return;
if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId))
_player->PlayerTalkClass->SendQuestGiverOfferReward(quest, guid, true);
}
void WorldSession::HandleQuestgiverCancel(WorldPacket& /*recvData*/)
{
TC_LOG_DEBUG("network", "WORLD: Received CMSG_QUESTGIVER_CANCEL");
_player->PlayerTalkClass->SendCloseGossip();
}
void WorldSession::HandleQuestLogSwapQuest(WorldPacket& recvData)
{
uint8 slot1, slot2;
recvData >> slot1 >> slot2;
if (slot1 == slot2 || slot1 >= MAX_QUEST_LOG_SIZE || slot2 >= MAX_QUEST_LOG_SIZE)
return;
TC_LOG_DEBUG("network", "WORLD: Received CMSG_QUESTLOG_SWAP_QUEST slot 1 = %u, slot 2 = %u", slot1, slot2);
GetPlayer()->SwapQuestSlot(slot1, slot2);
}
void WorldSession::HandleQuestLogRemoveQuest(WorldPacket& recvData)
{
uint8 slot;
recvData >> slot;
TC_LOG_DEBUG("network", "WORLD: Received CMSG_QUESTLOG_REMOVE_QUEST slot = %u", slot);
if (slot < MAX_QUEST_LOG_SIZE)
{
if (uint32 questId = _player->GetQuestSlotQuestId(slot))
{
if (!_player->TakeQuestSourceItem(questId, true))
return; // can't un-equip some items, reject quest cancel
if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId))
{
if (quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_TIMED))
_player->RemoveTimedQuest(questId);
if (quest->HasFlag(QUEST_FLAGS_FLAGS_PVP))
{
_player->pvpInfo.IsHostile = _player->pvpInfo.IsInHostileArea || _player->HasPvPForcingQuest();
_player->UpdatePvPState();
}
}
_player->TakeQuestSourceItem(questId, true); // remove quest src item from player
_player->AbandonQuest(questId); // remove all quest items player received before abandoning quest. Note, this does not remove normal drop items that happen to be quest requirements.
_player->RemoveActiveQuest(questId);
_player->RemoveTimedAchievement(ACHIEVEMENT_TIMED_TYPE_QUEST, questId);
TC_LOG_INFO("network", "Player %s abandoned quest %u", _player->GetGUID().ToString().c_str(), questId);
if (sWorld->getBoolConfig(CONFIG_QUEST_ENABLE_QUEST_TRACKER)) // check if Quest Tracker is enabled
{
// prepare Quest Tracker datas
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_QUEST_TRACK_ABANDON_TIME);
stmt->setUInt32(0, questId);
stmt->setUInt32(1, _player->GetGUID().GetCounter());
// add to Quest Tracker
CharacterDatabase.Execute(stmt);
}
sScriptMgr->OnQuestStatusChange(_player, questId);
}
_player->SetQuestSlot(slot, 0);
_player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_QUEST_ABANDONED, 1);
}
}
void WorldSession::HandleQuestConfirmAccept(WorldPacket& recvData)
{
uint32 questId;
recvData >> questId;
TC_LOG_DEBUG("network", "WORLD: Received CMSG_QUEST_CONFIRM_ACCEPT quest = %u", questId);
if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId))
{
if (!quest->HasFlag(QUEST_FLAGS_PARTY_ACCEPT))
return;
Player* originalPlayer = ObjectAccessor::FindPlayer(_player->GetPlayerSharingQuest());
if (!originalPlayer)
return;
if (!_player->IsInSameRaidWith(originalPlayer))
return;
if (!originalPlayer->IsActiveQuest(questId))
return;
if (!_player->CanTakeQuest(quest, true))
return;
if (_player->CanAddQuest(quest, true))
{
_player->AddQuestAndCheckCompletion(quest, nullptr); // NULL, this prevent DB script from duplicate running
if (quest->GetSrcSpell() > 0)
_player->CastSpell(_player, quest->GetSrcSpell(), true);
}
}
_player->ClearQuestSharingInfo();
}
void WorldSession::HandleQuestgiverCompleteQuest(WorldPacket& recvData)
{
uint32 questId;
ObjectGuid guid;
recvData >> guid >> questId;
TC_LOG_DEBUG("network", "WORLD: Received CMSG_QUESTGIVER_COMPLETE_QUEST npc = %s, quest = %u", guid.ToString().c_str(), questId);
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
if (!quest)
return;
Object* object = ObjectAccessor::GetObjectByTypeMask(*_player, guid, TYPEMASK_UNIT | TYPEMASK_GAMEOBJECT);
if (!object || !object->hasInvolvedQuest(questId))
return;
// some kind of WPE protection
if (!_player->CanInteractWithQuestGiver(object))
return;
if (!_player->CanSeeStartQuest(quest) && _player->GetQuestStatus(questId) == QUEST_STATUS_NONE)
{
TC_LOG_ERROR("entities.player.cheat", "Possible hacking attempt: Player %s %s tried to complete quest [entry: %u] without being in possession of the quest!",
_player->GetName().c_str(), _player->GetGUID().ToString().c_str(), questId);
return;
}
if (Battleground* bg = _player->GetBattleground())
bg->HandleQuestComplete(questId, _player);
if (_player->GetQuestStatus(questId) != QUEST_STATUS_COMPLETE)
{
if (quest->IsRepeatable())
_player->PlayerTalkClass->SendQuestGiverRequestItems(quest, guid, _player->CanCompleteRepeatableQuest(quest), false);
else
_player->PlayerTalkClass->SendQuestGiverRequestItems(quest, guid, _player->CanRewardQuest(quest, false), false);
}
else
{
if (quest->GetReqItemsCount()) // some items required
_player->PlayerTalkClass->SendQuestGiverRequestItems(quest, guid, _player->CanRewardQuest(quest, false), false);
else // no items required
_player->PlayerTalkClass->SendQuestGiverOfferReward(quest, guid, true);
}
}
void WorldSession::HandleQuestgiverQuestAutoLaunch(WorldPacket& /*recvPacket*/)
{
TC_LOG_DEBUG("network", "WORLD: Received CMSG_QUESTGIVER_QUEST_AUTOLAUNCH");
}
void WorldSession::HandlePushQuestToParty(WorldPacket& recvPacket)
{
uint32 questId;
recvPacket >> questId;
if (!_player->CanShareQuest(questId))
return;
TC_LOG_DEBUG("network", "WORLD: Received CMSG_PUSHQUESTTOPARTY questId = %u", questId);
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
if (!quest)
return;
Player * const sender = GetPlayer();
Group* group = sender->GetGroup();
if (!group)
{
sender->SendPushToPartyResponse(sender, QUEST_PARTY_MSG_NOT_IN_PARTY);
return;
}
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* receiver = itr->GetSource();
if (!receiver || receiver == sender)
continue;
if (!receiver->SatisfyQuestStatus(quest, false))
{
sender->SendPushToPartyResponse(receiver, QUEST_PARTY_MSG_HAVE_QUEST);
continue;
}
if (receiver->GetQuestStatus(questId) == QUEST_STATUS_COMPLETE)
{
sender->SendPushToPartyResponse(receiver, QUEST_PARTY_MSG_FINISH_QUEST);
continue;
}
if (!receiver->SatisfyQuestDay(quest, false))
{
sender->SendPushToPartyResponse(receiver, QUEST_PARTY_MSG_NOT_ELIGIBLE_TODAY);
continue;
}
if (!receiver->CanTakeQuest(quest, false))
{
sender->SendPushToPartyResponse(receiver, QUEST_PARTY_MSG_CANT_TAKE_QUEST);
continue;
}
if (!receiver->SatisfyQuestLog(false))
{
sender->SendPushToPartyResponse(receiver, QUEST_PARTY_MSG_LOG_FULL);
continue;
}
if (receiver->GetPlayerSharingQuest())
{
sender->SendPushToPartyResponse(receiver, QUEST_PARTY_MSG_BUSY);
continue;
}
sender->SendPushToPartyResponse(receiver, QUEST_PARTY_MSG_SHARING_QUEST);
if ((quest->IsAutoComplete() && quest->IsRepeatable() && !quest->IsDailyOrWeekly()) || quest->HasFlag(QUEST_FLAGS_AUTOCOMPLETE))
receiver->PlayerTalkClass->SendQuestGiverRequestItems(quest, sender->GetGUID(), receiver->CanCompleteRepeatableQuest(quest), true);
else
{
receiver->SetQuestSharingInfo(sender->GetGUID(), questId);
receiver->PlayerTalkClass->SendQuestGiverQuestDetails(quest, receiver->GetGUID(), true);
if (quest->IsAutoAccept() && receiver->CanAddQuest(quest, true) && receiver->CanTakeQuest(quest, true))
{
receiver->AddQuestAndCheckCompletion(quest, sender);
sender->SendPushToPartyResponse(receiver, QUEST_PARTY_MSG_ACCEPT_QUEST);
receiver->ClearQuestSharingInfo();
}
}
}
}
void WorldSession::HandleQuestPushResult(WorldPacket& recvPacket)
{
ObjectGuid guid;
uint32 questId;
uint8 msg;
recvPacket >> guid >> questId >> msg;
TC_LOG_DEBUG("network", "WORLD: Received MSG_QUEST_PUSH_RESULT");
if (!_player->GetPlayerSharingQuest())
return;
if (_player->GetPlayerSharingQuest() == guid)
{
Player* player = ObjectAccessor::FindPlayer(guid);
if (player)
player->SendPushToPartyResponse(_player, static_cast(msg));
}
_player->ClearQuestSharingInfo();
}
void WorldSession::HandleQuestgiverStatusMultipleQuery(WorldPacket& /*recvPacket*/)
{
TC_LOG_DEBUG("network", "WORLD: Received CMSG_QUESTGIVER_STATUS_MULTIPLE_QUERY");
_player->SendQuestGiverStatusMultiple();
}
void WorldSession::HandleQueryQuestsCompleted(WorldPacket & /*recvData*/)
{
size_t rew_count = _player->GetRewardedQuestCount();
WorldPacket data(SMSG_QUERY_QUESTS_COMPLETED_RESPONSE, 4 + 4 * rew_count);
data << uint32(rew_count);
RewardedQuestSet const& rewQuests = _player->getRewardedQuests();
for (RewardedQuestSet::const_iterator itr = rewQuests.begin(); itr != rewQuests.end(); ++itr)
data << uint32(*itr);
SendPacket(&data);
}