View Single Post
  #2  
Old 04-28-2011, 09:58 AM
Zothen
Hill Giant
 
Join Date: Apr 2011
Location: Germany
Posts: 163
Thumbs up

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
Code:
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
Code:
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()
Code:
		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_name,leaderbuf)) && strlen(leader)>1){
						EQApplicationPacket* outapp3 = new EQApplicationPacket(OP_GroupUpdate,sizeof(GroupJoin_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)
Code:
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
Reply With Quote