Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(Core/ServerMail): Refactor to Dedicated Manager Class with Multi-Item & Condition Support #21590

Merged
merged 49 commits into from
Mar 9, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
3814967
refactor(Core/Mail): Allow ServerMail to send multiple items
Kitzunu Feb 23, 2025
cc5950f
fix sql error
Kitzunu Feb 23, 2025
2ed3a48
add check for no item count
Kitzunu Feb 23, 2025
591b5fc
Merge branch 'master' into mail_server_template_items
sudlud Feb 24, 2025
e279adf
Update data/sql/updates/pending_db_characters/rev_1740310084140595600…
Kitzunu Mar 1, 2025
cea8458
refactor(Core/Mail): Move server mail into it's own file
Kitzunu Mar 1, 2025
1f0d733
Merge branch 'mail_server_template_items' of https://github.com/Kitzu…
Kitzunu Mar 1, 2025
c1a8d5e
Merge branch 'master' into mail_server_template_items
Kitzunu Mar 1, 2025
afce941
Add a conditions table
Kitzunu Mar 1, 2025
a899e65
Merge branch 'mail_server_template_items' of https://github.com/Kitzu…
Kitzunu Mar 1, 2025
d6ab065
MailConditionType -> ServerMailConditionType
Kitzunu Mar 1, 2025
63446cc
remove not needed log
Kitzunu Mar 1, 2025
945b7f2
move faction check for money into playerscript
Kitzunu Mar 1, 2025
63f77b1
typo
Kitzunu Mar 1, 2025
74e7050
typo 2
Kitzunu Mar 1, 2025
124b054
might as well make the sql check happy, even if we dont need it becau…
Kitzunu Mar 1, 2025
dd4c50b
add an excessive amount of doxygen comments
Kitzunu Mar 2, 2025
a7ccdb2
remove double blank
Kitzunu Mar 2, 2025
b6348a3
Add support for reputation and a new column `conditionState`
Kitzunu Mar 2, 2025
70794ed
Update rev_1740310084140595600.sql
Kitzunu Mar 2, 2025
c739f72
add support for quest status
Kitzunu Mar 2, 2025
3748682
change log for using conditionState on unsupported conditionType to warn
Kitzunu Mar 2, 2025
16c3d93
fix some logs
Kitzunu Mar 2, 2025
88e9e54
Add support for faction and race
Kitzunu Mar 2, 2025
a8b0b56
logic
Kitzunu Mar 2, 2025
2b36dce
add class
Kitzunu Mar 2, 2025
5b0dfce
fix logic, im stupid
Kitzunu Mar 2, 2025
75cc6c6
Update ServerMailMgr.cpp
Kitzunu Mar 2, 2025
20cc2a9
remove weird indent and document state
Kitzunu Mar 3, 2025
190c1c0
Merge branch 'master' into mail_server_template_items
Kitzunu Mar 3, 2025
b1b84f6
Update ServerMailMgr.h
sudlud Mar 5, 2025
c29f05b
fix include order
Kitzunu Mar 5, 2025
5e07ef1
indent
Kitzunu Mar 5, 2025
b4d2c79
some reviews
Kitzunu Mar 6, 2025
7b7d8db
cant be stringview
Kitzunu Mar 6, 2025
0e60ab5
fix string_view
Kitzunu Mar 6, 2025
9325ffe
import player class
Kitzunu Mar 6, 2025
3f0939d
define
Kitzunu Mar 6, 2025
b72918d
Update World.cpp
Kitzunu Mar 6, 2025
c925a20
implement getter for db conditionType to core ServerMailConditionType
Kitzunu Mar 6, 2025
33dc47b
Update ServerMailMgr.h
Kitzunu Mar 6, 2025
0bb1c20
fix check
Kitzunu Mar 6, 2025
e49a822
cleanup ConditionType check
Kitzunu Mar 6, 2025
1e3c1c3
Merge branch 'master' into mail_server_template_items
Kitzunu Mar 8, 2025
3825eac
mark as const
Kitzunu Mar 8, 2025
4101c6b
Merge branch 'mail_server_template_items' of https://github.com/Kitzu…
Kitzunu Mar 8, 2025
c741b56
Update ServerMailMgr.h
Kitzunu Mar 8, 2025
a9b50f3
cleanup
Kitzunu Mar 8, 2025
0809b4c
optimize a little
Kitzunu Mar 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions data/sql/updates/pending_db_characters/rev_1740310084140595600.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
--
DROP TABLE IF EXISTS `mail_server_template_items`;
CREATE TABLE `mail_server_template_items` (
`id` INT UNSIGNED AUTO_INCREMENT,
`templateID` INT UNSIGNED NOT NULL,
`faction` ENUM('Alliance', 'Horde') NOT NULL,
`item` INT UNSIGNED NOT NULL,
`itemCount` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `fk_mail_template`
FOREIGN KEY (`templateID`) REFERENCES `mail_server_template`(`id`)
ON DELETE CASCADE
) ENGINE=InnoDB COLLATE='utf8mb4_unicode_ci';

INSERT INTO `mail_server_template_items` (`templateID`, `faction`, `item`, `itemCount`)
SELECT `id`, 'Alliance', `itemA`, `itemCountA` FROM `mail_server_template` WHERE `itemA` > 0;

INSERT INTO `mail_server_template_items` (`templateID`, `faction`, `item`, `itemCount`)
SELECT `id`, 'Horde', `itemH`, `itemCountH` FROM `mail_server_template` WHERE `itemH` > 0;

ALTER TABLE `mail_server_template`
DROP COLUMN `itemA`,
DROP COLUMN `itemCountA`,
DROP COLUMN `itemH`,
DROP COLUMN `itemCountH`;

-- Make sure we dont have invalid instances in mail_server_character.mailId before we add the foregin key to avoid SQL errors
DELETE FROM `mail_server_character` WHERE `mailId` NOT IN (SELECT `id` FROM `mail_server_template`);

-- Add foreign key for mail_server_character.mailId
ALTER TABLE `mail_server_character`
DROP PRIMARY KEY,
ADD PRIMARY KEY (`guid`, `mailId`),
ADD CONSTRAINT `fk_mail_template_character`
FOREIGN KEY (`mailId`) REFERENCES `mail_server_template`(`id`)
ON DELETE CASCADE;
146 changes: 98 additions & 48 deletions src/server/game/Globals/ObjectMgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10298,38 +10298,45 @@ uint32 ObjectMgr::GetQuestMoneyReward(uint8 level, uint32 questMoneyDifficulty)
return 0;
}

void ObjectMgr::SendServerMail(Player* player, uint32 id, uint32 reqLevel, uint32 reqPlayTime, uint32 rewardMoneyA, uint32 rewardMoneyH, uint32 rewardItemA, uint32 rewardItemCountA, uint32 rewardItemH, uint32 rewardItemCountH, std::string subject, std::string body, uint8 active) const
void ObjectMgr::SendServerMail(Player* player, uint32 id, uint32 reqLevel, uint32 reqPlayTime, uint32 rewardMoneyA, uint32 rewardMoneyH, std::vector<ServerMailItems> const& items, std::string subject, std::string body, uint8 active) const
{
if (active)
{
if (player->GetLevel() < reqLevel)
return;
if (!active)
return;

if (player->GetTotalPlayedTime() < reqPlayTime)
return;
if (player->GetLevel() < reqLevel)
return;

if (player->GetTotalPlayedTime() < reqPlayTime)
return;

CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();

MailSender sender(MAIL_NORMAL, player->GetGUID().GetCounter(), MAIL_STATIONERY_GM);
MailDraft draft(subject, body);
MailSender sender(MAIL_NORMAL, player->GetGUID().GetCounter(), MAIL_STATIONERY_GM);
MailDraft draft(subject, body);

draft.AddMoney(player->GetTeamId() == TEAM_ALLIANCE ? rewardMoneyA : rewardMoneyH);
if (Item* mailItem = Item::CreateItem(player->GetTeamId() == TEAM_ALLIANCE ? rewardItemA : rewardItemH, player->GetTeamId() == TEAM_ALLIANCE ? rewardItemCountA : rewardItemCountH))
draft.AddMoney(player->GetTeamId() == TEAM_ALLIANCE ? rewardMoneyA : rewardMoneyH);
// Loop through all items and attach them to the mail
for (auto const& mailItem : items)
{
if (!mailItem.item || !mailItem.itemCount)
continue;

if (Item* newItem = Item::CreateItem(mailItem.item, mailItem.itemCount))
{
mailItem->SaveToDB(trans);
draft.AddItem(mailItem);
newItem->SaveToDB(trans);
draft.AddItem(newItem);
}
}

draft.SendMailTo(trans, MailReceiver(player), sender);
CharacterDatabase.CommitTransaction(trans);
draft.SendMailTo(trans, MailReceiver(player), sender);
CharacterDatabase.CommitTransaction(trans);

CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_MAIL_SERVER_CHARACTER);
stmt->SetData(0, player->GetGUID().GetCounter());
stmt->SetData(1, id);
CharacterDatabase.Execute(stmt);
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_MAIL_SERVER_CHARACTER);
stmt->SetData(0, player->GetGUID().GetCounter());
stmt->SetData(1, id);
CharacterDatabase.Execute(stmt);

LOG_DEBUG("entities.player", "ObjectMgr::SendServerMail() Sent mail id {} to {}", id, player->GetGUID().ToString());
}
LOG_DEBUG("entities.player", "ObjectMgr::SendServerMail() Sent mail id {} to {}", id, player->GetGUID().ToString());
}

void ObjectMgr::LoadMailServerTemplates()
Expand All @@ -10338,8 +10345,8 @@ void ObjectMgr::LoadMailServerTemplates()

_serverMailStore.clear(); // for reload case

// 0 1 2 3 4 5 6 7 8 9 10 11
QueryResult result = CharacterDatabase.Query("SELECT `id`, `reqLevel`, `reqPlayTime`, `moneyA`, `moneyH`, `itemA`, `itemCountA`, `itemH`,`itemCountH`, `subject`, `body`, `active` FROM `mail_server_template`");
// 0 1 2 3 4 5 6 7
QueryResult result = CharacterDatabase.Query("SELECT `id`, `reqLevel`, `reqPlayTime`, `moneyA`, `moneyH`, `subject`, `body`, `active` FROM `mail_server_template`");
if (!result)
{
LOG_INFO("sql.sql", ">> Loaded 0 server mail rewards. DB table `mail_server_template` is empty.");
Expand All @@ -10362,13 +10369,9 @@ void ObjectMgr::LoadMailServerTemplates()
servMail.reqPlayTime = fields[2].Get<uint32>();
servMail.moneyA = fields[3].Get<uint32>();
servMail.moneyH = fields[4].Get<uint32>();
servMail.itemA = fields[5].Get<uint32>();
servMail.itemCountA = fields[6].Get<uint32>();
servMail.itemH = fields[7].Get<uint32>();
servMail.itemCountH = fields[8].Get<uint32>();
servMail.subject = fields[9].Get<std::string>();
servMail.body = fields[10].Get<std::string>();
servMail.active = fields[11].Get<uint8>();
servMail.subject = fields[5].Get<std::string>();
servMail.body = fields[6].Get<std::string>();
servMail.active = fields[7].Get<uint8>();

if (servMail.reqLevel > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
{
Expand All @@ -10381,32 +10384,79 @@ void ObjectMgr::LoadMailServerTemplates()
LOG_ERROR("sql.sql", "Table `mail_server_template` has moneyA {} or moneyH {} larger than MAX_MONEY_AMOUNT {} for id {}, skipped.", servMail.moneyA, servMail.moneyH, MAX_MONEY_AMOUNT, servMail.id);
return;
}
} while (result->NextRow());

ItemTemplate const* itemTemplateA = sObjectMgr->GetItemTemplate(servMail.itemA);
if (!itemTemplateA && servMail.itemA)
LoadMailServerTemplatesItems();

LOG_INFO("server.loading", ">> Loaded {} Mail Server Template in {} ms", _serverMailStore.size(), GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}

void ObjectMgr::LoadMailServerTemplatesItems()
{
QueryResult result = CharacterDatabase.Query("SELECT `templateID`, `faction`, `item`, `itemCount` FROM `mail_server_template_items`");
if (!result)
{
LOG_INFO("sql.sql", ">> Loaded 0 server mail items. DB table `mail_server_template_items` is empty.");
LOG_INFO("server.loading", " ");
return;
}

do
{
Field* fields = result->Fetch();

uint32 templateID = fields[0].Get<uint32>();
std::string faction = fields[1].Get<std::string>();
uint32 item = fields[2].Get<uint32>();
uint32 itemCount = fields[3].Get<uint32>();

if (_serverMailStore.find(templateID) == _serverMailStore.end())
{
LOG_ERROR("sql.sql", "Table `mail_server_template` has invalid item in itemA {} for id {}, skipped.", servMail.itemA, servMail.id);
return;
LOG_ERROR("sql.sql", "Table `mail_server_template_items` has an invalid templateID {}, skipped.", templateID);
continue;
}
ItemTemplate const* itemTemplateH = sObjectMgr->GetItemTemplate(servMail.itemH);
if (!itemTemplateH && servMail.itemH)

ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(item);
if (!itemTemplate)
{
LOG_ERROR("sql.sql", "Table `mail_server_template` has invalid item in itemH {} for id {}, skipped.", servMail.itemH, servMail.id);
return;
LOG_ERROR("sql.sql", "Table `mail_server_template_items` has an invalid item {} for templateID {}, skipped.", item, templateID);
continue;
}

if (!servMail.itemA && servMail.itemCountA)
if (!itemCount)
{
LOG_ERROR("sql.sql", "Table `mail_server_template` has itemCountA {} with no ItemA, set to 0", servMail.itemCountA);
servMail.itemCountA = 0;
LOG_ERROR("sql.sql", "Table `mail_server_template_items` has itemCount 0 for item {}, skipped.", item);
continue;
}
if (!servMail.itemH && servMail.itemCountH)

uint32 stackable = itemTemplate->Stackable;
if (itemCount > stackable)
{
LOG_ERROR("sql.sql", "Table `mail_server_template` has itemCountH {} with no ItemH, set to 0", servMail.itemCountH);
servMail.itemCountH = 0;
LOG_ERROR("sql.sql", "Table `mail_server_template_items` has itemCount {} exceeding item_template.Stackable {} for item {}, skipped.", itemCount, stackable, item);
continue;
}
} while (result->NextRow());

LOG_INFO("server.loading", ">> Loaded {} Mail Server Template in {} ms", _serverMailStore.size(), GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
uint32 maxCount = itemTemplate->MaxCount;
if (maxCount && itemCount > maxCount)
{
LOG_ERROR("sql.sql", "Table `mail_server_template_items` has itemCount {} exceeding item_template.MaxCount {} for item {}, skipped", itemCount, maxCount, item);
continue;
}

ServerMailItems mailItem;
mailItem.item = item;
mailItem.itemCount = itemCount;

if (faction == "Alliance")
_serverMailStore[templateID].itemsA.push_back(mailItem);
else if (faction == "Horde")
_serverMailStore[templateID].itemsH.push_back(mailItem);
else
{
LOG_ERROR("sql.sql", "Table `mail_server_template_items` has invalid faction value '{}' for id {}, skipped.", faction, templateID);
continue;
}

} while (result->NextRow());
}
3 changes: 2 additions & 1 deletion src/server/game/Globals/ObjectMgr.h
Original file line number Diff line number Diff line change
Expand Up @@ -1056,6 +1056,7 @@ class ObjectMgr
void LoadInstanceEncounters();
void LoadMailLevelRewards();
void LoadMailServerTemplates();
void LoadMailServerTemplatesItems();
void LoadVehicleTemplateAccessories();
void LoadVehicleAccessories();
void LoadVehicleSeatAddon();
Expand Down Expand Up @@ -1449,7 +1450,7 @@ class ObjectMgr
}

[[nodiscard]] uint32 GetQuestMoneyReward(uint8 level, uint32 questMoneyDifficulty) const;
void SendServerMail(Player* player, uint32 id, uint32 reqLevel, uint32 reqPlayTime, uint32 rewardMoneyA, uint32 rewardMoneyH, uint32 rewardItemA, uint32 rewardItemCountA, uint32 rewardItemH, uint32 rewardItemCountH, std::string subject, std::string body, uint8 active) const;
void SendServerMail(Player* player, uint32 id, uint32 reqLevel, uint32 reqPlayTime, uint32 rewardMoneyA, uint32 rewardMoneyH, std::vector<ServerMailItems> const& items, std::string subject, std::string body, uint8 active) const;
private:
// first free id for selected id type
uint32 _auctionId; // pussywizard: accessed by a single thread
Expand Down
15 changes: 11 additions & 4 deletions src/server/game/Mails/Mail.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,13 @@ struct Mail
[[nodiscard]] bool IsReturnedMail() const { return checked & MAIL_CHECK_MASK_RETURNED; }
};

struct ServerMailItems
{
ServerMailItems() = default;
uint32 item{ 0 };
uint32 itemCount{ 0 };
};

struct ServerMail
{
ServerMail() = default;
Expand All @@ -218,13 +225,13 @@ struct ServerMail
uint32 reqPlayTime{ 0 };
uint32 moneyA{ 0 };
uint32 moneyH{ 0 };
uint32 itemA{ 0 };
uint32 itemCountA{ 0 };
uint32 itemH{ 0 };
uint32 itemCountH{ 0 };
std::string subject;
std::string body;
uint8 active{ 0 };

// Items from mail_server_template_items
std::vector<ServerMailItems> itemsA;
std::vector<ServerMailItems> itemsH;
};

#endif
10 changes: 5 additions & 5 deletions src/server/scripts/World/server_mail.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class ServerMailReward : public PlayerScript
return;

uint32 playerGUID = player->GetGUID().GetCounter();
bool isAlliance = player->GetTeamId() == TEAM_ALLIANCE;

for (auto const& [mailId, servMail] : serverMailStore)
{
Expand All @@ -45,23 +46,22 @@ class ServerMailReward : public PlayerScript
stmt->SetData(1, mailId);

// Capture servMail by value
auto callback = [session, servMailWrapper = std::reference_wrapper<ServerMail const>(servMail)](PreparedQueryResult result)
auto callback = [session, servMailWrapper = std::reference_wrapper<ServerMail const>(servMail), isAlliance](PreparedQueryResult result)
{
ServerMail const& servMail = servMailWrapper.get(); // Dereference the wrapper to get the original object

if (!result)
{
std::vector<ServerMailItems> const& items = isAlliance ? servMail.itemsA : servMail.itemsH;

sObjectMgr->SendServerMail(
session->GetPlayer(),
servMail.id,
servMail.reqLevel,
servMail.reqPlayTime,
servMail.moneyA,
servMail.moneyH,
servMail.itemA,
servMail.itemCountA,
servMail.itemH,
servMail.itemCountH,
items,
servMail.subject,
servMail.body,
servMail.active
Expand Down
Loading