#include "Mail.h" #include #include #include #include #include #include #include "GeneralUtils.h" #include "Database.h" #include "Game.h" #include "dServer.h" #include "Entity.h" #include "Character.h" #include "BitStreamUtils.h" #include "Logger.h" #include "EntityManager.h" #include "InventoryComponent.h" #include "GameMessages.h" #include "Item.h" #include "MissionComponent.h" #include "ChatPackets.h" #include "Character.h" #include "dZoneManager.h" #include "WorldConfig.h" #include "eMissionTaskType.h" #include "eReplicaComponentType.h" #include "eConnectionType.h" #include "User.h" #include "StringifiedEnum.h" namespace { const std::string DefaultSender = "%[MAIL_SYSTEM_NOTIFICATION]"; } namespace Mail { std::map()>> g_Handlers = { {eMessageID::SendRequest, []() { return std::make_unique(); }}, {eMessageID::DataRequest, []() { return std::make_unique(); }}, {eMessageID::AttachmentCollectRequest, []() { return std::make_unique(); }}, {eMessageID::DeleteRequest, []() { return std::make_unique(); }}, {eMessageID::ReadRequest, []() { return std::make_unique(); }}, {eMessageID::NotificationRequest, []() { return std::make_unique(); }}, }; void MailLUBitStream::Serialize(RakNet::BitStream& bitStream) const { bitStream.Write(messageID); } bool MailLUBitStream::Deserialize(RakNet::BitStream& bitstream) { VALIDATE_READ(bitstream.Read(messageID)); return true; } bool SendRequest::Deserialize(RakNet::BitStream& bitStream) { VALIDATE_READ(mailInfo.Deserialize(bitStream)); return true; } void SendRequest::Handle() { SendResponse response; auto* character = player->GetCharacter(); if (character && !(character->HasPermission(ePermissionMap::RestrictedMailAccess) || character->GetParentUser()->GetIsMuted())) { mailInfo.recipient = std::regex_replace(mailInfo.recipient, std::regex("[^0-9a-zA-Z]+"), ""); auto receiverID = Database::Get()->GetCharacterInfo(mailInfo.recipient); if (!receiverID) { response.status = eSendResponse::RecipientNotFound; } else if (GeneralUtils::CaseInsensitiveStringCompare(mailInfo.recipient, character->GetName()) || receiverID->id == character->GetID()) { response.status = eSendResponse::CannotMailSelf; } else { uint32_t mailCost = Game::zoneManager->GetWorldConfig()->mailBaseFee; uint32_t stackSize = 0; auto inventoryComponent = player->GetComponent(); Item* item = nullptr; bool hasAttachment = mailInfo.itemID != 0 && mailInfo.itemCount > 0; if (hasAttachment) { item = inventoryComponent->FindItemById(mailInfo.itemID); if (item) { mailCost += (item->GetInfo().baseValue * Game::zoneManager->GetWorldConfig()->mailPercentAttachmentFee); mailInfo.itemLOT = item->GetLot(); } } if (hasAttachment && !item) { response.status = eSendResponse::AttachmentNotFound; } else if (player->GetCharacter()->GetCoins() - mailCost < 0) { response.status = eSendResponse::NotEnoughCoins; } else { bool removeSuccess = true; // Remove coins and items from the sender player->GetCharacter()->SetCoins(player->GetCharacter()->GetCoins() - mailCost, eLootSourceType::MAIL); if (inventoryComponent && hasAttachment && item) { removeSuccess = inventoryComponent->RemoveItem(mailInfo.itemLOT, mailInfo.itemCount, INVALID, true); auto* missionComponent = player->GetComponent(); if (missionComponent && removeSuccess) missionComponent->Progress(eMissionTaskType::GATHER, mailInfo.itemLOT, LWOOBJID_EMPTY, "", -mailInfo.itemCount); } // we passed all the checks, now we can actully send the mail if (removeSuccess) { mailInfo.senderId = character->GetID(); mailInfo.senderUsername = character->GetName(); mailInfo.receiverId = receiverID->id; mailInfo.itemSubkey = LWOOBJID_EMPTY; //clear out the attachementID mailInfo.itemID = 0; Database::Get()->InsertNewMail(mailInfo); response.status = eSendResponse::Success; character->SaveXMLToDatabase(); } else { response.status = eSendResponse::AttachmentNotFound; } } } } else { response.status = eSendResponse::SenderAccountIsMuted; } response.Send(sysAddr); } void SendResponse::Serialize(RakNet::BitStream& bitStream) const { MailLUBitStream::Serialize(bitStream); bitStream.Write(status); } void NotificationResponse::Serialize(RakNet::BitStream& bitStream) const { MailLUBitStream::Serialize(bitStream); bitStream.Write(status); bitStream.Write(0); // unused bitStream.Write(0); // unused bitStream.Write(auctionID); bitStream.Write(0); // unused bitStream.Write(mailCount); bitStream.Write(0); // packing } void DataRequest::Handle() { auto playerMail = Database::Get()->GetMailForPlayer(player->GetCharacter()->GetID(), 20); DataResponse response; response.playerMail = playerMail; response.Send(sysAddr); } void DataResponse::Serialize(RakNet::BitStream& bitStream) const { MailLUBitStream::Serialize(bitStream); bitStream.Write(this->throttled); bitStream.Write(this->playerMail.size()); bitStream.Write(0); // packing for (const auto& mail : this->playerMail) { mail.Serialize(bitStream); } } bool AttachmentCollectRequest::Deserialize(RakNet::BitStream& bitStream) { uint32_t unknown; VALIDATE_READ(bitStream.Read(unknown)); VALIDATE_READ(bitStream.Read(mailID)); VALIDATE_READ(bitStream.Read(playerID)); return true; } void AttachmentCollectRequest::Handle() { AttachmentCollectResponse response; auto inv = player->GetComponent(); if (mailID > 0 && playerID == player->GetObjectID() && inv) { auto playerMail = Database::Get()->GetMail(mailID); if (!playerMail) { response.status = eAttachmentCollectResponse::MailNotFound; } else if (!inv->HasSpaceForLoot({ {playerMail->itemLOT, playerMail->itemCount} })) { response.status = eAttachmentCollectResponse::NoSpaceInInventory; } else { inv->AddItem(playerMail->itemLOT, playerMail->itemCount, eLootSourceType::MAIL); Database::Get()->ClaimMailItem(mailID); response.status = eAttachmentCollectResponse::Success; } } response.Send(sysAddr); } void AttachmentCollectResponse::Serialize(RakNet::BitStream& bitStream) const { MailLUBitStream::Serialize(bitStream); bitStream.Write(status); bitStream.Write(mailID); } bool DeleteRequest::Deserialize(RakNet::BitStream& bitStream) { int32_t unknown; VALIDATE_READ(bitStream.Read(unknown)); VALIDATE_READ(bitStream.Read(mailID)); VALIDATE_READ(bitStream.Read(playerID)); return true; } void DeleteRequest::Handle() { DeleteResponse response; response.mailID = mailID; auto mailData = Database::Get()->GetMail(mailID); if (mailData && !(mailData->itemLOT != 0 && mailData->itemCount > 0)) { Database::Get()->DeleteMail(mailID); response.status = eDeleteResponse::Success; } else if (mailData && mailData->itemLOT != 0 && mailData->itemCount > 0) { response.status = eDeleteResponse::HasAttachments; } else { response.status = eDeleteResponse::NotFound; } response.Send(sysAddr); } void DeleteResponse::Serialize(RakNet::BitStream& bitStream) const { MailLUBitStream::Serialize(bitStream); bitStream.Write(status); bitStream.Write(mailID); } bool ReadRequest::Deserialize(RakNet::BitStream& bitStream) { int32_t unknown; VALIDATE_READ(bitStream.Read(unknown)); VALIDATE_READ(bitStream.Read(mailID)); return true; } void ReadRequest::Handle() { ReadResponse response; response.mailID = mailID; if (Database::Get()->GetMail(mailID)) { response.status = eReadResponse::Success; Database::Get()->MarkMailRead(mailID); } response.Send(sysAddr); } void ReadResponse::Serialize(RakNet::BitStream& bitStream) const { MailLUBitStream::Serialize(bitStream); bitStream.Write(status); bitStream.Write(mailID); } void NotificationRequest::Handle() { NotificationResponse response; auto character = player->GetCharacter(); if (character) { auto unreadMailCount = Database::Get()->GetUnreadMailCount(character->GetID()); response.status = eNotificationResponse::NewMail; response.mailCount = unreadMailCount; } response.Send(sysAddr); } } // Non Stuct Functions void Mail::HandleMail(RakNet::BitStream& inStream, const SystemAddress& sysAddr, Entity* player) { MailLUBitStream data; if (!data.Deserialize(inStream)) { LOG_DEBUG("Error Reading Mail header"); return; } auto it = g_Handlers.find(data.messageID); if (it != g_Handlers.end()) { auto request = it->second(); request->sysAddr = sysAddr; request->player = player; if (!request->Deserialize(inStream)) { LOG_DEBUG("Error Reading Mail Request: %s", StringifiedEnum::ToString(data.messageID).data()); return; } request->Handle(); } else { LOG_DEBUG("Unhandled Mail Request with ID: %i", data.messageID); } } void Mail::SendMail(const Entity* recipient, const std::string& subject, const std::string& body, const LOT attachment, const uint16_t attachmentCount) { SendMail( LWOOBJID_EMPTY, DefaultSender, recipient->GetObjectID(), recipient->GetCharacter()->GetName(), subject, body, attachment, attachmentCount, recipient->GetSystemAddress() ); } void Mail::SendMail(const LWOOBJID recipient, const std::string& recipientName, const std::string& subject, const std::string& body, const LOT attachment, const uint16_t attachmentCount, const SystemAddress& sysAddr) { SendMail( LWOOBJID_EMPTY, DefaultSender, recipient, recipientName, subject, body, attachment, attachmentCount, sysAddr ); } void Mail::SendMail(const LWOOBJID sender, const std::string& senderName, const Entity* recipient, const std::string& subject, const std::string& body, const LOT attachment, const uint16_t attachmentCount) { SendMail( sender, senderName, recipient->GetObjectID(), recipient->GetCharacter()->GetName(), subject, body, attachment, attachmentCount, recipient->GetSystemAddress() ); } void Mail::SendMail(const LWOOBJID sender, const std::string& senderName, LWOOBJID recipient, const std::string& recipientName, const std::string& subject, const std::string& body, const LOT attachment, const uint16_t attachmentCount, const SystemAddress& sysAddr) { MailInfo mailInsert; mailInsert.senderUsername = senderName; mailInsert.recipient = recipientName; mailInsert.subject = subject; mailInsert.body = body; mailInsert.senderId = sender; mailInsert.receiverId = recipient; mailInsert.itemCount = attachmentCount; mailInsert.itemID = LWOOBJID_EMPTY; mailInsert.itemLOT = attachment; mailInsert.itemSubkey = LWOOBJID_EMPTY; Database::Get()->InsertNewMail(mailInsert); if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) return; // TODO: Echo to chat server NotificationResponse response; response.status = eNotificationResponse::NewMail; response.Send(sysAddr); }