/*
* Ascent MMORPG Server
* Copyright (C) 2005-2007 Ascent Team
*
* 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 3 of the License, or
* 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 "StdAfx.h"
void Auction::DeleteFromDB()
{
CharacterDatabase.WaitExecute("DELETE FROM auctions WHERE auctionId = %u", Id);
}
void Auction::SaveToDB(uint32 AuctionHouseId)
{
CharacterDatabase.Execute("INSERT INTO auctions VALUES(%u, %u, "I64FMTD", %u, %u, %u, %u, %u, %u)",
Id, AuctionHouseId, pItem->GetGUID(), Owner, BuyoutPrice, ExpiryTime, HighestBidder, HighestBid,
DepositAmount);
}
void Auction::UpdateInDB()
{
CharacterDatabase.Execute("UPDATE auctions SET bidder = %u WHERE auctionId = %u", HighestBidder, Owner);
CharacterDatabase.Execute("UPDATE auctions SET bid = %u WHERE auctionId = %u", HighestBid, Owner);
}
AuctionHouse::AuctionHouse(uint32 ID)
{
dbc = sAuctionHouseStore.LookupEntry(ID);
assert(dbc);
cut_percent = float( float(dbc->fee) / 100.0f );
deposit_percent = float( float(dbc->tax ) / 100.0f );
}
AuctionHouse::~AuctionHouse()
{
HM_NAMESPACE::hash_map::iterator itr = auctions.begin();
for(; itr != auctions.end(); ++itr)
delete itr->second;
}
void AuctionHouse::QueueDeletion(Auction * auct, uint32 Reason)
{
if(auct->Deleted)
return;
auct->Deleted = true;
auct->DeletedReason = Reason;
removalLock.Acquire();
removalList.push_back(auct);
removalLock.Release();
}
void AuctionHouse::UpdateDeletionQueue()
{
removalLock.Acquire();
Auction * auct;
list::iterator it = removalList.begin();
for(; it != removalList.end(); ++it)
{
auct = *it;
assert(auct->Deleted);
RemoveAuction(auct);
}
removalList.clear();
removalLock.Release();
}
void AuctionHouse::UpdateAuctions()
{
auctionLock.AcquireReadLock();
removalLock.Acquire();
uint32 t = time(NULL);
HM_NAMESPACE::hash_map::iterator itr = auctions.begin();
Auction * auct;
for(; itr != auctions.end();)
{
auct = itr->second;
++itr;
if(t >= auct->ExpiryTime)
{
if(auct->HighestBidder == 0)
auct->DeletedReason = AUCTION_REMOVE_EXPIRED;
else
auct->DeletedReason = AUCTION_REMOVE_WON;
auct->Deleted = true;
removalList.push_back(auct);
}
}
removalLock.Release();
auctionLock.ReleaseReadLock();
}
void AuctionHouse::AddAuction(Auction * auct)
{
// add to the map
auctionLock.AcquireWriteLock();
auctions.insert( HM_NAMESPACE::hash_map::value_type( auct->Id , auct ) );
auctionLock.ReleaseWriteLock();
// add the item
itemLock.AcquireWriteLock();
auctionedItems.insert( HM_NAMESPACE::hash_map::value_type( auct->pItem->GetGUID(), auct->pItem ) );
itemLock.ReleaseWriteLock();
sLog.outString("Auction House %u: Adding auction %u, expiry time %u.", dbc->id, auct->Id, auct->ExpiryTime);
}
Auction * AuctionHouse::GetAuction(uint32 Id)
{
Auction * ret;
HM_NAMESPACE::hash_map::iterator itr;
auctionLock.AcquireReadLock();
itr = auctions.find(Id);
ret = (itr == auctions.end()) ? 0 : itr->second;
auctionLock.ReleaseReadLock();
return ret;
}
void AuctionHouse::RemoveAuction(Auction * auct)
{
sLog.outString("Auction House %u: Removing auction %u because of reason %u.", dbc->id, auct->Id, auct->DeletedReason);
char subject[100];
char body[200];
switch(auct->DeletedReason)
{
case AUCTION_REMOVE_EXPIRED:
{
// ItemEntry:0:3
snprintf(subject, 100, "%u:0:3", (unsigned int)auct->pItem->GetEntry());
// Auction expired, resend item, no money to owner.
sMailSystem.SendAutomatedMessage(AUCTION, dbc->id, auct->Owner, subject, "", 0, 0, auct->pItem->GetGUID(), 62);
}break;
case AUCTION_REMOVE_WON:
{
// ItemEntry:0:1
snprintf(subject, 100, "%u:0:1", (unsigned int)auct->pItem->GetEntry());
// :bid:buyout
snprintf(body, 200, "%X:%u:%u", (unsigned int)auct->Owner, (unsigned int)auct->HighestBid, (unsigned int)auct->BuyoutPrice);
// Auction won by highest bidder. He gets the item.
sMailSystem.SendAutomatedMessage(AUCTION, dbc->id, auct->HighestBidder, subject, body, 0, 0, auct->pItem->GetGUID(), 62);
// Send a mail to the owner with his cut of the price.
uint32 auction_cut = FL2UINT(float(cut_percent * float(auct->HighestBid)));
int32 amount = auct->HighestBid - auction_cut + auct->DepositAmount;
if(amount < 0)
amount = 0;
// ItemEntry:0:2
snprintf(subject, 100, "%u:0:2", (int)auct->pItem->GetEntry());
// :bid:0:deposit:cut
if(auct->HighestBid == auct->BuyoutPrice) // Buyout
snprintf(body, 200, "%X:%u:%u:%u:%u", (unsigned int)auct->HighestBidder, (unsigned int)auct->HighestBid, (unsigned int)auct->BuyoutPrice, (unsigned int)auct->DepositAmount, (unsigned int)auction_cut);
else
snprintf(body, 200, "%X:%u:0:%u:%u", (unsigned int)auct->HighestBidder, (unsigned int)auct->HighestBid, (unsigned int)auct->DepositAmount, (unsigned int)auction_cut);
// send message away.
sMailSystem.SendAutomatedMessage(AUCTION, dbc->id, auct->Owner, subject, body, amount, 0, 0, 62);
}break;
case AUCTION_REMOVE_CANCELLED:
{
snprintf(subject, 100, "%u:0:5", (unsigned int)auct->pItem->GetEntry());
uint32 cut = uint32(float(cut_percent * auct->HighestBid));
Player * plr = objmgr.GetPlayer(auct->Owner);
if(cut && plr && plr->GetUInt32Value(PLAYER_FIELD_COINAGE) >= cut)
plr->ModUInt32Value(PLAYER_FIELD_COINAGE, -((int32)cut));
sMailSystem.SendAutomatedMessage(AUCTION, GetID(), auct->Owner, subject, "", 0, 0, auct->pItem->GetGUID(), 62);
// return bidders money
if(auct->HighestBidder)
{
sMailSystem.SendAutomatedMessage(AUCTION, GetID(), auct->HighestBidder, subject, "", auct->HighestBid,
0, 0, 62);
}
}break;
}
// Remove the auction from the hashmap.
auctionLock.AcquireWriteLock();
itemLock.AcquireWriteLock();
auctions.erase(auct->Id);
auctionedItems.erase(auct->pItem->GetGUID());
auctionLock.ReleaseWriteLock();
itemLock.ReleaseWriteLock();
// Destroy the item from memory (it still remains in the db)
delete auct->pItem;
// Finally destroy the auction instance.
auct->DeleteFromDB();
delete auct;
}
void WorldSession::HandleAuctionListBidderItems( WorldPacket & recv_data )
{
if(!_player->IsInWorld())
return;
uint64 guid;
recv_data >> guid;
Creature * pCreature = _player->GetMapMgr()->GetCreature(guid);
if(!pCreature || !pCreature->auctionHouse)
return;
pCreature->auctionHouse->SendBidListPacket(_player, &recv_data);
}
void Auction::AddToPacket(WorldPacket & data)
{
data << Id;
data << pItem->GetEntry();
for (uint32 i = 0; i < 6; i++)
{
data << pItem->GetUInt32Value(ITEM_FIELD_ENCHANTMENT + (3 * i)); // Enchantment ID
data << pItem->GetEnchantmentApplytime(i); // Unknown / maybe ApplyTime
data << pItem->GetUInt32Value(ITEM_FIELD_SPELL_CHARGES + i); // Charges
}
data << pItem->GetUInt32Value(ITEM_FIELD_RANDOM_PROPERTIES_ID); // -ItemRandomSuffix / random property : If the value is negative its ItemRandomSuffix if its possitive its RandomItemProperty
data << uint32(0); // when ItemRandomSuffix is used this is the modifier
/******************** ItemRandomSuffix***************************
* For what I have seen ItemRandomSuffix is like RandomItemProperty
* The only difference is has is that it has a modifier.
* That is the result of jewelcrafting, the effect is that the
* enchantment is variable. That means that a enchantment can be +1 and
* with more Jem's +12 or so.
* Decription for lookup: You get the enchantmentSuffixID and search the
* DBC for the last 1 - 3 value's(depending on the enchantment).
* That value is what I call EnchantmentValue. You guys might find a
* better name but for now its good enough. The formula to calculate
* The ingame "GAIN" is:
* (Modifier / 10000) * enchantmentvalue = EnchantmentGain;
*/
data << pItem->GetUInt32Value(ITEM_FIELD_STACK_COUNT); // Amount
data << uint32(0); // Unknown
data << uint32(0); // Unknown
data << uint64(Owner); // Owner guid
data << HighestBid; // Current prize
// hehe I know its evil, this creates a nice trough put of money
data << uint32(50); // Next bid value modifier, like current bid + this value
data << BuyoutPrice; // Buyout
data << uint32((ExpiryTime - time(NULL)) * 1000); // Time left
data << uint64(HighestBidder); // Last bidder
data << HighestBid; // The bid of the last bidder
}
void AuctionHouse::SendBidListPacket(Player * plr, WorldPacket * packet)
{
uint32 count = 0;
WorldPacket data(SMSG_AUCTION_BIDDER_LIST_RESULT, 1024);
data << uint32(0); // Placeholder
Auction * auct;
auctionLock.AcquireReadLock();
HM_NAMESPACE::hash_map::iterator itr = auctions.begin();
for(; itr != auctions.end(); ++itr)
{
auct = itr->second;
if(auct->HighestBidder == plr->GetGUID())
{
if(auct->Deleted) continue;
auct->AddToPacket(data);
(*(uint32*)&data.contents()[0])++;
++count;
}
}
#ifdef USING_BIG_ENDIAN
swap32((uint32*)&data.contents()[0]);
#endif
data << count;
auctionLock.ReleaseReadLock();
plr->GetSession()->SendPacket(&data);
}
void AuctionHouse::SendOwnerListPacket(Player * plr, WorldPacket * packet)
{
uint32 count = 0;
WorldPacket data(SMSG_AUCTION_OWNER_LIST_RESULT, 1024);
data << uint32(0); // Placeholder
Auction * auct;
auctionLock.AcquireReadLock();
HM_NAMESPACE::hash_map::iterator itr = auctions.begin();
for(; itr != auctions.end(); ++itr)
{
auct = itr->second;
if(auct->Owner == plr->GetGUID())
{
if(auct->Deleted) continue;
auct->AddToPacket(data);
(*(uint32*)&data.contents()[0])++;
++count;
}
}
data << count;
#ifdef USING_BIG_ENDIAN
swap32((uint32*)&data.contents()[0]);
#endif
auctionLock.ReleaseReadLock();
plr->GetSession()->SendPacket(&data);
}
void AuctionHouse::SendAuctionNotificationPacket(Player * plr, Auction * auct)
{
WorldPacket data(SMSG_AUCTION_BIDDER_NOTIFICATION, 32);
data << GetID();
data << auct->Id;
data << uint64(auct->HighestBidder);
data << uint32(0);
data << uint32(0);
data << auct->pItem->GetEntry();
data << uint32(0);
plr->GetSession()->SendPacket(&data);
}
void WorldSession::HandleAuctionPlaceBid( WorldPacket & recv_data )
{
if(!_player->IsInWorld())
return;
uint64 guid;
recv_data >> guid;
uint32 auction_id, price;
recv_data >> auction_id >> price;
Creature * pCreature = _player->GetMapMgr()->GetCreature(guid);
if(!pCreature || !pCreature->auctionHouse)
return;
// Find Item
AuctionHouse * ah = pCreature->auctionHouse;
Auction * auct = ah->GetAuction(auction_id);
if(auct == 0 || !auct->Owner || !_player || auct->Owner == _player->GetGUID())
return;
if(auct->HighestBid > price && price != auct->BuyoutPrice)
return;
if(_player->GetUInt32Value(PLAYER_FIELD_COINAGE) < price)
return;
_player->ModUInt32Value(PLAYER_FIELD_COINAGE, -((int32)price));
if(auct->HighestBidder != 0)
{
// Return the money to the last highest bidder.
char subject[100];
snprintf(subject, 100, "%u:0:0", (int)auct->pItem->GetEntry());
sMailSystem.SendAutomatedMessage(AUCTION, ah->GetID(), auct->HighestBidder, subject, "", auct->HighestBid,
0, 0, 62);
}
if(auct->BuyoutPrice == price)
{
auct->HighestBidder = _player->GetGUID();
auct->HighestBid = price;
// we used buyout on the item.
ah->QueueDeletion(auct, AUCTION_REMOVE_WON);
// send response packet
WorldPacket data(SMSG_AUCTION_COMMAND_RESULT, 12);
data << auct->Id << uint32(AUCTION_BID) << uint32(0) << uint32(0);
SendPacket(&data);
}
else
{
// update most recent bid
auct->HighestBidder = _player->GetGUID();
auct->HighestBid = price;
auct->UpdateInDB();
// send response packet
WorldPacket data(SMSG_AUCTION_COMMAND_RESULT, 12);
data << auct->Id << uint32(AUCTION_BID) << uint32(0);
SendPacket(&data);
}
}
void WorldSession::HandleCancelAuction( WorldPacket & recv_data)
{
if(!_player->IsInWorld())
return;
uint64 guid;
recv_data >> guid;
uint32 auction_id;
recv_data >> auction_id;
Creature * pCreature = _player->GetMapMgr()->GetCreature(guid);
if(!pCreature || !pCreature->auctionHouse)
return;
// Find Item
Auction * auct = pCreature->auctionHouse->GetAuction(auction_id);
if(auct == 0) return;
pCreature->auctionHouse->QueueDeletion(auct, AUCTION_REMOVE_CANCELLED);
// Send response packet.
WorldPacket data(SMSG_AUCTION_COMMAND_RESULT, 8);
data << auction_id << uint32(AUCTION_CANCEL) << uint32(0);
SendPacket(&data);
// Re-send the owner list.
pCreature->auctionHouse->SendOwnerListPacket(_player, 0);
}
void WorldSession::HandleAuctionSellItem( WorldPacket & recv_data )
{
if(!_player->IsInWorld())
return;
uint64 guid;
uint64 item;
uint32 bid,buyout,etime;
recv_data >> guid;
recv_data >> item;
recv_data >> bid >> buyout >> etime;
Creature * pCreature = _player->GetMapMgr()->GetCreature(guid);
if(!pCreature || !pCreature->auctionHouse)
return;
// Find Item
AuctionHouse * ah = pCreature->auctionHouse;
Item * pItem = _player->GetItemInterface()->SafeRemoveAndRetreiveItemByGuid(item, false);
if(!pItem)
return;
// Re-save item without its owner.
pItem->RemoveFromWorld();
pItem->SetOwner(0);
pItem->SaveToDB(INVENTORY_SLOT_NOT_SET, 0,true);
// Create auction structure.
Auction * auct = new Auction;
auct->BuyoutPrice = buyout;
auct->ExpiryTime = time(NULL) + (etime * 60);
auct->HighestBid = bid;
auct->HighestBidder = 0;
auct->Id = sAuctionMgr.GenerateAuctionId();
auct->Owner = _player->GetGUID();
auct->pItem = pItem;
auct->Deleted = false;
auct->DeletedReason = 0;
// calculate deposit
uint32 item_worth = pItem->GetProto()->SellPrice * pItem->GetUInt32Value(ITEM_FIELD_STACK_COUNT);
auct->DepositAmount = FL2UINT(float(float(float(item_worth) * ah->deposit_percent) * float(float(etime) / 120.0f)));
// Add to the auction house.
ah->AddAuction(auct);
auct->SaveToDB(ah->GetID());
// Send result packet.
WorldPacket data(SMSG_AUCTION_COMMAND_RESULT, 8);
data << auct->Id;
data << uint32(AUCTION_CREATE);
data << uint32(0);
SendPacket(&data);
}
void WorldSession::HandleAuctionListOwnerItems( WorldPacket & recv_data )
{
if(!_player->IsInWorld())
return;
uint64 guid;
recv_data >> guid;
Creature * pCreature = _player->GetMapMgr()->GetCreature(guid);
if(!pCreature || !pCreature->auctionHouse)
return;
pCreature->auctionHouse->SendOwnerListPacket(_player, &recv_data);
}
void AuctionHouse::SendAuctionList(Player * plr, WorldPacket * packet)
{
uint32 start_index, current_index = 0;
uint32 counted_items = 0;
std::string auctionString;
uint8 levelRange1, levelRange2, usableCheck;
int32 inventory_type, itemclass, itemsubclass, rarityCheck;
*packet >> start_index;
*packet >> auctionString;
*packet >> levelRange1 >> levelRange2;
*packet >> inventory_type >> itemclass >> itemsubclass;
*packet >> rarityCheck >> usableCheck;
// convert auction string to lowercase for faster parsing.
if(auctionString.length() > 0)
{
for(uint32 j = 0; j < auctionString.length(); ++j)
auctionString[j] = tolower(auctionString[j]);
}
WorldPacket data(SMSG_AUCTION_LIST_RESULT, 7000);
data << uint32(0);
auctionLock.AcquireReadLock();
HM_NAMESPACE::hash_map::iterator itr = auctions.begin();
ItemPrototype * proto;
for(; itr != auctions.end(); ++itr)
{
if(itr->second->Deleted) continue;
proto = itr->second->pItem->GetProto();
// Check the auction for parameters
// inventory type
if(inventory_type != -1 && inventory_type != (int32)proto->InventoryType)
continue;
// class
if(itemclass != -1 && itemclass != (int32)proto->Class)
continue;
// subclass
if(itemsubclass != -1 && itemsubclass != (int32)proto->SubClass)
continue;
// this is going to hurt. - name
if(auctionString.length() > 0 && !FindXinYString(auctionString, proto->lowercase_name))
continue;
// rarity
if(rarityCheck != -1 && rarityCheck != (int32)proto->Quality)
continue;
// level range check - lower boundary
if(levelRange1 && proto->ItemLevel < levelRange1)
continue;
// level range check - high boundary
if(levelRange2 && proto->ItemLevel > levelRange2)
continue;
// usable check - this will hurt too :(
if(usableCheck)
{
// allowed class
if(proto->AllowableClass && !(plr->getClassMask() & proto->AllowableClass))
continue;
if(proto->RequiredLevel && proto->RequiredLevel > plr->getLevel())
continue;
if(proto->AllowableRace && !(plr->getRaceMask() & proto->AllowableRace))
continue;
if(proto->Class == 4 && proto->SubClass && !(plr->GetArmorProficiency()&(((uint32)(1))<SubClass)))
continue;
if(proto->Class == 2 && proto->SubClass && !(plr->GetWeaponProficiency()&(((uint32)(1))<SubClass)))
continue;
}
// Page system.
++counted_items;
if(counted_items >= start_index + 50)
continue;
current_index++;
if(start_index && current_index < start_index) continue;
// all checks passed -> add to packet.
itr->second->AddToPacket(data);
(*(uint32*)&data.contents()[0])++;
}
// total count
data << uint32(1 + counted_items);
#ifdef USING_BIG_ENDIAN
swap32((uint32*)&data.contents()[0]);
#endif
auctionLock.ReleaseReadLock();
plr->GetSession()->SendPacket(&data);
}
void WorldSession::HandleAuctionListItems( WorldPacket & recv_data )
{
uint64 guid;
recv_data >> guid;
Creature * pCreature = _player->GetMapMgr()->GetCreature(guid);
if(!pCreature || !pCreature->auctionHouse)
return;
pCreature->auctionHouse->SendAuctionList(_player, &recv_data);
}
void AuctionHouse::LoadAuctions()
{
QueryResult *result = CharacterDatabase.Query("SELECT * FROM auctions WHERE auctionhouse =%u", GetID());
if( !result )
return;
Auction * auct;
Field * fields;
do
{
fields = result->Fetch();
auct = new Auction;
auct->Id = fields++->GetUInt32();
fields++;
Item * pItem = objmgr.LoadItem(fields++->GetUInt64());
if(!pItem)
{
CharacterDatabase.Execute("DELETE FROM auctions WHERE auctionId=%u",auct->Id);
delete auct;
continue;
}
auct->pItem = pItem;
auct->Owner = fields++->GetUInt32();
auct->BuyoutPrice = fields++->GetUInt32();
auct->ExpiryTime = fields++->GetUInt32();
auct->HighestBidder = fields++->GetUInt32();
auct->HighestBid = fields++->GetUInt32();
auct->DepositAmount = fields++->GetUInt32();
auct->DeletedReason = 0;
auct->Deleted = false;
auctions.insert( HM_NAMESPACE::hash_map::value_type( auct->Id, auct ) );
} while (result->NextRow());
delete result;
}