/* EQEMu: Everquest Server Emulator Copyright (C) 2001-2006 EQEMu Development Team (http://eqemulator.net) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY except by those people which sell it, which are required to give you total support for your newly bought product; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "worlddb.h" //#include "../common/item.h" #include "../common/string_util.h" #include "../common/eq_packet_structs.h" #include "../common/item.h" #include "../common/dbasync.h" #include "../common/rulesys.h" #include #include #include #include "sof_char_create_data.h" WorldDatabase database; extern std::vector character_create_allocations; extern std::vector character_create_race_class_combos; // solar: the current stuff is at the bottom of this function void WorldDatabase::GetCharSelectInfo(uint32 account_id, CharacterSelect_Struct* cs) { /* Initialize Player Profile for the small time it is being used for item material */ PlayerProfile_Struct pp; memset(&pp, 0, sizeof(PlayerProfile_Struct)); /* Initialize Variables */ for (int i=0; i<10; i++) { strcpy(cs->name[i], ""); cs->zone[i] = 0; cs->level[i] = 0; cs->tutorial[i] = 0; cs->gohome[i] = 0; } /* Get Character Info */ std::string cquery = StringFormat( "SELECT " "`id`, " // 0 "name, " // 1 "gender, " // 2 "race, " // 3 "class, " // 4 "`level`, " // 5 "deity, " // 6 "last_login, " // 7 "time_played, " // 8 "hair_color, " // 9 "beard_color, " // 10 "eye_color_1, " // 11 "eye_color_2, " // 12 "hair_style, " // 13 "beard, " // 14 "face, " // 15 "drakkin_heritage, " // 16 "drakkin_tattoo, " // 17 "drakkin_details, " // 18 "zone_id " // 19 "FROM " "character_data " "WHERE `account_id` = %i LIMIT 10 ", account_id); auto results = database.QueryDatabase(cquery); uint16 char_num = 0; for (auto row = results.begin(); row != results.end(); ++row) { printf("id is %i \n", atoi(row[0])); uint32 character_id = atoi(row[0]); strcpy(cs->name[char_num], row[1]); uint8 lvl = atoi(row[5]); cs->level[char_num] = lvl; cs->class_[char_num] = atoi(row[4]); cs->race[char_num] = atoi(row[3]); cs->gender[char_num] = atoi(row[2]); cs->deity[char_num] = atoi(row[6]); cs->zone[char_num] = atoi(row[19]); cs->face[char_num] = atoi(row[15]); cs->haircolor[char_num] = atoi(row[9]); cs->beardcolor[char_num] = atoi(row[10]); cs->eyecolor2[char_num] = atoi(row[12]); cs->eyecolor1[char_num] = atoi(row[11]); cs->hairstyle[char_num] = atoi(row[13]); cs->beard[char_num] = atoi(row[14]); cs->drakkin_heritage[char_num] = atoi(row[16]); cs->drakkin_tattoo[char_num] = atoi(row[17]); cs->drakkin_details[char_num] = atoi(row[18]); if (RuleB(World, EnableTutorialButton) && (lvl <= RuleI(World, MaxLevelForTutorial))) cs->tutorial[char_num] = 1; if (RuleB(World, EnableReturnHomeButton)) { int now = time(nullptr); if ((now - atoi(row[8])) >= RuleI(World, MinOfflineTimeToReturnHome)) cs->gohome[char_num] = 1; } /* This part creates home city entries for characters created before the home bind point was tracked. Do it here because the player profile is already loaded and it's as good a spot as any. This whole block should probably be removed at some point, when most accounts are safely converted. */ /* Load Character Bind Data */ cquery = StringFormat("SELECT zone_id, instance_id, x, y, z, heading FROM character_bind_home WHERE `id` = %i LIMIT 1", character_id); auto results_bind = database.QueryDatabase(cquery); int r = 0; for (auto row_b = results_bind.begin(); row_b != results_bind.end(); ++row_b) { uint8 bind_zone_id = atoi(row_b[r]); r++; uint8 bind_instance_id = atoi(row_b[r]); r++; uint8 bind_x_id = atoi(row_b[r]); r++; uint8 bind_y_id = atoi(row_b[r]); r++; uint8 bind_z_id = atoi(row_b[r]); r++; uint8 bind_heading_id = atoi(row_b[r]); r++; } // if (pp->binds[4].zoneId == 0) { // bool altered = false; // MYSQL_RES *result2; // MYSQL_ROW row2; // char startzone[50] = { 0 }; // // // check for start zone variable (I didn't even know any variables were still being used...) // if (database.GetVariable("startzone", startzone, 50)) { // uint32 zoneid = database.GetZoneID(startzone); // if (zoneid) { // pp->binds[4].zoneId = zoneid; // GetSafePoints(zoneid, 0, &pp->binds[4].x, &pp->binds[4].y, &pp->binds[4].z); // altered = true; // } // } // else { // RunQuery(query, // MakeAnyLenString(&query, // "SELECT zone_id,bind_id,x,y,z FROM start_zones " // "WHERE player_class=%i AND player_deity=%i AND player_race=%i", // pp->class_, // pp->deity, // pp->race // ), // errbuf, // &result2 // ); // safe_delete_array(query); // // // if there is only one possible start city, set it // if (mysql_num_rows(result2) == 1) { // row2 = mysql_fetch_row(result2); // if (atoi(row2[1]) != 0) { // if a bind_id is specified, make them start there // pp->binds[4].zoneId = (uint32)atoi(row2[1]); // GetSafePoints(pp->binds[4].zoneId, 0, &pp->binds[4].x, &pp->binds[4].y, &pp->binds[4].z); // } // else { // otherwise, use the zone and coordinates given // pp->binds[4].zoneId = (uint32)atoi(row2[0]); // float x = atof(row2[2]); // float y = atof(row2[3]); // float z = atof(row2[4]); // if (x == 0 && y == 0 && z == 0) // GetSafePoints(pp->binds[4].zoneId, 0, &x, &y, &z); // // pp->binds[4].x = x; // pp->binds[4].y = y; // pp->binds[4].z = z; // } // altered = true; // } // // mysql_free_result(result2); // } // } /* Character's equipped items @merth: Haven't done bracer01/bracer02 yet. Also: this needs a second look after items are a little more solid NOTE: items don't have a color, players MAY have a tint, if the use_tint part is set. otherwise use the regular color */ /* Load Character Material Data for Char Select */ cquery = StringFormat("SELECT slot, use_tint, color FROM `character_material` WHERE `id` = %u", character_id); auto results_b = database.QueryDatabase(cquery); uint8 slot = 0; for (auto row_b = results_b.begin(); row_b != results_b.end(); ++row_b) { slot = atoi(row_b[0]); if (atoi(row_b[1]) == 1){ pp.item_tint[slot].rgb.use_tint = 0xFF; } pp.item_tint[slot].color = atoul(row_b[2]); printf("charid: %u \n", character_id); printf("slot: %u \n", slot); printf("use_tint: %u item_tint: %u \n", pp.item_tint[slot].rgb.use_tint, atoul(row_b[2])); cs->cs_colors[char_num][slot].color = atoul(row_b[2]); } /* Load Inventory */ Inventory *inv; inv = new Inventory; if (GetInventory(account_id, cs->name[char_num], inv)) { for (uint8 material = 0; material <= 8; material++) { uint32 color = 0; ItemInst *item = inv->GetItem(Inventory::CalcSlotFromMaterial(material)); if (item == 0) continue; cs->equip[char_num][material] = item->GetItem()->Material; color = pp.item_tint[material].color; // if (pp.item_tint[material].rgb.use_tint){ color = pp.item_tint[material].color; } // else{ color = item->GetItem()->Color; } cs->cs_colors[char_num][material].color = color; // the weapons are kept elsewhere if ((material == MaterialPrimary) || (material == MaterialSecondary)) { if (strlen(item->GetItem()->IDFile) > 2) { uint32 idfile = atoi(&item->GetItem()->IDFile[2]); if (material == MaterialPrimary) cs->primary[char_num] = idfile; else cs->secondary[char_num] = idfile; } } } } else { printf("Error loading inventory for %s\n", cs->name[char_num]); } safe_delete(inv); if (++char_num > 10) break; } return; } int WorldDatabase::MoveCharacterToBind(int CharID, uint8 bindnum) { // if an invalid bind point is specified, use the primary bind if (bindnum > 4) bindnum = 0; char errbuf[MYSQL_ERRMSG_SIZE]; char *query = 0; MYSQL_RES *result; MYSQL_ROW row; uint32 affected_rows = 0; PlayerProfile_Struct pp; bool PPValid = false; if (RunQuery(query, MakeAnyLenString(&query, "SELECT profile from character_ where id='%i'", CharID), errbuf, &result)) { row = mysql_fetch_row(result); unsigned long* lengths = mysql_fetch_lengths(result); if (lengths[0] == sizeof(PlayerProfile_Struct)) { memcpy(&pp, row[0], sizeof(PlayerProfile_Struct)); PPValid = true; } mysql_free_result(result); } safe_delete_array(query); if(!PPValid) return 0; const char *BindZoneName = StaticGetZoneName(pp.binds[bindnum].zoneId); if(!strcmp(BindZoneName, "UNKNWN")) return pp.zone_id; if (!RunQuery(query, MakeAnyLenString(&query, "UPDATE character_ SET zonename = '%s',zoneid=%i,x=%f, y=%f, z=%f, instanceid=0 WHERE id='%i'", BindZoneName, pp.binds[bindnum].zoneId, pp.binds[bindnum].x, pp.binds[bindnum].y, pp.binds[bindnum].z, CharID), errbuf, 0,&affected_rows)) { return pp.zone_id; } safe_delete_array(query); return pp.binds[bindnum].zoneId; } bool WorldDatabase::GetStartZone(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc) { char errbuf[MYSQL_ERRMSG_SIZE]; char *query = 0; MYSQL_RES *result; MYSQL_ROW row = 0; int rows; if(!in_pp || !in_cc) return false; in_pp->x = in_pp->y = in_pp->z = in_pp->heading = in_pp->zone_id = 0; in_pp->binds[0].x = in_pp->binds[0].y = in_pp->binds[0].z = in_pp->binds[0].zoneId = 0; if(!RunQuery(query, MakeAnyLenString(&query, "SELECT x,y,z,heading,zone_id,bind_id FROM start_zones WHERE player_choice=%i AND player_class=%i " "AND player_deity=%i AND player_race=%i", in_cc->start_zone, in_cc->class_, in_cc->deity, in_cc->race), errbuf, &result)) { LogFile->write(EQEMuLog::Error, "Start zone query failed: %s : %s\n", query, errbuf); safe_delete_array(query); return false; } LogFile->write(EQEMuLog::Status, "Start zone query: %s\n", query); safe_delete_array(query); if((rows = mysql_num_rows(result)) > 0) row = mysql_fetch_row(result); if(row) { LogFile->write(EQEMuLog::Status, "Found starting location in start_zones"); in_pp->x = atof(row[0]); in_pp->y = atof(row[1]); in_pp->z = atof(row[2]); in_pp->heading = atof(row[3]); in_pp->zone_id = atoi(row[4]); in_pp->binds[0].zoneId = atoi(row[5]); } else { printf("No start_zones entry in database, using defaults\n"); switch(in_cc->start_zone) { case 0: { in_pp->zone_id = 24; // erudnext in_pp->binds[0].zoneId = 38; // tox break; } case 1: { in_pp->zone_id =2; // qeynos2 in_pp->binds[0].zoneId = 2; // qeynos2 break; } case 2: { in_pp->zone_id =29; // halas in_pp->binds[0].zoneId = 30; // everfrost break; } case 3: { in_pp->zone_id =19; // rivervale in_pp->binds[0].zoneId = 20; // kithicor break; } case 4: { in_pp->zone_id =9; // freportw in_pp->binds[0].zoneId = 9; // freportw break; } case 5: { in_pp->zone_id =40; // neriaka in_pp->binds[0].zoneId = 25; // nektulos break; } case 6: { in_pp->zone_id =52; // gukta in_pp->binds[0].zoneId = 46; // innothule break; } case 7: { in_pp->zone_id =49; // oggok in_pp->binds[0].zoneId = 47; // feerrott break; } case 8: { in_pp->zone_id =60; // kaladima in_pp->binds[0].zoneId = 68; // butcher break; } case 9: { in_pp->zone_id =54; // gfaydark in_pp->binds[0].zoneId = 54; // gfaydark break; } case 10: { in_pp->zone_id =61; // felwithea in_pp->binds[0].zoneId = 54; // gfaydark break; } case 11: { in_pp->zone_id =55; // akanon in_pp->binds[0].zoneId = 56; // steamfont break; } case 12: { in_pp->zone_id =82; // cabwest in_pp->binds[0].zoneId = 78; // fieldofbone break; } case 13: { in_pp->zone_id =155; // sharvahl in_pp->binds[0].zoneId = 155; // sharvahl break; } } } if(in_pp->x == 0 && in_pp->y == 0 && in_pp->z == 0) database.GetSafePoints(in_pp->zone_id, 0, &in_pp->x, &in_pp->y, &in_pp->z); if(in_pp->binds[0].x == 0 && in_pp->binds[0].y == 0 && in_pp->binds[0].z == 0) database.GetSafePoints(in_pp->binds[0].zoneId, 0, &in_pp->binds[0].x, &in_pp->binds[0].y, &in_pp->binds[0].z); if(result) mysql_free_result(result); return true; } bool WorldDatabase::GetStartZoneSoF(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc) { // SoF doesn't send the player_choice field in character creation, it now sends the real zoneID instead. // // For SoF, search for an entry in start_zones with a matching zone_id, class, race and deity. // // For now, if no row matching row is found, send them to Crescent Reach, as that is probably the most likely // reason for no match being found. // char errbuf[MYSQL_ERRMSG_SIZE]; char *query = 0; MYSQL_RES *result; MYSQL_ROW row = 0; int rows; if(!in_pp || !in_cc) return false; in_pp->x = in_pp->y = in_pp->z = in_pp->heading = in_pp->zone_id = 0; in_pp->binds[0].x = in_pp->binds[0].y = in_pp->binds[0].z = in_pp->binds[0].zoneId = 0; if(!RunQuery(query, MakeAnyLenString(&query, "SELECT x,y,z,heading,bind_id FROM start_zones WHERE zone_id=%i AND player_class=%i " "AND player_deity=%i AND player_race=%i", in_cc->start_zone, in_cc->class_, in_cc->deity, in_cc->race), errbuf, &result)) { LogFile->write(EQEMuLog::Status, "SoF Start zone query failed: %s : %s\n", query, errbuf); safe_delete_array(query); return false; } LogFile->write(EQEMuLog::Status, "SoF Start zone query: %s\n", query); safe_delete_array(query); if((rows = mysql_num_rows(result)) > 0) row = mysql_fetch_row(result); if(row) { LogFile->write(EQEMuLog::Status, "Found starting location in start_zones"); in_pp->x = atof(row[0]); in_pp->y = atof(row[1]); in_pp->z = atof(row[2]); in_pp->heading = atof(row[3]); in_pp->zone_id = in_cc->start_zone; in_pp->binds[0].zoneId = atoi(row[4]); } else { printf("No start_zones entry in database, using defaults\n"); if(in_cc->start_zone == RuleI(World, TutorialZoneID)) in_pp->zone_id = in_cc->start_zone; else { in_pp->x = in_pp->binds[0].x = -51; in_pp->y = in_pp->binds[0].y = -20; in_pp->z = in_pp->binds[0].z = 0.79; in_pp->zone_id = in_pp->binds[0].zoneId = 394; // Crescent Reach. } } if(in_pp->x == 0 && in_pp->y == 0 && in_pp->z == 0) database.GetSafePoints(in_pp->zone_id, 0, &in_pp->x, &in_pp->y, &in_pp->z); if(in_pp->binds[0].x == 0 && in_pp->binds[0].y == 0 && in_pp->binds[0].z == 0) database.GetSafePoints(in_pp->binds[0].zoneId, 0, &in_pp->binds[0].x, &in_pp->binds[0].y, &in_pp->binds[0].z); if(result) mysql_free_result(result); return true; } void WorldDatabase::GetLauncherList(std::vector &rl) { char errbuf[MYSQL_ERRMSG_SIZE]; char* query = 0; MYSQL_RES *result; MYSQL_ROW row; rl.clear(); if (RunQuery(query, MakeAnyLenString(&query, "SELECT name FROM launcher" ) , errbuf, &result)) { while ((row = mysql_fetch_row(result))) { rl.push_back(row[0]); } mysql_free_result(result); } else { LogFile->write(EQEMuLog::Error, "WorldDatabase::GetLauncherList: %s", errbuf); } safe_delete_array(query); } void WorldDatabase::SetMailKey(int CharID, int IPAddress, int MailKey) { char errbuf[MYSQL_ERRMSG_SIZE]; char *query = 0; char MailKeyString[17]; if(RuleB(Chat, EnableMailKeyIPVerification) == true) sprintf(MailKeyString, "%08X%08X", IPAddress, MailKey); else sprintf(MailKeyString, "%08X", MailKey); if (!RunQuery(query, MakeAnyLenString(&query, "UPDATE character_ SET mailkey = '%s' WHERE id='%i'", MailKeyString, CharID), errbuf)) LogFile->write(EQEMuLog::Error, "WorldDatabase::SetMailKey(%i, %s) : %s", CharID, MailKeyString, errbuf); safe_delete_array(query); } bool WorldDatabase::GetCharacterLevel(const char *name, int &level) { char errbuf[MYSQL_ERRMSG_SIZE]; char* query = 0; MYSQL_RES *result; MYSQL_ROW row; if(RunQuery(query, MakeAnyLenString(&query, "SELECT level FROM character_ WHERE name='%s'", name), errbuf, &result)) { if(row = mysql_fetch_row(result)) { level = atoi(row[0]); mysql_free_result(result); safe_delete_array(query); return true; } mysql_free_result(result); } else { LogFile->write(EQEMuLog::Error, "WorldDatabase::GetCharacterLevel: %s", errbuf); } safe_delete_array(query); return false; } bool WorldDatabase::LoadCharacterCreateAllocations() { character_create_allocations.clear(); char errbuf[MYSQL_ERRMSG_SIZE]; char* query = 0; MYSQL_RES *result; MYSQL_ROW row; if(RunQuery(query, MakeAnyLenString(&query, "SELECT * FROM char_create_point_allocations order by id"), errbuf, &result)) { safe_delete_array(query); while(row = mysql_fetch_row(result)) { RaceClassAllocation allocate; int r = 0; allocate.Index = atoi(row[r++]); allocate.BaseStats[0] = atoi(row[r++]); allocate.BaseStats[3] = atoi(row[r++]); allocate.BaseStats[1] = atoi(row[r++]); allocate.BaseStats[2] = atoi(row[r++]); allocate.BaseStats[4] = atoi(row[r++]); allocate.BaseStats[5] = atoi(row[r++]); allocate.BaseStats[6] = atoi(row[r++]); allocate.DefaultPointAllocation[0] = atoi(row[r++]); allocate.DefaultPointAllocation[3] = atoi(row[r++]); allocate.DefaultPointAllocation[1] = atoi(row[r++]); allocate.DefaultPointAllocation[2] = atoi(row[r++]); allocate.DefaultPointAllocation[4] = atoi(row[r++]); allocate.DefaultPointAllocation[5] = atoi(row[r++]); allocate.DefaultPointAllocation[6] = atoi(row[r++]); character_create_allocations.push_back(allocate); } mysql_free_result(result); } else { safe_delete_array(query); return false; } return true; } bool WorldDatabase::LoadCharacterCreateCombos() { character_create_race_class_combos.clear(); char errbuf[MYSQL_ERRMSG_SIZE]; char* query = 0; MYSQL_RES *result; MYSQL_ROW row; if(RunQuery(query, MakeAnyLenString(&query, "select * from char_create_combinations order by race, class, deity, start_zone"), errbuf, &result)) { safe_delete_array(query); while(row = mysql_fetch_row(result)) { RaceClassCombos combo; int r = 0; combo.AllocationIndex = atoi(row[r++]); combo.Race = atoi(row[r++]); combo.Class = atoi(row[r++]); combo.Deity = atoi(row[r++]); combo.Zone = atoi(row[r++]); combo.ExpansionRequired = atoi(row[r++]); character_create_race_class_combos.push_back(combo); } mysql_free_result(result); } else { safe_delete_array(query); return false; } return true; }