Zothen
04-28-2011, 09:58 AM
Okay, I figured it out myself. Tested and working, enjoy!
Lets say we want a char limit of 16 instead of 10, so we change the following files:
eq_packet_structs.h
struct CharacterSelect_Struct {
/*0000*/ int32 race[16]; // Characters Race
/*0040*/ Color_Struct cs_colors[16][9]; // Characters Equipment Colors
/*0400*/ int8 beardcolor[16]; // Characters beard Color
/*0410*/ int8 hairstyle[16]; // Characters hair style
/*0420*/ int32 equip[16][9]; // 0=helm, 1=chest, 2=arm, 3=bracer, 4=hand, 5=leg, 6=boot, 7=melee1, 8=melee2 (Might not be)
/*0780*/ int32 secondary[16]; // Characters secondary IDFile number
/*0820*/ int32 drakkin_heritage[16]; // added for SoF
/*0860*/ int32 drakkin_tattoo[16]; // added for SoF
/*0900*/ int32 drakkin_details[16]; // added for SoF
/*0940*/ int32 deity[16]; // Characters Deity
/*0980*/ int8 gohome[16]; // 1=Go Home available, 0=not
/*0990*/ int8 tutorial[16]; // 1=Tutorial available, 0=not
/*1000*/ int8 beard[16]; // Characters Beard Type
/*1010*/ int8 unknown902[16]; // 10x ff
/*1020*/ int32 primary[16]; // Characters primary IDFile number
/*1060*/ int8 haircolor[16]; // Characters Hair Color
/*1070*/ int8 unknown0962[2]; // 2x 00
/*1072*/ int32 zone[16]; // Characters Current Zone
/*1112*/ int8 class_[16]; // Characters Classes
/*1022*/ int8 face[16]; // Characters Face Type
/*1032*/ char name[16][64]; // Characters Names
/*1672*/ int8 gender[16]; // Characters Gender
/*1682*/ int8 eyecolor1[16]; // Characters Eye Color
/*1692*/ int8 eyecolor2[16]; // Characters Eye 2 Color
/*1702*/ int8 level[16]; // Characters Levels
/*1712*/
};
worlddb.cpp
void WorldDatabase::GetCharSelectInfo(int32 account_id, CharacterSelect_Struct* cs) {
char errbuf[MYSQL_ERRMSG_SIZE];
char* query = 0;
MYSQL_RES *result;
MYSQL_ROW row;
Inventory *inv;
for (int i=0; i<16; i++) {
strcpy(cs->name[i], "<none>");
cs->zone[i] = 0;
cs->level[i] = 0;
cs->tutorial[i] = 0;
cs->gohome[i] = 0;
}
int char_num = 0;
unsigned long* lengths;
// Populate character info
if (RunQuery(query, MakeAnyLenString(&query, "SELECT name,profile,zonename,class,level FROM character_ WHERE account_id=%i order by name limit 16", 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]);
// 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, EnableTutorialButton) && (lvl <= RuleI(World, MaxLevelForTutorial)))
cs->tutorial[char_num] = 1;
if(RuleB(World, EnableReturnHomeButton)) {
int now = time(NULL);
if((now - pp->lastlogin) >= 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.
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);
}
// 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);
}
mysql_free_result(result2);
}
} // end of "set start zone" block
// 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==MATERIAL_PRIMARY) || (material==MATERIAL_SECONDARY))
{
if(strlen(item->GetItem()->IDFile) > 2) {
int32 idfile=atoi(&item->GetItem()->IDFile[2]);
if (material==MATERIAL_PRIMARY)
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 > 16)
break;
}
else
{
cout << "Got a bogus character (" << row[0] << ") Ignoring!!!" << endl;
cout << "PP length ="<<lengths[1]<<" but PP should be "<<sizeof(PlayerProfile_Struct)<<endl;
//DeleteCharacter(row[0]);
}
}
mysql_free_result(result);
}
else
{
cerr << "Error in GetCharSelectInfo query '" << query << "' " << errbuf << endl;
safe_delete_array(query);
return;
}
return;
}
client.cpp
in HandlePacket()
case OP_EnterWorld: // Enter world
{
if (GetAccountID() == 0) {
clog(WORLD__CLIENT_ERR,"Enter world with no logged in account");
eqs->Close();
break;
}
if(GetAdmin() < 0)
{
clog(WORLD__CLIENT,"Account banned or suspended.");
eqs->Close();
break;
}
if (RuleI(World, MaxClientsPerIP) >= 0) {
client_list.GetCLEIP(this->GetIP()); //Lieka Edit Begin: Check current CLE Entry IPs against incoming connection
}
EnterWorld_Struct *ew=(EnterWorld_Struct *)app->pBuffer;
strn0cpy(char_name, ew->name, 64);
EQApplicationPacket *outapp;
int32 tmpaccid = 0;
charid = database.GetCharacterInfo(char_name, &tmpaccid, &zoneID, &instanceID);
if (charid == 0 || tmpaccid != GetAccountID()) {
clog(WORLD__CLIENT_ERR,"Could not get CharInfo for '%s'",char_name);
eqs->Close();
break;
}
// Make sure this account owns this character
if (tmpaccid != GetAccountID()) {
clog(WORLD__CLIENT_ERR,"This account does not own the character named '%s'",char_name);
eqs->Close();
break;
}
if(!pZoning && ew->return_home)
{
CharacterSelect_Struct* cs = new CharacterSelect_Struct;
memset(cs, 0, sizeof(CharacterSelect_Struct));
database.GetCharSelectInfo(GetAccountID(), cs);
bool home_enabled = false;
for(int x = 0; x < 16; ++x)
{
if(strcasecmp(cs->name[x], char_name) == 0)
{
if(cs->gohome[x] == 1)
{
home_enabled = true;
break;
}
}
}
safe_delete(cs);
if(home_enabled)
{
zoneID = database.MoveCharacterToBind(charid,4);
}
else
{
clog(WORLD__CLIENT_ERR,"'%s' is trying to go home before they're able...",char_name);
database.SetHackerFlag(GetAccountName(), char_name, "MQGoHome: player tried to go home before they were able.");
eqs->Close();
break;
}
}
if(!pZoning && (RuleB(World, EnableTutorialButton) && (ew->tutorial || StartInTutorial))) {
CharacterSelect_Struct* cs = new CharacterSelect_Struct;
memset(cs, 0, sizeof(CharacterSelect_Struct));
database.GetCharSelectInfo(GetAccountID(), cs);
bool tutorial_enabled = false;
for(int x = 0; x < 16; ++x)
{
if(strcasecmp(cs->name[x], char_name) == 0)
{
if(cs->tutorial[x] == 1)
{
tutorial_enabled = true;
break;
}
}
}
safe_delete(cs);
if(tutorial_enabled)
{
zoneID = RuleI(World, TutorialZoneID);
database.MoveCharacterToZone(charid, database.GetZoneName(zoneID));
}
else
{
clog(WORLD__CLIENT_ERR,"'%s' is trying to go to tutorial but are not allowed...",char_name);
database.SetHackerFlag(GetAccountName(), char_name, "MQTutorial: player tried to enter the tutorial without having tutorial enabled for this character.");
eqs->Close();
break;
}
}
if (zoneID == 0 || !database.GetZoneName(zoneID)) {
// This is to save people in an invalid zone, once it's removed from the DB
database.MoveCharacterToZone(charid, "arena");
clog(WORLD__CLIENT_ERR, "Zone not found in database zone_id=%i, moveing char to arena character:%s", zoneID, char_name);
}
if(instanceID > 0)
{
if(!database.VerifyInstanceAlive(instanceID, GetCharID()))
{
zoneID = database.MoveCharacterToBind(charid);
instanceID = 0;
}
else
{
if(!database.VerifyZoneInstance(zoneID, instanceID))
{
zoneID = database.MoveCharacterToBind(charid);
instanceID = 0;
}
}
}
if(!pZoning) {
database.SetGroupID(char_name, 0, charid);
database.SetLFP(charid, false);
database.SetLFG(charid, false);
}
else{
int32 groupid=database.GetGroupID(char_name);
if(groupid>0){
char* leader=0;
char leaderbuf[64]={0};
if((leader=database.GetGroupLeaderForLogin(char_na me,leaderbuf)) && strlen(leader)>1){
EQApplicationPacket* outapp3 = new EQApplicationPacket(OP_GroupUpdate,sizeof(GroupJoi n_Struct));
GroupJoin_Struct* gj=(GroupJoin_Struct*)outapp3->pBuffer;
gj->action=8;
strcpy(gj->yourname,char_name);
strcpy(gj->membername,leader);
QueuePacket(outapp3);
safe_delete(outapp3);
}
}
}
outapp = new EQApplicationPacket(OP_MOTD);
char tmp[500] = {0};
if (database.GetVariable("MOTD", tmp, 500)) {
outapp->size = strlen(tmp)+1;
outapp->pBuffer = new uchar[outapp->size];
memset(outapp->pBuffer,0,outapp->size);
strcpy((char*)outapp->pBuffer, tmp);
} else {
// Null Message of the Day. :)
outapp->size = 1;
outapp->pBuffer = new uchar[outapp->size];
outapp->pBuffer[0] = 0;
}
QueuePacket(outapp);
safe_delete(outapp);
int MailKey = MakeRandomInt(1, INT_MAX);
database.SetMailKey(charid, GetIP(), MailKey);
char ConnectionType;
if(ClientVersionBit & BIT_UnderfootAndLater)
ConnectionType = 'U';
else if(ClientVersionBit & BIT_SoFAndLater)
ConnectionType = 'S';
else
ConnectionType = 'C';
EQApplicationPacket *outapp2 = new EQApplicationPacket(OP_SetChatServer);
char buffer[112];
sprintf(buffer,"%s,%i,%s.%s,%c%08X",
Config->ChatHost.c_str(),
Config->ChatPort,
Config->ShortName.c_str(),
this->GetCharName(), ConnectionType, MailKey
);
outapp2->size=strlen(buffer)+1;
outapp2->pBuffer = new uchar[outapp2->size];
memcpy(outapp2->pBuffer,buffer,outapp2->size);
QueuePacket(outapp2);
safe_delete(outapp2);
outapp2 = new EQApplicationPacket(OP_SetChatServer2);
if(ClientVersionBit & BIT_TitaniumAndEarlier)
ConnectionType = 'M';
sprintf(buffer,"%s,%i,%s.%s,%c%08X",
Config->MailHost.c_str(),
Config->MailPort,
Config->ShortName.c_str(),
this->GetCharName(), ConnectionType, MailKey
);
outapp2->size=strlen(buffer)+1;
outapp2->pBuffer = new uchar[outapp2->size];
memcpy(outapp2->pBuffer,buffer,outapp2->size);
QueuePacket(outapp2);
safe_delete(outapp2);
EnterWorld();
break;
}
and finally in SoD.cpp (or any other patch file, depending on your client)
ENCODE(OP_SendCharInfo) {
ENCODE_LENGTH_EXACT(CharacterSelect_Struct);
SETUP_VAR_ENCODE(CharacterSelect_Struct);
//EQApplicationPacket *packet = *p;
//const CharacterSelect_Struct *emu = (CharacterSelect_Struct *) packet->pBuffer;
int char_count;
int namelen = 0;
for(char_count = 0; char_count < 16; char_count++) {
if(emu->name[char_count][0] == '\0')
break;
if(strcmp(emu->name[char_count], "<none>") == 0)
break;
namelen += strlen(emu->name[char_count]);
}
int total_length = sizeof(structs::CharacterSelect_Struct)
+ char_count * sizeof(structs::CharacterSelectEntry_Struct)
+ namelen;
ALLOC_VAR_ENCODE(structs::CharacterSelect_Struct, total_length);
//unsigned char *eq_buffer = new unsigned char[total_length];
//structs::CharacterSelect_Struct *eq_head = (structs::CharacterSelect_Struct *) eq_buffer;
eq->char_count = char_count;
eq->total_chars = 16;
unsigned char *bufptr = (unsigned char *) eq->entries;
int r;
for(r = 0; r < char_count; r++) {
{ //pre-name section...
structs::CharacterSelectEntry_Struct *eq2 = (structs::CharacterSelectEntry_Struct *) bufptr;
eq2->level = emu->level[r];
eq2->hairstyle = emu->hairstyle[r];
eq2->gender = emu->gender[r];
memcpy(eq2->name, emu->name[r], strlen(emu->name[r])+1);
}
//adjust for name.
bufptr += strlen(emu->name[r]);
{ //post-name section...
structs::CharacterSelectEntry_Struct *eq2 = (structs::CharacterSelectEntry_Struct *) bufptr;
eq2->beard = emu->beard[r];
eq2->haircolor = emu->haircolor[r];
eq2->face = emu->face[r];
int k;
for(k = 0; k < MAX_MATERIALS; k++) {
eq2->equip[k].equip0 = emu->equip[r][k];
eq2->equip[k].equip1 = 0;
eq2->equip[k].itemid = 0;
eq2->equip[k].color.color = emu->cs_colors[r][k].color;
}
eq2->primary = emu->primary[r];
eq2->secondary = emu->secondary[r];
eq2->tutorial = emu->tutorial[r]; // was u15
eq2->u15 = 0xff;
eq2->deity = emu->deity[r];
eq2->zone = emu->zone[r];
eq2->u19 = 0xFF;
eq2->race = emu->race[r];
eq2->gohome = emu->gohome[r];
eq2->class_ = emu->class_[r];
eq2->eyecolor1 = emu->eyecolor1[r];
eq2->beardcolor = emu->beardcolor[r];
eq2->eyecolor2 = emu->eyecolor2[r];
eq2->drakkin_heritage = emu->drakkin_heritage[r];
eq2->drakkin_tattoo = emu->drakkin_tattoo[r];
eq2->drakkin_details = emu->drakkin_details[r];
}
bufptr += sizeof(structs::CharacterSelectEntry_Struct);
}
FINISH_ENCODE();
}
I hope I havent forgotten something ;)
provocating
04-28-2011, 04:04 PM
You missed this in /world/worlddb.cpp
if (RunQuery(query, MakeAnyLenString(&query, "SELECT name,profile,zonename,class,level FROM character_ WHERE account_id=%i order by name limit 16", account_id), errbuf, &result)) {
vBulletin® v3.8.11, Copyright ©2000-2025, vBulletin Solutions Inc.