uox3/source/vendor.cpp
2022-06-06 09:47:35 -04:00

547 lines
16 KiB
C++

#include "uox3.h"
#include "commands.h"
#include "cEffects.h"
#include "CPacketSend.h"
#include "classes.h"
#include "townregion.h"
#include "CJSMapping.h"
#include "cScript.h"
#include "cServerDefinitions.h"
#include "ObjectFactory.h"
#include <algorithm>
//o-----------------------------------------------------------------------------------------------o
//| Function - UI32 calcValue( CItem *i, UI32 value )
///o-----------------------------------------------------------------------------------------------o
//| Purpose - Calculate the value of an item
//o-----------------------------------------------------------------------------------------------o
UI32 calcValue( CItem *i, UI32 value )
{
if( i->GetType() == IT_POTION )
{
UI32 mod = 10;
if( i->GetTempVar( CITV_MOREX ) > 500 )
++mod;
if( i->GetTempVar( CITV_MOREX ) > 900 )
++mod;
if( i->GetTempVar( CITV_MOREX ) > 1000 )
++mod;
if( i->GetTempVar( CITV_MOREZ ) > 1 )
mod += (3*(i->GetTempVar( CITV_MOREZ )-1));
value=(value*mod)/10;
}
if( i->GetRank() > 0 && i->GetRank() < 10 && cwmWorldState->ServerData()->RankSystemStatus() )
value = (UI32)( i->GetRank() * value ) / 10;
if( value < 1 )
value = 1;
if( !i->GetRndValueRate() )
i->SetRndValueRate( 0 );
if( i->GetRndValueRate() != 0 && cwmWorldState->ServerData()->TradeSystemStatus() )
value += (UI32)(value*i->GetRndValueRate())/1000;
if( value < 1 )
value = 1;
return value;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - UI32 calcGoodValue( CChar *npcnum2, CItem *i, UI32 value, bool isSelling )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Calculate the value of a good
//o-----------------------------------------------------------------------------------------------o
UI32 calcGoodValue( CTownRegion *tReg, CItem *i, UI32 value, bool isSelling )
{
if( tReg == nullptr )
return value;
SI16 good = i->GetGood();
SI32 regvalue = 0;
if( good <= -1 )
return value;
if( isSelling )
regvalue = tReg->GetGoodSell( static_cast<UI08>(i->GetGood()) );
else
regvalue = tReg->GetGoodBuy( static_cast<UI08>(i->GetGood()) );
UI32 x = (UI32)( value * abs( regvalue ) ) / 1000;
if( regvalue < 0 )
value -= x;
else
value += x;
if( value < 1 )
value = 1;
return value;
}
bool ApplyItemSection( CItem *applyTo, ScriptSection *toApply, std::string sectionID );
//o-----------------------------------------------------------------------------------------------o
//| Function - bool CPIBuyItem::Handle( void )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Called when player buys an item from a vendor
//o-----------------------------------------------------------------------------------------------o
bool CPIBuyItem::Handle( void )
{
UI16 i;
UI32 playergoldtotal, goldtotal = 0;
bool soldout = false, clear = false;
CChar *mChar = tSock->CurrcharObj();
CItem *p = mChar->GetPackItem();
if( !ValidateObject( p ) )
return true;
std::vector< CItem* > bitems;
std::vector< UI08 > layer;
std::vector< UI16 > amount;
// vector for storing all objects that actually end up in user backpack
std::vector< CItem * > boughtItems;
CChar *npc = calcCharObjFromSer( tSock->GetDWord( 3 ) );
UI16 itemtotal = static_cast<UI16>((tSock->GetWord( 1 ) - 8) / 7);
if( itemtotal > 511 )
return true;
boughtItems.reserve( itemtotal );
bitems.resize( itemtotal );
amount.resize( itemtotal );
layer.resize( itemtotal );
SI32 baseOffset = 0;
for( i = 0; i < itemtotal; ++i )
{
baseOffset = 7 * i;
layer[i] = tSock->GetByte( 8 + static_cast<size_t>(baseOffset) );
bitems[i] = calcItemObjFromSer( tSock->GetDWord( 9 + static_cast<size_t>(baseOffset) ) );
amount[i] = tSock->GetWord( 13 + static_cast<size_t>(baseOffset) );
goldtotal += ( amount[i] * ( bitems[i]->GetBuyValue() ) );
}
bool useBank = (goldtotal >= static_cast<UI32>(cwmWorldState->ServerData()->BuyThreshold() ));
if( useBank )
playergoldtotal = GetBankCount( mChar, 0x0EED );
else
playergoldtotal = GetItemAmount( mChar, 0x0EED );
if( playergoldtotal >= goldtotal || mChar->IsGM() )
{
for( i = 0; i < itemtotal; ++i )
{
if( bitems[i]->GetAmount() < amount[i] )
soldout = true;
// Check if onBuyFromVendor JS event is present for each item being purchased
// If return false or 0 has been returned from the script, halt the purchase
std::vector<UI16> scriptTriggers = bitems[i]->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
if( toExecute->OnBuyFromVendor( tSock, npc, bitems[i] ) == 0 )
{
bitems.clear();
bitems.shrink_to_fit();
return true;
}
}
}
}
if( soldout )
{
npc->TextMessage( tSock, 1336, TALK, false ); // Alas, I no longer have all those goods in stock. Let me know if there is something else thou wouldst buy.
clear = true;
}
else
{
if( mChar->IsGM() )
npc->TextMessage( nullptr, 1337, TALK, 0, mChar->GetName().c_str() ); // Here you are, %s. Someone as special as thee will receive my wares for free of course.
else
{
if( goldtotal == 1 )
npc->TextMessage( nullptr, 1338, TALK, 0, mChar->GetName().c_str(), goldtotal ); // Here you are, %s. That will be %d gold coin. I thank thee for thy business.
else
npc->TextMessage( nullptr, 1339, TALK, 0, mChar->GetName().c_str(), goldtotal ); // Here you are, %s. That will be %d gold coins. I thank thee for thy business.
Effects->goldSound( tSock, goldtotal );
}
clear = true;
if( !mChar->IsGM() )
{
if( useBank )
DeleteBankItem( mChar, goldtotal, 0x0EED );
else
DeleteItemAmount( mChar, goldtotal, 0x0EED );
}
CItem *biTemp;
CItem *iMade = nullptr;
UI16 j;
for( i = 0; i < itemtotal; ++i )
{
biTemp = bitems[i];
iMade = nullptr;
if( biTemp->GetAmount() > amount[i] )
{
if( biTemp->isPileable() )
{
iMade = Items->DupeItem( tSock, biTemp, amount[i] );
if( iMade != nullptr )
{
iMade->SetCont( p );
iMade->PlaceInPack();
boughtItems.push_back( iMade );
}
}
else
{
for( j = 0; j < amount[i]; ++j )
{
iMade = Items->DupeItem( tSock, biTemp, 1 );
if( iMade != nullptr )
{
CItem * biTempCont = static_cast<CItem *>(biTemp->GetCont());
if( biTempCont->GetLayer() == 0x1A )
{
// Only do this for new items sold from vendor's sell container
SI16 iMadeHP = iMade->GetHP();
SI16 iMadeMaxHP = static_cast<SI16>(iMade->GetMaxHP());
if( iMadeMaxHP > iMadeHP )
{
// Randomize the item's HP from HP to MaxHP
iMade->SetHP( static_cast<SI16>( RandomNum( iMadeHP, iMadeMaxHP )));
}
}
iMade->SetCont( p );
iMade->PlaceInPack();
boughtItems.push_back( iMade );
}
}
}
biTemp->IncAmount( -amount[i], true );
biTemp->SetRestock( biTemp->GetRestock() + amount[i] );
}
else
{
CItem * biTempCont = static_cast<CItem *>(biTemp->GetCont());
switch( biTempCont->GetLayer() )
{
case 0x1A: // Sell Container
if( biTemp->isPileable() )
{
iMade = Items->DupeItem( tSock, biTemp, amount[i] );
if( iMade != nullptr )
{
iMade->SetCont( p );
iMade->PlaceInPack();
boughtItems.push_back( iMade );
}
}
else
{
for( j = 0; j < amount[i]; ++j )
{
iMade = Items->DupeItem( tSock, biTemp, 1 );
if( iMade != nullptr )
{
SI16 iMadeHP = iMade->GetHP();
SI16 iMadeMaxHP = static_cast<SI16>(iMade->GetMaxHP());
if( iMadeMaxHP > iMadeHP )
{
// Randomize the item's HP from HP to MaxHP
iMade->SetHP( static_cast<SI16>( RandomNum( iMadeHP, iMadeMaxHP )));
}
iMade->SetCont( p );
iMade->PlaceInPack();
boughtItems.push_back( iMade );
}
}
}
biTemp->IncAmount( -amount[i], true );
biTemp->SetRestock( biTemp->GetRestock() + amount[i] );
break;
case 0x1B: // Bought Container
if( biTemp->isPileable() )
{
biTemp->SetCont( p );
boughtItems.push_back( biTemp );
}
else
{
for( j = 0; j < amount[i]-1; ++j )
{
iMade = Items->DupeItem( tSock, biTemp, 1 );
if( iMade != nullptr )
{
iMade->SetCont( p );
iMade->PlaceInPack();
boughtItems.push_back( iMade );
}
}
biTemp->SetCont( p );
biTemp->SetAmount( 1 );
boughtItems.push_back( biTemp );
}
break;
default:
Console.error( " Fallout of switch statement without default. vendor.cpp, buyItem()" );
break;
}
}
}
for( i = 0; i < boughtItems.size(); ++i )
{
if( boughtItems[i] )
{
std::vector<UI16> scriptTriggers = boughtItems[i]->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
// If script returns 1, prevent other scripts with event from running
if( toExecute->OnBoughtFromVendor( tSock, npc, boughtItems[i] ) == 1 )
{
break;
}
}
}
}
}
}
}
else
npc->TextMessage( nullptr, 1340, TALK, false ); // Alas, thou dost not possess sufficient gold for this purchase!
if( clear )
{
CPBuyItem clrSend;
clrSend.Serial( tSock->GetDWord( 3 ) );
tSock->Send( &clrSend );
}
return true;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool CPISellItem::Handle( void )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Player sells an item to the vendor
//o-----------------------------------------------------------------------------------------------o
bool CPISellItem::Handle( void )
{
if( tSock->GetByte( 8 ) != 0 )
{
CChar *mChar = tSock->CurrcharObj();
CChar *n = calcCharObjFromSer( tSock->GetDWord( 3 ) );
if( !ValidateObject( n ) || !ValidateObject( mChar ) )
return true;
CItem *buyPack = n->GetItemAtLayer( IL_BUYCONTAINER );
CItem *boughtPack = n->GetItemAtLayer( IL_BOUGHTCONTAINER );
CItem *sellPack = n->GetItemAtLayer( IL_SELLCONTAINER );
if( !ValidateObject( buyPack ) || !ValidateObject( sellPack ) || !ValidateObject( boughtPack ) )
return true;
CItem *j = nullptr, *k = nullptr, *l = nullptr;
UI16 amt = 0, maxsell = 0;
UI08 i = 0;
UI32 totgold = 0, value = 0;
for( i = 0; i < tSock->GetByte( 8 ); ++i )
{
//j = calcItemObjFromSer( tSock->GetDWord( 9 + (6 * static_cast<size_t>(i)) ) );
amt = tSock->GetWord( 13 + ( 6 * static_cast<size_t>(i)) );
maxsell += amt;
}
if( maxsell > cwmWorldState->ServerData()->SellMaxItemsStatus() )
{
n->TextMessage( nullptr, 1342, TALK, 0, mChar->GetName().c_str(), cwmWorldState->ServerData()->SellMaxItemsStatus() ); // Sorry %s, but I can only buy %i items at a time!
return true;
}
// Loop through each item being sold
for( i = 0; i < tSock->GetByte( 8 ); ++i )
{
j = calcItemObjFromSer( tSock->GetDWord( 9 + ( 6 * static_cast<size_t>(i)) ) );
amt = tSock->GetWord( 13 + ( 6 * static_cast<size_t>(i)) );
if( ValidateObject( j ) )
{
if( j->GetAmount() < amt || FindItemOwner( j ) != mChar )
{
n->TextMessage( nullptr, 1343, TALK, false ); // Cheating scum! Leave now, before I call the guards!
return true;
}
// Check if onSellToVendor JS event is present for item being sold
// If present and a value of "false" or 0 was returned from the script, halt the sale
bool saleHalted = false;
std::vector<UI16> scriptTriggers = j->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
if( toExecute->OnSellToVendor( tSock, n, j ) == 0 )
{
// Halt sale!
saleHalted = true;
break;
}
}
}
// If sale was halted by onSellToVendor event, continue to next item
if( saleHalted )
{
continue;
}
CItem *join = nullptr;
GenericList< CItem * > *pCont = boughtPack->GetContainsList();
for( k = pCont->First(); !pCont->Finished(); k = pCont->Next() )
{
if( ValidateObject( k ) )
{
if( k->GetID() == j->GetID() && j->GetType() == k->GetType() )
join = k;
}
}
pCont = buyPack->GetContainsList();
for( k = pCont->First(); !pCont->Finished(); k = pCont->Next() )
{
if( ValidateObject( k ) )
{
if( k->GetID() == j->GetID() && j->GetType() == k->GetType() )
value = calcValue( j, k->GetSellValue() );
}
}
// If an object already exist in the boughtPack that this one can be joined to...
if( ValidateObject( join ) )
{
join->IncAmount( amt );
join->SetRestock( join->GetRestock() - amt );
l = join;
totgold += ( amt * value );
if( j->GetAmount() == amt )
j->Delete();
else
j->IncAmount( -amt );
}
else
{
//Otherwise, move this item to the vendor's boughtPack
totgold += ( amt * value );
if( j->GetAmount() != amt )
{
l = Items->DupeItem( tSock, j, amt );
j->SetAmount( j->GetAmount() - amt );
}
else
l = j;
if( ValidateObject( l ) )
{
l->SetCont( boughtPack );
l->RemoveFromSight();
}
}
if( l )
{
std::vector<UI16> scriptTriggers = l->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
// If script returns true/1, prevent other scripts with event from running
if( toExecute->OnSoldToVendor( tSock, n, l ) == 1 )
{
break;
}
}
}
}
}
}
Effects->goldSound( tSock, totgold );
while( totgold > MAX_STACK )
{
Items->CreateScriptItem( tSock, mChar, "0x0EED", MAX_STACK, OT_ITEM, true );
totgold -= MAX_STACK;
}
if( totgold > 0 )
Items->CreateScriptItem( tSock, mChar, "0x0EED", totgold, OT_ITEM, true );
}
CPBuyItem clrSend;
clrSend.Serial( tSock->GetDWord( 3 ) );
tSock->Send( &clrSend );
return true;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void restockNPC( CChar *i, bool stockAll )
///o-----------------------------------------------------------------------------------------------o
//| Purpose - Restock NPC Vendors
//o-----------------------------------------------------------------------------------------------o
void restockNPC( CChar& i, bool stockAll )
{
if( !i.IsShop() )
return; // if we aren't a shopkeeper, why bother?
CItem *ci = i.GetItemAtLayer( IL_SELLCONTAINER );
if( ValidateObject( ci ) )
{
GenericList< CItem * > *ciCont = ci->GetContainsList();
for( CItem *c = ciCont->First(); !ciCont->Finished(); c = ciCont->Next() )
{
if( ValidateObject( c ) )
{
if( stockAll )
{
c->IncAmount( c->GetRestock() );
c->SetRestock( 0 );
}
else if( c->GetRestock() )
{
UI16 stockAmt = std::min( c->GetRestock(), static_cast<UI16>(( c->GetRestock() / 2 ) + 1) );
c->IncAmount( stockAmt );
c->SetRestock( c->GetRestock() - stockAmt );
}
if( cwmWorldState->ServerData()->TradeSystemStatus() )
{
CTownRegion *tReg = calcRegionFromXY( i.GetX(), i.GetY(), i.WorldNumber(), i.GetInstanceID() );
Items->StoreItemRandomValue( c, tReg );
}
}
}
}
}
bool restockFunctor( CBaseObject *a, UI32 &b, void *extraData )
{
bool retVal = true;
CChar *c = static_cast< CChar * >(a);
if( ValidateObject( c ) )
restockNPC( (*c), (b == 1) );
return retVal;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void restock( bool stockAll )
//| Date - 3/15/2003
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Restock all NPC Vendors
//o-----------------------------------------------------------------------------------------------o
void restock( bool stockAll )
{
UI32 b = (stockAll ? 1 : 0);
ObjectFactory::getSingleton().IterateOver( OT_CHAR, b, nullptr, &restockFunctor );
}