Browse Source

classic/DoF show book support

Fix #160
Image 3 years ago
parent
commit
55b98c6cf7

+ 76 - 108
EQ2/source/WorldServer/Bots/BotCommands.cpp

@@ -668,117 +668,85 @@ void Commands::Command_Bot_Settings(Client* client, Seperator* sep) {
 void Commands::Command_Bot_Help(Client* client, Seperator* sep) {
 	if (sep && sep->IsSet(0)) {
 		if (strncasecmp("race", sep->arg[0], 4) == 0) {
-			PacketStruct* packet = configReader.getStruct("WS_EqShowBook", client->GetVersion());
-			if (packet) {
-				packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer()));
-
-				string title = "Race ID's";
-				packet->setDataByName("book_title", title.c_str());
-				packet->setDataByName("book_type", "simple");
-				packet->setDataByName("unknown2", 1);
-				packet->setDataByName("unknown5", 1, 4);
-				packet->setArrayLengthByName("num_pages", 1);
-
-				string details;
-				details += "0\tBarbarian\n";
-				details += "1\tDark Elf\n";
-				details += "2\tDwarf\n";
-				details += "3\tErudite\n";
-				details += "4\tFroglok\n";
-				details += "5\tGnome\n";
-				details += "6\tHalf Elf\n";
-				details += "7\tHalfling\n";
-				details += "8\tHigh Elf\n";
-				details += "9\tHuman\n";
-				details += "10\tIksar\n";
-				details += "11\tKerra\n";
-				details += "12\tOgre\n";
-				details += "13\tRatonga\n";
-				details += "14\tTroll\n";
-				details += "15\tWood Elf\n";
-				details += "16\tFae\n";
-				details += "17\tArasai\n";
-				details += "18\tSarnak\n";
-				details += "19\tVampire\n";
-				details += "20\tAerakyn\n";
-				
-				packet->setArrayDataByName("page_text", details.c_str());
-
-				client->QueuePacket(packet->serialize());
-				safe_delete(packet);
-			}
-
+			string title = "Race ID's";
+			string details;
+			details += "0\tBarbarian\n";
+			details += "1\tDark Elf\n";
+			details += "2\tDwarf\n";
+			details += "3\tErudite\n";
+			details += "4\tFroglok\n";
+			details += "5\tGnome\n";
+			details += "6\tHalf Elf\n";
+			details += "7\tHalfling\n";
+			details += "8\tHigh Elf\n";
+			details += "9\tHuman\n";
+			details += "10\tIksar\n";
+			details += "11\tKerra\n";
+			details += "12\tOgre\n";
+			details += "13\tRatonga\n";
+			details += "14\tTroll\n";
+			details += "15\tWood Elf\n";
+			details += "16\tFae\n";
+			details += "17\tArasai\n";
+			details += "18\tSarnak\n";
+			details += "19\tVampire\n";
+			details += "20\tAerakyn\n";
+			client->SendShowBook(client->GetPlayer(), title, 1, details);
 			return;
 		}
 		else if (strncasecmp("class", sep->arg[0], 5) == 0) {
-			PacketStruct* packet = configReader.getStruct("WS_EqShowBook", client->GetVersion());
-			if (packet) {
-				packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer()));
-
-				string title = "Class ID's";
-				packet->setDataByName("book_title", title.c_str());
-				packet->setDataByName("book_type", "simple");
-				packet->setDataByName("unknown2", 1);
-				packet->setDataByName("unknown5", 1, 4);
-				packet->setArrayLengthByName("num_pages", 3);
-
-				string details;
-				details += "0\tCOMMONER\n";
-				details += "1\tFIGHTER\n";
-				details += "2\tWARRIOR\n";
-				details += "3\tGUARDIAN\n";
-				details += "4\tBERSERKER\n";
-				details += "5\tBRAWLER\n";
-				details += "6\tMONK\n";
-				details += "7\tBRUISER\n";
-				details += "8\tCRUSADER\n";
-				details += "9\tSHADOWKNIGHT\n";
-				details += "10\tPALADIN\n";
-				details += "11\tPRIEST\n";
-				details += "12\tCLERIC\n";
-				details += "13\tTEMPLAR\n";
-				details += "14\tINQUISITOR\n";
-				details += "15\tDRUID\n";
-				details += "16\tWARDEN\n";
-				details += "17\tFURY\n";
-				details += "18\tSHAMAN\n";
-				details += "19\tMYSTIC\n";
-				details += "20\tDEFILER\n";
-				
-				string details2 = "21\tMAGE\n";
-				details2 += "22\tSORCERER\n";
-				details2 += "23\tWIZARD\n";
-				details2 += "24\tWARLOCK\n";
-				details2 += "25\tENCHANTER\n";
-				details2 += "26\tILLUSIONIST\n";
-				details2 += "27\tCOERCER\n";
-				details2 += "28\tSUMMONER\n";
-				details2 += "29\tCONJUROR\n";
-				details2 += "30\tNECROMANCER\n";
-				details2 += "31\tSCOUT\n";
-				details2 += "32\tROGUE\n";
-				details2 += "33\tSWASHBUCKLER\n";
-				details2 += "34\tBRIGAND\n";
-				details2 += "35\tBARD\n";
-				details2 += "36\tTROUBADOR\n";
-				details2 += "37\tDIRGE\n";
-				details2 += "38\tPREDATOR\n";
-				details2 += "39\tRANGER\n";
-				details2 += "40\tASSASSIN\n";
-				
-				string details3 = "\\#FF0000Following aren't implemented yet.\\#000000\n";
-				details3 += "41\tANIMALIST\n";
-				details3 += "42\tBEASTLORD\n";
-				details3 += "43\tSHAPER\n";
-				details3 += "44\tCHANNELER\n";
-
-				packet->setArrayDataByName("page_text", details.c_str());
-				packet->setArrayDataByName("page_text", details2.c_str(), 1);
-				packet->setArrayDataByName("page_text", details3.c_str(), 2);
-
-				client->QueuePacket(packet->serialize());
-				safe_delete(packet);
-			}
+			string title = "Class ID's";
+			string details;
+			details += "0\tCOMMONER\n";
+			details += "1\tFIGHTER\n";
+			details += "2\tWARRIOR\n";
+			details += "3\tGUARDIAN\n";
+			details += "4\tBERSERKER\n";
+			details += "5\tBRAWLER\n";
+			details += "6\tMONK\n";
+			details += "7\tBRUISER\n";
+			details += "8\tCRUSADER\n";
+			details += "9\tSHADOWKNIGHT\n";
+			details += "10\tPALADIN\n";
+			details += "11\tPRIEST\n";
+			details += "12\tCLERIC\n";
+			details += "13\tTEMPLAR\n";
+			details += "14\tINQUISITOR\n";
+			details += "15\tDRUID\n";
+			details += "16\tWARDEN\n";
+			details += "17\tFURY\n";
+			details += "18\tSHAMAN\n";
+			details += "19\tMYSTIC\n";
+			details += "20\tDEFILER\n";
+
+			string details2 = "21\tMAGE\n";
+			details2 += "22\tSORCERER\n";
+			details2 += "23\tWIZARD\n";
+			details2 += "24\tWARLOCK\n";
+			details2 += "25\tENCHANTER\n";
+			details2 += "26\tILLUSIONIST\n";
+			details2 += "27\tCOERCER\n";
+			details2 += "28\tSUMMONER\n";
+			details2 += "29\tCONJUROR\n";
+			details2 += "30\tNECROMANCER\n";
+			details2 += "31\tSCOUT\n";
+			details2 += "32\tROGUE\n";
+			details2 += "33\tSWASHBUCKLER\n";
+			details2 += "34\tBRIGAND\n";
+			details2 += "35\tBARD\n";
+			details2 += "36\tTROUBADOR\n";
+			details2 += "37\tDIRGE\n";
+			details2 += "38\tPREDATOR\n";
+			details2 += "39\tRANGER\n";
+			details2 += "40\tASSASSIN\n";
+
+			string details3 = "\\#FF0000Following aren't implemented yet.\\#000000\n";
+			details3 += "41\tANIMALIST\n";
+			details3 += "42\tBEASTLORD\n";
+			details3 += "43\tSHAPER\n";
+			details3 += "44\tCHANNELER\n";
+
+			client->SendShowBook(client->GetPlayer(), title, 3, details, details2, details3);
 			return;
 		}
 	}

+ 83 - 132
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -1187,44 +1187,13 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 				Item* item = client->GetPlayer()->item_list.GetItemFromIndex(item_index);
 				if (item) {
 					Spawn* spawn = cmdTarget;
-					int8 numpages = item->book_pages.size();
-					
-					string page1 = string(item->book_pages[0]->page_text.data);
-					PacketStruct* packet = configReader.getStruct("WS_EqShowBook", client->GetVersion());
-					string title = item->name;
-					
-					
-					
-					if (packet) {
-							packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer()));
-							packet->setDataByName("book_title", title.c_str());
-							packet->setDataByName("book_type", "simple");
-							packet->setDataByName("unknown2", 1);
-							packet->setDataByName("unknown5", 1, 4);
-							packet->setArrayLengthByName("num_pages", numpages);
-							for (int8 pages = 1; pages <= numpages; pages++) {
-
-								int8 pagenum = int8(item->book_pages[pages - 1]->page);
-								string page = string(item->book_pages[pages-1]->page_text.data);
-								int8 valign = int8(item->book_pages[pages - 1]->valign);
-								int8 halign = int8(item->book_pages[pages - 1]->halign);
-								packet->setArrayDataByName("page_text", page.c_str(), pagenum -1);
-								packet->setArrayDataByName("page_text_valign", valign , pagenum - 1);
-								packet->setArrayDataByName("page_text_halign", halign, pagenum - 1);
-
-								// Need to add support for images
-							}
-							client->QueuePacket(packet->serialize());
-							safe_delete(packet);
-					}
-
-					
+					client->SendShowBook(client->GetPlayer(), item->name, item->book_pages);
 					break;
 				}
 			}
-			}
-
-}
+		}
+		break;
+	}
 		case COMMAND_USEABILITY:{
 			if (sep && sep->arg[0][0] && sep->IsNumber(0)) {
 				if (client->GetPlayer()->GetHP() == 0) {
@@ -3476,7 +3445,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 			}
 			break;
 									 }
-		case COMMAND_SPAWN_DETAILS:{
+		case COMMAND_SPAWN_DETAILS: {
 			Spawn* spawn = cmdTarget;
 			if (sep && sep->arg[0][0]) {
 				if (cmdTarget)
@@ -3496,7 +3465,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 						glm::vec3 targPos(cmdTarget->GetX(), cmdTarget->GetZ(), cmdTarget->GetY());
 
 						float bestZ = client->GetPlayer()->FindDestGroundZ(targPos, cmdTarget->GetYOffset());
-							client->Message(CHANNEL_COLOR_YELLOW, "Best Z for %s is %f", spawn->GetName(), bestZ);
+						client->Message(CHANNEL_COLOR_YELLOW, "Best Z for %s is %f", spawn->GetName(), bestZ);
 						break;
 					}
 					else if (ToLower(string(sep->arg[0])) == "pathto")
@@ -3518,15 +3487,15 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 					break;
 				}
 			}
-			if(spawn){
+			if (spawn) {
 				const char* type = "NPC";
-				if(spawn->IsObject())
+				if (spawn->IsObject())
 					type = "Object";
-				else if(spawn->IsSign())
+				else if (spawn->IsSign())
 					type = "Sign";
-				else if(spawn->IsWidget())
+				else if (spawn->IsWidget())
 					type = "Widget";
-				else if(spawn->IsGroundSpawn())
+				else if (spawn->IsGroundSpawn())
 					type = "GroundSpawn";
 				client->Message(CHANNEL_COLOR_YELLOW, "Name: %s, %s ID: %u", spawn->GetName(), type, spawn->GetDatabaseID());
 				client->Message(CHANNEL_COLOR_YELLOW, "Last Name: %s, Sub-Title: %s, Prefix: %s, Suffix: %s", spawn->GetLastName(), spawn->GetSubTitle(), spawn->GetPrefixTitle(), spawn->GetSuffixTitle());
@@ -3539,7 +3508,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 				client->Message(CHANNEL_COLOR_YELLOW, "Collision Radius: %i, Size: %i, Difficulty: %i, Heroic: %i", spawn->GetCollisionRadius(), spawn->GetSize(), spawn->GetEncounterLevel(), spawn->GetHeroic());
 				client->Message(CHANNEL_COLOR_YELLOW, "Targetable: %i, Show Name: %i, Attackable: %i, Show Level: %i", spawn->GetTargetable(), spawn->GetShowName(), spawn->GetAttackable(), spawn->GetShowLevel());
 				client->Message(CHANNEL_COLOR_YELLOW, "Show Command Icon: %i, Display Hand Icon: %i", spawn->GetShowCommandIcon(), spawn->GetShowHandIcon());
-				if(spawn->IsEntity()){
+				if (spawn->IsEntity()) {
 					client->Message(CHANNEL_COLOR_YELLOW, "Facial Hair Type: %i, Hair Type: %i, Chest Type: %i, Legs Type: %i", ((Entity*)spawn)->GetFacialHairType(), ((Entity*)spawn)->GetHairType(), ((Entity*)spawn)->GetChestType(), ((Entity*)spawn)->GetLegsType());
 					client->Message(CHANNEL_COLOR_YELLOW, "Soga Facial Hair Type: %i, Soga Hair Type: %i, Wing Type: %i", ((Entity*)spawn)->GetSogaFacialHairType(), ((Entity*)spawn)->GetSogaHairType(), ((Entity*)spawn)->GetWingType());
 				}
@@ -3550,97 +3519,79 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 				if (spawn->IsNPC())
 					client->Message(CHANNEL_COLOR_YELLOW, "Randomize: %u", ((NPC*)spawn)->GetRandomize());
 
-				PacketStruct* packet = configReader.getStruct("WS_EqShowBook", client->GetVersion());
-				if (packet) {
-					packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer()));
-
-					string title = string(spawn->GetName()) + "(" + to_string(spawn->GetDatabaseID()) + ")";
-					packet->setDataByName("book_title", title.c_str());
-					packet->setDataByName("book_type", "simple");
-					packet->setDataByName("unknown2", 1);
-					packet->setDataByName("unknown5", 1, 4);
-					packet->setArrayLengthByName("num_pages", 4);
-					string details;
-					details += "\\#0000FFName:	" + string(spawn->GetName()) + "\n";
-					details += "Type:	" + string(type) + "\n";
-					details += "ID:	" + to_string(spawn->GetDatabaseID()) + "\n";
-					details += "Last Name:	" + string(spawn->GetLastName()) + "\n";
-					details += "Sub-Title:	" + string(spawn->GetSubTitle()) + "\n";
-					details += "Prefix:	" + string(spawn->GetPrefixTitle()) + "\n";
-					details += "Suffix:	" + string(spawn->GetSuffixTitle()) + "\n";
-					details += "Race:		" + to_string(spawn->GetRace()) + "\n";
-					details += "Class:		" + to_string(spawn->GetAdventureClass()) + "\n";
-					details += "Gender:		" + to_string(spawn->GetGender()) + "\n";
-					details += "Level:		" + to_string(spawn->GetLevel()) + "\n";
-					details += "HP:		" + to_string(spawn->GetHP()) + "\n";
-					details += "Power:		" + to_string(spawn->GetPower()) + "\n";
-					details += "Difficulty:		" + to_string(spawn->GetEncounterLevel()) + "\n";
-					details += "Heroic:		" + to_string(spawn->GetHeroic()) + "\n";
-					details += "Group ID:		" + to_string(spawn->GetSpawnGroupID()) + "\n";
-					details += "Faction ID:		" + to_string(spawn->GetFactionID()) + "\n";
-					details += "Merchant ID:	" + to_string(spawn->GetMerchantID()) + "\n";
-					details += "Transport ID:	" + to_string(spawn->GetTransporterID()) + "\n";
-					details += "Location ID:	" + to_string(spawn->GetSpawnLocationID()) + "\n";
-					char x[16];
-					sprintf(x, "%.2f", spawn->GetX());
-					char y[16];
-					sprintf(y, "%.2f", spawn->GetY());
-					char z[16];
-					sprintf(z, "%.2f", spawn->GetZ());
-					details += "Location:	" + string(x) + ", " + string(y) + ", " + string(z) + "\n";
-					
-					string details2;
-					details2 += "Heading:		" + to_string(spawn->GetHeading()) + "\n";
-					details2 += "Grid ID:		" + to_string(spawn->GetLocation()) + "\n";
-					details2 += "Size:		" + to_string(spawn->GetSize()) + "\n";
-					details2 += "Collision Radius:	" + to_string(spawn->GetCollisionRadius()) + "\n";
-					details2 += "Respawn Time:	" + to_string(spawn->GetRespawnTime()) + "\n";
-					details2 += "Targetable:		" + to_string(spawn->GetTargetable()) + "\n";
-					details2 += "Show Name:	" + to_string(spawn->GetShowName()) + "\n";
-					details2 += "Attackable:		" + to_string(spawn->GetAttackable()) + "\n";
-					details2 += "Show Level:		" + to_string(spawn->GetShowLevel()) + "\n";
-					details2 += "Show Command Icon:	" + to_string(spawn->GetShowCommandIcon()) + "\n";
-					details2 += "Display Hand Icon:	" + to_string(spawn->GetShowHandIcon()) + "\n";
-					details2 += "Model Type:		" + to_string(spawn->GetModelType()) + "\n";
-					details2 += "Soga Race Type:	" + to_string(spawn->GetSogaModelType()) + "\n";
-					details2 += "Primary Command ID:	" + to_string(spawn->GetPrimaryCommandListID()) + "\n";
-					details2 += "Secondary Cmd ID:	" + to_string(spawn->GetSecondaryCommandListID()) + "\n";
-					details2 += "Visual State:		" + to_string(spawn->GetVisualState()) + "\n";
-					details2 += "Action State:	" + to_string(spawn->GetActionState()) + "\n";
-					details2 += "Mood State:		" + to_string(spawn->GetMoodState()) + "\n";
-					details2 += "Initial State:		" + to_string(spawn->GetInitialState()) + "\n";
-					details2 += "Activity Status:	" + to_string(spawn->GetActivityStatus()) + "\n";
-					details2 += "Emote State:		" + to_string(spawn->GetEmoteState()) + "\n";
-
-					string details3;
-					details3 += "Pitch:	" + to_string(spawn->GetPitch()) + "\n";
-					details3 += "Roll:	" + to_string(spawn->GetRoll()) + "\n";
-					details3 += "Hide Hood:	" + to_string(spawn->appearance.hide_hood) + "\n";
-
-					string details4;
-					if (spawn->IsEntity()) {
-						details4 += "Facial Hair Type:	" + to_string(((Entity*)spawn)->GetFacialHairType()) + "\n";
-						details4 += "Hair Type:		" + to_string(((Entity*)spawn)->GetHairType()) + "\n";
-						details4 += "Chest Type:		" + to_string(((Entity*)spawn)->GetChestType()) + "\n";
-						details4 += "Legs Type:		" + to_string(((Entity*)spawn)->GetLegsType()) + "\n";
-						details4 += "Soga Facial Hair Type:	" + to_string(((Entity*)spawn)->GetSogaFacialHairType()) + "\n";
-						details4 += "Soga Hair Type:	" + to_string(((Entity*)spawn)->GetSogaHairType()) + "\n";
-						details4 += "Wing Type:		" + to_string(((Entity*)spawn)->GetWingType()) + "\n";
-						if (spawn->IsNPC()) {
-							details4 += "\nRandomize:		" + to_string(((NPC*)spawn)->GetRandomize()) + "\n";
-						}
+				string details;
+				details += "\\#0000FFName:	" + string(spawn->GetName()) + "\n";
+				details += "Type:	" + string(type) + "\n";
+				details += "ID:	" + to_string(spawn->GetDatabaseID()) + "\n";
+				details += "Last Name:	" + string(spawn->GetLastName()) + "\n";
+				details += "Sub-Title:	" + string(spawn->GetSubTitle()) + "\n";
+				details += "Prefix:	" + string(spawn->GetPrefixTitle()) + "\n";
+				details += "Suffix:	" + string(spawn->GetSuffixTitle()) + "\n";
+				details += "Race:		" + to_string(spawn->GetRace()) + "\n";
+				details += "Class:		" + to_string(spawn->GetAdventureClass()) + "\n";
+				details += "Gender:		" + to_string(spawn->GetGender()) + "\n";
+				details += "Level:		" + to_string(spawn->GetLevel()) + "\n";
+				details += "HP:		" + to_string(spawn->GetHP()) + "\n";
+				details += "Power:		" + to_string(spawn->GetPower()) + "\n";
+				details += "Difficulty:		" + to_string(spawn->GetEncounterLevel()) + "\n";
+				details += "Heroic:		" + to_string(spawn->GetHeroic()) + "\n";
+				details += "Group ID:		" + to_string(spawn->GetSpawnGroupID()) + "\n";
+				details += "Faction ID:		" + to_string(spawn->GetFactionID()) + "\n";
+				details += "Merchant ID:	" + to_string(spawn->GetMerchantID()) + "\n";
+				details += "Transport ID:	" + to_string(spawn->GetTransporterID()) + "\n";
+				details += "Location ID:	" + to_string(spawn->GetSpawnLocationID()) + "\n";
+				char x[16];
+				sprintf(x, "%.2f", spawn->GetX());
+				char y[16];
+				sprintf(y, "%.2f", spawn->GetY());
+				char z[16];
+				sprintf(z, "%.2f", spawn->GetZ());
+				details += "Location:	" + string(x) + ", " + string(y) + ", " + string(z) + "\n";
+
+				string details2;
+				details2 += "Heading:		" + to_string(spawn->GetHeading()) + "\n";
+				details2 += "Grid ID:		" + to_string(spawn->GetLocation()) + "\n";
+				details2 += "Size:		" + to_string(spawn->GetSize()) + "\n";
+				details2 += "Collision Radius:	" + to_string(spawn->GetCollisionRadius()) + "\n";
+				details2 += "Respawn Time:	" + to_string(spawn->GetRespawnTime()) + "\n";
+				details2 += "Targetable:		" + to_string(spawn->GetTargetable()) + "\n";
+				details2 += "Show Name:	" + to_string(spawn->GetShowName()) + "\n";
+				details2 += "Attackable:		" + to_string(spawn->GetAttackable()) + "\n";
+				details2 += "Show Level:		" + to_string(spawn->GetShowLevel()) + "\n";
+				details2 += "Show Command Icon:	" + to_string(spawn->GetShowCommandIcon()) + "\n";
+				details2 += "Display Hand Icon:	" + to_string(spawn->GetShowHandIcon()) + "\n";
+				details2 += "Model Type:		" + to_string(spawn->GetModelType()) + "\n";
+				details2 += "Soga Race Type:	" + to_string(spawn->GetSogaModelType()) + "\n";
+				details2 += "Primary Command ID:	" + to_string(spawn->GetPrimaryCommandListID()) + "\n";
+				details2 += "Secondary Cmd ID:	" + to_string(spawn->GetSecondaryCommandListID()) + "\n";
+				details2 += "Visual State:		" + to_string(spawn->GetVisualState()) + "\n";
+				details2 += "Action State:	" + to_string(spawn->GetActionState()) + "\n";
+				details2 += "Mood State:		" + to_string(spawn->GetMoodState()) + "\n";
+				details2 += "Initial State:		" + to_string(spawn->GetInitialState()) + "\n";
+				details2 += "Activity Status:	" + to_string(spawn->GetActivityStatus()) + "\n";
+				details2 += "Emote State:		" + to_string(spawn->GetEmoteState()) + "\n";
+
+				string details3;
+				details3 += "Pitch:	" + to_string(spawn->GetPitch()) + "\n";
+				details3 += "Roll:	" + to_string(spawn->GetRoll()) + "\n";
+				details3 += "Hide Hood:	" + to_string(spawn->appearance.hide_hood) + "\n";
+
+				string details4;
+				if (spawn->IsEntity()) {
+					details4 += "Facial Hair Type:	" + to_string(((Entity*)spawn)->GetFacialHairType()) + "\n";
+					details4 += "Hair Type:		" + to_string(((Entity*)spawn)->GetHairType()) + "\n";
+					details4 += "Chest Type:		" + to_string(((Entity*)spawn)->GetChestType()) + "\n";
+					details4 += "Legs Type:		" + to_string(((Entity*)spawn)->GetLegsType()) + "\n";
+					details4 += "Soga Facial Hair Type:	" + to_string(((Entity*)spawn)->GetSogaFacialHairType()) + "\n";
+					details4 += "Soga Hair Type:	" + to_string(((Entity*)spawn)->GetSogaHairType()) + "\n";
+					details4 += "Wing Type:		" + to_string(((Entity*)spawn)->GetWingType()) + "\n";
+					if (spawn->IsNPC()) {
+						details4 += "\nRandomize:		" + to_string(((NPC*)spawn)->GetRandomize()) + "\n";
 					}
-					
-					//details2 += "" + to_string() + "\n";
-
-					packet->setArrayDataByName("page_text", details.c_str());
-					packet->setArrayDataByName("page_text", details2.c_str(), 1);
-					packet->setArrayDataByName("page_text", details3.c_str(), 2);
-					packet->setArrayDataByName("page_text", details4.c_str(), 3);
-
-					client->QueuePacket(packet->serialize());
-					safe_delete(packet);
 				}
+
+				string title = string(spawn->GetName()) + "(" + to_string(spawn->GetDatabaseID()) + ")";
+				client->SendShowBook(client->GetPlayer(), title, 4, details, details2, details3, details4);
 			}
 			else {
 				client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /spawn details (radius)");

+ 133 - 1
EQ2/source/WorldServer/client.cpp

@@ -76,7 +76,6 @@ along with EQ2Emulator.  If not, see <http://www.gnu.org/licenses/>.
 #include "../common/EQ2_Common_Structs.h"
 #include "net.h"
 #include "../common/MiscFunctions.h"
-#include "Items/Items.h"
 #include "Skills.h"
 #include "LuaInterface.h"
 #include "Quests.h"
@@ -9105,4 +9104,137 @@ void Client::SendFlightAutoMount(int32 path_id, int16 mount_id, int8 mount_red_c
 
 	if (mount_id)
 		((Entity*)GetPlayer())->SetMount(mount_id, mount_red_color, mount_green_color, mount_blue_color);
+}
+
+void Client::SendShowBook(Spawn* sender, string title, int8 num_pages, ...)
+{
+	if (!sender)
+	{
+		LogWrite(CCLIENT__ERROR, 0, "Client", "SendShowBook missing sender for Player %s, book title %s", GetPlayer()->GetName(), title);
+		return;
+	}
+
+	PacketStruct* packet = configReader.getStruct("WS_EqShowBook", GetVersion());
+	if (!packet) {
+		LogWrite(CCLIENT__ERROR, 0, "Client", "WS_EqShowBook missing for version %u", GetVersion());
+		return;
+	}
+	packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(sender));
+	packet->setDataByName("book_title", title.c_str());
+	packet->setDataByName("book_type", "simple");
+	packet->setDataByName("unknown2", 1);
+
+	if (GetVersion() > 546)
+		packet->setDataByName("unknown5", 1, 4);
+
+	packet->setArrayLengthByName("num_pages", num_pages);
+
+	va_list args;
+	va_start(args, num_pages);
+	std::string endString("");
+	for (int8 p = 0; p < num_pages; p++)
+	{
+		std::string page = va_arg(args, string);
+		switch (GetVersion())
+		{
+			// release client
+			case 283:
+			{
+				endString.append(page);
+				break;
+			}
+			// DoF trial
+			case 546:
+			{
+				if (p == 0)
+					packet->setDataByName("cover_page", page.c_str());
+				else
+					packet->setArrayDataByName("page_text", page.c_str(), p - 1);
+				break;
+			}
+			// all other clients
+			default:
+			{
+				packet->setArrayDataByName("page_text", page.c_str(), p);
+				break;
+			}
+		}
+	}
+
+	if (GetVersion() == 283)
+	{
+		packet->setDataByName("page_text", endString.c_str());
+	}
+
+	va_end(args);
+
+	QueuePacket(packet->serialize());
+	safe_delete(packet);
+}
+
+void Client::SendShowBook(Spawn* sender, string title, vector<Item::BookPage*> pages)
+{
+	if (!sender)
+	{
+		LogWrite(CCLIENT__ERROR, 0, "Client", "SendShowBook missing sender for Player %s, book title %s", GetPlayer()->GetName(), title);
+		return;
+	}
+
+	PacketStruct* packet = configReader.getStruct("WS_EqShowBook", GetVersion());
+	if (!packet) {
+		LogWrite(CCLIENT__ERROR, 0, "Client", "WS_EqShowBook missing for version %u", GetVersion());
+		return;
+	}
+	packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(sender));
+	packet->setDataByName("book_title", title.c_str());
+	packet->setDataByName("book_type", "simple");
+	packet->setDataByName("unknown2", 1);
+
+	if (GetVersion() > 546)
+		packet->setDataByName("unknown5", 1, 4);
+
+	packet->setArrayLengthByName("num_pages", pages.size());
+
+	std::string endString("");
+	for (int8 p = 0; p < pages.size(); p++)
+	{
+		Item::BookPage* page = pages[p];
+		std::string pageText = string(page->page_text.data);
+		switch (GetVersion())
+		{
+			// release client
+		case 283:
+		{
+			endString.append(pageText);
+			break;
+		}
+		// DoF trial
+		case 546:
+		{
+			if (p == 0)
+				packet->setDataByName("cover_page", pageText.c_str());
+			else
+				packet->setArrayDataByName("page_text", pageText.c_str(), p - 1);
+			break;
+		}
+		// all other clients
+		default:
+		{
+			int8 valign = int8(page->valign);
+			int8 halign = int8(page->halign);
+			packet->setArrayDataByName("page_text", pageText.c_str(), p);
+			packet->setArrayDataByName("page_text_valign", valign, p);
+			packet->setArrayDataByName("page_text_halign", halign, p);
+			break;
+		}
+		}
+	}
+
+	if (GetVersion() == 283)
+	{
+		packet->setDataByName("page_text", endString.c_str());
+	}
+
+	QueuePacket(packet->serialize());
+	safe_delete(packet);
 }

+ 4 - 0
EQ2/source/WorldServer/client.h

@@ -23,6 +23,7 @@
 #include "../common/EQStream.h"
 #include <list>
 #include "../common/timer.h"
+#include "Items/Items.h"
 #include "zoneserver.h"
 #include "Player.h"
 #include "Quests.h"
@@ -427,6 +428,9 @@ public:
 
 	void SendFlightAutoMount(int32 path_id, int16 mount_id = 0, int8 mount_red_color = 0xFF, int8 mount_green_color = 0xFF, int8 mount_blue_color=0xFF);
 
+	void SendShowBook(Spawn* sender, string title, int8 num_pages, ...);
+	void SendShowBook(Spawn* sender, string title, vector<Item::BookPage*> pages);
+
 	void SetTemporaryTransportID(int32 id) { temporary_transport_id = id; }
 	int32 GetTemporaryTransportID() { return temporary_transport_id; }
 private:

+ 14 - 15
server/WorldStructs.xml

@@ -9736,26 +9736,25 @@ to zero and treated like placeholders." />
 <Data ElementName="page" Type="int32" Size="1" />
 </Struct>
 <Struct Name="WS_EqShowBook" ClientVersion="1" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqShowBookCmd" >
+<Data ElementName="book_title" Type="EQ2_16Bit_String" Size="1" />
+<Data ElementName="page_text" Type="EQ2_16Bit_String" Size="1" />
+<Data ElementName="book_type" Type="EQ2_16Bit_String" Size="1" />
+<Data ElementName="language" Type="int8" Size="1" />
+<Data ElementName="unknown2" Type="int8" Size="1" /> <!-- isbook? -->
+</Struct>
+<Struct Name="WS_EqShowBook" ClientVersion="546" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqShowBookCmd" >
 <Data ElementName="spawn_id" Type="int32" Size="1" />
 <Data ElementName="book_title" Type="EQ2_16Bit_String" Size="1" />
 <Data ElementName="unknown" Type="int16" Size="1" />
 <Data ElementName="book_type" Type="EQ2_16Bit_String" Size="1" />
-<Data ElementName="unknown2" Type="int8" Size="1" />
-<Data ElementName="unknown3" Type="int16" Size="1" />
-<Data ElementName="unknown4" Type="int32" Size="1" />
-<Data ElementName="unknown5" Type="int8" Size="1" />
+<Data ElementName="unknown2" Type="int16" Size="1" />
 <Data ElementName="num_pages" Type="int8" Size="1" />
-<Data ElementName="page_array" Type="Array" ArraySizeVariable="num_pages">
-  <Data ElementName="page_text" Type="EQ2_16Bit_String" Size="1" />
-  <Data ElementName="page_text_valign" Type="int8" Size="1" />
-  <Data ElementName="page_text_halign" Type="int8" Size="1" />
-  <Data ElementName="num_images" Type="int8" Size="1" />
-  <Data ElementName="image_array" Type="Array" ArraySizeVariable="num_images">
-    <Data ElementName="image_file" Type="EQ2_16Bit_String" Size="1" />
-    <Data ElementName="unknown6" Type="int8" Size="1" />
-    <Data ElementName="image_id" Type="int8" Size="1" />
-    <Data ElementName="unknown7" Type="int8" Size="12" />
-  </Data>
+<Data ElementName="cover_page" Type="EQ2_16Bit_String" Size="1" />
+<Data ElementName="book_page_array" Type="Array" ArraySizeVariable="num_pages">
+	<Data ElementName="unknown1_array" Type="int8" Size="1" />
+	<Data ElementName="unknown2_array" Type="int8" Size="1" />
+	<Data ElementName="unknown3_array" Type="int8" Size="1" />
+	<Data ElementName="page_text" Type="EQ2_16Bit_String" Size="1" />
 </Data>
 </Struct>
 <Struct Name="WS_EqShowBook" ClientVersion="1096" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqShowBookCmd" >