knightonline/server/GameServer/TradeHandler.cpp

572 lines
13 KiB
C++

#include "stdafx.h"
#include "Map.h"
#include "../shared/DateTime.h"
using namespace std;
void CUser::ExchangeProcess(Packet & pkt)
{
uint8 opcode = pkt.read<uint8>();
if (isStoreOpen() || isGM() || isMerchanting() || isSellingMerchant() || isBuyingMerchant() || isDead() || m_bMerchantStatex)
opcode = 8;
switch (opcode)
{
case EXCHANGE_REQ:
ExchangeReq(pkt);
break;
case EXCHANGE_AGREE:
ExchangeAgree(pkt);
break;
case EXCHANGE_ADD:
ExchangeAdd(pkt);
break;
case EXCHANGE_DECIDE:
ExchangeDecide();
break;
case EXCHANGE_CANCEL:
ExchangeCancel();
break;
}
}
void CUser::ExchangeReq(Packet & pkt)
{
Packet result(WIZ_EXCHANGE);
CUser * pUser;
uint16 destid;
if (isDead() || isStoreOpen() || isMerchanting() || m_bMerchantStatex)
goto fail_return;
else if (isTrading())
{
ExchangeCancel();
return;
}
destid = pkt.read<uint16>();
pUser = g_pMain->GetUserPtr(destid);
if (pUser == nullptr
|| pUser == this
|| pUser->isGM()
|| pUser->isTrading()
|| pUser->m_bMerchantStatex
|| pUser->GetZoneID() != GetZoneID()
|| !isInRange(pUser->GetX(),pUser->GetZ(),RANGE_50M)
|| (GetNation() != GetNation() && GetMap()->canTradeWithOtherNation()))
goto fail_return;
m_sExchangeUser = destid;
pUser->m_sExchangeUser = GetSocketID();
if (pUser->isDead() || pUser->isStoreOpen() || pUser->isMerchanting())
goto fail_return;
if (GetLevel() < 20 || pUser->GetLevel() < 20)
{
Packet resultmer;
std::string bufferpro = string_format("[Trade Message] must be at least 20 level");
ChatPacket::Construct(&resultmer, 7, &bufferpro);
Send(&resultmer);
goto fail_return;
}
result << uint8(EXCHANGE_REQ) << GetSocketID();
pUser->Send(&result);
return;
fail_return:
result << uint8(EXCHANGE_CANCEL);
Send(&result);
}
void CUser::ExchangeAgree(Packet & pkt)
{
uint8 bResult = pkt.read<uint8>();
CUser *pUser = g_pMain->GetUserPtr(m_sExchangeUser);
if (pUser == nullptr || pUser == this)
{
m_sExchangeUser = -1;
return;
}
if (pUser->isDead()
|| isDead()
|| pUser->isStoreOpen()
|| isStoreOpen()
|| pUser->isMerchanting()
|| isMerchanting()
|| pUser->m_bMerchantStatex
|| m_bMerchantStatex
|| !pUser->isTrading()
|| !isTrading()
|| pUser->GetZoneID() != GetZoneID()
|| !isInRange(pUser->GetX(),pUser->GetZ(),RANGE_50M))
{
m_sExchangeUser = -1;
return;
}
if (!bResult || pUser->isDead()) // declined
{
m_sExchangeUser = -1;
pUser->m_sExchangeUser = -1;
bResult = 0;
}
string errorMessage = string_format(_T("TRADE uId-%s- tId-%s- r-%d- Z-%d- X-%d- Y-%d-"),
GetName().c_str(),pUser->GetName().c_str(),bResult,GetZoneID(), uint16(GetX()), uint16(GetZ()));
g_pMain->WriteTradeUserLogFile(errorMessage);
Packet result(WIZ_EXCHANGE, uint8(EXCHANGE_AGREE));
result << uint16(bResult);
pUser->Send(&result);
}
void CUser::ExchangeAdd(Packet & pkt)
{
if (!isTrading()
|| isDead()
|| isStoreOpen()
|| isMerchanting()
|| m_bMerchantStatex)
return;
Packet result(WIZ_EXCHANGE, uint8(EXCHANGE_ADD));
uint64 nSerialNum;
uint32 nItemID, count = 0;
uint16 duration = 0;
_ITEM_DATA * pSrcItem = nullptr;
list<_EXCHANGE_ITEM*>::iterator Iter;
uint8 pos;
bool bAdd = true, bGold = false;
CUser *pUser = g_pMain->GetUserPtr(m_sExchangeUser);
if (pUser == nullptr
|| pUser == this
|| pUser->isDead()
|| isDead())
{
ExchangeCancel();
return;
}
if (pUser->isDead() || pUser->isStoreOpen()
|| pUser->isMerchanting()
|| pUser->m_bMerchantStatex
|| !pUser->isTrading())
goto add_fail;
pkt >> pos >> nItemID >> count;
if(count == 0)
goto add_fail;
_ITEM_TABLE *pTable = g_pMain->GetItemPtr(nItemID);
if (pTable == nullptr
|| (nItemID != ITEM_GOLD
&& (pos >= HAVE_MAX // Invalid position
|| (nItemID >= ITEM_NO_TRADE && nItemID < ITEM_NO_TRADE_MAX) // Cannot be traded, stored or sold.
|| pTable->m_bRace == RACE_UNTRADEABLE)) // Cannot be traded or sold.
|| m_bExchangeOK)
goto add_fail;
if (nItemID == ITEM_GOLD)
{
if (count <= 0 || count > m_iGold)
goto add_fail;
// If we have coins in the list already
// add to the amount of coins listed.
foreach (itr, m_ExchangeItemList)
{
if ((*itr)->nItemID == ITEM_GOLD)
{
(*itr)->nCount += count;
bAdd = false; /* don't need to add a new entry */
break;
}
}
m_iGold -= count;
}
else if ((pSrcItem = GetItem(SLOT_MAX + pos)) != nullptr && pSrcItem->nNum == nItemID)
{
if (pSrcItem->sCount < count
|| pSrcItem->isRented()
|| pSrcItem->isSealed()
|| pSrcItem->isBound()
|| pSrcItem->isDuplicate()
|| pSrcItem->nExpirationTime !=0)
goto add_fail;
if (pTable->m_bCountable)
{
foreach (itr, m_ExchangeItemList)
{
if ((*itr)->nItemID == nItemID)
{
(*itr)->nCount += count;
bAdd = false;
break;
}
}
}
if(pTable->isStackable())
pSrcItem->sCount -= count;
else
pSrcItem->sCount = 0;
duration = pSrcItem->sDuration;
nSerialNum = pSrcItem->nSerialNum;
}
else
goto add_fail;
foreach (itr, m_ExchangeItemList)
{
if ((*itr)->nItemID == ITEM_GOLD)
{
bGold = true;
break;
}
}
if ((int)m_ExchangeItemList.size() > (bGold ? 13 : 12))
goto add_fail;
if (bAdd)
{
_EXCHANGE_ITEM * pItem = new _EXCHANGE_ITEM;
pItem->nItemID = nItemID;
pItem->sDurability = duration;
pItem->nCount = count;
pItem->nSerialNum = nSerialNum;
pItem->bSrcPos = SLOT_MAX + pos;
m_ExchangeItemList.push_back(pItem);
}
result << uint8(1);
Send(&result);
result.clear();
result << uint8(EXCHANGE_OTHERADD)
<< nItemID << count << duration;
SetSpecialItemData(pSrcItem,result);
pUser->Send(&result);
return;
add_fail:
result << uint8(0);
Send(&result);
}
void CUser::ExchangeDecide()
{
CUser *pUser = g_pMain->GetUserPtr(m_sExchangeUser);
if (pUser == nullptr
|| pUser->isDead()
|| pUser == this
|| isDead()
|| !isTrading()
|| m_bMerchantStatex
|| isStoreOpen()
|| isMerchanting()
|| m_bExchangeOK
|| m_sExchangeUser == -1)
{
ExchangeCancel();
return;
}
if(pUser->isDead()
|| !pUser->isTrading()
|| pUser->m_bMerchantStatex
|| pUser->isStoreOpen()
|| pUser->isMerchanting())
{
pUser->ExchangeCancel();
ExchangeCancel();
return;
}
Packet result(WIZ_EXCHANGE);
if (!pUser->m_bExchangeOK)
{
m_bExchangeOK = 1;
result << uint8(EXCHANGE_OTHERDECIDE);
pUser->Send(&result);
return;
}
// Did the exchange requirements fail?
if (!CheckExchange() || !pUser->CheckExchange())
{
// At this stage, neither user has their items exchanged.
// However, their coins were removed -- these will be removed by ExchangeFinish().
result << uint8(EXCHANGE_DONE) << uint8(0);
Send(&result);
pUser->Send(&result);
ExchangeCancel();
pUser->ExchangeCancel();
}
else
{
ExecuteExchange();
pUser->ExecuteExchange();
Packet result(WIZ_EXCHANGE);
result << uint8(EXCHANGE_DONE) << uint8(1)
<< GetCoins()
<< uint16(pUser->m_ExchangeItemList.size());
foreach (itr, pUser->m_ExchangeItemList)
{
result << (*itr)->bDstPos << (*itr)->nItemID
<< uint16((*itr)->nCount) << (*itr)->sDurability;
pUser->SetExchangeSpecialItemData((*itr),result);
}
Send(&result);
result.clear();
result << uint8(EXCHANGE_DONE) << uint8(1)
<< pUser->GetCoins()
<< uint16(m_ExchangeItemList.size());
foreach (itr, m_ExchangeItemList)
{
result << (*itr)->bDstPos << (*itr)->nItemID
<< uint16((*itr)->nCount) << (*itr)->sDurability;
SetExchangeSpecialItemData((*itr),result);
}
pUser->Send(&result);
DateTime time;
foreach (itr, pUser->m_ExchangeItemList)
{
g_pMain->WriteTradeUserLogFile(string_format("[ %d:%d:%d ] User Trade: %s - Recv Item User: %s - Item: %d , count %d , Serial: %I64d \n", time.GetHour(),time.GetMinute(),time.GetSecond() , pUser->GetName().c_str() , GetName().c_str() , (*itr)->nItemID , (*itr)->nCount , (*itr)->nSerialNum ));
}
foreach (itr, m_ExchangeItemList)
{
g_pMain->WriteTradeUserLogFile(string_format("[ %d:%d:%d ] User Trade: %s - Recv Item User: %s - Item: %d , count %d , Serial: %I64d \n", time.GetHour(),time.GetMinute(),time.GetSecond() , GetName().c_str() , pUser->GetName().c_str() , (*itr)->nItemID , (*itr)->nCount , (*itr)->nSerialNum ));
}
SetUserAbility(false);
SendItemWeight();
ExchangeFinish();
pUser->SetUserAbility(false);
pUser->SendItemWeight();
pUser->ExchangeFinish();
}
}
void CUser::ExchangeCancel(bool bIsOnDeath)
{
if (!isTrading()
|| (!bIsOnDeath && isDead()))
return;
// Restore coins and items...
while (m_ExchangeItemList.size())
{
_EXCHANGE_ITEM *pItem = m_ExchangeItemList.front();
if (pItem != nullptr)
{
// Restore coins to owner
if (pItem->nItemID == ITEM_GOLD)
m_iGold += pItem->nCount;
// Restore items to owner
else if(GetItem(pItem->bSrcPos)->nNum == pItem->nItemID)
GetItem(pItem->bSrcPos)->sCount += pItem->nCount;
delete pItem;
}
m_ExchangeItemList.pop_front();
}
CUser *pUser = g_pMain->GetUserPtr(m_sExchangeUser);
ExchangeFinish();
if (pUser != nullptr)
{
pUser->ExchangeCancel();
string errorMessage = string_format(_T("TRADE_CANCEL uId-%s- tId-%s- Z-%d- X-%d- Y-%d-"),
GetName().c_str(),pUser->GetName().c_str(),GetZoneID(), uint16(GetX()), uint16(GetZ()));
g_pMain->WriteTradeUserLogFile(errorMessage);
Packet result(WIZ_EXCHANGE, uint8(EXCHANGE_CANCEL));
pUser->Send(&result);
}
}
void CUser::ExchangeFinish()
{
m_sExchangeUser = -1;
m_bExchangeOK = 0;
m_ExchangeItemList.clear();
}
/**
* @brief Determines if a trade will be successful.
* If it's successful, we can exchange the items.
*
* @return true if it succeeds, false if it fails.
*/
bool CUser::CheckExchange()
{
uint32 money = 0;
uint16 weight = 0;
CUser *pUser = g_pMain->GetUserPtr(m_sExchangeUser);
if (pUser == nullptr || pUser == this)
return false;
if (pUser->isDead()
|| pUser->isStoreOpen()
|| pUser->isMerchanting()
|| pUser->m_bMerchantStatex)
return false;
if (isDead()
|| isStoreOpen()
|| isMerchanting()
|| m_bMerchantStatex)
return false;
// Get the total number of free slots in the player's inventory
uint8 bFreeSlots = 0, bItemCount = 0;
for (uint8 i = SLOT_MAX; i < SLOT_MAX+HAVE_MAX; i++)
{
_ITEM_DATA * pItem = GetItem(i);
if (pItem->nNum == 0)
bFreeSlots++;
}
// Loop through the other user's list of items up for trade.
foreach (Iter, pUser->m_ExchangeItemList)
{
// If we're trading coins, ensure we don't exceed our limit.
if ((*Iter)->nItemID == ITEM_GOLD)
{
money += (*Iter)->nCount;
if ((m_iGold + money) > COIN_MAX)
return false;
continue;
}
// Does this item exist?
_ITEM_TABLE *pTable = g_pMain->GetItemPtr((*Iter)->nItemID);
if (pTable == nullptr)
return false;
if(pTable->isStackable()
&& (*Iter)->nItemID != 0 // slot in use
&& (*Iter)->nItemID != pTable->m_iNum)
return false;
// Is there enough room for this item?
// NOTE: Also ensures we have enough space in our inventory (with one exchange in mind anyway)
if (!CheckWeight(pTable, (*Iter)->nItemID, (*Iter)->nCount))
return false;
// Total up the weight.
weight += pTable->m_sWeight;
bItemCount++;
}
// Do we have enough free slots for all these items?
if (bItemCount > bFreeSlots)
return false; /* note: ignores item stacks for now */
// Ensure the total combined item weight does not exceed our weight limit
return ((weight + m_sItemWeight) <= m_sMaxWeight);
}
bool CUser::ExecuteExchange()
{
CUser *pUser = g_pMain->GetUserPtr(m_sExchangeUser);
if (pUser == nullptr)
return false;
ItemList::iterator coinItr = pUser->m_ExchangeItemList.end();
foreach (Iter, pUser->m_ExchangeItemList)
{
if ((*Iter)->nItemID == ITEM_GOLD)
{
m_iGold += (*Iter)->nCount;
coinItr = Iter;
continue;
}
_ITEM_TABLE *pTable = g_pMain->GetItemPtr((*Iter)->nItemID);
if (pTable == nullptr)
continue;
int nSlot = FindSlotForItem((*Iter)->nItemID, (*Iter)->nCount);
ASSERT(nSlot > 0); /* this shouldn't happen, CheckExchange() prevents this */
_ITEM_DATA * pDstItem = GetItem(nSlot);
_ITEM_DATA * pSrcItem = pUser->GetItem((*Iter)->bSrcPos);
if(pDstItem->nNum != pSrcItem->nNum
&& pDstItem->nNum != 0)
continue;
pDstItem->nNum = pSrcItem->nNum;
if(pTable->isStackable())
pDstItem->sCount += (*Iter)->nCount;
else
pDstItem->sCount = (*Iter)->nCount;
if (pDstItem->sCount > MAX_ITEM_COUNT)
pDstItem->sCount = MAX_ITEM_COUNT;
pDstItem->sDuration = pSrcItem->sDuration;
if (!pTable->isStackable() || (*Iter)->nCount == pDstItem->sCount)
pDstItem->nSerialNum = pSrcItem->nSerialNum;
if (!pTable->isStackable() && pDstItem->nSerialNum == 0)
pSrcItem->nSerialNum = g_pMain->GenerateItemSerial();
// This is really silly, but match the count up with the duration
// for this special items that behave this way.
if (pTable->m_bKind == 255)
pDstItem->sCount = pDstItem->sDuration;
// Set destination position for use in packet to client
// to let them know where the item is.
(*Iter)->bDstPos = (uint8) (nSlot - SLOT_MAX);
string errorMessage = string_format(_T("TRADE_FINISH uId-%s- tId-%s- I-%d- Z-%d- X-%d- Y-%d-"),
GetName().c_str(),pUser->GetName().c_str(),pSrcItem->nNum,GetZoneID(), uint16(GetX()), uint16(GetZ()));
g_pMain->WriteTradeUserLogFile(errorMessage);
// Remove the item from the other player.
if (pSrcItem->sCount == 0 || pTable->m_bKind == 255)
memset(pSrcItem, 0, sizeof(_ITEM_DATA));
}
// Remove coins from the list so it doesn't get sent
// with the rest of the packet.
if (coinItr != pUser->m_ExchangeItemList.end())
{
delete *coinItr;
pUser->m_ExchangeItemList.erase(coinItr);
}
return true;
}