PDA

View Full Version : Fix: Tradeskills - recipe lookups and combines


Darkonig
05-22-2007, 05:41 AM
Here are the diffs for correcting the operation of tradeskills so that they work like they do in live. I am reposting this as right after I posted the original fix there were several patches that affected some of the same functions. I integrated those patches into my fix and am reposting as diffs to make it easier to use. An explanation of the changes can be found in my earlier post http://www.eqemulator.net/forums/showthread.php?t=22490

Since they are too big to fit into a single post, They will appear in the next 2 replies.

Darkonig
05-22-2007, 05:44 AM
cvs diff -- client.h client_packet.cpp tradeskills.cpp zonedb.h (in directory M:\cvswork\EQEmuCVS\Source\zone\)
Index: client.h
================================================== =================
RCS file: /cvsroot/eqemulator/EQEmuCVS/Source/zone/client.h,v
retrieving revision 1.49
diff -r1.49 client.h
496c496
< bool TradeskillExecute(DBTradeskillRecipe_Struct *spec, SkillType tradeskill);
---
> bool TradeskillExecute(DBTradeskillRecipe_Struct *spec);
Index: client_packet.cpp
================================================== =================
RCS file: /cvsroot/eqemulator/EQEmuCVS/Source/zone/client_packet.cpp,v
retrieving revision 1.46
diff -r1.46 client_packet.cpp
3979,3982c3979,3991
< uint32 tskill = Object::TypeToSkill(tsf->object_type);
< if(tskill == 0) {
< LogFile->write(EQEMuLog::Error, "Unknown container type for OP_RecipesFavorite: %d\n", tsf->object_type);
< return;
---
> LogFile->write(EQEMuLog::Debug, "Requested Favorites for: %d - %d\n", tsf->object_type, tsf->some_id);
>
> // results show that object_type is combiner type
> // some_id = 0 if world combiner, item number otherwise
>
> // make where clause segment for container(s)
> char containers[30];
> if (tsf->some_id == 0) {
> // world combiner so no item number
> snprintf(containers,29, "= %u", tsf->object_type);
> } else {
> // container in inventory
> snprintf(containers,29, "in (%u,%u)", tsf->object_type, tsf->some_id);
4014,4015c4023,4026
< " WHERE tr.id IN (%s) AND tradeskill=%lu "
< " GROUP BY tr.id LIMIT 100 ", buf, tskill);
---
> " WHERE tr.id IN (%s) "
> " GROUP BY tr.id "
> " HAVING sum(if(tre.item_id %s AND tre.iscontainer > 0,1,0)) > 0 "
> " LIMIT 100 ", buf, containers);
4035,4038c4046,4055
< uint32 tskill = Object::TypeToSkill(rss->object_type);
< if(tskill == 0) {
< LogFile->write(EQEMuLog::Error, "Unknown container type for OP_RecipesSearch: %d\n", rss->object_type);
< return;
---
> LogFile->write(EQEMuLog::Debug, "Requested search recipes for: %d - %d\n", rss->object_type, rss->some_id);
>
> // make where clause segment for container(s)
> char containers[30];
> if (rss->some_id == 0) {
> // world combiner so no item number
> snprintf(containers,29, "= %u", rss->object_type);
> } else {
> // container in inventory
> snprintf(containers,29, "in (%u,%u)", rss->object_type, rss->some_id);
4059,4060c4076,4080
< " WHERE %s tr.trivial >= %u AND tr.trivial <= %u AND tradeskill=%lu "
< " GROUP BY tr.id LIMIT 200 ", searchclause, rss->mintrivial, rss->maxtrivial, tskill);
---
> " WHERE %s tr.trivial >= %u AND tr.trivial <= %u "
> " GROUP BY tr.id "
> " HAVING sum(if(tre.item_id %s AND tre.iscontainer > 0,1,0)) > 0 "
> " LIMIT 200 "
> , searchclause, rss->mintrivial, rss->maxtrivial, containers);
Index: zonedb.h
================================================== =================
RCS file: /cvsroot/eqemulator/EQEmuCVS/Source/zone/zonedb.h,v
retrieving revision 1.7
diff -r1.7 zonedb.h
37a38
> Skilltype tradeskill;
226,227c227,228
< bool GetTradeRecipe(const ItemInst* container, uint8 c_type, uint8 tradeskill, DBTradeskillRecipe_Struct *spec);
< bool GetTradeRecipe(uint32 recipe_id, uint8 c_type, uint8 tradeskill, DBTradeskillRecipe_Struct *spec);
---
> bool GetTradeRecipe(const ItemInst* container, uint8 c_type, uint32 some_id, DBTradeskillRecipe_Struct *spec);
> bool GetTradeRecipe(uint32 recipe_id, uint8 c_type, uint32 some_id, DBTradeskillRecipe_Struct *spec);

Darkonig
05-22-2007, 05:49 AM
Index: tradeskills.cpp
================================================== =================
RCS file: /cvsroot/eqemulator/EQEmuCVS/Source/zone/tradeskills.cpp,v
retrieving revision 1.17
diff -r1.17 tradeskills.cpp
108,109c108,109
< int8 tstype = 0xE8;
< uint8 passtype = 0;
---
> uint8 c_type = 0xE8;
> uint32 some_id = 0;
117a118
> c_type = worldo->m_type;
125c126,127
< tstype = item->BagType;
---
> c_type = item->BagType;
> some_id = item->ID;
137,139c139,148
< // Convert container type to tradeskill type
< SkillType tradeskill = TradeskillUnknown;
< switch (tstype)
---
> DBTradeskillRecipe_Struct spec;
> if (!database.GetTradeRecipe(container, c_type, some_id, &spec)) {
> user->Message_StringID(4,TRADESKILL_NOCOMBINE);
> EQApplicationPacket* outapp = new EQApplicationPacket(OP_TradeSkillCombine, 0);
> user->QueuePacket(outapp);
> safe_delete(outapp);
> return;
> }
>
> switch (spec.tradeskill)
141,177c150,151
< case 16:
< tradeskill = TAILORING;
< break;
< case 0xE8: //Generic World Container
< if(!worldcontainer) //just to garuntee that worldo is valid
< return;
< passtype = worldo->m_type;
<
< if(worldo->m_type == OT_MEDICINEBAG) {
< if ((user_pp.class_ == SHAMAN) & (user_pp.level >= MIN_LEVEL_ALCHEMY))
< tradeskill = ALCHEMY;
< else if (user_pp.class_ != SHAMAN)
< user->Message(13, "This tradeskill can only be performed by a shaman.");
< else if (user_pp.level < MIN_LEVEL_ALCHEMY)
< user->Message(13, "You cannot perform alchemy until you reach level %i.", MIN_LEVEL_ALCHEMY);
< break;
< } else {
< tradeskill = TypeToSkill(worldo->m_type);
< }
< break;
< case 18:
< tradeskill = FLETCHING;
< break;
< case 20:
< tradeskill = JEWELRY_MAKING;
< break;
< case 30: //Pottery Still needs completion
< tradeskill = POTTERY;
< break;
< case 14: // Baking
< case 15:
< tradeskill = BAKING;
< break;
< case 9: //Alchemy Still needs completion
< if ((user_pp.class_ == SHAMAN) & (user_pp.level >= MIN_LEVEL_ALCHEMY))
< tradeskill = ALCHEMY;
< else if (user_pp.class_ != SHAMAN)
---
> case ALCHEMY:
> if (user_pp.class_ != SHAMAN)
180a155
> return;
182,185c157,158
< case 10: //Tinkering Still needs completion
< if (user_pp.race == GNOME)
< tradeskill = TINKERING;
< else
---
> case TINKERING:
> if (user_pp.race != GNOME)
186a160
> return;
188,200c162,163
< case 24: //Research Still needs completion
< case 25:
< case 26:
< case 27:
< tradeskill = RESEARCH;
< break;
< case 28: // Another Quest Containers.. Cavedude asked for this
< tradeskill = GENERIC_TRADESKILL;
< break;
< case 12:
< if (user_pp.class_ == ROGUE)
< tradeskill = MAKE_POISON;
< else
---
> case MAKE_POISON:
> if (user_pp.class_ != ROGUE)
202,223d164
< break;
< case 13: //Quest Containers
< tradeskill = GENERIC_TRADESKILL;
< break;
< case 46: //Fishing Still needs completion
< tradeskill = FISHING;
< break;
< default:
< user->Message(13, "This tradeskill has not been implemented yet, if you get this message send a "
< "petition and let them know what tradeskill you were trying to use. and give them the following code: 0x%02X", tradeskill);
< }
<
< if (tradeskill == TradeskillUnknown) {
< return;
< }
<
< DBTradeskillRecipe_Struct spec;
< if (!database.GetTradeRecipe(container, passtype, tradeskill, &spec)) {
< user->Message_StringID(4,TRADESKILL_NOCOMBINE);
< EQApplicationPacket* outapp = new EQApplicationPacket(OP_TradeSkillCombine, 0);
< user->QueuePacket(outapp);
< safe_delete(outapp);
224a166
> break;
228c170
< bool success = user->TradeskillExecute(&spec, tradeskill);
---
> bool success = user->TradeskillExecute(&spec);
272,279d213
< SkillType tskill = Object::TypeToSkill(rac->object_type);
< if(tskill == TradeskillUnknown) {
< LogFile->write(EQEMuLog::Error, "Unknown container type for HandleAutoCombine: %d\n", rac->object_type);
< user->QueuePacket(outapp);
< safe_delete(outapp);
< return;
< }
<
282c216
< if (!database.GetTradeRecipe(rac->recipe_id, rac->object_type, tskill, &spec)) {
---
> if (!database.GetTradeRecipe(rac->recipe_id, rac->object_type, rac->some_id, &spec)) {
395c329
< bool success = user->TradeskillExecute(&spec, tskill);
---
> bool success = user->TradeskillExecute(&spec);
653,654c587,588
< bool Client::TradeskillExecute(DBTradeskillRecipe_Struc t *spec, SkillType tradeskill) {
< if(spec == NULL || tradeskill == 0)
---
> bool Client::TradeskillExecute(DBTradeskillRecipe_Struc t *spec) {
> if(spec == NULL)
657c591
< int16 user_skill = GetSkill(tradeskill);
---
> int16 user_skill = GetSkill(spec->tradeskill);
676c610
< switch(tradeskill) {
---
> switch(spec->tradeskill) {
698c632
< if (tradeskill == FLETCHING || tradeskill == MAKE_POISON) {
---
> if (spec->tradeskill == FLETCHING || spec->tradeskill == MAKE_POISON) {
701c635
< } else if (tradeskill == BLACKSMITHING) {
---
> } else if (spec->tradeskill == BLACKSMITHING) {
780c714
< if (((tradeskill==75) || GetGM() || (chance > res)) || MakeRandomInt(0, 99) < AAChance){
---
> if (((spec->tradeskill==75) || GetGM() || (chance > res)) || MakeRandomInt(0, 99) < AAChance){
784c718
< CheckIncreaseTradeskill(bonusstat, stat_modifier, skillup_modifier, success_modifier, tradeskill);
---
> CheckIncreaseTradeskill(bonusstat, stat_modifier, skillup_modifier, success_modifier, spec->tradeskill);
801c735
< CheckIncreaseTradeskill(bonusstat, stat_modifier, skillup_modifier, success_modifier, tradeskill);
---
> CheckIncreaseTradeskill(bonusstat, stat_modifier, skillup_modifier, success_modifier, spec->tradeskill);


continued next post, still too big

Darkonig
05-22-2007, 05:49 AM
858c792
< bool ZoneDatabase::GetTradeRecipe(const ItemInst* container, uint8 c_type, uint8 tradeskill,
---
> bool ZoneDatabase::GetTradeRecipe(const ItemInst* container, uint8 c_type, uint32 some_id,
872,881c806,813
< //use the world item type as type if we have a world item
< //otherwise use the item's ID... this make the assumption that
< //no tradeskill containers will have an item ID which is
< //below the highest ID of objects, which is currently 0x30
< uint32 type = c_type;
<
< //dunno why I have to cast this up to call GetItem
< const Item_Struct *istruct = ((const ItemInst *) container)->GetItem();
< if(c_type == 0 && istruct) {
< type = istruct->ID;
---
> // make where clause segment for container(s)
> char containers[30];
> if (some_id == 0) {
> // world combiner so no item number
> snprintf(containers,29, "= %u", c_type);
> } else {
> // container in inventory
> snprintf(containers,29, "in (%u,%u)", c_type, some_id);
913,916d844
< //add in the container.
< count++;
< sum += type;
<
920,922c848,850
< " OR ( tre.item_id=%u AND tre.iscontainer=1 )"
< " GROUP BY tre.recipe_id HAVING sum(tre.componentcount+tre.iscontainer) = %u"
< " AND sum(tre.item_id * (tre.componentcount+tre.iscontainer)) = %u", buf2, type, count, sum);
---
> " OR ( tre.item_id %s AND tre.iscontainer=1 )"
> " GROUP BY tre.recipe_id HAVING sum(tre.componentcount) = %u"
> " AND sum(tre.item_id * tre.componentcount) = %u", buf2, containers, count, sum);
925c853
< LogFile->write(EQEMuLog::Error, "Error in GetTradeRecept search, query: %s", query);
---
> LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe search, query: %s", query);
927c855
< LogFile->write(EQEMuLog::Error, "Error in GetTradeRecept search, error: %s", errbuf);
---
> LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe search, error: %s", errbuf);
956,959c884,886
< " WHERE tre.recipe_id IN (%s) "
< " AND (tre.iscontainer=0 OR tre.item_id=%u) "
< " GROUP BY tre.recipe_id HAVING sum(tre.componentcount+tre.iscontainer) = %u"
< " AND sum(tre.item_id * (tre.componentcount+tre.iscontainer)) = %u", buf2, type, count, sum);
---
> " WHERE tre.recipe_id IN (%s)"
> " GROUP BY tre.recipe_id HAVING sum(tre.componentcount) = %u"
> " AND sum(tre.item_id * tre.componentcount) = %u", buf2, count, sum);
962c889
< LogFile->write(EQEMuLog::Error, "Error in GetTradeRecept, re-query: %s", query);
---
> LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe, re-query: %s", query);
964c891
< LogFile->write(EQEMuLog::Error, "Error in GetTradeRecept, error: %s", errbuf);
---
> LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe, error: %s", errbuf);
1018c945
< return(GetTradeRecipe(recipe_id, c_type, tradeskill, spec));
---
> return(GetTradeRecipe(recipe_id, c_type, some_id, spec));
1023c950
< bool ZoneDatabase::GetTradeRecipe(uint32 recipe_id, uint8 c_type, uint8 tradeskill,
---
> bool ZoneDatabase::GetTradeRecipe(uint32 recipe_id, uint8 c_type, uint32 some_id,
1034,1036c961,976
< qlen = MakeAnyLenString(&query, "SELECT tr.skillneeded, tr.trivial, tr.nofail, tr.replace_container"
< " FROM tradeskill_recipe AS tr"
< " WHERE tr.id = %lu AND tr.tradeskill = %u", recipe_id, tradeskill);
---
> // make where clause segment for container(s)
> char containers[30];
> if (some_id == 0) {
> // world combiner so no item number
> snprintf(containers,29, "= %u", c_type);
> } else {
> // container in inventory
> snprintf(containers,29, "in (%u,%u)", c_type, some_id);
> }
>
> qlen = MakeAnyLenString(&query, "SELECT tr.id, tr.tradeskill, tr.skillneeded,"
> " tr.trivial, tr.nofail, tr.replace_container"
> " FROM tradeskill_recipe AS tr inner join tradeskill_recipe_entries as tre"
> " ON tr.id = tre.recipe_id"
> " WHERE tr.id = %lu AND tre.item_id %s"
> " GROUP BY tr.id", recipe_id, containers);
1039c979
< LogFile->write(EQEMuLog::Error, "Error in GetTradeRecept, query: %s", query);
---
> LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe, query: %s", query);
1041c981
< LogFile->write(EQEMuLog::Error, "Error in GetTradeRecept, error: %s", errbuf);
---
> LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe, error: %s", errbuf);
1053,1056c993,997
< spec->skill_needed = (sint16)atoi(row[0]);
< spec->trivial = (uint16)atoi(row[1]);
< spec->nofail = atoi(row[2]) ? true : false;
< spec->replace_container = atoi(row[3]) ? true : false;
---
> spec->tradeskill = (SkillType)atoi(row[1]);
> spec->skill_needed = (sint16)atoi(row[2]);
> spec->trivial = (uint16)atoi(row[3]);
> spec->nofail = atoi(row[4]) ? true : false;
> spec->replace_container = atoi(row[5]) ? true : false;

KLS
05-25-2007, 08:38 AM
I was asked to take a look at this stuff. So I am, but SVN has been a little =x lately with all the server issues so might not get in right away of course.

KLS
05-25-2007, 09:31 AM
Hey, could you post the changed switch statement in tradeskills.cpp?

I'm having trouble reading the diff at that part.

Darkonig
05-25-2007, 10:55 AM
Not sure which switch statement you are referring to so will post each of the modified methods.

// Perform tradeskill combine
// complete tradeskill rewrite by father nitwit, 8/2004
void Object::HandleCombine(Client* user, const NewCombine_Struct* in_combine, Object *worldo)
{
if (!user || !in_combine) {
LogFile->write(EQEMuLog::Error, "Client or NewCombine_Struct not set in Object::HandleCombine");
return;
}

Inventory& user_inv = user->GetInv();
PlayerProfile_Struct& user_pp = user->GetPP();
ItemInst* container = NULL;
ItemInst* inst = NULL;
uint8 c_type = 0xE8;
uint32 some_id = 0;
bool worldcontainer=false;

if (in_combine->container_slot == SLOT_WORLDCONTAINER) {
if(!worldo) {
user->Message(13, "Error: Server is not aware of the tradeskill container you are attempting to use");
return;
}
inst = worldo->m_inst;
c_type = worldo->m_type;
worldcontainer=true;
}
else {
inst = user_inv.GetItem(in_combine->container_slot);
if (inst) {
const Item_Struct* item = inst->GetItem();
if (item && inst->IsType(ItemClassContainer)) {
c_type = item->BagType;
some_id = item->ID;
}
}
}

if (!inst || !inst->IsType(ItemClassContainer)) {
user->Message(13, "Error: Server does not recognize specified tradeskill container");
return;
}

container = inst;

DBTradeskillRecipe_Struct spec;
if (!database.GetTradeRecipe(container, c_type, some_id, &spec)) {
user->Message_StringID(4,TRADESKILL_NOCOMBINE);
EQApplicationPacket* outapp = new EQApplicationPacket(OP_TradeSkillCombine, 0);
user->QueuePacket(outapp);
safe_delete(outapp);
return;
}

switch (spec.tradeskill)
{
case ALCHEMY:
if (user_pp.class_ != SHAMAN)
user->Message(13, "This tradeskill can only be performed by a shaman.");
else if (user_pp.level < MIN_LEVEL_ALCHEMY)
user->Message(13, "You cannot perform alchemy until you reach level %i.", MIN_LEVEL_ALCHEMY);
return;
break;
case TINKERING:
if (user_pp.race != GNOME)
user->Message(13, "Only gnomes can tinker.");
return;
break;
case MAKE_POISON:
if (user_pp.class_ != ROGUE)
user->Message(13, "Only rogues can mix poisons.");
return;
break;
}

//do the check and send results...
bool success = user->TradeskillExecute(&spec);

// Send acknowledgement packets to client
EQApplicationPacket* outapp = new EQApplicationPacket(OP_TradeSkillCombine, 0);
user->QueuePacket(outapp);
safe_delete(outapp);

//now clean out the containers.
if(worldcontainer){
container->Clear();
outapp = new EQApplicationPacket(OP_ClearObject,0);
user->QueuePacket(outapp);
safe_delete(outapp);
database.DeleteWorldContainer(worldo->m_id, zone->GetZoneID());
if(success && spec.replace_container) {
//should report this error, but we dont have the recipe ID, so its not very useful
LogFile->write(EQEMuLog::Error, "Replace container combine executed in a world container.");
}
} else{
for (uint8 i=0; i<10; i++){
const ItemInst* inst = container->GetItem(i);
if (inst) {
user->DeleteItemInInventory(Inventory::CalcSlotId(in_com bine->container_slot,i),0,true);
}
}
container->Clear();
if(success && spec.replace_container) {
user->DeleteItemInInventory(in_combine->container_slot, 0, true);
}
}
}

void Object::HandleAutoCombine(Client* user, const RecipeAutoCombine_Struct* rac) {

//get our packet ready, gotta send one no matter what...
EQApplicationPacket* outapp = new EQApplicationPacket(OP_RecipeAutoCombine, sizeof(RecipeAutoCombine_Struct));
RecipeAutoCombine_Struct *outp = (RecipeAutoCombine_Struct *)outapp->pBuffer;
outp->object_type = rac->object_type;
outp->some_id = rac->some_id;
outp->unknown1 = rac->unknown1;
outp->recipe_id = rac->recipe_id;
outp->reply_code = 0xFFFFFFF5; //default fail.


//ask the database for the recipe to make sure it exists...
DBTradeskillRecipe_Struct spec;
if (!database.GetTradeRecipe(rac->recipe_id, rac->object_type, rac->some_id, &spec)) {
LogFile->write(EQEMuLog::Error, "Unknown recipe for HandleAutoCombine: %u\n", rac->recipe_id);
user->QueuePacket(outapp);
safe_delete(outapp);
return;
}

char errbuf[MYSQL_ERRMSG_SIZE];
MYSQL_RES *result;
MYSQL_ROW row;
char *query = 0;

uint32 qlen = 0;
uint8 qcount = 0;

//pull the list of components
qlen = MakeAnyLenString(&query, "SELECT tre.item_id,tre.componentcount "
" FROM tradeskill_recipe_entries AS tre "
" WHERE tre.componentcount > 0 AND tre.recipe_id=%u", rac->recipe_id);

if (!database.RunQuery(query, qlen, errbuf, &result)) {
LogFile->write(EQEMuLog::Error, "Error in HandleAutoCombine query '%s': %s", query, errbuf);
safe_delete_array(query);
user->QueuePacket(outapp);
safe_delete(outapp);
return;
}
safe_delete_array(query);

qcount = mysql_num_rows(result);
if(qcount < 1) {
LogFile->write(EQEMuLog::Error, "Error in HandleAutoCombine: no components returned");
user->QueuePacket(outapp);
safe_delete(outapp);
return;
}
if(qcount > 10) {
LogFile->write(EQEMuLog::Error, "Error in HandleAutoCombine: too many components returned (%u)", qcount);
user->QueuePacket(outapp);
safe_delete(outapp);
return;
}

uint32 items[10];
memset(items, 0, sizeof(items));
uint8 counts[10];
memset(counts, 0, sizeof(counts));


//search for all the crap in their inventory
Inventory& user_inv = user->GetInv();
uint8 count = 0;
uint8 needcount = 0;
uint8 r,k;
for(r = 0; r < qcount; r++) {
row = mysql_fetch_row(result);
uint32 item = (uint32)atoi(row[0]);
uint8 num = (uint8) atoi(row[1]);

needcount += num;

//because a HasItem on items with num > 1 only returns the
//last-most slot... the results of this are useless to us
//when we go to delete them because we cannot assume it is in a single stack.
if(user_inv.HasItem(item, num, invWherePersonal) != SLOT_INVALID)
count += num;
//dont start deleting anything until we have found it all.
items[r] = item;
counts[r] = num;
}
mysql_free_result(result);

//make sure we found it all...
if(count != needcount) {
user->QueuePacket(outapp);
safe_delete(outapp);
return;
}

//now we know they have everything...

//remove all the items from the players inventory, with updates...
sint16 slot;
for(r = 0; r < qcount; r++) {
if(items[r] == 0 || counts[r] == 0)
continue; //skip empties, could prolly break here

//we have to loop here to delete 1 at a time in case its in multiple stacks.
for(k = 0; k < counts[r]; k++) {
slot = user_inv.HasItem(items[r], 1, invWherePersonal);
if(slot == SLOT_INVALID) {
//WTF... I just checked this above, but just to be sure...
//we cant undo the previous deletes without a lot of work.
//so just call it quits, this shouldent ever happen anyways.
user->QueuePacket(outapp);
safe_delete(outapp);
return;
}

user->DeleteItemInInventory(slot, 1, true);
}
}

//otherwise, we found it all...
outp->reply_code = 0x00000000; //success for finding it...

user->QueuePacket(outapp);
//DumpPacket(outapp);
safe_delete(outapp);


//now actually try to make something...

bool success = user->TradeskillExecute(&spec);

//TODO: find in-pack containers in inventory, make sure they are really
//there, and then use that slot to handle replace_container too.
if(success && spec.replace_container) {
// user->DeleteItemInInventory(in_combine->container_slot, 0, true);
}

}

Darkonig
05-25-2007, 10:57 AM
//returns true on success
bool Client::TradeskillExecute(DBTradeskillRecipe_Struc t *spec) {
if(spec == NULL)
return(false);

int16 user_skill = GetSkill(spec->tradeskill);
float chance = 0;
float skillup_modifier;
sint16 thirdstat = 0;
sint16 stat_modifier = 15;
uint16 success_modifier;

// Rework based on the info on eqtraders.com
// http://mboards.eqtraders.com/eq/showthread.php?t=22246
// 09/10/2006 v0.1 (eq4me)
// 09/11/2006 v0.2 (eq4me)
// Todo:
// Implementing AAs
// Success modifiers based on recipes
// Skillup modifiers based on the rarity of the ingredients

// Some tradeskills are more eqal then others. ;-)
// If you want to customize the stage1 success rate do it here.
// Remember: skillup_modifier is (float). Lower is better
switch(spec->tradeskill) {
case FLETCHING:
case ALCHEMY:
case JEWELRY_MAKING:
case POTTERY:
skillup_modifier = 4;
break;
case BAKING:
case BREWING:
skillup_modifier = 3;
break;
case RESEARCH:
skillup_modifier = 1;
break;
default:
skillup_modifier = 2;
break;
}

// Some tradeskills take the higher of one additional stat beside INT and WIS
// to determine the skillup rate. Additionally these tradeskills do not have an
// -15 modifier on their statbonus.
switch(spec->tradeskill) {
case FLETCHING:
case MAKE_POISON:
thirdstat = GetDEX();
stat_modifier = 0;
break;
case BLACKSMITHING:
thirdstat = GetSTR();
stat_modifier = 0;
break;
default:
break;
}

sint16 higher_from_int_wis = (GetINT() > GetWIS()) ? GetINT() : GetWIS();
sint16 bonusstat = (higher_from_int_wis > thirdstat) ? higher_from_int_wis : thirdstat;

vector< pair<uint32,uint8> >::iterator itr;


//calculate the base success chance
// For trivials over 68 the chance is (skill - 0.75*trivial) +51.5
// For trivial up to 68 the chance is (skill - trivial) + 66
if (spec->trivial >= 68) {
chance = (user_skill - (0.75*spec->trivial)) + 51.5;
} else {
chance = (user_skill - spec->trivial) + 66;
}

sint16 over_trivial = (sint16)user_skill - (sint16)spec->trivial;

//handle caps
if(spec->nofail) {
chance = 100; //cannot fail.
_log(TRADESKILLS__TRACE, "...This combine cannot fail.");
} else if(over_trivial > 0) {
// At reaching trivial the chance goes to 95% going up an additional
// percent for every 40 skillpoints above the trivial.
// The success rate is not modified through stats.
// Mastery AAs are unaccounted for so far.
// chance_AA = chance + ((100 - chance) * mastery_modifier)
// But the 95% limit with an additional 1% for every 40 skill points
// above critical still stands.
// Mastery modifier is: 10%/25%/50% for rank one/two/three
chance = 95.0f + (float(user_skill - spec->trivial) / 40.0f);
Message_StringID(4, TRADESKILL_TRIVIAL);
} else if(chance < 5) {
// Minimum chance is always 5
chance = 5;
} else if(chance > 95) {
//cap is 95, shouldent reach this before trivial, but just in case.
chance = 95;
}

_log(TRADESKILLS__TRACE, "...Current skill: %d , Trivial: %d , Success chance: %f percent", user_skill , spec->trivial , chance);
_log(TRADESKILLS__TRACE, "...Bonusstat: %d , INT: %d , WIS: %d , DEX: %d , STR: %d", bonusstat , GetINT() , GetWIS() , GetDEX() , GetSTR());

float res = MakeRandomFloat(0, 99);
if ((spec->tradeskill==75) || GetGM() || (chance > res)){
success_modifier = 1;

if(over_trivial < 0)
CheckIncreaseTradeskill(bonusstat, stat_modifier, skillup_modifier, success_modifier, spec->tradeskill);

Message_StringID(4,TRADESKILL_SUCCEED);

_log(TRADESKILLS__TRACE, "Tradeskill success");

itr = spec->onsuccess.begin();
while(itr != spec->onsuccess.end()) {
//should we check this crap?
SummonItem(itr->first, itr->second);
itr++;
}
return(true);
} else {
success_modifier = 2; // Halves the chance

if(over_trivial < 0)
CheckIncreaseTradeskill(bonusstat, stat_modifier, skillup_modifier, success_modifier, spec->tradeskill);

Message_StringID(4,TRADESKILL_FAILED);

_log(TRADESKILLS__TRACE, "Tradeskill failed");

itr = spec->onfail.begin();
while(itr != spec->onfail.end()) {
//should we check these arguments?
SummonItem(itr->first, itr->second);
itr++;
}
}
return(false);
}

Darkonig
05-25-2007, 10:58 AM
bool ZoneDatabase::GetTradeRecipe(const ItemInst* container, uint8 c_type, uint32 some_id,
DBTradeskillRecipe_Struct *spec)
{
char errbuf[MYSQL_ERRMSG_SIZE];
MYSQL_RES *result;
MYSQL_ROW row;
char *query = 0;
char buf2[2048];

uint32 sum = 0;
uint32 count = 0;
uint32 qcount = 0;
uint32 qlen = 0;

// make where clause segment for container(s)
char containers[30];
if (some_id == 0) {
// world combiner so no item number
snprintf(containers,29, "= %u", c_type);
} else {
// container in inventory
snprintf(containers,29, "in (%u,%u)", c_type, some_id);
}

buf2[0] = '\0';

//Could prolly watch for stacks in this loop and handle them properly...
//just increment sum and count accordingly
bool first = true;
uint8 i;
char *pos = buf2;
for (i=0; i<10; i++) {
const ItemInst* inst = container->GetItem(i);
if (inst) {
const Item_Struct* item = GetItem(inst->GetItem()->ID);
if (item) {
if(first) {
pos += snprintf(pos, 19, "%d", item->ID);
first = false;
} else {
pos += snprintf(pos, 19, ",%d", item->ID);
}
sum += item->ID;
count++;
}
}
}
*pos = '\0';

if(count < 1) {
return(false); //no items == no recipe
}

qlen = MakeAnyLenString(&query, "SELECT tre.recipe_id "
" FROM tradeskill_recipe_entries AS tre"
" WHERE ( tre.item_id IN(%s) AND tre.componentcount>0 )"
" OR ( tre.item_id %s AND tre.iscontainer=1 )"
" GROUP BY tre.recipe_id HAVING sum(tre.componentcount) = %u"
" AND sum(tre.item_id * tre.componentcount) = %u", buf2, containers, count, sum);

if (!RunQuery(query, qlen, errbuf, &result)) {
LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe search, query: %s", query);
safe_delete_array(query);
LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe search, error: %s", errbuf);
return(false);
}
safe_delete_array(query);

qcount = mysql_num_rows(result);
if(qcount > 1) {
//multiple recipes, partial match... do an extra query to get it exact.
//this happens when combining components for a smaller recipe
//which is completely contained within another recipe

first = true;
pos = buf2;
for (i = 0; i < qcount; i++) {
row = mysql_fetch_row(result);
uint32 recipeid = (uint32) atoi(row[0]);
if(first) {
pos += snprintf(pos, 19, "%u", recipeid);
first = false;
} else {
pos += snprintf(pos, 19, ",%u", recipeid);
}
//length sanity check on buf2
if(pos > (buf2 + 2020))
break;
}

qlen = MakeAnyLenString(&query, "SELECT tre.recipe_id"
" FROM tradeskill_recipe_entries AS tre"
" WHERE tre.recipe_id IN (%s)"
" GROUP BY tre.recipe_id HAVING sum(tre.componentcount) = %u"
" AND sum(tre.item_id * tre.componentcount) = %u", buf2, count, sum);

if (!RunQuery(query, qlen, errbuf, &result)) {
LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe, re-query: %s", query);
safe_delete_array(query);
LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe, error: %s", errbuf);
return(false);
}
safe_delete_array(query);

qcount = mysql_num_rows(result);
}
if (qcount != 1) {
if(qcount > 1) {
LogFile->write(EQEMuLog::Error, "Combine error: Recipe is not unique!");
}
//else, just not found i guess..
return(false);
}

row = mysql_fetch_row(result);
uint32 recipe_id = (uint32)atoi(row[0]);
mysql_free_result(result);

return(GetTradeRecipe(recipe_id, c_type, some_id, spec));
}

bool ZoneDatabase::GetTradeRecipe(uint32 recipe_id, uint8 c_type, uint32 some_id,
DBTradeskillRecipe_Struct *spec)
{
char errbuf[MYSQL_ERRMSG_SIZE];
MYSQL_RES *result;
MYSQL_ROW row;
char *query = 0;

uint32 qcount = 0;
uint32 qlen;

// make where clause segment for container(s)
char containers[30];
if (some_id == 0) {
// world combiner so no item number
snprintf(containers,29, "= %u", c_type);
} else {
// container in inventory
snprintf(containers,29, "in (%u,%u)", c_type, some_id);
}

qlen = MakeAnyLenString(&query, "SELECT tr.id, tr.tradeskill, tr.skillneeded,"
" tr.trivial, tr.nofail, tr.replace_container"
" FROM tradeskill_recipe AS tr inner join tradeskill_recipe_entries as tre"
" ON tr.id = tre.recipe_id"
" WHERE tr.id = %lu AND tre.item_id %s"
" GROUP BY tr.id", recipe_id, containers);

if (!RunQuery(query, qlen, errbuf, &result)) {
LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe, query: %s", query);
safe_delete_array(query);
LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe, error: %s", errbuf);
return(false);
}
safe_delete_array(query);

qcount = mysql_num_rows(result);
if(qcount != 1) {
//just not found i guess..
return(false);
}

row = mysql_fetch_row(result);
spec->tradeskill = (SkillType)atoi(row[1]);
spec->skill_needed = (sint16)atoi(row[2]);
spec->trivial = (uint16)atoi(row[3]);
spec->nofail = atoi(row[4]) ? true : false;
spec->replace_container = atoi(row[5]) ? true : false;
mysql_free_result(result);

//Pull the on-success items...
qlen = MakeAnyLenString(&query, "SELECT item_id,successcount FROM tradeskill_recipe_entries"
" WHERE successcount>0 AND componentcount=0 AND recipe_id=%u", recipe_id);

if (!RunQuery(query, qlen, errbuf, &result)) {
LogFile->write(EQEMuLog::Error, "Error in GetTradeRecept success query '%s': %s", query, errbuf);
safe_delete_array(query);
return(false);
}
safe_delete_array(query);

qcount = mysql_num_rows(result);
if(qcount < 1) {
LogFile->write(EQEMuLog::Error, "Error in GetTradeRecept success: no success items returned");
return(false);
}
uint8 r;
spec->onsuccess.clear();
for(r = 0; r < qcount; r++) {
row = mysql_fetch_row(result);
/*if(r == 0) {
*product1_id = (uint32)atoi(row[0]);
*productcount = (uint32)atoi(row[1]);
} else if(r == 1) {
*product2_id = (uint32)atoi(row[0]);
} else {
LogFile->write(EQEMuLog::Warning, "Warning: recipe returned more than 2 products, not yet supported.");
}*/
uint32 item = (uint32)atoi(row[0]);
uint8 num = (uint8) atoi(row[1]);
spec->onsuccess.push_back(pair<uint32,uint8>::pair(item, num));
}
mysql_free_result(result);


//Pull the on-fail items...
qlen = MakeAnyLenString(&query, "SELECT item_id,failcount FROM tradeskill_recipe_entries"
" WHERE failcount>0 AND componentcount=0 AND recipe_id=%u", recipe_id);

spec->onfail.clear();
if (RunQuery(query, qlen, errbuf, &result)) {

qcount = mysql_num_rows(result);
uint8 r;
for(r = 0; r < qcount; r++) {
row = mysql_fetch_row(result);
/*if(r == 0) {
*failproduct_id = (uint32)atoi(row[0]);
} else {
LogFile->write(EQEMuLog::Warning, "Warning: recipe returned more than 1 fail product, not yet supported.");
}*/
uint32 item = (uint32)atoi(row[0]);
uint8 num = (uint8) atoi(row[1]);
spec->onfail.push_back(pair<uint32,uint8>::pair(item, num));
}
mysql_free_result(result);
}
safe_delete_array(query);

return(true);
}

KLS
05-25-2007, 11:19 AM
That's fine, thanks. Didn't have patch installed for diffs and was doing them by eye but without context diffs I got turned around. =p