PDA

View Full Version : Fix for merchant price bugs


greggg230
06-12-2008, 06:27 PM
As of right now, the displayed price for an item (whether selling or buying) does not match up to the actual price you see. For example, when you sell an item, you get more money than what is displayed. In addition, everyone, regardless of faction or charisma, receives the same prices.

I looked through the code and it was really a mess. There is an existing function in client.cpp which determines a modifier based on charisma and function, but it isn't used anywhere. Further, the code contains no less than three different, contradictory rates for merchants.

So, I polished up the code and fixed the relevant functions, they're down below. If you want to just download the updated .cpp files, here they are: http://70.189.190.124:8000/merchantfix.rar

Client::BulkSendMerchantInventory in zone/client_process.cpp:


void Client::BulkSendMerchantInventory(int merchant_id, int16 npcid) {
const Item_Struct* handyitem = NULL;
int32 numItemSlots=80; //The max number of items passed in the transaction.
const Item_Struct *item;
std::list<MerchantList> merlist = zone->merchanttable[merchant_id];
std::list<MerchantList>::const_iterator itr;
Mob* merch = entity_list.GetMobByNpcTypeID(npcid);
if(merlist.size()==0){ //Attempt to load the data, it might have been missed if someone spawned the merchant after the zone was loaded
zone->LoadNewMerchantData(merchant_id);
merlist = zone->merchanttable[merchant_id];
if(merlist.size()==0)
return;
}
std::list<TempMerchantList> tmp_merlist = zone->tmpmerchanttable[npcid];
std::list<TempMerchantList>::iterator tmp_itr;

int32 i=1;
int8 handychance = 0;
for(itr = merlist.begin();itr != merlist.end() && i<numItemSlots;itr++){
MerchantList ml = *itr;
handychance = MakeRandomInt(0, merlist.size() + tmp_merlist.size() - 1 );

item = database.GetItem(ml.item);
if(item) {
if(handychance==0)
handyitem=item;
else
handychance--;
int charges=1;
if(item->ItemClass==ItemClassCommon)
charges=item->MaxCharges;
ItemInst* inst = database.CreateItem(item, charges);
if (inst) {
inst->SetPrice((item->Price*(1/.884)*Client::CalcPriceMod(merch,false)));
inst->SetMerchantSlot(ml.slot);
inst->SetMerchantCount(-1); //unlimited
if(charges > 0)
inst->SetCharges(charges);
else
inst->SetCharges(1);
SendItemPacket(ml.slot-1, inst, ItemPacketMerchant);
safe_delete(inst);
}
}
i++;
}
std::list<TempMerchantList> origtmp_merlist = zone->tmpmerchanttable[npcid];
tmp_merlist.clear();
for(tmp_itr = origtmp_merlist.begin();tmp_itr != origtmp_merlist.end() && i<numItemSlots;tmp_itr++){
TempMerchantList ml = *tmp_itr;
item=database.GetItem(ml.item);
ml.slot=i;
if (item) {
if(handychance==0)
handyitem=item;
else
handychance--;
int charges=1;
if(item->ItemClass==ItemClassCommon && (sint16)ml.charges <= item->MaxCharges)
charges=ml.charges;
else
charges = item->MaxCharges;
ItemInst* inst = database.CreateItem(item, charges);
if (inst) {
inst->SetPrice((item->Price*(1/.884)*Client::CalcPriceMod(merch,false)));
inst->SetMerchantSlot(ml.slot);
inst->SetMerchantCount(1);
if(charges > 0)
inst->SetCharges(charges);
else
inst->SetCharges(1);
SendItemPacket(ml.slot-1, inst, ItemPacketMerchant);
safe_delete(inst);
}
}
tmp_merlist.push_back(ml);
i++;
}
//this resets the slot
zone->tmpmerchanttable[npcid] = tmp_merlist;
if(merch != NULL && handyitem){
char handy_id[8]={0};
int greeting=rand()%5;
int greet_id=0;
switch(greeting){
case 1:
greet_id=MERCHANT_GREETING;
break;
case 2:
greet_id=MERCHANT_HANDY_ITEM1;
break;
case 3:
greet_id=MERCHANT_HANDY_ITEM2;
break;
case 4:
greet_id=MERCHANT_HANDY_ITEM3;
break;
default:
greet_id=MERCHANT_HANDY_ITEM4;
}
sprintf(handy_id,"%i",greet_id);

if(greet_id!=MERCHANT_GREETING)
Message_StringID(10,GENERIC_STRINGID_SAY,merch->GetCleanName(),handy_id,this->GetName(),handyitem->Name);
else
Message_StringID(10,GENERIC_STRINGID_SAY,merch->GetCleanName(),handy_id,this->GetName());

merch->CastToNPC()->FaceTarget(this->CastToMob());
}

// safe_delete_array(cpi);
}


Client::Handle_OP_ShopRequest in zone/client_packet.cpp


void Client::Handle_OP_ShopRequest(const EQApplicationPacket *app)
{
// this works
Merchant_Click_Struct* mc=(Merchant_Click_Struct*)app->pBuffer;
if (app->size != sizeof(Merchant_Click_Struct))
return;
// Send back opcode OP_ShopRequest - tells client to open merchant window.
//EQApplicationPacket* outapp = new EQApplicationPacket(OP_ShopRequest, sizeof(Merchant_Click_Struct));
//Merchant_Click_Struct* mco=(Merchant_Click_Struct*)outapp->pBuffer;
int merchantid=0;
Mob* tmp = entity_list.GetMob(mc->npcid);

if (tmp == 0 || !tmp->IsNPC() || tmp->GetClass() != MERCHANT)
return;

//you have to be somewhat close to them to be properly using them
if(DistNoRoot(*tmp) > USE_NPC_RANGE2)
return;

merchantid=tmp->CastToNPC()->MerchantType;

int action = 1;
if(merchantid == 0) {
EQApplicationPacket* outapp = new EQApplicationPacket(OP_ShopRequest, sizeof(Merchant_Click_Struct));
Merchant_Click_Struct* mco=(Merchant_Click_Struct*)outapp->pBuffer;
mco->npcid = mc->npcid;
mco->playerid = 0;
mco->command = 1; //open...
mco->rate = 1.0;
QueuePacket(outapp);
safe_delete(outapp);
return;
}
if(tmp->IsEngaged()){
this->Message_StringID(0,MERCHANT_BUSY);
action = 0;
}
if (GetFeigned() || IsInvisible())
{
Message(0,"You cannot use a merchant right now.");
action = 0;
}
int factionlvl = GetFactionLevel(CharacterID(), tmp->CastToNPC()->GetNPCTypeID(), GetRace(), GetClass(), GetDeity(), tmp->CastToNPC()->GetPrimaryFaction(), tmp);
if(factionlvl >= 6 && factionlvl != 9)
{
Message(0,"I will not deal with one such as you!");
action = 0;
}
if (tmp->Charmed())
{
action = 0;
}

EQApplicationPacket* outapp = new EQApplicationPacket(OP_ShopRequest, sizeof(Merchant_Click_Struct));
Merchant_Click_Struct* mco=(Merchant_Click_Struct*)outapp->pBuffer;

mco->npcid = mc->npcid;
mco->playerid = 0;
mco->command = action; // Merchant command 0x01 = open
mco->rate = 1/(.884*Client::CalcPriceMod(tmp,true)); // works

outapp->priority = 6;
QueuePacket(outapp);
safe_delete(outapp);

if (action == 1)
BulkSendMerchantInventory(merchantid,tmp->GetNPCTypeID());

return;
}


Client::Handle_OP_ShopPlayerBuy in zone/client_packet.cpp


void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app)
{
RDTSC_Timer t1;
t1.start();
Merchant_Sell_Struct* mp=(Merchant_Sell_Struct*)app->pBuffer;
#if EQDEBUG >= 5
LogFile->write(EQEMuLog::Debug, "%s, purchase item..", GetName());
DumpPacket(app);
#endif

int merchantid;
bool tmpmer_used = false;
Mob* tmp = entity_list.GetMob(mp->npcid);

if (tmp == 0 || !tmp->IsNPC() || tmp->GetClass() != MERCHANT)
return;

if (mp->quantity < 1) return;

//you have to be somewhat close to them to be properly using them
if(DistNoRoot(*tmp) > USE_NPC_RANGE2)
return;

merchantid=tmp->CastToNPC()->MerchantType;

uint32 item_id = 0;
std::list<MerchantList> merlist = zone->merchanttable[merchantid];
std::list<MerchantList>::const_iterator itr;
for(itr = merlist.begin();itr != merlist.end();itr++){
MerchantList ml = *itr;
if(mp->itemslot == ml.slot){
item_id = ml.item;
break;
}
}
const Item_Struct* item = NULL;
int32 prevcharges = 0;
if (item_id == 0) { //check to see if its on the temporary table
std::list<TempMerchantList> tmp_merlist = zone->tmpmerchanttable[tmp->GetNPCTypeID()];
std::list<TempMerchantList>::const_iterator tmp_itr;
TempMerchantList ml;
for(tmp_itr = tmp_merlist.begin();tmp_itr != tmp_merlist.end();tmp_itr++){
ml = *tmp_itr;
if(mp->itemslot == ml.slot){
item_id = ml.item;
tmpmer_used = true;
prevcharges = ml.charges;
break;
}
}
}
item = database.GetItem(item_id);
if (!item){
//error finding item, client didnt get the update packet for whatever reason, roleplay a tad
Message(15,"%s tells you 'Sorry, that item is for display purposes only.' as they take the item off the shelf.",tmp->GetCleanName());
EQApplicationPacket* delitempacket = new EQApplicationPacket(OP_ShopDelItem, sizeof(Merchant_DelItem_Struct));
Merchant_DelItem_Struct* delitem = (Merchant_DelItem_Struct*)delitempacket->pBuffer;
delitem->itemslot = mp->itemslot;
delitem->npcid = mp->npcid;
delitem->playerid = mp->playerid;
delitempacket->priority = 6;
entity_list.QueueCloseClients(tmp,delitempacket); //que for anyone that could be using the merchant so they see the update
safe_delete(delitempacket);
return;
}
if (CheckLoreConflict(item))
{
Message(15,"You can only have one of a lore item.");
return;
}
if(tmpmer_used && (mp->quantity > prevcharges))
mp->quantity = prevcharges;

EQApplicationPacket* outapp = new EQApplicationPacket(OP_ShopPlayerBuy, sizeof(Merchant_Sell_Struct));
Merchant_Sell_Struct* mpo=(Merchant_Sell_Struct*)outapp->pBuffer;
mpo->quantity = mp->quantity;
mpo->playerid = mp->playerid;
mpo->npcid = mp->npcid;
mpo->itemslot=mp->itemslot;

sint16 freeslotid=0;
ItemInst* inst = database.CreateItem(item, mp->quantity);

bool stacked = TryStacking(inst);
if(!stacked)
freeslotid = m_inv.FindFreeSlot(false, true, item->Size);

//make sure we are not completely full...
if(freeslotid == SLOT_CURSOR) {
if(m_inv.GetItem(SLOT_CURSOR) != NULL) {
Message(13, "You do not have room for any more items.");
safe_delete(outapp);
safe_delete(inst);
return;
}
}

mpo->price = (item->Price*(1/.884)*Client::CalcPriceMod(tmp,false))*mp->quantity;
if(freeslotid == SLOT_INVALID || !TakeMoneyFromPP(mpo->price))
{
safe_delete(outapp);
safe_delete(inst);
return;
}

string packet;
if(mp->quantity==1 && item->MaxCharges>0 && item->MaxCharges<255)
mp->quantity=item->MaxCharges;

if (!stacked && inst) {
PutItemInInventory(freeslotid, *inst);
SendItemPacket(freeslotid, inst, ItemPacketTrade);
}
else if(!stacked){
LogFile->write(EQEMuLog::Error, "OP_ShopPlayerBuy: item->ItemClass Unknown! Type: %i", item->ItemClass);
}

QueuePacket(outapp);
if(inst && tmpmer_used){
sint32 new_charges = prevcharges - mp->quantity;
zone->SaveTempItem(merchantid, tmp->GetNPCTypeID(),item_id,new_charges);
if(new_charges<=0){
EQApplicationPacket* delitempacket = new EQApplicationPacket(OP_ShopDelItem, sizeof(Merchant_DelItem_Struct));
Merchant_DelItem_Struct* delitem = (Merchant_DelItem_Struct*)delitempacket->pBuffer;
delitem->itemslot = mp->itemslot;
delitem->npcid = mp->npcid;
delitem->playerid = mp->playerid;
delitempacket->priority = 6;
entity_list.QueueClients(tmp,delitempacket); //que for anyone that could be using the merchant so they see the update
safe_delete(delitempacket);
}
}
safe_delete(inst);
safe_delete(outapp);

if (zone->merchantvar!=0){
if (zone->merchantvar==7){
LogMerchant(this,tmp,mpo,item,true);
}
else if ((admin>=10) && (admin<20)){
if ((zone->merchantvar<8) && (zone->merchantvar>5))
LogMerchant(this,tmp,mpo,item,true);
}
else if (admin<=20){
if ((zone->merchantvar<8) && (zone->merchantvar>4))
LogMerchant(this,tmp,mpo,item,true);
}
else if (admin<=80){
if ((zone->merchantvar<8) && (zone->merchantvar>3))
LogMerchant(this,tmp,mpo,item,true);
}
else if (admin<=100){
if ((zone->merchantvar<9) && (zone->merchantvar>2))
LogMerchant(this,tmp,mpo,item,true);
}
else if (admin<=150){
if (((zone->merchantvar<8) && (zone->merchantvar>1)) || (zone->merchantvar==9))
LogMerchant(this,tmp,mpo,item,true);
}
else if (admin<=255){
if ((zone->merchantvar<8) && (zone->merchantvar>0))
LogMerchant(this,tmp,mpo,item,true);
}
}
t1.stop();
cout << "At 1: " << t1.getDuration() << endl;
return;
}


Client::Handle_OP_ShopPlayerSell in zone/client_packet.cpp


void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
{
RDTSC_Timer t1(true);
Merchant_Purchase_Struct* mp=(Merchant_Purchase_Struct*)app->pBuffer;

Mob* vendor = entity_list.GetMob(mp->npcid);

if (vendor == 0 || !vendor->IsNPC() || vendor->GetClass() != MERCHANT)
return;

//you have to be somewhat close to them to be properly using them
if(DistNoRoot(*vendor) > USE_NPC_RANGE2)
return;

int32 price=0;
int32 itemid = GetItemIDAt(mp->itemslot);
if(itemid == 0)
return;
const Item_Struct* item = database.GetItem(itemid);
ItemInst* inst = GetInv().GetItem(mp->itemslot);
if(!inst){
Message(13,"You seemed to have misplaced that item..");
return;
}
if(mp->quantity > 1 && (sint16)mp->quantity > inst->GetCharges())
return;

if (item && !item->NoDrop) {
return;
}

if (item){
price=(int)((item->Price*mp->quantity)*.884*Client::CalcPriceMod(vendor,true)+0 .5); // need to round up, because client does it automatically when displaying price
AddMoneyToPP(price,false);
if (zone->merchantvar!=0){
if (zone->merchantvar==7) {
LogMerchant(this,vendor,mp,item,false);
}
else if ((admin>=10) && (admin<20)) {
if ((zone->merchantvar<8) && (zone->merchantvar>5))
LogMerchant(this,vendor,mp,item,false);
}
else if (admin<=20) {
if ((zone->merchantvar<8) && (zone->merchantvar>4))
LogMerchant(this,vendor,mp,item,false);
}
else if (admin<=80) {
if ((zone->merchantvar<8) && (zone->merchantvar>3))
LogMerchant(this,vendor,mp,item,false);
}
else if (admin<=100) {
if ((zone->merchantvar<9) && (zone->merchantvar>2))
LogMerchant(this,vendor,mp,item,false);
}
else if (admin<=150) {
if (((zone->merchantvar<8) && (zone->merchantvar>1)) || (zone->merchantvar==9))
LogMerchant(this,vendor,mp,item,false);
}
else if (admin<=255) {
if ((zone->merchantvar<8) && (zone->merchantvar>0))
LogMerchant(this,vendor,mp,item,false);
}
}
}
else
Message(0, "Error #1, item == 0");


if (item && inst->IsStackable())
{
unsigned int i_quan = inst->GetCharges();
if (mp->quantity > i_quan)
mp->quantity = i_quan;
}
else
{
mp->quantity = 1;
}
int freeslot = 0;
int charges = 0;
if(inst->IsStackable())
charges = mp->quantity;
else
charges = inst->GetCharges();
if((freeslot = zone->SaveTempItem(vendor->CastToNPC()->MerchantType, vendor->GetNPCTypeID(),itemid,charges,true)) > 0){
ItemInst* inst2 = inst->Clone();
inst2->SetPrice(item->Price*(1/.884)*Client::CalcPriceMod(vendor,false));
inst2->SetMerchantSlot(freeslot);
if(inst2->IsStackable())
inst2->SetCharges(mp->quantity);
SendItemPacket(freeslot-1, inst2, ItemPacketMerchant);
safe_delete(inst2);
}

// Now remove the item from the player, this happens irrguardless of outcome
if (!inst->IsStackable())
this->DeleteItemInInventory(mp->itemslot,0,false);
else
this->DeleteItemInInventory(mp->itemslot,mp->quantity,false);

EQApplicationPacket* outapp = new EQApplicationPacket(OP_ShopPlayerSell, sizeof(Merchant_Purchase_Struct));
Merchant_Purchase_Struct* mco=(Merchant_Purchase_Struct*)outapp->pBuffer;
mco->npcid = vendor->GetID();
mco->itemslot=mp->itemslot;
mco->quantity=mp->quantity;
mco->price=price;
QueuePacket(outapp);
safe_delete(outapp);
t1.start();
Save(1);
t1.stop();
cout << "Save took: " << t1.getDuration() << endl;
return;
}


Also, should this make the official release, there is a comment in Live_structs.h which says the "rate" variable in Merchant_Click_Struct no longer works. This is no longer true, so it should be taken out.

Angelox
06-12-2008, 06:43 PM
Thanks for the fix, I don't know what's going on with the "official" stuff, nothing seems to get posted. I'm sure Trevius will have added/posted to his upcoming 1110c release.
Can't wait to try this one out, I'll have it in my source today -
Also, I always wonder why items sold to vendors never stay there - with me they poof by the next time i click on vendor so as to see what I sold.
Thanks again, your fixes are much appreciated.

greggg230
06-12-2008, 06:49 PM
I should mention the base rate for merchants is .884 if they're buying and 1/.884 if they're selling. This was just one of the rates in the code so I decided to run with it. A few project for me or someone else could be to make this into a database entry so that people could customize the rates merchants give to people.

leslamarch
06-12-2008, 07:28 PM
Thanks for the great fix, I did limited testing and seems to work just as you intended :D

trevius
06-13-2008, 12:21 AM
Sweet! I will definitely try to get this added to my unofficial updates for the 1110e release. Of course, I will make sure to run some tests on it before posting the new version. Looks like we might have a new coder out to help fix some issues :D I will test out your LoS agro changes too and see how it works. That one seems complex to fix since you don't want things agroing through walls in separate rooms. If it seems pretty good, I will try to get that one added as well.

ChaosSlayer
06-13-2008, 02:07 AM
greggg, do you know if this will also affect the "salerate" field in "items" table of the DB, which suposely should control... SOMETHING price related?

if yes, then how?

greggg230
06-13-2008, 02:26 AM
greggg, do you know if this will also affect the "salerate" field in "items" table of the DB, which suposely should control... SOMETHING price related?

if yes, then how?

From everything I can tell, it does nothing. It's probably an obsolete variable at this point. It's not clear what it might have done in the past. Unless I'm missing something faction, charisma, and the base price of an item should be the only variables needed to determine actual price.

Fine Steel Short Sword has a value of 5500, and I was able to accurately predict its sell and buy prices without even knowing about sellrate. Its sellrate is 9, apparently.

Someone please correct me if I'm wrong, though.

ChaosSlayer
06-13-2008, 02:32 AM
i was under impression, that 'salerate' was some sort of an option to set a custom sell back for each specific item - could realy be usefull in order to create items which would sell back for 100% base price (i can think for dozen fo reason for having such items) - or at least its the only logical explation that coudl posibly fit the description of the field name.

of course the thing never worked, so... =)

but it would be nice to have it actualy working this way =)
(could also be interesting to have item which would sell back for ABOVE base price - again for various reasons) =)

cavedude
06-13-2008, 10:09 AM
Moving to submission. To make it easier on KLS/Scorp, could you diff your changes?

greggg230
06-13-2008, 01:21 PM
Moving to submission. To make it easier on KLS/Scorp, could you diff your changes?

Sure.

Line 3717 of zone/client_packet.cpp should be changed from:

mco->rate = 1.6;

to

mco->rate = 1/(.884*Client::CalcPriceMod(tmp,true));

line 3842 of zone/client_packet.cpp should be changed from:

mpo->price = (item->Price*127/100)*mp->quantity;

to

mpo->price = (item->Price*(1/.884)*Client::CalcPriceMod(tmp,false))*mp->quantity;

line 3947 of zone/client_packet.cpp should be changed from:

price=(int)((item->Price*mp->quantity)*.884);

to

price=(int)((item->Price*mp->quantity)*.884*Client::CalcPriceMod(vendor,true)+0 .5); // need to round, because client does it automatically when displaying price

line 4001 of zone/client_packet.cpp should be changed from:

inst2->SetPrice(item->Price*127/100);

to

inst2->SetPrice(item->Price*(1/.884)*Client::CalcPriceMod(vendor,false));

in zone/client_process.cpp, the following should be moved from 878 to 804:

Mob* merch = entity_list.GetMobByNpcTypeID(npcid);

line 831 of zone/client_process.cpp should be changed from:

inst->SetPrice(item->Price*127/100);

to

inst->SetPrice((item->Price*(1/.884)*Client::CalcPriceMod(merch,false)));

line 862 of zone/client_process.cpp should be changed from:

inst->SetPrice(item->Price*127/100);

to

inst->SetPrice((item->Price*(1/.884)*Client::CalcPriceMod(merch,false)));

Aramid
06-13-2008, 01:50 PM
Moving to submission. To make it easier on KLS/Scorp, could you diff your changes?

CD, for those of us who used to be WinDoze users, can you tell us what the correct Syntax is to create a diff file?

Thanks

So_1337
06-17-2008, 12:45 PM
Another great fix. Keep up the great work, Greg! =)

Angelox
06-17-2008, 02:49 PM
CD, for those of us who used to be WinDoze users, can you tell us what the correct Syntax is to create a diff file?

Thanks

In Linux, diff is integrated in the os. You can make diffs/patches via command line. There are also some very nice graphical interfaces you can add, that help you see even better (kompare, xxdiff).
When I first started here at EqEmu, I couldn't for the best of me, get the source to compile under my windows box. So I took it to my Linux box where I had little trouble there - it was better this happened to me, because Linux Distros usually come "developer ready" and have all these neat tools like diff, kompare, compilers, etc. They are all just as available as my "dir" or "copy" command in windows.
I think I'm gonna try windows compile again with that new compiler package that's posted here, just for the challenge.
Anyways, you usually can google what you need for windows - I just googled "diff program for windows xp", and came up with a whole swarm of stuff. The diff toolshould be self explanatory on how you present it here.

moydock
06-17-2008, 03:35 PM
Great fix man thanks, I've been hoping someone would fix factions, and I didn't even realize it was giving the incorrect gold when selling.

Aramid
06-17-2008, 04:45 PM
In Linux, diff is integrated in the os. You can make diffs/patches via command line. .
I think I'm gonna try windows compile again with that new compiler package that's posted here, just for the challenge.
Anyways, you usually can google what you need for windows - I just googled "diff program for windows xp", and came up with a whole swarm of stuff. The diff toolshould be self explanatory on how you present it here.

I know about the diff command, was looking for a specific command line.. but that's ok.

Hey Angelox, try compiling the BOT CODE in Windows and let me know if it works for you. I can't get it to recognize that the BOTS are compiled in. Have everything set as AFGAIK in Preprocessor and files are all in place, but no go.

erde
06-17-2008, 04:52 PM
I think "diff -Nur ..." is the right command

Scorpious2k
06-18-2008, 10:52 AM
This will be in version 1113.

ChaosSlayer
07-10-2008, 12:16 AM
i am looking at new sell prices after thsi patch and it seems to me than vendor now pays you way to much.

with only amiable faction and 100 cha i am geting back like 95% item price, while on most items 50% shoudl be best what you can get.

is it posible to change fixed 1.27 vendor modifier into an X variable and put into rules, so we can customly adjust how much extra vendor will charge/pay?

curent buy back prices way to high - you should only get to 95% buy back with Ally faction and 500 CHA

PS may as well include a rule on MAX % of item price vendor will ever pay regardless your faction

cavedude
07-26-2008, 12:44 PM
To go along with the above post, there is a problem with this patch... It can happen that with faction/CHA modifiers, the price the merchant buys an item from a player can actually be HIGHER than they sell it for. A quick example, a player sells an item to a merchant for 300pp, the merchant then sells that item back to the player for 280pp, the player can buy/sell over and over making 20pp per each hand in. Of course, the player needs really high CHA and very good faction for this to work, but it is possible as it was done on TGC.

ChaosSlayer
09-28-2008, 11:41 PM
OK I have been looking at this for a bit, and I belive the reason players can sell back for more than they pay is couse vendors mark up price is not high enough to counter the bonuses you get faction and charisma.


On top of that there is STILL a problem that money you get back from NPC do not match up with price shown.
Npc tells you : i will pay 1 plat, you click sell, it says - you sold for 1 plat, but if you looking at your invetory you only getting 9 gold and change.

But this problem is BEYOND my understanding of code.

So I will try to fix the issue with general price scaling.

Thsi is the code I dig up from client.cpp



float Client::CalcPriceMod(Mob* other, bool reverse)
{
float chaformula = 0;

if (GetCHA() > 100)
{
chaformula = (GetCHA() - 100)*-0.1;
if (chaformula < -5)
chaformula = -5;
}
else if (GetCHA() < 75)
{
chaformula = (75 - GetCHA())*0.1;
if (chaformula > 5)
chaformula = 5;
}
if (other)
{
int factionlvl = GetFactionLevel(CharacterID(), other->CastToNPC()->GetNPCTypeID(), GetRace(), GetClass(), GetDeity(), other->CastToNPC()->GetPrimaryFaction(), other);
switch (factionlvl)
{
case 9: //Apprehensive
chaformula += 10;
break;
case 4: //Amiable
chaformula -= 3;
break;
case 3: //Warmly
chaformula -= 4;
break;
case 2: //Kindly
chaformula -= 5;
break;
case 1: //Ally
chaformula -= 6;
break;
}
}
if (reverse)
chaformula *= -1; //For selling
//Now we have, for example, 10
chaformula /= 100; //Convert to 0.10
chaformula += 1; //Convert to 1.10;
return chaformula; //Returns 1.10, expensive stuff!
}


the problem with this code IMHO are insignificant returns form both faction and CHA. Specialy CHA- with accordance to this function more than 150 CHA complitly useless

determiend here: if (chaformula < -5) chaformula = -5;

The formula may have been created with pre-kunark EQ in midn when 150 CHA was UBER, but in days of PoP my level 1 bazar mule was runing around 200 CHA on him wearing junk i obtained for less than 100 plat a peace
ANd whats important there was definite benefit from going even over 250 CHA

So this is changes proposed by me. The intent is to slightly improve the effect from faction bonus and MASSIVLY increase scaling of CHA (note that I am reducing gain increment6s from each 10 CHA above 100, to each 25 CHA but allow it to scale all way up to 400 CHA before CHA becomes useless.

I am also increasing penalty for CHA less than 75, per each 5 points all way down to 0.

With new formulas the max bonus you can obtain with Ally faction and 400 CHA is 26%, and max penalty with Apprehensive faction and 0 CHA is -25% (from original 10 and 10) (Idealy and realisticly you want faction bonsu to scale in accordance with EXACT numeric value of your curent faction - like 115, rather than just Amiable, then rather than havign price junp by 5 ranks, it woudl scale smoothly, but atm I am not sure how to retrive this value so I am working with what allready in the function (it is probobly GetFactionLevel but I am not sure)

the new code



float Client::CalcPriceMod(Mob* other, bool reverse)
{
float chaformula = 0;

if (GetCHA() > 100)
{
chaformula = (GetCHA() - 100)*-0.04;
if (chaformula < -16)
chaformula = -16;
}
else if (GetCHA() < 75)
{
chaformula = (75 - GetCHA())*0.2;
if (chaformula > 15)
chaformula = 15;
}
if (other)
{
int factionlvl = GetFactionLevel(CharacterID(), other->CastToNPC()->GetNPCTypeID(), GetRace(), GetClass(), GetDeity(), other->CastToNPC()->GetPrimaryFaction(), other);
switch (factionlvl)
{
case 9: //Apprehensive
chaformula += 10;
break;
case 4: //Amiable
chaformula -= 2;
break;
case 3: //Warmly
chaformula -= 4;
break;
case 2: //Kindly
chaformula -= 7;
break;
case 1: //Ally
chaformula -= 10;
break;
}
}
if (reverse)
chaformula *= -1; //For selling
//Now we have, for example, 10
chaformula /= 100; //Convert to 0.10
chaformula += 1; //Convert to 1.10;
return chaformula; //Returns 1.10, expensive stuff!



NOW looking at the previous fixed staff. In order to get new price scalign to work as I intented I need to change the logic beyodn how vendor pricing is handled.

By default vendor uses +27% mark up when selling and gives 88% when buying basing of scaled price.
To easy calculation (and pricing of items) I want to alter thsi approach.
The mark up becomes +100%, and buy back is 100% (since ist adjusted by new scalign formulas anyway and fact that mark up price now HUGE)


Goign by dif changes made by Greggg, here are my alternatives:

Line 3717 of zone/client_packet.cpp should be changed from:


mco->rate = 1/(.884*Client::CalcPriceMod(tmp,true));

to:

mco->rate = (1*Client::CalcPriceMod(tmp,true));


line 3842 of zone/client_packet.cpp should be changed from

mpo->price = (item->Price*(1/.884)*Client::CalcPriceMod(tmp,false))*mp->quantity;

to:

mpo->price = (item->Price*(2)*Client::CalcPriceMod(tmp,false))*mp->quantity;


line 3947 of zone/client_packet.cpp should be changed from:

price=(int)((item->Price*mp->quantity)*.884*Client::CalcPriceMod(vendor,true)+0 .5); // need to round, because client does it automatically when displaying price

to:

price=(int)((item->Price*mp->quantity)*1*Client::CalcPriceMod(vendor,true)+0.5) ; // need to round, because client does it automatically when displaying price


line 4001 of zone/client_packet.cpp should be changed from:

inst2->SetPrice(item->Price*(1/.884)*Client::CalcPriceMod(vendor,false));

to:

inst2->SetPrice(item->Price*(2)*Client::CalcPriceMod(vendor,false));


line 831 of zone/client_process.cpp should be changed from:

inst->SetPrice((item->Price*(1/.884)*Client::CalcPriceMod(merch,false)));

to:

inst->SetPrice((item->Price*(2)*Client::CalcPriceMod(merch,false)));


line 862 of zone/client_process.cpp should be changed from:

inst->SetPrice((item->Price*(1/.884)*Client::CalcPriceMod(merch,false)));

to:

inst->SetPrice((item->Price*(2)*Client::CalcPriceMod(merch,false)));



Overall: I replaced all 0.884 with 1 and all 1/0.884 with 2. (i left *1 in code on purpose for easier tracking) (idealy 1 and 2 can later be repalced by DB Rule variables to give custom server admins chance to make them even more specific)

The logic is following: the merchants now will have *2 times mark up when they selling to players, but are 100% of default item price when buying.

So a player with 100 cha and indifirent faction vs a Item X with default Db price of 1 plat:
will buy it from vendor for: 2 plat
will sell it to vendor for: 1 plat (imho this is great cuase this makes item pricing in Db very easy - sicne ist excatly the price player will get)

and then the actul price will be adjusted by faction and cha.

A player with Ally faction and 400 cha: (-26% total bonus)
will buy item from vendor for: 1.48 plat
will sell item to vendor for: 1.26 plat

so there is a decent margins left in between to prevent the problem cavedude curently described (of course given my assumtions for cuases for the problem are correct)

Now.. All modification are based purely on COMMON LOGIC of calculation statements - no actual C code function and calls have been altered.

I also cannot test this atm, since I have no compiler. Hopefuly someone can, and tell me if result of my alteration is anything close to what I have predicted :cool:

ChaosSlayer
09-28-2008, 11:48 PM
few corrections:

the line # for first chunk of code is line 2475 in client.cpp

also - i left out } at the end of my replacement code :cool: