feat: limit user sent mail so that it can't be spammed

This commit is contained in:
Aronwk
2025-08-31 02:21:10 -05:00
parent 21a2ddcfd9
commit 277b415196
7 changed files with 78 additions and 43 deletions

View File

@@ -24,6 +24,9 @@ public:
// Get the number of unread mail for the given character id. // Get the number of unread mail for the given character id.
virtual uint32_t GetUnreadMailCount(const uint32_t characterId) = 0; virtual uint32_t GetUnreadMailCount(const uint32_t characterId) = 0;
// Get the number of mail for a given character id.
virtual uint32_t GetMailCount(const uint32_t characterId) = 0;
// Mark the given mail as read. // Mark the given mail as read.
virtual void MarkMailRead(const uint64_t mailId) = 0; virtual void MarkMailRead(const uint64_t mailId) = 0;

View File

@@ -89,6 +89,7 @@ public:
std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override; std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<MailInfo> GetMail(const uint64_t mailId) override; std::optional<MailInfo> GetMail(const uint64_t mailId) override;
uint32_t GetUnreadMailCount(const uint32_t characterId) override; uint32_t GetUnreadMailCount(const uint32_t characterId) override;
uint32_t GetMailCount(const uint32_t characterId) override;
void MarkMailRead(const uint64_t mailId) override; void MarkMailRead(const uint64_t mailId) override;
void DeleteMail(const uint64_t mailId) override; void DeleteMail(const uint64_t mailId) override;
void ClaimMailItem(const uint64_t mailId) override; void ClaimMailItem(const uint64_t mailId) override;

View File

@@ -71,6 +71,16 @@ uint32_t MySQLDatabase::GetUnreadMailCount(const uint32_t characterId) {
return res->getInt("number_unread"); return res->getInt("number_unread");
} }
uint32_t MySQLDatabase::GetMailCount(const uint32_t characterId) {
auto res = ExecuteSelect("SELECT COUNT(*) AS mail_count FROM mail WHERE receiver_id=?;", characterId);
if (!res->next()) {
return 0;
}
return res->getInt("mail_count");
}
void MySQLDatabase::MarkMailRead(const uint64_t mailId) { void MySQLDatabase::MarkMailRead(const uint64_t mailId) {
ExecuteUpdate("UPDATE mail SET was_read=1 WHERE id=? LIMIT 1;", mailId); ExecuteUpdate("UPDATE mail SET was_read=1 WHERE id=? LIMIT 1;", mailId);
} }

View File

@@ -87,6 +87,7 @@ public:
std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override; std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<MailInfo> GetMail(const uint64_t mailId) override; std::optional<MailInfo> GetMail(const uint64_t mailId) override;
uint32_t GetUnreadMailCount(const uint32_t characterId) override; uint32_t GetUnreadMailCount(const uint32_t characterId) override;
uint32_t GetMailCount(const uint32_t characterId) override;
void MarkMailRead(const uint64_t mailId) override; void MarkMailRead(const uint64_t mailId) override;
void DeleteMail(const uint64_t mailId) override; void DeleteMail(const uint64_t mailId) override;
void ClaimMailItem(const uint64_t mailId) override; void ClaimMailItem(const uint64_t mailId) override;

View File

@@ -70,6 +70,16 @@ uint32_t SQLiteDatabase::GetUnreadMailCount(const uint32_t characterId) {
return res.getIntField("number_unread"); return res.getIntField("number_unread");
} }
uint32_t SQLiteDatabase::GetMailCount(const uint32_t characterId) {
auto [_, res] = ExecuteSelect("SELECT COUNT(*) AS mail_count FROM mail WHERE receiver_id=?;", characterId);
if (res.eof()) {
return 0;
}
return res.getIntField("mail_count");
}
void SQLiteDatabase::MarkMailRead(const uint64_t mailId) { void SQLiteDatabase::MarkMailRead(const uint64_t mailId) {
ExecuteUpdate("UPDATE mail SET was_read=1 WHERE id=?;", mailId); ExecuteUpdate("UPDATE mail SET was_read=1 WHERE id=?;", mailId);
} }
@@ -81,3 +91,4 @@ void SQLiteDatabase::ClaimMailItem(const uint64_t mailId) {
void SQLiteDatabase::DeleteMail(const uint64_t mailId) { void SQLiteDatabase::DeleteMail(const uint64_t mailId) {
ExecuteDelete("DELETE FROM mail WHERE id=?;", mailId); ExecuteDelete("DELETE FROM mail WHERE id=?;", mailId);
} }

View File

@@ -66,6 +66,7 @@ class TestSQLDatabase : public GameDatabase {
std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override; std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<MailInfo> GetMail(const uint64_t mailId) override; std::optional<MailInfo> GetMail(const uint64_t mailId) override;
uint32_t GetUnreadMailCount(const uint32_t characterId) override; uint32_t GetUnreadMailCount(const uint32_t characterId) override;
uint32_t GetMailCount(const uint32_t characterId) override;
void MarkMailRead(const uint64_t mailId) override; void MarkMailRead(const uint64_t mailId) override;
void DeleteMail(const uint64_t mailId) override; void DeleteMail(const uint64_t mailId) override;
void ClaimMailItem(const uint64_t mailId) override; void ClaimMailItem(const uint64_t mailId) override;

View File

@@ -81,56 +81,64 @@ namespace Mail {
} else if (GeneralUtils::CaseInsensitiveStringCompare(mailInfo.recipient, character->GetName()) || receiverID->id == character->GetID()) { } else if (GeneralUtils::CaseInsensitiveStringCompare(mailInfo.recipient, character->GetName()) || receiverID->id == character->GetID()) {
response.status = eSendResponse::CannotMailSelf; response.status = eSendResponse::CannotMailSelf;
} else { } else {
uint32_t mailCost = Game::zoneManager->GetWorldConfig().mailBaseFee; // check if recipient mailbox is full
uint32_t stackSize = 0; if (Database::Get()->GetMailCount(receiverID->id) >= 20) {
// There is no Mailbox full response, so we will do this instead
auto inventoryComponent = player->GetComponent<InventoryComponent>(); response.status = eSendResponse::UnknownError;
Item* item = nullptr; // send system chat message to player
ChatPackets::SendSystemMessage(player->GetSystemAddress(), u"Recipient's mailbox is full. Please try again later.");
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 { } else {
bool removeSuccess = true; uint32_t mailCost = Game::zoneManager->GetWorldConfig().mailBaseFee;
// Remove coins and items from the sender uint32_t stackSize = 0;
player->GetCharacter()->SetCoins(player->GetCharacter()->GetCoins() - mailCost, eLootSourceType::MAIL);
if (inventoryComponent && hasAttachment && item) { auto inventoryComponent = player->GetComponent<InventoryComponent>();
removeSuccess = inventoryComponent->RemoveItem(mailInfo.itemLOT, mailInfo.itemCount, INVALID, true); Item* item = nullptr;
auto* missionComponent = player->GetComponent<MissionComponent>();
if (missionComponent && removeSuccess) missionComponent->Progress(eMissionTaskType::GATHER, mailInfo.itemLOT, LWOOBJID_EMPTY, "", -mailInfo.itemCount); 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();
}
} }
// we passed all the checks, now we can actully send the mail if (hasAttachment && !item) {
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; 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<MissionComponent>();
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;
} }
} else {
response.status = eSendResponse::SenderAccountIsMuted;
} }
LOG("Finished send with status %s", StringifiedEnum::ToString(response.status).data()); LOG("Finished send with status %s", StringifiedEnum::ToString(response.status).data());
response.Send(sysAddr); response.Send(sysAddr);