/*
* 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 "BlackMarketMgr.h"
#include "AccountMgr.h"
#include "BlackMarketPackets.h"
#include "CharacterCache.h"
#include "Containers.h"
#include "DatabaseEnv.h"
#include "GameTime.h"
#include "Item.h"
#include "Language.h"
#include "Log.h"
#include "Mail.h"
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "RealmList.h"
#include "StringConvert.h"
#include "World.h"
#include "WorldSession.h"
#include
BlackMarketMgr::BlackMarketMgr()
{
}
BlackMarketMgr::~BlackMarketMgr()
{
for (auto itr = _auctions.begin(); itr != _auctions.end(); ++itr)
delete itr->second;
for (auto itr = _templates.begin(); itr != _templates.end(); ++itr)
delete itr->second;
}
BlackMarketMgr* BlackMarketMgr::Instance()
{
static BlackMarketMgr instance;
return &instance;
}
void BlackMarketMgr::LoadTemplates()
{
uint32 oldMSTime = getMSTime();
// Clear in case we are reloading
if (!_templates.empty())
{
for (BlackMarketTemplateMap::iterator itr = _templates.begin(); itr != _templates.end(); ++itr)
delete itr->second;
_templates.clear();
}
QueryResult result = WorldDatabase.Query("SELECT marketId, sellerNpc, itemEntry, quantity, minBid, duration, chance, bonusListIDs FROM blackmarket_template");
if (!result)
{
TC_LOG_INFO("server.loading", ">> Loaded 0 black market templates. DB table `blackmarket_template` is empty.");
return;
}
do
{
Field* fields = result->Fetch();
BlackMarketTemplate* templ = new BlackMarketTemplate();
if (!templ->LoadFromDB(fields)) // Add checks
{
delete templ;
continue;
}
AddTemplate(templ);
} while (result->NextRow());
TC_LOG_INFO("server.loading", ">> Loaded {} black market templates in {} ms.", uint32(_templates.size()), GetMSTimeDiffToNow(oldMSTime));
}
void BlackMarketMgr::LoadAuctions()
{
uint32 oldMSTime = getMSTime();
// Clear in case we are reloading
if (!_auctions.empty())
{
for (BlackMarketEntryMap::iterator itr = _auctions.begin(); itr != _auctions.end(); ++itr)
delete itr->second;
_auctions.clear();
}
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_BLACKMARKET_AUCTIONS);
PreparedQueryResult result = CharacterDatabase.Query(stmt);
if (!result)
{
TC_LOG_INFO("server.loading", ">> Loaded 0 black market auctions. DB table `blackmarket_auctions` is empty.");
return;
}
_lastUpdate = GameTime::GetGameTime(); //Set update time before loading
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
do
{
Field* fields = result->Fetch();
BlackMarketEntry* auction = new BlackMarketEntry();
if (!auction->LoadFromDB(fields))
{
auction->DeleteFromDB(trans);
delete auction;
continue;
}
if (auction->IsCompleted())
{
auction->DeleteFromDB(trans);
delete auction;
continue;
}
AddAuction(auction);
} while (result->NextRow());
CharacterDatabase.CommitTransaction(trans);
TC_LOG_INFO("server.loading", ">> Loaded {} black market auctions in {} ms.", uint32(_auctions.size()), GetMSTimeDiffToNow(oldMSTime));
}
void BlackMarketMgr::Update(bool updateTime)
{
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
time_t now = GameTime::GetGameTime();
for (BlackMarketEntryMap::iterator itr = _auctions.begin(); itr != _auctions.end(); ++itr)
{
BlackMarketEntry* entry = itr->second;
if (entry->IsCompleted() && entry->GetBidder())
SendAuctionWonMail(entry, trans);
if (updateTime)
entry->Update(now);
}
if (updateTime)
_lastUpdate = now;
CharacterDatabase.CommitTransaction(trans);
}
void BlackMarketMgr::RefreshAuctions()
{
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
// Delete completed auctions
for (BlackMarketEntryMap::iterator itr = _auctions.begin(); itr != _auctions.end();)
{
BlackMarketEntry* entry = itr->second;
if (!entry->IsCompleted())
{
++itr;
continue;
}
entry->DeleteFromDB(trans);
itr = _auctions.erase(itr);
delete entry;
}
CharacterDatabase.CommitTransaction(trans);
trans = CharacterDatabase.BeginTransaction();
std::list templates;
for (auto const& pair : _templates)
{
if (GetAuctionByID(pair.second->MarketID))
continue;
if (!roll_chance_f(pair.second->Chance))
continue;
templates.push_back(pair.second);
}
Trinity::Containers::RandomResize(templates, sWorld->getIntConfig(CONFIG_BLACKMARKET_MAXAUCTIONS));
for (BlackMarketTemplate const* templat : templates)
{
BlackMarketEntry* entry = new BlackMarketEntry();
entry->Initialize(templat->MarketID, templat->Duration);
entry->SaveToDB(trans);
AddAuction(entry);
}
CharacterDatabase.CommitTransaction(trans);
Update(true);
}
bool BlackMarketMgr::IsEnabled() const
{
return sWorld->getBoolConfig(CONFIG_BLACKMARKET_ENABLED);
}
void BlackMarketMgr::BuildItemsResponse(WorldPackets::BlackMarket::BlackMarketRequestItemsResult& packet, Player* player)
{
packet.LastUpdateID = _lastUpdate;
packet.Items.reserve(_auctions.size());
for (auto itr = _auctions.begin(); itr != _auctions.end(); ++itr)
{
BlackMarketTemplate const* templ = itr->second->GetTemplate();
WorldPackets::BlackMarket::BlackMarketItem item;
item.MarketID = itr->second->GetMarketId();
item.SellerNPC = templ->SellerNPC;
item.Item = templ->Item;
item.Quantity = templ->Quantity;
// No bids yet
if (!itr->second->GetNumBids())
{
item.MinBid = templ->MinBid;
item.MinIncrement = 1;
}
else
{
item.MinIncrement = itr->second->GetMinIncrement(); // 5% increment minimum
item.MinBid = itr->second->GetCurrentBid() + item.MinIncrement;
}
item.CurrentBid = itr->second->GetCurrentBid();
item.SecondsRemaining = itr->second->GetSecondsRemaining();
item.HighBid = (itr->second->GetBidder() == player->GetGUID().GetCounter());
item.NumBids = itr->second->GetNumBids();
packet.Items.push_back(item);
}
}
void BlackMarketMgr::AddAuction(BlackMarketEntry* auction)
{
_auctions[auction->GetMarketId()] = auction;
}
void BlackMarketMgr::AddTemplate(BlackMarketTemplate* templ)
{
_templates[templ->MarketID] = templ;
}
void BlackMarketMgr::SendAuctionWonMail(BlackMarketEntry* entry, CharacterDatabaseTransaction trans)
{
// Mail already sent
if (entry->GetMailSent())
return;
uint32 bidderAccId = 0;
ObjectGuid bidderGuid = ObjectGuid::Create(entry->GetBidder());
Player* bidder = ObjectAccessor::FindConnectedPlayer(bidderGuid);
// data for gm.log
std::string bidderName;
bool logGmTrade = false;
if (bidder)
{
bidderAccId = bidder->GetSession()->GetAccountId();
bidderName = bidder->GetName();
logGmTrade = bidder->GetSession()->HasPermission(rbac::RBAC_PERM_LOG_GM_TRADE);
}
else
{
bidderAccId = sCharacterCache->GetCharacterAccountIdByGuid(bidderGuid);
if (!bidderAccId) // Account exists
return;
logGmTrade = AccountMgr::HasPermission(bidderAccId, rbac::RBAC_PERM_LOG_GM_TRADE, sRealmList->GetCurrentRealmId().Realm);
if (logGmTrade && !sCharacterCache->GetCharacterNameByGuid(bidderGuid, bidderName))
bidderName = sObjectMgr->GetTrinityStringForDBCLocale(LANG_UNKNOWN);
}
// Create item
BlackMarketTemplate const* templ = entry->GetTemplate();
Item* item = Item::CreateItem(templ->Item.ItemID, templ->Quantity, ItemContext::Black_Market);
if (!item)
return;
if (templ->Item.ItemBonus)
for (int32 bonusList : templ->Item.ItemBonus->BonusListIDs)
item->AddBonuses(bonusList);
item->SetOwnerGUID(bidderGuid);
item->SaveToDB(trans);
// Log trade
if (logGmTrade)
sLog->OutCommand(bidderAccId, "GM {} (Account: {}) won item in blackmarket auction: {} (Entry: {} Count: {}) and payed gold : {}.",
bidderName, bidderAccId, item->GetTemplate()->GetDefaultLocaleName(), item->GetEntry(), item->GetCount(), entry->GetCurrentBid() / GOLD);
if (bidder)
bidder->GetSession()->SendBlackMarketWonNotification(entry, item);
MailDraft(entry->BuildAuctionMailSubject(BMAH_AUCTION_WON), entry->BuildAuctionMailBody())
.AddItem(item)
.SendMailTo(trans, MailReceiver(bidder, entry->GetBidder()), entry, MAIL_CHECK_MASK_COPIED);
entry->MailSent();
}
void BlackMarketMgr::SendAuctionOutbidMail(BlackMarketEntry* entry, CharacterDatabaseTransaction trans)
{
ObjectGuid oldBidder_guid = ObjectGuid::Create(entry->GetBidder());
Player* oldBidder = ObjectAccessor::FindConnectedPlayer(oldBidder_guid);
uint32 oldBidder_accId = 0;
if (!oldBidder)
oldBidder_accId = sCharacterCache->GetCharacterAccountIdByGuid(oldBidder_guid);
// old bidder exist
if (!oldBidder && !oldBidder_accId)
return;
if (oldBidder)
oldBidder->GetSession()->SendBlackMarketOutbidNotification(entry->GetTemplate());
MailDraft(entry->BuildAuctionMailSubject(BMAH_AUCTION_OUTBID), entry->BuildAuctionMailBody())
.AddMoney(entry->GetCurrentBid())
.SendMailTo(trans, MailReceiver(oldBidder, entry->GetBidder()), entry, MAIL_CHECK_MASK_COPIED);
}
BlackMarketEntry* BlackMarketMgr::GetAuctionByID(int32 marketId) const
{
BlackMarketEntryMap::const_iterator itr = _auctions.find(marketId);
if (itr != _auctions.end())
return itr->second;
return nullptr;
}
BlackMarketTemplate const* BlackMarketMgr::GetTemplateByID(int32 marketId) const
{
BlackMarketTemplateMap::const_iterator itr = _templates.find(marketId);
if (itr != _templates.end())
return itr->second;
return nullptr;
}
bool BlackMarketTemplate::LoadFromDB(Field* fields)
{
MarketID = fields[0].GetInt32();
SellerNPC = fields[1].GetInt32();
Item.ItemID = fields[2].GetUInt32();
Quantity = fields[3].GetInt32();
MinBid = fields[4].GetUInt64();
Duration = static_cast(fields[5].GetUInt32());
Chance = fields[6].GetFloat();
std::vector bonusListIDs;
for (std::string_view token : Trinity::Tokenize(fields[7].GetStringView(), ' ', false))
if (Optional bonusListID = Trinity::StringTo(token))
bonusListIDs.push_back(*bonusListID);
if (!bonusListIDs.empty())
{
Item.ItemBonus.emplace();
Item.ItemBonus->BonusListIDs = bonusListIDs;
}
if (!sObjectMgr->GetCreatureTemplate(SellerNPC))
{
TC_LOG_ERROR("misc", "Black market template {} does not have a valid seller. (Entry: {})", MarketID, SellerNPC);
return false;
}
if (!sObjectMgr->GetItemTemplate(Item.ItemID))
{
TC_LOG_ERROR("misc", "Black market template {} does not have a valid item. (Entry: {})", MarketID, Item.ItemID);
return false;
}
return true;
}
void BlackMarketEntry::Update(time_t newTimeOfUpdate)
{
_secondsRemaining = _secondsRemaining - (newTimeOfUpdate - sBlackMarketMgr->GetLastUpdate());
}
BlackMarketTemplate const* BlackMarketEntry::GetTemplate() const
{
return sBlackMarketMgr->GetTemplateByID(_marketId);
}
uint32 BlackMarketEntry::GetSecondsRemaining() const
{
return _secondsRemaining - (GameTime::GetGameTime() - sBlackMarketMgr->GetLastUpdate());
}
time_t BlackMarketEntry::GetExpirationTime() const
{
return GameTime::GetGameTime() + GetSecondsRemaining();
}
bool BlackMarketEntry::IsCompleted() const
{
return GetSecondsRemaining() <= 0;
}
bool BlackMarketEntry::LoadFromDB(Field* fields)
{
_marketId = fields[0].GetInt32();
// Invalid MarketID
BlackMarketTemplate const* templ = sBlackMarketMgr->GetTemplateByID(_marketId);
if (!templ)
{
TC_LOG_ERROR("misc", "Black market auction {} does not have a valid id.", _marketId);
return false;
}
_currentBid = fields[1].GetUInt64();
_secondsRemaining = static_cast(fields[2].GetInt64()) - sBlackMarketMgr->GetLastUpdate();
_numBids = fields[3].GetInt32();
_bidder = fields[4].GetUInt64();
// Either no bidder or existing player
if (_bidder && !sCharacterCache->GetCharacterAccountIdByGuid(ObjectGuid::Create(_bidder))) // Probably a better way to check if player exists
{
TC_LOG_ERROR("misc", "Black market auction {} does not have a valid bidder (GUID: {} ).", _marketId, _bidder);
return false;
}
return true;
}
void BlackMarketEntry::SaveToDB(CharacterDatabaseTransaction trans) const
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_BLACKMARKET_AUCTIONS);
stmt->setInt32(0, _marketId);
stmt->setUInt64(1, _currentBid);
stmt->setInt64(2, GetExpirationTime());
stmt->setInt32(3, _numBids);
stmt->setUInt64(4, _bidder);
trans->Append(stmt);
}
void BlackMarketEntry::DeleteFromDB(CharacterDatabaseTransaction trans) const
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_BLACKMARKET_AUCTIONS);
stmt->setInt32(0, _marketId);
trans->Append(stmt);
}
bool BlackMarketEntry::ValidateBid(uint64 bid) const
{
if (bid <= _currentBid)
return false;
if (bid < _currentBid + GetMinIncrement())
return false;
if (bid >= BMAH_MAX_BID)
return false;
return true;
}
void BlackMarketEntry::PlaceBid(uint64 bid, Player* player, CharacterDatabaseTransaction trans) //Updated
{
if (bid < _currentBid)
return;
_currentBid = bid;
++_numBids;
if (GetSecondsRemaining() < 30 * MINUTE)
_secondsRemaining += 30 * MINUTE;
_bidder = player->GetGUID().GetCounter();
player->ModifyMoney(-static_cast(bid));
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_BLACKMARKET_AUCTIONS);
stmt->setUInt64(0, _currentBid);
stmt->setInt64(1, GetExpirationTime());
stmt->setInt32(2, _numBids);
stmt->setUInt64(3, _bidder);
stmt->setInt32(4, _marketId);
trans->Append(stmt);
sBlackMarketMgr->Update(true);
}
std::string BlackMarketEntry::BuildAuctionMailSubject(BMAHMailAuctionAnswers response) const
{
std::ostringstream strm;
strm << GetTemplate()->Item.ItemID << ":0:" << response << ':' << GetMarketId() << ':' << GetTemplate()->Quantity;
return strm.str();
}
std::string BlackMarketEntry::BuildAuctionMailBody()
{
std::ostringstream strm;
strm << GetTemplate()->SellerNPC << ':' << _currentBid;
return strm.str();
}