Merge pull request #268 from addtheice/RunQueryToDatabaseQuery_zone_tradeskills

Run query to database query zone tradeskills
This commit is contained in:
Alex 2014-10-08 16:31:55 -07:00
commit ee71fa0d56
3 changed files with 355 additions and 490 deletions

View File

@ -314,7 +314,7 @@ public:
/* New PP Save Functions */ /* New PP Save Functions */
bool SaveCurrency(){ return database.SaveCharacterCurrency(this->CharacterID(), &m_pp); } bool SaveCurrency(){ return database.SaveCharacterCurrency(this->CharacterID(), &m_pp); }
bool SaveAA(); bool SaveAA();
inline bool ClientDataLoaded() const { return client_data_loaded; } inline bool ClientDataLoaded() const { return client_data_loaded; }
inline bool Connected() const { return (client_state == CLIENT_CONNECTED); } inline bool Connected() const { return (client_state == CLIENT_CONNECTED); }
inline bool InZone() const { return (client_state == CLIENT_CONNECTED || client_state == CLIENT_LINKDEAD); } inline bool InZone() const { return (client_state == CLIENT_CONNECTED || client_state == CLIENT_LINKDEAD); }
@ -664,7 +664,7 @@ public:
void IncreaseLanguageSkill(int skill_id, int value = 1); void IncreaseLanguageSkill(int skill_id, int value = 1);
virtual uint16 GetSkill(SkillUseTypes skill_id) const { if (skill_id <= HIGHEST_SKILL) { return((itembonuses.skillmod[skill_id] > 0) ? m_pp.skills[skill_id] * (100 + itembonuses.skillmod[skill_id]) / 100 : m_pp.skills[skill_id]); } return 0; } virtual uint16 GetSkill(SkillUseTypes skill_id) const { if (skill_id <= HIGHEST_SKILL) { return((itembonuses.skillmod[skill_id] > 0) ? m_pp.skills[skill_id] * (100 + itembonuses.skillmod[skill_id]) / 100 : m_pp.skills[skill_id]); } return 0; }
uint32 GetRawSkill(SkillUseTypes skill_id) const { if (skill_id <= HIGHEST_SKILL) { return(m_pp.skills[skill_id]); } return 0; } uint32 GetRawSkill(SkillUseTypes skill_id) const { if (skill_id <= HIGHEST_SKILL) { return(m_pp.skills[skill_id]); } return 0; }
bool HasSkill(SkillUseTypes skill_id) const; bool HasSkill(SkillUseTypes skill_id) const;
bool CanHaveSkill(SkillUseTypes skill_id) const; bool CanHaveSkill(SkillUseTypes skill_id) const;
void SetSkill(SkillUseTypes skill_num, uint16 value); void SetSkill(SkillUseTypes skill_num, uint16 value);
void AddSkill(SkillUseTypes skillid, uint16 value); void AddSkill(SkillUseTypes skillid, uint16 value);
@ -681,7 +681,7 @@ public:
inline uint16 MaxSkill(SkillUseTypes skillid) const { return MaxSkill(skillid, GetClass(), GetLevel()); } inline uint16 MaxSkill(SkillUseTypes skillid) const { return MaxSkill(skillid, GetClass(), GetLevel()); }
uint8 SkillTrainLevel(SkillUseTypes skillid, uint16 class_); uint8 SkillTrainLevel(SkillUseTypes skillid, uint16 class_);
void TradeskillSearchResults(const char *query, unsigned long qlen, unsigned long objtype, unsigned long someid); void TradeskillSearchResults(const std::string query, unsigned long objtype, unsigned long someid);
void SendTradeskillDetails(uint32 recipe_id); void SendTradeskillDetails(uint32 recipe_id);
bool TradeskillExecute(DBTradeskillRecipe_Struct *spec); bool TradeskillExecute(DBTradeskillRecipe_Struct *spec);
void CheckIncreaseTradeskill(int16 bonusstat, int16 stat_modifier, float skillup_modifier, uint16 success_modifier, SkillUseTypes tradeskill); void CheckIncreaseTradeskill(int16 bonusstat, int16 stat_modifier, float skillup_modifier, uint16 success_modifier, SkillUseTypes tradeskill);
@ -1452,7 +1452,7 @@ private:
unsigned int RestRegenHP; unsigned int RestRegenHP;
unsigned int RestRegenMana; unsigned int RestRegenMana;
unsigned int RestRegenEndurance; unsigned int RestRegenEndurance;
bool EngagedRaidTarget; bool EngagedRaidTarget;
uint32 SavedRaidRestTimer; uint32 SavedRaidRestTimer;

View File

@ -523,7 +523,7 @@ void Client::CompleteConnect()
switch (rank) { switch (rank) {
case 0: { rank = 5; break; } // GUILD_MEMBER 0 case 0: { rank = 5; break; } // GUILD_MEMBER 0
case 1: { rank = 3; break; } // GUILD_OFFICER 1 case 1: { rank = 3; break; } // GUILD_OFFICER 1
case 2: { rank = 1; break; } // GUILD_LEADER 2 case 2: { rank = 1; break; } // GUILD_LEADER 2
default: { break; } // GUILD_NONE default: { break; } // GUILD_NONE
} }
} }
@ -848,7 +848,7 @@ void Client::CompleteConnect()
void Client::CheatDetected(CheatTypes CheatType, float x, float y, float z) void Client::CheatDetected(CheatTypes CheatType, float x, float y, float z)
{ {
//ToDo: Break warp down for special zones. Some zones have special teleportation pads or bad .map files which can trigger the detector without a legit zone request. //ToDo: Break warp down for special zones. Some zones have special teleportation pads or bad .map files which can trigger the detector without a legit zone request.
switch (CheatType) switch (CheatType)
{ {
case MQWarp: //Some zones may still have issues. Database updates will eliminate most if not all problems. case MQWarp: //Some zones may still have issues. Database updates will eliminate most if not all problems.
@ -1304,7 +1304,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
if(strlen(cze->char_name) > 63) if(strlen(cze->char_name) > 63)
return; return;
conn_state = ReceivedZoneEntry; conn_state = ReceivedZoneEntry;
ClientVersion = Connection()->ClientVersion(); ClientVersion = Connection()->ClientVersion();
if (ClientVersion != EQClientUnknown) if (ClientVersion != EQClientUnknown)
@ -1329,7 +1329,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
client_state = CLIENT_KICKED; client_state = CLIENT_KICKED;
return; return;
} }
strcpy(name, cze->char_name); strcpy(name, cze->char_name);
/* Check for Client Spoofing */ /* Check for Client Spoofing */
if (client != 0) { if (client != 0) {
@ -1342,7 +1342,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
client->Disconnect(); client->Disconnect();
} }
uint32 pplen = 0; uint32 pplen = 0;
EQApplicationPacket* outapp = 0; EQApplicationPacket* outapp = 0;
MYSQL_RES* result = 0; MYSQL_RES* result = 0;
bool loaditems = 0; bool loaditems = 0;
@ -1366,8 +1366,8 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
if (lsaccountid && atoi(row[2]) > 0){ lsaccountid = atoi(row[2]); } if (lsaccountid && atoi(row[2]) > 0){ lsaccountid = atoi(row[2]); }
else{ lsaccountid = 0; } else{ lsaccountid = 0; }
gmspeed = atoi(row[3]); gmspeed = atoi(row[3]);
revoked = atoi(row[4]); revoked = atoi(row[4]);
gmhideme = atoi(row[5]); gmhideme = atoi(row[5]);
if (account_creation){ account_creation = atoul(row[6]); } if (account_creation){ account_creation = atoul(row[6]); }
} }
@ -1375,17 +1375,17 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
query = StringFormat("SELECT `lfp`, `lfg`, `xtargets`, `firstlogon`, `guild_id`, `rank` FROM `character_data` LEFT JOIN `guild_members` ON `id` = `char_id` WHERE `id` = %i", cid); query = StringFormat("SELECT `lfp`, `lfg`, `xtargets`, `firstlogon`, `guild_id`, `rank` FROM `character_data` LEFT JOIN `guild_members` ON `id` = `char_id` WHERE `id` = %i", cid);
results = database.QueryDatabase(query); results = database.QueryDatabase(query);
for (auto row = results.begin(); row != results.end(); ++row) { for (auto row = results.begin(); row != results.end(); ++row) {
if (row[4] && atoi(row[4]) > 0){ if (row[4] && atoi(row[4]) > 0){
guild_id = atoi(row[4]); guild_id = atoi(row[4]);
if (row[5] != nullptr){ guildrank = atoi(row[5]); } if (row[5] != nullptr){ guildrank = atoi(row[5]); }
else{ guildrank = GUILD_RANK_NONE; } else{ guildrank = GUILD_RANK_NONE; }
} }
if (LFP){ LFP = atoi(row[0]); } if (LFP){ LFP = atoi(row[0]); }
if (LFG){ LFG = atoi(row[1]); } if (LFG){ LFG = atoi(row[1]); }
if (firstlogon){ firstlogon = atoi(row[3]); } if (firstlogon){ firstlogon = atoi(row[3]); }
} }
if (RuleB(Character, SharedBankPlat)) if (RuleB(Character, SharedBankPlat))
m_pp.platinum_shared = database.GetSharedPlatinum(this->AccountID()); m_pp.platinum_shared = database.GetSharedPlatinum(this->AccountID());
@ -1417,7 +1417,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
/* Set Con State for Reporting */ /* Set Con State for Reporting */
conn_state = PlayerProfileLoaded; conn_state = PlayerProfileLoaded;
m_pp.zone_id = zone->GetZoneID(); m_pp.zone_id = zone->GetZoneID();
m_pp.zoneInstance = zone->GetInstanceID(); m_pp.zoneInstance = zone->GetInstanceID();
/* Set Total Seconds Played */ /* Set Total Seconds Played */
@ -1426,7 +1426,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
max_AAXP = RuleI(AA, ExpPerPoint); max_AAXP = RuleI(AA, ExpPerPoint);
/* If we can maintain intoxication across zones, check for it */ /* If we can maintain intoxication across zones, check for it */
if (!RuleB(Character, MaintainIntoxicationAcrossZones)) if (!RuleB(Character, MaintainIntoxicationAcrossZones))
m_pp.intoxication = 0; m_pp.intoxication = 0;
strcpy(name, m_pp.name); strcpy(name, m_pp.name);
strcpy(lastname, m_pp.last_name); strcpy(lastname, m_pp.last_name);
@ -1435,14 +1435,14 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
m_pp.x = zone->safe_x(); m_pp.x = zone->safe_x();
m_pp.y = zone->safe_y(); m_pp.y = zone->safe_y();
m_pp.z = zone->safe_z(); m_pp.z = zone->safe_z();
} }
/* If too far below ground, then fix */ /* If too far below ground, then fix */
// float ground_z = GetGroundZ(m_pp.x, m_pp.y, m_pp.z); // float ground_z = GetGroundZ(m_pp.x, m_pp.y, m_pp.z);
// if (m_pp.z < (ground_z - 500)) // if (m_pp.z < (ground_z - 500))
// m_pp.z = ground_z; // m_pp.z = ground_z;
/* Set Mob variables for spawn */ /* Set Mob variables for spawn */
class_ = m_pp.class_; class_ = m_pp.class_;
level = m_pp.level; level = m_pp.level;
x_pos = m_pp.x; x_pos = m_pp.x;
y_pos = m_pp.y; y_pos = m_pp.y;
@ -1471,7 +1471,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
/* Load Guild */ /* Load Guild */
if (!IsInAGuild()) { m_pp.guild_id = GUILD_NONE; } if (!IsInAGuild()) { m_pp.guild_id = GUILD_NONE; }
else { else {
m_pp.guild_id = GuildID(); m_pp.guild_id = GuildID();
if (zone->GetZoneID() == RuleI(World, GuildBankZoneID)) if (zone->GetZoneID() == RuleI(World, GuildBankZoneID))
GuildBanker = (guild_mgr.IsGuildLeader(GuildID(), CharacterID()) || guild_mgr.GetBankerFlag(CharacterID())); GuildBanker = (guild_mgr.IsGuildLeader(GuildID(), CharacterID()) || guild_mgr.GetBankerFlag(CharacterID()));
} }
@ -1711,12 +1711,12 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
#endif #endif
/* Reset to max so they dont drown on zone in if its underwater */ /* Reset to max so they dont drown on zone in if its underwater */
m_pp.air_remaining = 60; m_pp.air_remaining = 60;
/* Check for PVP Zone status*/ /* Check for PVP Zone status*/
if (zone->IsPVPZone()) if (zone->IsPVPZone())
m_pp.pvp = 1; m_pp.pvp = 1;
/* Time entitled on Account: Move to account */ /* Time entitled on Account: Move to account */
m_pp.timeentitledonaccount = database.GetTotalTimeEntitledOnAccount(AccountID()) / 1440; m_pp.timeentitledonaccount = database.GetTotalTimeEntitledOnAccount(AccountID()) / 1440;
/* Reset rest timer if the durations have been lowered in the database */ /* Reset rest timer if the durations have been lowered in the database */
if ((m_pp.RestTimer > RuleI(Character, RestRegenTimeToActivate)) && (m_pp.RestTimer > RuleI(Character, RestRegenRaidTimeToActivate))) if ((m_pp.RestTimer > RuleI(Character, RestRegenTimeToActivate)) && (m_pp.RestTimer > RuleI(Character, RestRegenRaidTimeToActivate)))
m_pp.RestTimer = 0; m_pp.RestTimer = 0;
@ -1836,7 +1836,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
QueuePacket(outapp); QueuePacket(outapp);
safe_delete(outapp); safe_delete(outapp);
SetAttackTimer(); SetAttackTimer();
conn_state = ZoneInfoSent; conn_state = ZoneInfoSent;
return; return;
@ -11311,56 +11311,46 @@ void Client::Handle_OP_RecipesFavorite(const EQApplicationPacket *app)
// some_id = 0 if world combiner, item number otherwise // some_id = 0 if world combiner, item number otherwise
// make where clause segment for container(s) // make where clause segment for container(s)
char containers[30]; std::string containers;
if (tsf->some_id == 0) { if (tsf->some_id == 0)
// world combiner so no item number containers += StringFormat(" = %u ", tsf->object_type); // world combiner so no item number
snprintf(containers, 29, "= %u", tsf->object_type); else
} containers += StringFormat(" in (%u, %u) ", tsf->object_type, tsf->some_id); // container in inventory
else {
// container in inventory
snprintf(containers, 29, "in (%u,%u)", tsf->object_type, tsf->some_id);
}
char *query = 0;
char buf[5500]; //gotta be big enough for 500 IDs
std::string favoriteIDs; //gotta be big enough for 500 IDs
bool first = true; bool first = true;
uint16 r;
char *pos = buf;
//Assumes item IDs are <10 characters long //Assumes item IDs are <10 characters long
for (r = 0; r < 500; r++) { for (uint16 favoriteIndex = 0; favoriteIndex < 500; ++favoriteIndex) {
if (tsf->favorite_recipes[r] == 0) if (tsf->favorite_recipes[favoriteIndex] == 0)
continue; continue;
if (first) { if (first) {
pos += snprintf(pos, 10, "%u", tsf->favorite_recipes[r]); favoriteIDs += StringFormat("%u", tsf->favorite_recipes[favoriteIndex]);
first = false; first = false;
} }
else { else
pos += snprintf(pos, 10, ",%u", tsf->favorite_recipes[r]); favoriteIDs += StringFormat(",%u", tsf->favorite_recipes[favoriteIndex]);
}
} }
if (first) //no favorites.... if (first) //no favorites....
return; return;
//To be a good kid, I should move this SQL somewhere else... const std::string query = StringFormat("SELECT tr.id, tr.name, tr.trivial, "
//but im lazy right now, so it stays here "SUM(tre.componentcount), crl.madecount,tr.tradeskill "
uint32 qlen = 0; "FROM tradeskill_recipe AS tr "
qlen = MakeAnyLenString(&query, "SELECT tr.id,tr.name,tr.trivial,SUM(tre.componentcount),crl.madecount,tr.tradeskill " "LEFT JOIN tradeskill_recipe_entries AS tre ON tr.id=tre.recipe_id "
" FROM tradeskill_recipe AS tr " "LEFT JOIN (SELECT recipe_id, madecount "
" LEFT JOIN tradeskill_recipe_entries AS tre ON tr.id=tre.recipe_id " "FROM char_recipe_list "
" LEFT JOIN (SELECT recipe_id, madecount FROM char_recipe_list WHERE char_id = %u) AS crl ON tr.id=crl.recipe_id " "WHERE char_id = %u) AS crl ON tr.id=crl.recipe_id "
" WHERE tr.enabled <> 0 AND tr.id IN (%s) " "WHERE tr.enabled <> 0 AND tr.id IN (%s) "
" AND tr.must_learn & 0x20 <> 0x20 AND ((tr.must_learn & 0x3 <> 0 AND crl.madecount IS NOT NULL) OR (tr.must_learn & 0x3 = 0)) " "AND tr.must_learn & 0x20 <> 0x20 AND "
" GROUP BY tr.id " "((tr.must_learn & 0x3 <> 0 AND crl.madecount IS NOT NULL) "
" HAVING sum(if(tre.item_id %s AND tre.iscontainer > 0,1,0)) > 0 " "OR (tr.must_learn & 0x3 = 0)) "
" LIMIT 100 ", CharacterID(), buf, containers); "GROUP BY tr.id "
"HAVING sum(if(tre.item_id %s AND tre.iscontainer > 0,1,0)) > 0 "
"LIMIT 100 ", CharacterID(), favoriteIDs.c_str(), containers.c_str());
TradeskillSearchResults(query, qlen, tsf->object_type, tsf->some_id); TradeskillSearchResults(query, tsf->object_type, tsf->some_id);
safe_delete_array(query);
return; return;
} }
@ -11389,36 +11379,33 @@ void Client::Handle_OP_RecipesSearch(const EQApplicationPacket *app)
snprintf(containers, 29, "in (%u,%u)", rss->object_type, rss->some_id); snprintf(containers, 29, "in (%u,%u)", rss->object_type, rss->some_id);
} }
char *query = 0; std::string searchClause;
char searchclause[140]; //2X rss->query + SQL crap
//omit the rlike clause if query is empty //omit the rlike clause if query is empty
if (rss->query[0] != 0) { if (rss->query[0] != 0) {
char buf[120]; //larger than 2X rss->query char buf[120]; //larger than 2X rss->query
database.DoEscapeString(buf, rss->query, strlen(rss->query)); database.DoEscapeString(buf, rss->query, strlen(rss->query));
searchClause = StringFormat("name rlike '%s' AND", buf);
snprintf(searchclause, 139, "name rlike '%s' AND", buf);
} }
else {
searchclause[0] = '\0';
}
uint32 qlen = 0;
//arbitrary limit of 200 recipes, makes sense to me. //arbitrary limit of 200 recipes, makes sense to me.
qlen = MakeAnyLenString(&query, "SELECT tr.id,tr.name,tr.trivial,SUM(tre.componentcount),crl.madecount,tr.tradeskill " const std::string query = StringFormat("SELECT tr.id, tr.name, tr.trivial, "
" FROM tradeskill_recipe AS tr " "SUM(tre.componentcount), crl.madecount,tr.tradeskill "
" LEFT JOIN tradeskill_recipe_entries AS tre ON tr.id=tre.recipe_id " "FROM tradeskill_recipe AS tr "
" LEFT JOIN (SELECT recipe_id, madecount FROM char_recipe_list WHERE char_id = %u) AS crl ON tr.id=crl.recipe_id " "LEFT JOIN tradeskill_recipe_entries AS tre ON tr.id = tre.recipe_id "
" WHERE %s tr.trivial >= %u AND tr.trivial <= %u AND tr.enabled <> 0 " "LEFT JOIN (SELECT recipe_id, madecount "
" AND tr.must_learn & 0x20 <> 0x20 AND((tr.must_learn & 0x3 <> 0 AND crl.madecount IS NOT NULL) OR (tr.must_learn & 0x3 = 0)) " "FROM char_recipe_list WHERE char_id = %u) AS crl ON tr.id=crl.recipe_id "
" GROUP BY tr.id " "WHERE %s tr.trivial >= %u AND tr.trivial <= %u AND tr.enabled <> 0 "
" HAVING sum(if(tre.item_id %s AND tre.iscontainer > 0,1,0)) > 0 " "AND tr.must_learn & 0x20 <> 0x20 "
" LIMIT 200 " "AND ((tr.must_learn & 0x3 <> 0 "
, CharacterID(), searchclause, rss->mintrivial, rss->maxtrivial, containers); "AND crl.madecount IS NOT NULL) "
"OR (tr.must_learn & 0x3 = 0)) "
TradeskillSearchResults(query, qlen, rss->object_type, rss->some_id); "GROUP BY tr.id "
"HAVING sum(if(tre.item_id %s AND tre.iscontainer > 0,1,0)) > 0 "
safe_delete_array(query); "LIMIT 200 ",
CharacterID(), searchClause.c_str(),
rss->mintrivial, rss->maxtrivial, containers);
TradeskillSearchResults(query, rss->object_type, rss->some_id);
return; return;
} }

View File

@ -142,7 +142,7 @@ void Object::HandleAugmentation(Client* user, const AugmentItem_Struct* in_augme
// Adding augment // Adding augment
if (in_augment->augment_slot == -1) if (in_augment->augment_slot == -1)
{ {
if (((slot=tobe_auged->AvailableAugmentSlot(auged_with->GetAugmentType()))!=-1) && if (((slot=tobe_auged->AvailableAugmentSlot(auged_with->GetAugmentType()))!=-1) &&
(tobe_auged->AvailableWearSlot(auged_with->GetItem()->Slots))) (tobe_auged->AvailableWearSlot(auged_with->GetItem()->Slots)))
{ {
tobe_auged->PutAugment(slot, *auged_with); tobe_auged->PutAugment(slot, *auged_with);
@ -424,38 +424,28 @@ void Object::HandleAutoCombine(Client* user, const RecipeAutoCombine_Struct* rac
return; return;
} }
//pull the list of components
char errbuf[MYSQL_ERRMSG_SIZE]; std::string query = StringFormat("SELECT tre.item_id, tre.componentcount "
MYSQL_RES *result; "FROM tradeskill_recipe_entries AS tre "
MYSQL_ROW row; "WHERE tre.componentcount > 0 AND tre.recipe_id = %u",
char *query = 0; rac->recipe_id);
auto results = database.QueryDatabase(query);
uint32 qlen = 0; if (!results.Success()) {
uint8 qcount = 0; LogFile->write(EQEMuLog::Error, "Error in HandleAutoCombine query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
//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); user->QueuePacket(outapp);
safe_delete(outapp); safe_delete(outapp);
return; return;
} }
safe_delete_array(query);
qcount = mysql_num_rows(result); if(results.RowCount() < 1) {
if(qcount < 1) {
LogFile->write(EQEMuLog::Error, "Error in HandleAutoCombine: no components returned"); LogFile->write(EQEMuLog::Error, "Error in HandleAutoCombine: no components returned");
user->QueuePacket(outapp); user->QueuePacket(outapp);
safe_delete(outapp); safe_delete(outapp);
return; return;
} }
if(qcount > 10) {
LogFile->write(EQEMuLog::Error, "Error in HandleAutoCombine: too many components returned (%u)", qcount); if(results.RowCount() > 10) {
LogFile->write(EQEMuLog::Error, "Error in HandleAutoCombine: too many components returned (%u)", results.RowCount());
user->QueuePacket(outapp); user->QueuePacket(outapp);
safe_delete(outapp); safe_delete(outapp);
return; return;
@ -466,17 +456,15 @@ void Object::HandleAutoCombine(Client* user, const RecipeAutoCombine_Struct* rac
uint8 counts[10]; uint8 counts[10];
memset(counts, 0, sizeof(counts)); memset(counts, 0, sizeof(counts));
//search for all the items in their inventory //search for all the items in their inventory
Inventory& user_inv = user->GetInv(); Inventory& user_inv = user->GetInv();
uint8 count = 0; uint8 count = 0;
uint8 needcount = 0; uint8 needcount = 0;
uint8 r,k;
std::list<int> MissingItems; std::list<int> MissingItems;
for(r = 0; r < qcount; r++) { uint8 needItemIndex = 0;
row = mysql_fetch_row(result); for (auto row = results.begin(); row != results.end(); ++row, ++needItemIndex) {
uint32 item = (uint32)atoi(row[0]); uint32 item = (uint32)atoi(row[0]);
uint8 num = (uint8) atoi(row[1]); uint8 num = (uint8) atoi(row[1]);
@ -491,10 +479,9 @@ void Object::HandleAutoCombine(Client* user, const RecipeAutoCombine_Struct* rac
MissingItems.push_back(item); MissingItems.push_back(item);
//dont start deleting anything until we have found it all. //dont start deleting anything until we have found it all.
items[r] = item; items[needItemIndex] = item;
counts[r] = num; counts[needItemIndex] = num;
} }
mysql_free_result(result);
//make sure we found it all... //make sure we found it all...
if(count != needcount) if(count != needcount)
@ -520,12 +507,12 @@ void Object::HandleAutoCombine(Client* user, const RecipeAutoCombine_Struct* rac
//remove all the items from the players inventory, with updates... //remove all the items from the players inventory, with updates...
int16 slot; int16 slot;
for(r = 0; r < qcount; r++) { for(uint8 r = 0; r < results.RowCount(); r++) {
if(items[r] == 0 || counts[r] == 0) if(items[r] == 0 || counts[r] == 0)
continue; //skip empties, could prolly break here continue; //skip empties, could prolly break here
//we have to loop here to delete 1 at a time in case its in multiple stacks. //we have to loop here to delete 1 at a time in case its in multiple stacks.
for(k = 0; k < counts[r]; k++) { for(uint8 k = 0; k < counts[r]; k++) {
slot = user_inv.HasItem(items[r], 1, invWherePersonal); slot = user_inv.HasItem(items[r], 1, invWherePersonal);
if (slot == INVALID_INDEX) { if (slot == INVALID_INDEX) {
//WTF... I just checked this above, but just to be sure... //WTF... I just checked this above, but just to be sure...
@ -539,19 +526,14 @@ void Object::HandleAutoCombine(Client* user, const RecipeAutoCombine_Struct* rac
const ItemInst* inst = user_inv.GetItem(slot); const ItemInst* inst = user_inv.GetItem(slot);
if (inst && !inst->IsStackable()) if (inst && !inst->IsStackable())
{
user->DeleteItemInInventory(slot, 0, true); user->DeleteItemInInventory(slot, 0, true);
}
else else
{
user->DeleteItemInInventory(slot, 1, true); user->DeleteItemInInventory(slot, 1, true);
}
} }
} }
//otherwise, we found it all... //otherwise, we found it all...
outp->reply_code = 0x00000000; //success for finding it... outp->reply_code = 0x00000000; //success for finding it...
user->QueuePacket(outapp); user->QueuePacket(outapp);
safe_delete(outapp); safe_delete(outapp);
@ -654,34 +636,26 @@ SkillUseTypes Object::TypeToSkill(uint32 type)
return TradeskillUnknown; return TradeskillUnknown;
} }
void Client::TradeskillSearchResults(const char *query, unsigned long qlen, unsigned long objtype, unsigned long someid) { void Client::TradeskillSearchResults(const std::string query, unsigned long objtype, unsigned long someid) {
char errbuf[MYSQL_ERRMSG_SIZE]; auto results = database.QueryDatabase(query);
MYSQL_RES *result; if (!results.Success()) {
MYSQL_ROW row; LogFile->write(EQEMuLog::Error, "Error in TradeskillSearchResults query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
if (!database.RunQuery(query, qlen, errbuf, &result)) {
LogFile->write(EQEMuLog::Error, "Error in TradeskillSearchResults query '%s': %s", query, errbuf);
return; return;
} }
uint8 qcount = 0; if(results.RowCount() < 1)
return; //search gave no results... not an error
qcount = mysql_num_rows(result); if(results.ColumnCount() != 6) {
if(qcount < 1) { LogFile->write(EQEMuLog::Error, "Error in TradeskillSearchResults query '%s': Invalid column count in result", query.c_str());
//search gave no results... not an error
return;
}
if(mysql_num_fields(result) != 6) {
LogFile->write(EQEMuLog::Error, "Error in TradeskillSearchResults query '%s': Invalid column count in result", query);
return; return;
} }
uint8 r; for(auto row = results.begin(); row != results.end(); ++row) {
for(r = 0; r < qcount; r++) {
row = mysql_fetch_row(result);
if(row == nullptr || row[0] == nullptr || row[1] == nullptr || row[2] == nullptr || row[3] == nullptr || row[5] == nullptr) if(row == nullptr || row[0] == nullptr || row[1] == nullptr || row[2] == nullptr || row[3] == nullptr || row[5] == nullptr)
continue; continue;
uint32 recipe = (uint32)atoi(row[0]); uint32 recipe = (uint32)atoi(row[0]);
const char *name = row[1]; const char *name = row[1];
uint32 trivial = (uint32) atoi(row[2]); uint32 trivial = (uint32) atoi(row[2]);
@ -691,14 +665,10 @@ void Client::TradeskillSearchResults(const char *query, unsigned long qlen, unsi
// Skip the recipes that exceed the threshold in skill difference // Skip the recipes that exceed the threshold in skill difference
// Recipes that have either been made before or were // Recipes that have either been made before or were
// explicitly learned are excempt from that limit // explicitly learned are excempt from that limit
if (RuleB(Skills, UseLimitTradeskillSearchSkillDiff)) { if (RuleB(Skills, UseLimitTradeskillSearchSkillDiff)
if (((int32)trivial - (int32)GetSkill((SkillUseTypes)tradeskill)) > RuleI(Skills, MaxTradeskillSearchSkillDiff) && ((int32)trivial - (int32)GetSkill((SkillUseTypes)tradeskill)) > RuleI(Skills, MaxTradeskillSearchSkillDiff)
&& row[4] == nullptr) && row[4] == nullptr)
{
continue; continue;
}
}
EQApplicationPacket* outapp = new EQApplicationPacket(OP_RecipeReply, sizeof(RecipeReply_Struct)); EQApplicationPacket* outapp = new EQApplicationPacket(OP_RecipeReply, sizeof(RecipeReply_Struct));
RecipeReply_Struct *reply = (RecipeReply_Struct *) outapp->pBuffer; RecipeReply_Struct *reply = (RecipeReply_Struct *) outapp->pBuffer;
@ -711,39 +681,30 @@ void Client::TradeskillSearchResults(const char *query, unsigned long qlen, unsi
strn0cpy(reply->recipe_name, name, sizeof(reply->recipe_name)); strn0cpy(reply->recipe_name, name, sizeof(reply->recipe_name));
FastQueuePacket(&outapp); FastQueuePacket(&outapp);
} }
mysql_free_result(result);
} }
void Client::SendTradeskillDetails(uint32 recipe_id) { void Client::SendTradeskillDetails(uint32 recipe_id) {
char errbuf[MYSQL_ERRMSG_SIZE]; //pull the list of components
MYSQL_RES *result; std::string query = StringFormat("SELECT tre.item_id,tre.componentcount,i.icon,i.Name "
MYSQL_ROW row; "FROM tradeskill_recipe_entries AS tre "
char *query = 0; "LEFT JOIN items AS i ON tre.item_id = i.id "
"WHERE tre.componentcount > 0 AND tre.recipe_id = %u",
uint32 qlen = 0; recipe_id);
uint8 qcount = 0; auto results = database.QueryDatabase(query);
if (!results.Success()) {
//pull the list of components LogFile->write(EQEMuLog::Error, "Error in SendTradeskillDetails query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
qlen = MakeAnyLenString(&query, "SELECT tre.item_id,tre.componentcount,i.icon,i.Name "
" FROM tradeskill_recipe_entries AS tre "
" LEFT JOIN items AS i ON tre.item_id = i.id "
" WHERE tre.componentcount > 0 AND tre.recipe_id=%u", recipe_id);
if (!database.RunQuery(query, qlen, errbuf, &result)) {
LogFile->write(EQEMuLog::Error, "Error in SendTradeskillDetails query '%s': %s", query, errbuf);
safe_delete_array(query);
return; return;
} }
safe_delete_array(query);
qcount = mysql_num_rows(result); if(results.RowCount() < 1) {
if(qcount < 1) {
LogFile->write(EQEMuLog::Error, "Error in SendTradeskillDetails: no components returned"); LogFile->write(EQEMuLog::Error, "Error in SendTradeskillDetails: no components returned");
return; return;
} }
if(qcount > 10) {
LogFile->write(EQEMuLog::Error, "Error in SendTradeskillDetails: too many components returned (%u)", qcount); if(results.RowCount() > 10) {
LogFile->write(EQEMuLog::Error, "Error in SendTradeskillDetails: too many components returned (%u)", results.RowCount());
return; return;
} }
@ -773,20 +734,18 @@ void Client::SendTradeskillDetails(uint32 recipe_id) {
uint32 len; uint32 len;
uint32 datalen = 0; uint32 datalen = 0;
uint8 count = 0; uint8 count = 0;
for(r = 0; r < qcount; r++) {
row = mysql_fetch_row(result); for(auto row = results.begin(); row != results.end(); ++row) {
//watch for references to items which are not in the //watch for references to items which are not in the
//items table, which the left join will make nullptr... //items table, which the left join will make nullptr...
if(row[2] == nullptr || row[3] == nullptr) { if(row[2] == nullptr || row[3] == nullptr)
continue; continue;
}
uint32 item = (uint32)atoi(row[0]); uint32 item = (uint32)atoi(row[0]);
uint8 num = (uint8) atoi(row[1]); uint8 num = (uint8) atoi(row[1]);
uint32 icon = (uint32) atoi(row[2]); uint32 icon = (uint32) atoi(row[2]);
const char *name = row[3]; const char *name = row[3];
len = strlen(name); len = strlen(name);
if(len > 63) if(len > 63)
@ -816,7 +775,6 @@ void Client::SendTradeskillDetails(uint32 recipe_id) {
} }
} }
mysql_free_result(result);
//now move the item data over top of the FFFFs //now move the item data over top of the FFFFs
uint8 dist = sizeof(uint32) * (10 - count); uint8 dist = sizeof(uint32) * (10 - count);
@ -1094,7 +1052,7 @@ bool Client::TradeskillExecute(DBTradeskillRecipe_Struct *spec) {
++itr; ++itr;
} }
return(true); return(true);
} }
/* Tradeskill Fail */ /* Tradeskill Fail */
else { else {
success_modifier = 2; // Halves the chance success_modifier = 2; // Halves the chance
@ -1172,7 +1130,7 @@ void Client::CheckIncreaseTradeskill(int16 bonusstat, int16 stat_modifier, float
chance_stage2 = 12.5 - (.08 * (current_raw_skill - 175)); chance_stage2 = 12.5 - (.08 * (current_raw_skill - 175));
} }
} }
chance_stage2 = mod_tradeskill_skillup(chance_stage2); chance_stage2 = mod_tradeskill_skillup(chance_stage2);
if (chance_stage2 > MakeRandomFloat(0, 99)) { if (chance_stage2 > MakeRandomFloat(0, 99)) {
@ -1191,249 +1149,212 @@ void Client::CheckIncreaseTradeskill(int16 bonusstat, int16 stat_modifier, float
bool ZoneDatabase::GetTradeRecipe(const ItemInst* container, uint8 c_type, uint32 some_id, bool ZoneDatabase::GetTradeRecipe(const ItemInst* container, uint8 c_type, uint32 some_id,
uint32 char_id, DBTradeskillRecipe_Struct *spec) uint32 char_id, DBTradeskillRecipe_Struct *spec)
{ {
char errbuf[MYSQL_ERRMSG_SIZE];
MYSQL_RES *result;
MYSQL_ROW row;
char *query = 0;
char buf2[4096];
uint32 sum = 0; std::string containers;// make where clause segment for container(s)
uint32 count = 0; if (some_id == 0)
uint32 qcount = 0; containers = StringFormat("= %u", c_type); // world combiner so no item number
uint32 qlen = 0; else
containers = StringFormat("IN (%u,%u)", c_type, some_id); // container in inventory
// 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... //Could prolly watch for stacks in this loop and handle them properly...
//just increment sum and count accordingly //just increment sum and count accordingly
bool first = true; bool first = true;
uint8 i; std::string buf2;
char *pos = buf2; uint32 count = 0;
for (i = 0; i < 10; i++) { // <watch> TODO: need to determine if this is bound to world/item container size uint32 sum = 0;
for (uint8 i = 0; i < 10; i++) { // <watch> TODO: need to determine if this is bound to world/item container size
const ItemInst* inst = container->GetItem(i); const ItemInst* inst = container->GetItem(i);
if (inst) { if (!inst)
const Item_Struct* item = GetItem(inst->GetItem()->ID); continue;
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) { const Item_Struct* item = GetItem(inst->GetItem()->ID);
return(false); //no items == no recipe if (!item)
continue;
if(first) {
buf2 += StringFormat("%d", item->ID);
first = false;
} else
buf2 += StringFormat(",%d", item->ID);
sum += item->ID;
count++;
} }
qlen = MakeAnyLenString(&query, "SELECT tre.recipe_id " if(count == 0)
" FROM tradeskill_recipe_entries AS tre" return false; //no items == no recipe
" INNER JOIN tradeskill_recipe AS tr ON (tre.recipe_id = tr.id) "
" WHERE tr.enabled AND (( 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)) { std::string query = StringFormat("SELECT tre.recipe_id "
LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe search, query: %s", query); "FROM tradeskill_recipe_entries AS tre "
safe_delete_array(query); "INNER JOIN tradeskill_recipe AS tr ON (tre.recipe_id = tr.id) "
LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe search, error: %s", errbuf); "WHERE tr.enabled AND (( tre.item_id IN(%s) AND tre.componentcount > 0) "
return(false); "OR ( tre.item_id %s AND tre.iscontainer=1 ))"
} "GROUP BY tre.recipe_id HAVING sum(tre.componentcount) = %u "
safe_delete_array(query); "AND sum(tre.item_id * tre.componentcount) = %u",
buf2.c_str(), containers.c_str(), count, sum);
qcount = mysql_num_rows(result); auto results = QueryDatabase(query);
if(qcount > 1) { if (!results.Success()) {
//multiple recipes, partial match... do an extra query to get it exact. LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe search, query: %s", query.c_str());
//this happens when combining components for a smaller recipe LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe search, error: %s", results.ErrorMessage().c_str());
//which is completely contained within another recipe return false;
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 limit on buf2
if(i == 214) { //Maximum number of recipe matches (19 * 215 = 4096)
LogFile->write(EQEMuLog::Error, "GetTradeRecipe warning: Too many matches. Unable to search all recipe entries. Searched %u of %u possible entries.", i + 1, qcount);
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 (results.RowCount() > 1) {
return(false); //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;
uint32 index = 0;
buf2 = "";
for (auto row = results.begin(); row != results.end(); ++row, ++index) {
uint32 recipeid = (uint32)atoi(row[0]);
if(first) {
buf2 += StringFormat("%u", recipeid);
first = false;
} else
buf2 += StringFormat(",%u", recipeid);
if(qcount > 1) //length limit on buf2
{ if(index == 214) { //Maximum number of recipe matches (19 * 215 = 4096)
LogFile->write(EQEMuLog::Error, "GetTradeRecipe warning: Too many matches. Unable to search all recipe entries. Searched %u of %u possible entries.", index + 1, results.RowCount());
break;
}
}
query = StringFormat("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.c_str(), count, sum);
auto results = QueryDatabase(query);
if (!results.Success()) {
LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe, re-query: %s", query.c_str());
LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe, error: %s", results.ErrorMessage().c_str());
return false;
}
}
if (results.RowCount() < 1)
return false;
if(results.RowCount() > 1) {
//The recipe is not unique, so we need to compare the container were using. //The recipe is not unique, so we need to compare the container were using.
uint32 containerId = 0; uint32 containerId = 0;
if(some_id) { //Standard container if(some_id) //Standard container
containerId = some_id; containerId = some_id;
} else if(c_type)//World container
else if(c_type) { //World container
containerId = c_type; containerId = c_type;
} else //Invalid container
else { //Invalid container return false;
return(false);
query = StringFormat("SELECT tre.recipe_id "
"FROM tradeskill_recipe_entries AS tre "
"WHERE tre.recipe_id IN (%s) "
"AND tre.item_id = %u;", buf2.c_str(), containerId);
results = QueryDatabase(query);
if (!results.Success()) {
LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe, re-query: %s", query.c_str());
LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe, error: %s", results.ErrorMessage().c_str());
return false;
} }
qlen = MakeAnyLenString(&query,"SELECT tre.recipe_id FROM tradeskill_recipe_entries as tre WHERE tre.recipe_id IN (%s)" if(results.RowCount() == 0) { //Recipe contents matched more than 1 recipe, but not in this container
" AND tre.item_id = %u;",buf2,containerId);
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);
uint32 resultRowTotal = mysql_num_rows(result);
if(resultRowTotal == 0) { //Recipe contents matched more than 1 recipe, but not in this container
LogFile->write(EQEMuLog::Error, "Combine error: Incorrect container is being used!"); LogFile->write(EQEMuLog::Error, "Combine error: Incorrect container is being used!");
return(false); return false;
}
if(resultRowTotal > 1) { //Recipe contents matched more than 1 recipe in this container
LogFile->write(EQEMuLog::Error, "Combine error: Recipe is not unique! %u matches found for container %u. Continuing with first recipe match.", resultRowTotal, containerId);
} }
if (results.RowCount() > 1) //Recipe contents matched more than 1 recipe in this container
LogFile->write(EQEMuLog::Error, "Combine error: Recipe is not unique! %u matches found for container %u. Continuing with first recipe match.", results.RowCount(), containerId);
} }
row = mysql_fetch_row(result); auto row = results.begin();
uint32 recipe_id = (uint32)atoi(row[0]); uint32 recipe_id = (uint32)atoi(row[0]);
mysql_free_result(result);
//Right here we verify that we actually have ALL of the tradeskill components.. //Right here we verify that we actually have ALL of the tradeskill components..
//instead of part which is possible with experimentation. //instead of part which is possible with experimentation.
//This is here because something's up with the query above.. it needs to be rethought out //This is here because something's up with the query above.. it needs to be rethought out
bool has_components = true; bool has_components = true;
char TSerrbuf[MYSQL_ERRMSG_SIZE]; query = StringFormat("SELECT item_id, componentcount "
char *TSquery = 0; "FROM tradeskill_recipe_entries "
MYSQL_RES *TSresult; "WHERE recipe_id = %i AND componentcount > 0",
MYSQL_ROW TSrow; recipe_id);
if (RunQuery(TSquery, MakeAnyLenString(&TSquery, "SELECT item_id, componentcount from tradeskill_recipe_entries where recipe_id=%i AND componentcount > 0", recipe_id), TSerrbuf, &TSresult)) { results = QueryDatabase(query);
while((TSrow = mysql_fetch_row(TSresult))!=nullptr) { if (!results.Success()) {
int ccnt = 0; LogFile->write(EQEMuLog::Error, "Error in tradeskill verify query: '%s': %s", query.c_str(), results.ErrorMessage().c_str());
for(int x = MAIN_BEGIN; x < EmuConstants::MAP_WORLD_SIZE; x++) { return GetTradeRecipe(recipe_id, c_type, some_id, char_id, spec);
const ItemInst* inst = container->GetItem(x); }
if(inst){
const Item_Struct* item = GetItem(inst->GetItem()->ID);
if (item) {
if(item->ID == atoi(TSrow[0])){
ccnt++;
}
}
}
}
if(ccnt != atoi(TSrow[1]))
has_components = false;
}
mysql_free_result(TSresult);
} else {
LogFile->write(EQEMuLog::Error, "Error in tradeskill verify query: '%s': %s", TSquery, TSerrbuf);
}
safe_delete_array(TSquery);
if(has_components == false){
return false; if (results.RowCount() == 0)
} return GetTradeRecipe(recipe_id, c_type, some_id, char_id, spec);
return(GetTradeRecipe(recipe_id, c_type, some_id, char_id, spec)); for (auto row = results.begin(); row != results.end(); ++row) {
int ccnt = 0;
for(int x = MAIN_BEGIN; x < EmuConstants::MAP_WORLD_SIZE; x++) {
const ItemInst* inst = container->GetItem(x);
if(!inst)
continue;
const Item_Struct* item = GetItem(inst->GetItem()->ID);
if (!item)
continue;
if(item->ID == atoi(row[0]))
ccnt++;
}
if(ccnt != atoi(row[1]))
return false;
}
return GetTradeRecipe(recipe_id, c_type, some_id, char_id, spec);
} }
bool ZoneDatabase::GetTradeRecipe(uint32 recipe_id, uint8 c_type, uint32 some_id, bool ZoneDatabase::GetTradeRecipe(uint32 recipe_id, uint8 c_type, uint32 some_id,
uint32 char_id, DBTradeskillRecipe_Struct *spec) uint32 char_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) // make where clause segment for container(s)
char containers[30]; std::string containers;
if (some_id == 0) { if (some_id == 0)
// world combiner so no item number containers = StringFormat("= %u", c_type); // world combiner so no item number
snprintf(containers,29, "= %u", c_type); else
} else { containers = StringFormat("IN (%u,%u)", c_type, some_id); // container in inventory
// container in inventory
snprintf(containers,29, "in (%u,%u)", c_type, some_id); std::string query = StringFormat("SELECT tr.id, tr.tradeskill, tr.skillneeded, "
"tr.trivial, tr.nofail, tr.replace_container, "
"tr.name, tr.must_learn, tr.quest, crl.madecount "
"FROM tradeskill_recipe AS tr "
"INNER JOIN tradeskill_recipe_entries AS tre "
"ON tr.id = tre.recipe_id "
"LEFT JOIN (SELECT recipe_id, madecount "
"FROM char_recipe_list WHERE char_id = %u) AS crl "
"ON tr.id = crl.recipe_id "
"WHERE tr.id = %lu AND tre.item_id %s AND tr.enabled "
"GROUP BY tr.id",
char_id, (unsigned long)recipe_id, containers.c_str());
auto results = QueryDatabase(query);
if (!results.Success()) {
LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe, query: %s", query.c_str());
LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe, error: %s", results.ErrorMessage().c_str());
return false;
} }
qlen = MakeAnyLenString(&query, "SELECT tr.id, tr.tradeskill, tr.skillneeded," if(results.RowCount() != 1)
" tr.trivial, tr.nofail, tr.replace_container, tr.name, tr.must_learn, tr.quest, crl.madecount" return false;//just not found i guess..
" FROM tradeskill_recipe AS tr inner join tradeskill_recipe_entries as tre"
" ON tr.id = tre.recipe_id"
" LEFT JOIN (SELECT recipe_id, madecount from char_recipe_list WHERE char_id = %u) AS crl "
" ON tr.id = crl.recipe_id "
" WHERE tr.id = %lu AND tre.item_id %s AND tr.enabled "
" GROUP BY tr.id", char_id, (unsigned long)recipe_id, containers);
if (!RunQuery(query, qlen, errbuf, &result)) { auto row = results.begin();
LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe, query: %s", query); spec->tradeskill = (SkillUseTypes)atoi(row[1]);
safe_delete_array(query); spec->skill_needed = (int16)atoi(row[2]);
LogFile->write(EQEMuLog::Error, "Error in GetTradeRecipe, error: %s", errbuf); spec->trivial = (uint16)atoi(row[3]);
return(false); spec->nofail = atoi(row[4]) ? true : 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 = (SkillUseTypes)atoi(row[1]);
spec->skill_needed = (int16)atoi(row[2]);
spec->trivial = (uint16)atoi(row[3]);
spec->nofail = atoi(row[4]) ? true : false;
spec->replace_container = atoi(row[5]) ? true : false; spec->replace_container = atoi(row[5]) ? true : false;
spec->name = row[6]; spec->name = row[6];
spec->must_learn = (uint8)atoi(row[7]); spec->must_learn = (uint8)atoi(row[7]);
spec->quest = atoi(row[8]) ? true : false; spec->quest = atoi(row[8]) ? true : false;
if (row[9] == nullptr) { if (row[9] == nullptr) {
spec->has_learnt = false; spec->has_learnt = false;
spec->madecount = 0; spec->madecount = 0;
@ -1442,141 +1363,109 @@ bool ZoneDatabase::GetTradeRecipe(uint32 recipe_id, uint8 c_type, uint32 some_id
spec->madecount = (uint32)atoul(row[9]); spec->madecount = (uint32)atoul(row[9]);
} }
spec->recipe_id = recipe_id; spec->recipe_id = recipe_id;
mysql_free_result(result);
//Pull the on-success items... //Pull the on-success items...
qlen = MakeAnyLenString(&query, "SELECT item_id,successcount FROM tradeskill_recipe_entries" query = StringFormat("SELECT item_id,successcount FROM tradeskill_recipe_entries "
" WHERE successcount>0 AND recipe_id=%u", recipe_id); "WHERE successcount > 0 AND recipe_id = %u", recipe_id);
results = QueryDatabase(query);
if (!RunQuery(query, qlen, errbuf, &result)) { if (!results.Success()) {
LogFile->write(EQEMuLog::Error, "Error in GetTradeRecept success query '%s': %s", query, errbuf); LogFile->write(EQEMuLog::Error, "Error in GetTradeRecept success query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
safe_delete_array(query); return false;
return(false);
} }
safe_delete_array(query);
qcount = mysql_num_rows(result); if(results.RowCount() < 1) {
if(qcount < 1) {
LogFile->write(EQEMuLog::Error, "Error in GetTradeRecept success: no success items returned"); LogFile->write(EQEMuLog::Error, "Error in GetTradeRecept success: no success items returned");
return(false); return false;
} }
uint8 r;
spec->onsuccess.clear(); spec->onsuccess.clear();
for(r = 0; r < qcount; r++) { for(auto row = results.begin(); row != results.end(); ++row) {
row = mysql_fetch_row(result);
uint32 item = (uint32)atoi(row[0]); uint32 item = (uint32)atoi(row[0]);
uint8 num = (uint8) atoi(row[1]); uint8 num = (uint8) atoi(row[1]);
spec->onsuccess.push_back(std::pair<uint32,uint8>(item, num)); spec->onsuccess.push_back(std::pair<uint32,uint8>(item, num));
} }
mysql_free_result(result);
spec->onfail.clear();
//Pull the on-fail items... //Pull the on-fail items...
qlen = MakeAnyLenString(&query, "SELECT item_id,failcount FROM tradeskill_recipe_entries" query = StringFormat("SELECT item_id, failcount FROM tradeskill_recipe_entries "
" WHERE failcount>0 AND recipe_id=%u", recipe_id); "WHERE failcount > 0 AND recipe_id = %u", recipe_id);
results = QueryDatabase(query);
spec->onfail.clear(); if (results.Success())
if (RunQuery(query, qlen, errbuf, &result)) { for(auto row = results.begin(); row != results.end(); ++row) {
qcount = mysql_num_rows(result);
uint8 r;
for(r = 0; r < qcount; r++) {
row = mysql_fetch_row(result);
uint32 item = (uint32)atoi(row[0]); uint32 item = (uint32)atoi(row[0]);
uint8 num = (uint8) atoi(row[1]); uint8 num = (uint8) atoi(row[1]);
spec->onfail.push_back(std::pair<uint32,uint8>(item, num)); spec->onfail.push_back(std::pair<uint32,uint8>(item, num));
} }
mysql_free_result(result);
} spec->salvage.clear();
// Don't bother with the query if TS is nofail
if (spec->nofail)
return true;
// Pull the salvage list // Pull the salvage list
qlen = MakeAnyLenString(&query, "SELECT item_id,salvagecount FROM tradeskill_recipe_entries WHERE salvagecount>0 AND recipe_id=%u", recipe_id); query = StringFormat("SELECT item_id, salvagecount "
"FROM tradeskill_recipe_entries "
spec->salvage.clear(); "WHERE salvagecount > 0 AND recipe_id = %u", recipe_id);
// Don't bother with the query if TS is nofail results = QueryDatabase(query);
if (!spec->nofail && RunQuery(query, qlen, errbuf, &result)) { if (results.Success())
qcount = mysql_num_rows(result); for(auto row = results.begin(); row != results.begin(); ++row) {
uint8 r;
for(r = 0; r < qcount; r++) {
row = mysql_fetch_row(result);
uint32 item = (uint32)atoi(row[0]); uint32 item = (uint32)atoi(row[0]);
uint8 num = (uint8)atoi(row[1]); uint8 num = (uint8)atoi(row[1]);
spec->salvage.push_back(std::pair<uint32,uint8>(item, num)); spec->salvage.push_back(std::pair<uint32,uint8>(item, num));
} }
mysql_free_result(result);
}
safe_delete_array(query); return true;
return(true);
} }
void ZoneDatabase::UpdateRecipeMadecount(uint32 recipe_id, uint32 char_id, uint32 madecount) void ZoneDatabase::UpdateRecipeMadecount(uint32 recipe_id, uint32 char_id, uint32 madeCount)
{ {
char *query = 0; std::string query = StringFormat("INSERT INTO char_recipe_list "
uint32 qlen; "SET recipe_id = %u, char_id = %u, madecount = %u "
char errbuf[MYSQL_ERRMSG_SIZE]; "ON DUPLICATE KEY UPDATE madecount = %u;",
recipe_id, char_id, madeCount, madeCount);
qlen = MakeAnyLenString(&query, "INSERT INTO char_recipe_list " auto results = QueryDatabase(query);
" SET recipe_id = %u, char_id = %u, madecount = %u " if (!results.Success())
" ON DUPLICATE KEY UPDATE madecount = %u;" LogFile->write(EQEMuLog::Error, "Error in UpdateRecipeMadecount query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
, recipe_id, char_id, madecount, madecount);
if (!RunQuery(query, qlen, errbuf)) {
LogFile->write(EQEMuLog::Error, "Error in UpdateRecipeMadecount query '%s': %s", query, errbuf);
}
safe_delete_array(query);
} }
void Client::LearnRecipe(uint32 recipeID) void Client::LearnRecipe(uint32 recipeID)
{ {
char *query = 0; std::string query = StringFormat("SELECT tr.name, crl.madecount "
uint32 qlen; "FROM tradeskill_recipe AS tr "
uint32 qcount = 0; "LEFT JOIN (SELECT recipe_id, madecount "
char errbuf[MYSQL_ERRMSG_SIZE]; "FROM char_recipe_list WHERE char_id = %u) AS crl "
MYSQL_RES *result; "ON tr.id = crl.recipe_id "
MYSQL_ROW row; "WHERE tr.id = %u ;", CharacterID(), recipeID);
auto results = database.QueryDatabase(query);
qlen = MakeAnyLenString(&query, "SELECT tr.name, crl.madecount " if (!results.Success()) {
" FROM tradeskill_recipe as tr " LogFile->write(EQEMuLog::Error, "Error in Client::LearnRecipe query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
" LEFT JOIN (SELECT recipe_id, madecount FROM char_recipe_list WHERE char_id = %u) AS crl "
" ON tr.id = crl.recipe_id "
" WHERE tr.id = %u ;", CharacterID(), recipeID);
if (!database.RunQuery(query, qlen, errbuf, &result)) {
LogFile->write(EQEMuLog::Error, "Error in Client::LearnRecipe query '%s': %s", query, errbuf);
safe_delete_array(query);
return; return;
} }
qcount = mysql_num_rows(result); if (results.RowCount() != 1) {
if (qcount != 1) { LogFile->write(EQEMuLog::Normal, "Client::LearnRecipe - RecipeID: %d had %d occurences.", recipeID, results.RowCount());
LogFile->write(EQEMuLog::Normal, "Client::LearnRecipe - RecipeID: %d had %d occurences.", recipeID, qcount);
mysql_free_result(result);
safe_delete_array(query);
return; return;
} }
safe_delete_array(query);
row = mysql_fetch_row(result); auto row = results.begin();
if (row != nullptr && row[0] != nullptr) { if (row[0] == nullptr)
// Only give Learn message if character doesn't know the recipe return;
if (row[1] == nullptr) {
Message_StringID(4, TRADESKILL_LEARN_RECIPE, row[0]);
// Actually learn the recipe now
qlen = MakeAnyLenString(&query, "INSERT INTO char_recipe_list "
" SET recipe_id = %u, char_id = %u, madecount = 0 "
" ON DUPLICATE KEY UPDATE madecount = madecount;"
, recipeID, CharacterID());
if (!database.RunQuery(query, qlen, errbuf)) { // Only give Learn message if character doesn't know the recipe
LogFile->write(EQEMuLog::Error, "Error in LearnRecipe query '%s': %s", query, errbuf); if (row[1] != nullptr)
} return;
safe_delete_array(query);
}
}
mysql_free_result(result); Message_StringID(4, TRADESKILL_LEARN_RECIPE, row[0]);
// Actually learn the recipe now
query = StringFormat("INSERT INTO char_recipe_list "
"SET recipe_id = %u, char_id = %u, madecount = 0 "
"ON DUPLICATE KEY UPDATE madecount = madecount;",
recipeID, CharacterID());
results = database.QueryDatabase(query);
if (!results.Success())
LogFile->write(EQEMuLog::Error, "Error in LearnRecipe query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
} }
@ -1622,33 +1511,22 @@ bool Client::CanIncreaseTradeskill(SkillUseTypes tradeskill) {
bool ZoneDatabase::EnableRecipe(uint32 recipe_id) bool ZoneDatabase::EnableRecipe(uint32 recipe_id)
{ {
char *query = 0; std::string query = StringFormat("UPDATE tradeskill_recipe SET enabled = 1 "
uint32 qlen; "WHERE id = %u;", recipe_id);
char errbuf[MYSQL_ERRMSG_SIZE]; auto results = QueryDatabase(query);
uint32 affected_rows = 0; if (!results.Success())
LogFile->write(EQEMuLog::Error, "Error in EnableRecipe query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
qlen = MakeAnyLenString(&query, "UPDATE tradeskill_recipe SET enabled = 1 WHERE id = %u;", recipe_id); return results.RowsAffected() > 0;
if (!RunQuery(query, qlen, errbuf, 0, &affected_rows)) {
LogFile->write(EQEMuLog::Error, "Error in EnableRecipe query '%s': %s", query, errbuf);
}
safe_delete_array(query);
return (affected_rows > 0);
} }
bool ZoneDatabase::DisableRecipe(uint32 recipe_id) bool ZoneDatabase::DisableRecipe(uint32 recipe_id)
{ {
char *query = 0; std::string query = StringFormat("UPDATE tradeskill_recipe SET enabled = 0 "
uint32 qlen; "WHERE id = %u;", recipe_id);
char errbuf[MYSQL_ERRMSG_SIZE]; auto results = QueryDatabase(query);
uint32 affected_rows = 0; if (!results.Success())
LogFile->write(EQEMuLog::Error, "Error in DisableRecipe query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
qlen = MakeAnyLenString(&query, "UPDATE tradeskill_recipe SET enabled = 0 WHERE id = %u;", recipe_id); return results.RowsAffected() > 0;
if (!RunQuery(query, qlen, errbuf, 0, &affected_rows)) {
LogFile->write(EQEMuLog::Error, "Error in DisableRecipe query '%s': %s", query, errbuf);
}
safe_delete_array(query);
return (affected_rows > 0);
} }