From 6497bdf45a9ff991b9bb916af063526bc0309637 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sun, 31 Aug 2014 21:31:44 -0500 Subject: [PATCH] More stuff --- common/database.cpp | 39 ++++- world/worlddb.cpp | 348 +++++++++++++++++++++-------------------- zone/client.cpp | 3 + zone/client_packet.cpp | 1 + zone/inventory.cpp | 6 +- zone/zonedb.cpp | 22 +++ zone/zonedb.h | 2 + 7 files changed, 243 insertions(+), 178 deletions(-) diff --git a/common/database.cpp b/common/database.cpp index fcfab3c52..1058cc5e1 100644 --- a/common/database.cpp +++ b/common/database.cpp @@ -1086,6 +1086,28 @@ bool Database::CheckDatabaseConversions() { QueryDatabase(rquery); printf(" done...\n"); } + /* Check for table `character_material` */ + rquery = StringFormat("SHOW TABLES LIKE 'character_material'"); + results = QueryDatabase(rquery); + // blue, green, red, use_tint, + if (results.RowCount() == 0){ + printf("Table: `character_material` doesn't exist... creating..."); + rquery = StringFormat( + "CREATE TABLE `character_material` ( " + "`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT," + "`slot` tinyint(11) UNSIGNED NOT NULL DEFAULT '0'," + "`blue` tinyint(11) UNSIGNED NOT NULL DEFAULT '0'," + "`green` tinyint(11) UNSIGNED NOT NULL DEFAULT '0'," + "`red` tinyint(11) UNSIGNED NOT NULL DEFAULT '0'," + "`use_tint` tinyint(11) UNSIGNED NOT NULL DEFAULT '0'," + "`color` int(11) UNSIGNED NOT NULL DEFAULT '0'," + "PRIMARY KEY(`id`, `slot`)," + "KEY `id` (`id`)" + ") ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = latin1;" + ); + QueryDatabase(rquery); + printf(" done...\n"); + } /* Done */ printf("Starting conversion...\n\n"); @@ -1093,7 +1115,7 @@ bool Database::CheckDatabaseConversions() { // querylen = MakeAnyLenString(&query, "SELECT `id` FROM `character_` WHERE `id` = 61238"); int char_iter_count = 0; - querylen = MakeAnyLenString(&query, "SELECT `id` FROM `character_` WHERE `id` >= 61238 LIMIT 100"); + querylen = MakeAnyLenString(&query, "SELECT `id` FROM `character_` WHERE `id` >= 61238 LIMIT 10"); if (RunQuery(query, querylen, errbuf, &result)) { safe_delete_array(query); while (row = mysql_fetch_row(result)) { @@ -1105,14 +1127,15 @@ bool Database::CheckDatabaseConversions() { pp = (PlayerProfile_Struct*)row2[1]; character_id = atoi(row[0]); account_id = atoi(row2[4]); + /* Verify PP Integrity */ lengths = mysql_fetch_lengths(result2); - if (lengths[1] == sizeof(PlayerProfile_Struct)) { + if (lengths[1] > 0) { memcpy(pp, row2[1], sizeof(PlayerProfile_Struct)); - // printf("FINE: Player profile '%s' %i length mismatch Expected: %i, Got: %i \n", row2[2], atoi(row2[3]), sizeof(PlayerProfile_Struct), lengths[1]); + // printf("FINE: Player profile '%s' %i length Expected: %i, Got: %i \n", row2[2], atoi(row2[3]), sizeof(PlayerProfile_Struct), lengths[1]); } /* Continue of PP Size does not match (Usually a created character never logged in) */ - else { + else { // printf("NO PP: Player profile '%s' %i length mismatch Expected: %i, Got: %i \n", row2[2], atoi(row2[3]), sizeof(PlayerProfile_Struct), lengths[1]); continue; } @@ -1483,6 +1506,14 @@ bool Database::CheckDatabaseConversions() { QueryDatabase(rquery); } } + /* Run Material Color Convert */ + for (i = 0; i < _MaterialCount; i++){ + if (pp->item_tint[i].color > 0){ + if (pp->item_tint[i].rgb.use_tint > 0){ pp->item_tint[i].rgb.use_tint = 1; } + rquery = StringFormat("REPLACE INTO `character_material` (id, slot, blue, green, red, use_tint, color) VALUES (%u, %u, %u, %u, %u, %u, %u)", character_id, i, pp->item_tint[i].rgb.blue, pp->item_tint[i].rgb.green, pp->item_tint[i].rgb.red, pp->item_tint[i].rgb.use_tint, pp->item_tint[i].color); + QueryDatabase(rquery); + } + } } /* Print out the entire Player Profile for testing */ diff --git a/world/worlddb.cpp b/world/worlddb.cpp index 02dfa4290..b07480031 100644 --- a/world/worlddb.cpp +++ b/world/worlddb.cpp @@ -35,12 +35,10 @@ 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) { - char errbuf[MYSQL_ERRMSG_SIZE]; - char* query = 0; - MYSQL_RES *result; - MYSQL_ROW row; - Inventory *inv; - + /* 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], ""); @@ -50,188 +48,194 @@ void WorldDatabase::GetCharSelectInfo(uint32 account_id, CharacterSelect_Struct* cs->gohome[i] = 0; } - int char_num = 0; - unsigned long* lengths; + /* 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]); - // Populate character info - if (RunQuery(query, MakeAnyLenString(&query, "SELECT name,profile,zonename,class,level FROM character_ WHERE account_id=%i order by name limit 10", account_id), errbuf, &result)) { - safe_delete_array(query); - while ((row = mysql_fetch_row(result))) { - lengths = mysql_fetch_lengths(result); - //////////// - //////////// This is the current one, the other are for converting - //////////// - if ((lengths[1] == sizeof(PlayerProfile_Struct))) { - strcpy(cs->name[char_num], row[0]); - PlayerProfile_Struct* pp = (PlayerProfile_Struct*)row[1]; - uint8 clas = atoi(row[3]); - uint8 lvl = atoi(row[4]); + if (RuleB(World, EnableTutorialButton) && (lvl <= RuleI(World, MaxLevelForTutorial))) + cs->tutorial[char_num] = 1; - // Character information - if(lvl == 0) - cs->level[char_num] = pp->level; //no level in DB, trust PP - else - cs->level[char_num] = lvl; - if(clas == 0) - cs->class_[char_num] = pp->class_; //no class in DB, trust PP - else - cs->class_[char_num] = clas; - cs->race[char_num] = pp->race; - cs->gender[char_num] = pp->gender; - cs->deity[char_num] = pp->deity; - cs->zone[char_num] = GetZoneID(row[2]); - cs->face[char_num] = pp->face; - cs->haircolor[char_num] = pp->haircolor; - cs->beardcolor[char_num] = pp->beardcolor; - cs->eyecolor2[char_num] = pp->eyecolor2; - cs->eyecolor1[char_num] = pp->eyecolor1; - cs->hairstyle[char_num] = pp->hairstyle; - cs->beard[char_num] = pp->beard; - cs->drakkin_heritage[char_num] = pp->drakkin_heritage; - cs->drakkin_tattoo[char_num] = pp->drakkin_tattoo; - cs->drakkin_details[char_num] = pp->drakkin_details; + if (RuleB(World, EnableReturnHomeButton)) { + int now = time(nullptr); + if ((now - atoi(row[8])) >= RuleI(World, MinOfflineTimeToReturnHome)) + cs->gohome[char_num] = 1; + } - if(RuleB(World, EnableTutorialButton) && (lvl <= RuleI(World, MaxLevelForTutorial))) - cs->tutorial[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. + */ - if(RuleB(World, EnableReturnHomeButton)) { - int now = time(nullptr); - if((now - pp->lastlogin) >= RuleI(World, MinOfflineTimeToReturnHome)) - cs->gohome[char_num] = 1; - } + /* 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); + // } + // } - // 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. - if(pp->binds[4].zoneId == 0) { - bool altered = false; - MYSQL_RES *result2; - MYSQL_ROW row2; - char startzone[50] = {0}; + /* + 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 + */ - // 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); + /* 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]); + } - // 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); + /* 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; - pp->binds[4].x = x; - pp->binds[4].y = y; - pp->binds[4].z = z; - } - altered = true; - } + cs->equip[char_num][material] = item->GetItem()->Material; - mysql_free_result(result2); - } + 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; } - // update the player profile - if(altered) { - uint32 char_id = GetCharacterID(cs->name[char_num]); - RunQuery(query,MakeAnyLenString(&query,"SELECT extprofile FROM character_ WHERE id=%i",char_id), errbuf, &result2); - safe_delete_array(query); - if(result2) { - row2 = mysql_fetch_row(result2); - ExtendedProfile_Struct* ext = (ExtendedProfile_Struct*)row2[0]; - // SetPlayerProfile(account_id,char_id,pp,inv,ext, 0, 0, 5); - } - mysql_free_result(result2); - } - } // end of "set start zone" block + cs->cs_colors[char_num][material].color = color; - - // 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 - inv = new Inventory; - if(GetInventory(account_id, cs->name[char_num], inv)) - { - for (uint8 material = 0; material <= 8; material++) - { - uint32 color; - ItemInst *item = inv->GetItem(Inventory::CalcSlotFromMaterial(material)); - if(item == 0) - continue; - - cs->equip[char_num][material] = item->GetItem()->Material; - - if(pp->item_tint[material].rgb.use_tint) // they have a tint (LoY dye) - color = pp->item_tint[material].color; - else // no tint, use regular item color - 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; - } - } + // 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; - } - else - { - std::cout << "Got a bogus character (" << row[0] << ") Ignoring!!!" << std::endl; - std::cout << "PP length ="<name[char_num]); + } + safe_delete(inv); + if (++char_num > 10) + break; } return; diff --git a/zone/client.cpp b/zone/client.cpp index 975e61dc1..6d2a784a4 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -3120,6 +3120,7 @@ void Client::SetTint(int16 in_slot, uint32 color) { Color_Struct new_color; new_color.color = color; SetTint(in_slot, new_color); + database.SaveCharacterMaterialColor(this->CharacterID(), in_slot, color); } // Still need to reconcile bracer01 versus bracer02 @@ -3147,6 +3148,8 @@ void Client::SetTint(int16 in_slot, Color_Struct& color) { m_pp.item_tint[MaterialLegs].color=color.color; else if (in_slot==MainFeet) m_pp.item_tint[MaterialFeet].color=color.color; + + database.SaveCharacterMaterialColor(this->CharacterID(), in_slot, color.color); } void Client::SetHideMe(bool flag) diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 9921298fe..bdd08b002 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -597,6 +597,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) loaditems = database.GetInventory(cid, &m_inv); /* Load Character Inventory */ database.LoadCharacterBindPoint(cid, &m_pp); /* Load Character Bind */ + database.LoadCharacterMaterial(cid, &m_pp); /* Load Character Material */ database.LoadCharacterCurrency(cid, &m_pp); /* Load Character Currency into PP */ database.LoadCharacterData(cid, &m_pp); /* Load Character Data from DB into PP */ database.GetPlayerInspectMessage(m_pp.name, &m_inspect_message); /* Move to another method when can, this is pointless... */ diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 197841c76..50b48fff9 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -1877,7 +1877,9 @@ void Client::DyeArmor(DyeStruct* dye){ uint8 slot2=SlotConvert(i); ItemInst* inst = this->m_inv.GetItem(slot2); if(inst){ - inst->SetColor((dye->dye[i].rgb.red*65536)+(dye->dye[i].rgb.green*256)+(dye->dye[i].rgb.blue)); + uint32 armor_color = (dye->dye[i].rgb.red * 65536) + (dye->dye[i].rgb.green * 256) + (dye->dye[i].rgb.blue); + inst->SetColor(armor_color); + database.SaveCharacterMaterialColor(this->CharacterID(), slot2, armor_color); database.SaveInventory(CharacterID(),inst,slot2); if(dye->dye[i].rgb.use_tint) m_pp.item_tint[i].rgb.use_tint = 0xFF; @@ -1898,7 +1900,7 @@ void Client::DyeArmor(DyeStruct* dye){ EQApplicationPacket* outapp=new EQApplicationPacket(OP_Dye,0); QueuePacket(outapp); safe_delete(outapp); - Save(); + } /*bool Client::DecreaseByItemType(uint32 type, uint8 amt) { diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 09aa26bc8..b998733c3 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -1145,6 +1145,28 @@ bool ZoneDatabase::LoadCharacterBindPoint(uint32 character_id, PlayerProfile_Str return true; } +bool ZoneDatabase::SaveCharacterMaterialColor(uint32 character_id, uint32 slot_id, uint32 color){ + std::string query = StringFormat("REPLACE INTO `character_material` (id, slot, color) VALUES (%u, %u, %u)", character_id, slot_id, color); QueryDatabase(query); + LogFile->write(EQEMuLog::Status, "ZoneDatabase::SaveCharacterMaterialColor for character ID: %i, slot_id: %u color: %u done", character_id, slot_id, color); + return true; +} + +bool ZoneDatabase::LoadCharacterMaterial(uint32 character_id, PlayerProfile_Struct* pp){ + std::string query = StringFormat("SELECT slot, blue, green, red, use_tint, color FROM `character_material` WHERE `id` = %u LIMIT 9", character_id); + auto results = database.QueryDatabase(query); int i = 0; int r = 0; + for (auto row = results.begin(); row != results.end(); ++row) { + r = 0; + i = atoi(row[r]); /* Slot */ r++; + pp->item_tint[i].rgb.blue = atoi(row[r]); r++; + pp->item_tint[i].rgb.green = atoi(row[r]); r++; + pp->item_tint[i].rgb.red = atoi(row[r]); r++; + if (row[r] && atoi(row[r]) > 0){ pp->item_tint[i].rgb.use_tint = 0xFF; } r++; + pp->item_tint[i].color = atoi(row[r]); r++; + printf("Loading color: %u tint: %u \n", pp->item_tint[i].color, pp->item_tint[i].rgb.use_tint); + } + return true; +} + bool ZoneDatabase::SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp){ clock_t t = std::clock(); /* Function timer start */ if (pp->tribute_time_remaining < 0 || pp->tribute_time_remaining == 4294967295){ pp->tribute_time_remaining = 0; } diff --git a/zone/zonedb.h b/zone/zonedb.h index 87aefd4cf..d98749f5e 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -266,6 +266,7 @@ public: bool LoadCharacterData(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterBindPoint(uint32 character_id, PlayerProfile_Struct* pp); + bool LoadCharacterMaterial(uint32 character_id, PlayerProfile_Struct* pp); /* Character Data Saves */ bool SaveCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp); @@ -274,6 +275,7 @@ public: bool SaveCharacterSpellSwap(uint32 character_id, uint32 spell_id, uint32 from_slot, uint32 to_slot); bool SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); + bool SaveCharacterMaterialColor(uint32 character_id, uint32 slot_id, uint32 color); /* Character Data Deletes */ bool DeleteCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id);