/*
* Copyright (C) 2008-2014 TrinityCore
*
* 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 "Log.h"
#include "Item.h"
#include "ItemPrototype.h"
#include "AuctionHouseBotBuyer.h"
AuctionBotBuyer::AuctionBotBuyer(): _checkInterval(20)
{
// Define faction for our main data class.
for (int i = 0; i < MAX_AUCTION_HOUSE_TYPE; ++i)
_houseConfig[i].Initialize(AuctionHouseType(i));
}
AuctionBotBuyer::~AuctionBotBuyer()
{
}
bool AuctionBotBuyer::Initialize()
{
LoadConfig();
bool activeHouse = false;
for (int i = 0; i < MAX_AUCTION_HOUSE_TYPE; ++i)
{
if (_houseConfig[i].BuyerEnabled)
{
activeHouse = true;
break;
}
}
if (!activeHouse)
return false;
//load Check interval
_checkInterval = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_RECHECK_INTERVAL) * MINUTE;
TC_LOG_DEBUG("ahbot", "AHBot buyer interval between 2 check = %u", _checkInterval);
return true;
}
void AuctionBotBuyer::LoadBuyerValues(BuyerConfiguration& config)
{
uint32 factionChance;
switch (config.GetHouseType())
{
case AUCTION_HOUSE_ALLIANCE:
config.BuyerPriceRatio = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_ALLIANCE_PRICE_RATIO) + 50;
factionChance = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCE_RATIO_ALLIANCE);
break;
case AUCTION_HOUSE_HORDE:
config.BuyerPriceRatio = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_HORDE_PRICE_RATIO) + 50;
factionChance = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCE_RATIO_HORDE);
break;
default:
config.BuyerPriceRatio = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_NEUTRAL_PRICE_RATIO) + 50;
factionChance = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCE_RATIO_NEUTRAL);
break;
}
config.FactionChance = 5000 * factionChance;
}
void AuctionBotBuyer::LoadConfig()
{
for (int i = 0; i < MAX_AUCTION_HOUSE_TYPE; ++i)
{
_houseConfig[i].BuyerEnabled = sAuctionBotConfig->GetConfigBuyerEnabled(AuctionHouseType(i));
if (_houseConfig[i].BuyerEnabled)
LoadBuyerValues(_houseConfig[i]);
}
}
uint32 AuctionBotBuyer::GetBuyableEntry(BuyerConfiguration& config)
{
config.SameItemInfo.clear();
uint32 count = 0;
time_t now = time(nullptr);
AuctionHouseObject* house = sAuctionMgr->GetAuctionsMap(config.GetHouseType());
for (AuctionHouseObject::AuctionEntryMap::const_iterator itr = house->GetAuctionsBegin(); itr != house->GetAuctionsEnd(); ++itr)
{
AuctionEntry* entry = itr->second;
Item* item = sAuctionMgr->GetAItem(entry->itemGUIDLow);
if (item)
{
ItemTemplate const * prototype = item->GetTemplate();
if (prototype)
{
++config.SameItemInfo[item->GetEntry()].ItemCount; // Structure constructor will make sure Element are correctly initialized if entry is created here.
config.SameItemInfo[item->GetEntry()].BuyPrice = config.SameItemInfo[item->GetEntry()].BuyPrice + (itr->second->buyout / item->GetCount());
config.SameItemInfo[item->GetEntry()].BidPrice = config.SameItemInfo[item->GetEntry()].BidPrice + (itr->second->startbid / item->GetCount());
if (itr->second->buyout != 0)
{
if (itr->second->buyout / item->GetCount() < config.SameItemInfo[item->GetEntry()].MinBuyPrice)
config.SameItemInfo[item->GetEntry()].MinBuyPrice = itr->second->buyout / item->GetCount();
else if (config.SameItemInfo[item->GetEntry()].MinBuyPrice == 0)
config.SameItemInfo[item->GetEntry()].MinBuyPrice = itr->second->buyout / item->GetCount();
}
if (itr->second->startbid / item->GetCount() < config.SameItemInfo[item->GetEntry()].MinBidPrice)
config.SameItemInfo[item->GetEntry()].MinBidPrice = itr->second->startbid / item->GetCount();
else if (config.SameItemInfo[item->GetEntry()].MinBidPrice == 0)
config.SameItemInfo[item->GetEntry()].MinBidPrice = itr->second->startbid / item->GetCount();
if (!entry->owner)
{
if (entry->bid != 0 && entry->bidder) // Add bid by player
{
config.CheckedEntry[entry->Id].LastExist = now;
config.CheckedEntry[entry->Id].AuctionId = entry->Id;
++count;
}
}
else
{
if (entry->bid != 0)
{
if (entry->bidder)
{
config.CheckedEntry[entry->Id].LastExist = now;
config.CheckedEntry[entry->Id].AuctionId = entry->Id;
++count;
}
}
else
{
config.CheckedEntry[entry->Id].LastExist = now;
config.CheckedEntry[entry->Id].AuctionId = entry->Id;
++count;
}
}
}
}
}
TC_LOG_DEBUG("ahbot", "AHBot: %u items added to buyable vector for ah type: %u", count, config.GetHouseType());
TC_LOG_DEBUG("ahbot", "AHBot: SameItemInfo size = %u", (uint32)config.SameItemInfo.size());
return count;
}
void AuctionBotBuyer::PrepareListOfEntry(BuyerConfiguration& config)
{
time_t now = time(nullptr) - 5;
for (CheckEntryMap::iterator itr = config.CheckedEntry.begin(); itr != config.CheckedEntry.end();)
{
if (itr->second.LastExist < (now - 5))
config.CheckedEntry.erase(itr++);
else
++itr;
}
TC_LOG_DEBUG("ahbot", "AHBot: CheckedEntry size = %u", (uint32)config.CheckedEntry.size());
}
bool AuctionBotBuyer::IsBuyableEntry(uint32 buyoutPrice, double inGameBuyPrice, uint32 maxBuyablePrice, uint32 minBuyPrice, uint32 maxChance, uint32 chanceRatio)
{
double ratio = 0;
uint32 chance = 0;
if (buyoutPrice <= minBuyPrice)
{
if (buyoutPrice <= maxBuyablePrice)
chance = maxChance;
else
{
if (buyoutPrice > 0 && maxBuyablePrice > 0)
{
ratio = double(buyoutPrice) / double(maxBuyablePrice);
if (ratio < 10)
chance = maxChance - (ratio * maxChance / 10);
else
chance = 1;
}
}
}
else if (buyoutPrice <= inGameBuyPrice)
{
if (buyoutPrice <= maxBuyablePrice)
chance = maxChance / 5;
else
{
if (buyoutPrice > 0 && maxBuyablePrice > 0)
{
ratio = double(buyoutPrice) / double(maxBuyablePrice);
if (ratio < 10)
chance = (maxChance / 5) - (ratio * maxChance / 50);
else
chance = 1;
}
}
}
else if (buyoutPrice <= maxBuyablePrice)
chance = maxChance / 10;
else
{
if (buyoutPrice > 0 && maxBuyablePrice > 0)
{
ratio = double(buyoutPrice) / double(maxBuyablePrice);
if (ratio < 10)
chance = (maxChance / 5) - (ratio* maxChance / 50);
else
chance = 0;
}
else
chance = 0;
}
if (urand(1, chanceRatio) <= chance)
{
TC_LOG_DEBUG("ahbot", "AHBot: WIN BUY! Chance = %u, num = %u.", chance, chanceRatio);
return true;
}
else
{
TC_LOG_DEBUG("ahbot", "AHBot: LOOSE BUY! Chance = %u, num = %u.", chance, chanceRatio);
return false;
}
}
bool AuctionBotBuyer::IsBidableEntry(uint32 bidPrice, double inGameBuyPrice, double maxBidablePrice, uint32 minBidPrice, uint32 maxChance, uint32 chanceRatio)
{
double ratio = 0;
uint32 chance = 0;
if (bidPrice <= minBidPrice)
{
if (inGameBuyPrice != 0 && bidPrice < inGameBuyPrice - (inGameBuyPrice / 30))
chance = maxChance;
else
{
if (bidPrice < maxBidablePrice)
{
ratio = maxBidablePrice / bidPrice;
if (ratio < 3)
chance = maxChance / 500 * ratio;
else
chance = maxChance / 500;
}
}
}
else if (bidPrice < (inGameBuyPrice - (inGameBuyPrice / 30)))
chance = (maxChance / 10);
else
{
if (bidPrice < maxBidablePrice)
{
ratio = maxBidablePrice / bidPrice;
if (ratio < 4)
chance = maxChance / 1000 * ratio;
else
chance = maxChance / 1000;
}
}
if (urand(1, chanceRatio) <= chance)
{
TC_LOG_DEBUG("ahbot", "AHBot: WIN BID! Chance = %u, num = %u.", chance, chanceRatio);
return true;
}
else
{
TC_LOG_DEBUG("ahbot", "AHBot: LOOSE BID! Chance = %u, num = %u.", chance, chanceRatio);
return false;
}
}
void AuctionBotBuyer::PlaceBidToEntry(AuctionEntry* auction, uint32 bidPrice)
{
TC_LOG_DEBUG("ahbot", "AHBot: Bid placed to entry %u, %.2fg", auction->Id, float(bidPrice) / 10000.0f);
auction->bid = bidPrice;
}
void AuctionBotBuyer::BuyEntry(AuctionEntry* auction)
{
TC_LOG_DEBUG("ahbot", "AHBot: Entry %u bought at %.2fg", auction->Id, float(auction->buyout) / 10000.0f);
auction->bid = auction->buyout;
}
void AuctionBotBuyer::AddNewAuctionBuyerBotBid(BuyerConfiguration& config)
{
AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(config.GetHouseType());
PrepareListOfEntry(config);
time_t now = time(nullptr);
uint32 buyCycles;
if (config.CheckedEntry.size() > sAuctionBotConfig->GetItemPerCycleBoost())
{
buyCycles = sAuctionBotConfig->GetItemPerCycleBoost();
TC_LOG_DEBUG("ahbot", "AHBot: Boost value used for Buyer! (if this happens often adjust both ItemsPerCycle in worldserver.conf)");
}
else
buyCycles = sAuctionBotConfig->GetItemPerCycleNormal();
for (CheckEntryMap::iterator itr = config.CheckedEntry.begin(); itr != config.CheckedEntry.end();)
{
AuctionEntry* auction = auctionHouse->GetAuction(itr->second.AuctionId);
if (!auction) // is auction not active now
{
TC_LOG_DEBUG("ahbot", "AHBot: Entry %u doesn't exists, perhaps bought already?",
itr->second.AuctionId);
config.CheckedEntry.erase(itr++);
continue;
}
if (itr->second.LastChecked != 0 && (now - itr->second.LastChecked) <= _checkInterval)
{
TC_LOG_DEBUG("ahbot", "AHBot: In time interval wait for entry %u!", auction->Id);
++itr;
continue;
}
if (buyCycles == 0)
break;
uint32 maxChance = 5000;
Item* item = sAuctionMgr->GetAItem(auction->itemGUIDLow);
if (!item) // auction item not accessible, possible auction in payment pending mode
{
config.CheckedEntry.erase(itr++);
continue;
}
ItemTemplate const* prototype = item->GetTemplate();
uint32 basePrice = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYPRICE_BUYER) ? prototype->BuyPrice : prototype->SellPrice;
basePrice *= item->GetCount();
uint32 maxBuyablePrice = (basePrice * config.BuyerPriceRatio) / 100;
BuyerItemInfoMap::iterator sameItemItr = config.SameItemInfo.find(item->GetEntry());
uint32 buyoutPrice = auction->buyout / item->GetCount();
uint32 bidPrice;
uint32 bidPriceByItem;
uint32 minBidPrice;
uint32 minBuyPrice;
if (auction->bid >= auction->startbid)
{
bidPrice = auction->GetAuctionOutBid();
bidPriceByItem = auction->bid / item->GetCount();
}
else
{
bidPrice = auction->startbid;
bidPriceByItem = auction->startbid / item->GetCount();
}
double inGameBuyPrice;
double inGameBidPrice;
if (sameItemItr == config.SameItemInfo.end())
{
inGameBuyPrice = 0;
inGameBidPrice = 0;
minBidPrice = 0;
minBuyPrice = 0;
}
else
{
if (sameItemItr->second.ItemCount == 1)
maxBuyablePrice = maxBuyablePrice * 5; // if only one item exist can be bought if the price is high too.
inGameBuyPrice = sameItemItr->second.BuyPrice / sameItemItr->second.ItemCount;
inGameBidPrice = sameItemItr->second.BidPrice / sameItemItr->second.ItemCount;
minBidPrice = sameItemItr->second.MinBidPrice;
minBuyPrice = sameItemItr->second.MinBuyPrice;
}
uint32 maxBidablePrice = maxBuyablePrice - (maxBuyablePrice / 30); // Max Bidable price defined to 70% of max buyable price
TC_LOG_DEBUG("ahbot", "AHBot: Auction added with data:");
TC_LOG_DEBUG("ahbot", "AHBot: MaxPrice of Entry %u is %.1fg.", itr->second.AuctionId, double(maxBuyablePrice) / 10000.0);
TC_LOG_DEBUG("ahbot", "AHBot: GamePrice buy=%.1fg, bid=%.1fg.", inGameBuyPrice / 10000, inGameBidPrice / 10000);
TC_LOG_DEBUG("ahbot", "AHBot: Minimal price see in AH Buy=%ug, Bid=%ug.",
minBuyPrice / 10000, minBidPrice / 10000);
TC_LOG_DEBUG("ahbot", "AHBot: Actual Entry price, Buy=%ug, Bid=%ug.", buyoutPrice / 10000, bidPrice / 10000);
if (!auction->owner) // Original auction owner
maxChance = maxChance / 5; // if Owner is AHBot this mean player placed bid on this auction. We divide by 5 chance for AhBuyer to place bid on it. (This make more challenge than ignore entry)
if (auction->buyout != 0) // Is the item directly buyable?
{
if (IsBuyableEntry(buyoutPrice, inGameBuyPrice, maxBuyablePrice, minBuyPrice, maxChance, config.FactionChance))
{
if (IsBidableEntry(bidPriceByItem, inGameBuyPrice, maxBidablePrice, minBidPrice, maxChance / 2, config.FactionChance))
{
if (urand(0, 5) == 0)
PlaceBidToEntry(auction, bidPrice);
else
BuyEntry(auction);
}
else
BuyEntry(auction);
}
else if (IsBidableEntry(bidPriceByItem, inGameBuyPrice, maxBidablePrice, minBidPrice, maxChance / 2, config.FactionChance))
PlaceBidToEntry(auction, bidPrice);
}
else if (IsBidableEntry(bidPriceByItem, inGameBuyPrice, maxBidablePrice, minBidPrice, maxChance, config.FactionChance))
PlaceBidToEntry(auction, bidPrice);
itr->second.LastChecked = now;
--buyCycles;
++itr;
}
}
bool AuctionBotBuyer::Update(AuctionHouseType houseType)
{
if (sAuctionBotConfig->GetConfigBuyerEnabled(houseType))
{
TC_LOG_DEBUG("ahbot", "AHBot: %s buying ...", AuctionBotConfig::GetHouseTypeName(houseType));
if (GetBuyableEntry(_houseConfig[houseType]) > 0)
AddNewAuctionBuyerBotBid(_houseConfig[houseType]);
return true;
}
return false;
}