Browse Source

- DoF Client Profile(Character) -> Options all the checkboxes set the wrong thing (afk, roleplaying, camping, linkdead, lfg, so on)
- DoF client Quest complete reward now includes temp rewards item data, previously you would see an empty quest reward window.
- DoF client properly in combat (no re-sheething of weapon while in combat)
- DoF client fixed spells to do start and end cast at appropriate times (struct fix).
- DoF client casting on self says "not a friend" -- now says not an enemy. When being too far away, instead of saying "too close" now says "too far away".
- DoF fixed selecting self interrupts /camp
- DoF client server/client properly synched for inventory (food/drink and other equipped slots previously not working right)
- DoF Broker no longer crashes client, limited implementation to get first 8 results. WIP
- DoF fixed examine equipped items didn't work for drink/potentially mismatched other slots
- DoF item stat review of resists now properly display
- DoF client is constantly sending HandleExamineInfoRequest packets for spells, fixed. Greatly improved performance of DoF cause it was constantly sending these packets for all spells in your book per second!
- DoF collections window is fixed, collections now display correctly in journal window. Upon turning in complete collections, client no longer crashes. Outstanding note: DoF client has EXP show up as % of level (60?) instead of just bare XP points. This might need more discovery, but isn't a huge issue right now.
- DoF player profile inspection inventory, DoF and AoM both display inventory (DoF does not support appearance gear). Cross client inspection works also. Avoidance (DoF and AOM) / ATK (DoF) stats need to be reviewed and matched in the structure, but not a big priority.
- DoF client /who list is broken (if more than one player, by myself /who works) -- structure updated for some unknown bytes tail of packet
- DoF client gestures now show up visually and not as an /emote-
- DoF item display fixed for house items (the items themselves in inventory/broker/so on were previosuly displayed corrupted)
- DoF Merchant "sell to merchant" just shows buy window, sell window was displaying buy items, there is already a sell window with the buy window.
- DoF merchant mender/repair no longer crashes the client -- currently worked around by sending buy screen then repair screen.. displays both, need to get buy screen disabled, will be its own git issue.
- Remove/Add skills via LUA will now properly update/reflect the database.
- starting_skills and starting_spells no longer applies on each login, this will stop the restoring of spells/skills on zoning that are removed with LUA
- Login sequence into world now initially load spells/skills (and also sync if first time into the world for the character). This will allow us to avoid an unneccessary blocking behavior with World::SyncCharAbilities (it waited for 5 loops and hold the thread to see if the DB is updating the character). Now we enforce this behavior with an updated state machine for the login, no longer blocking/holding the thread.
alter table characters add column first_world_login tinyint(1) unsigned not null default 0;
- R_World, DisplayItemTiers rule made to display item tiers (1) / disable (0)
- Bots are no longer impacted by /depop or /repop
- fixed crash with Player::CheckQuestFlag where we try to send updates based on quest, dead quest ptr when calling CheckQuestChatUpdate
- revive points now support an always_included option, otherwise it only shows the closest distance
- Database updates:
alter table revive_points add column always_included tinyint(1) unsigned not null default 0;
alter table characters add column first_world_login tinyint(1) unsigned not null default 0;

- LoginServer
* LoginServer.ini now supports (integer) fields expansionsflag, citiesflag, defaultsubscriptionlevel, enabledraces under the "LoginConfig" block
eg. enabledraces = 65535 allows all races
enabledraces = 57343 removes sarnak as playable race (-8192), another -4096 would remove Arasai. -2048 removes Fae.

Default values (in hex, you can only use integer in the ini):
// full support = 0x7CFF
// 1 << 12 (-4096) = missing echoes of faydwer, disables Fae and Arasai (black portraits) and kelethin as starting city
// 1 << 13 (-8192) = disables sarnak (black portraits) and gorowyn as starting city
expansionFlag = 0x7CFF; // 0x4CF5

/* dword_1ECBA18 operand for race flag packs (sublevel 0,1,2?) -- (sublevel -1) controls starting zones omission 0xEE vs 0xCF (CF misses halas)
1 = city of qeynos
2 = city of freeport
4 = city of kelethin
8 = city of neriak
16 = gorowyn
32 = new halas
64 = queens colony
128 = outpost overlord
*/
citiesFlag = 0xFF;

// sub_level 0xFFFFFFFF = blacks out all portraits for class alignments, considered non membership
// sub_level > 0 = class alignments still required, but portraits are viewable and race selectable
// sub_level = 2 membership, you can 'create characters on time locked servers' vs standard
// sub_level = 0 forces popup on close to web browser
defaultSubscriptionLevel = 0xFFFFFFFF;

// disable extra races FAE(16) ARASAI (17) SARNAK (18) -- with 4096/8192 flags, no visibility of portraits
enabledRaces = 0xFFFF; // 0xCFFF

Emagi 11 months ago
parent
commit
716f9affa3

+ 52 - 19
EQ2/source/LoginServer/client.cpp

@@ -565,43 +565,75 @@ void Client::SendLoginDenied(){
 	EQ2Packet* app = new EQ2Packet(OP_LoginReplyMsg, 0, sizeof(LS_LoginResponse));
 	LS_LoginResponse* ls_response = (LS_LoginResponse*)app->pBuffer;
 	ls_response->reply_code = 1;
+	// reply_codes for AoM:
+	/* 1 = Login rejected: Invalid username or password. Please try again.
+	   2 = Login rejected: Server thinks your account is currently playing; you may have to wait "
+              "a few minutes for it to clear, then try again
+	   6 = Login rejected: The client's version does not match the server's. Please re-run the patcher.
+	   7 = Login rejected: You have no scheduled playtimes.
+	   8 = Your account does not have the features required to play on this server.
+	   11 = The client's build does not match the server's. Please re-run the patcher.
+	   12 = You must update your password in order to log in.  Pressing OK will op"
+              "en your web browser to the SOE password management page
+		Other Value > 1 = Login rejected for an unknown reason.
+	 */
 	ls_response->unknown03 = 0xFFFFFFFF;
 	ls_response->unknown04 = 0xFFFFFFFF;
 	QueuePacket(app);
 	StartDisconnectTimer();
 }
 
-void Client::SendLoginAccepted() {
+void Client::SendLoginAccepted(int32 account_id, int8 login_response) {
 	PacketStruct* packet = configReader.getStruct("LS_LoginReplyMsg", GetVersion());
 	int i = 0;
 	if (packet)
 	{
-		packet->setDataByName("account_id", 1);
-		//packet->setDataByName("login_response", 0);
-		//packet->setDataByName("reset_appearance", 0);
+		packet->setDataByName("account_id", account_id);
+		
+		packet->setDataByName("login_response", login_response);
+		
 		packet->setDataByName("do_not_force_soga", 1);
-		//packet->setDataByName("race_unknown", 0x3F);
-		//packet->setDataByName("unknown11", 2); // can be 7
-		packet->setDataByName("sub_level", 2);
+		
+		// sub_level 0xFFFFFFFF = blacks out all portraits for class alignments, considered non membership
+		// sub_level > 0 = class alignments still required, but portraits are viewable and race selectable
+		// sub_level = 2 membership, you can 'create characters on time locked servers' vs standard
+		// sub_level = 0 forces popup on close to web browser
+		packet->setDataByName("sub_level", net.GetDefaultSubscriptionLevel());
 		packet->setDataByName("race_flag", 0x1FFFFF);
 		packet->setDataByName("class_flag", 0x7FFFFFE);
 		packet->setMediumStringByName("username", GetAccountName());
 		packet->setMediumStringByName("password", GetAccountName());
 
-		packet->setDataByName("unknown5", 0x7C);
-		packet->setDataByName("unknown7", 0xFF6FFFBF);
-
-		// Image 2020 Notes
-		// Login Server only supports AoM at current time, but we will need to keep in mind the structure for 60100 or later calls for additional custom fields
-		// >=60100
-		packet->setDataByName("unknown11", 0x0E);
-
-		packet->setDataByName("unknown7a", 0xFFFF);
-		packet->setDataByName("unknown8", 0xFF, 1);
+		// unknown5
+		// full support = 0x7CFF
+		// 1 << 12 (-4096) = missing echoes of faydwer, disables Fae and Arasai (black portraits) and kelethin as starting city
+		// 1 << 13 (-8192) = disables sarnak (black portraits) and gorowyn as starting city
+		packet->setDataByName("unknown5", net.GetExpansionFlag());
+		packet->setDataByName("unknown6", 0xFF);
+		packet->setDataByName("unknown6", 0xFF, 1);
+		packet->setDataByName("unknown6", 0xFF, 2);
+		
+		// controls class access / playable characters
+		packet->setDataByName("unknown10", 0xFF);
+
+	//	packet->setDataByName("unknown7a", 0x0101);
+	//	packet->setDataByName("race_unknown", 0x01);
+		packet->setDataByName("unknown7", net.GetEnabledRaces()); // 0x01-0xFF disable extra races FAE(16) ARASAI (17) SARNAK (18) -- with 4096/8192 flags, no visibility of portraits
+		packet->setDataByName("unknown7a", 0xEE);
+		packet->setDataByName("unknown8", net.GetCitiesFlag(), 1); // dword_1ECBA18 operand for race flag packs (sublevel 0,1,2?) -- (sublevel -1) controls starting zones omission 0xEE vs 0xCF (CF misses halas)
+
+		/*
+		1 = city of qeynos
+		2 = city of freeport
+		4 = city of kelethin
+		8 = city of neriak
+		16 = gorowyn
+		32 = new halas
+		64 = queens colony
+		128 = outpost overlord
+		*/
 
 		EQ2Packet* outapp = packet->serialize();
-		printf("Out struct:\n");
-		DumpPacket(outapp->pBuffer, outapp->size);
 		QueuePacket(outapp);
 		safe_delete(packet);
 	}
@@ -612,6 +644,7 @@ void Client::SendWorldList(){
 	EQ2Packet* dupe = pack->Copy();
 	DumpPacket(dupe->pBuffer,dupe->size);
 	QueuePacket(dupe);
+	SendLoginAccepted(0, 10); // triggers a different code path in the client to set certain flags
 	return;
 }
 

+ 1 - 1
EQ2/source/LoginServer/client.h

@@ -25,7 +25,7 @@ public:
     ~Client();
 	void	SendLoginDenied();
 	void	SendLoginDeniedBadVersion();
-	void	SendLoginAccepted();
+	void	SendLoginAccepted(int32 account_id = 1, int8 login_response = 0);
 	void	SendWorldList();
 	void	SendCharList();
 	int16	AddWorldToList2(uchar* buffer, char* name, int32 id, int16* flags);

+ 20 - 0
EQ2/source/LoginServer/net.cpp

@@ -248,6 +248,26 @@ bool NetConnection::ReadLoginConfig() {
 					allowAccountCreation = atoi(buf);
 				}
 			}
+			if (!strncasecmp(type, "expansionflag", 13)) {
+				if (Seperator::IsNumber(buf)) {
+					expansionFlag = atoul(buf);
+				}
+			}
+			if (!strncasecmp(type, "citiesflag", 10)) {
+				if (Seperator::IsNumber(buf)) {
+					citiesFlag = atoul(buf);
+				}
+			}
+			if (!strncasecmp(type, "defaultsubscriptionlevel", 24)) {
+				if (Seperator::IsNumber(buf)) {
+					defaultSubscriptionLevel = atoul(buf);
+				}
+			}
+			if (!strncasecmp(type, "enabledraces", 12)) {
+				if (Seperator::IsNumber(buf)) {
+					enabledRaces = atoul(buf);
+				}
+			}
 		}
 	}
 

+ 35 - 1
EQ2/source/LoginServer/net.h

@@ -39,6 +39,32 @@ public:
 		numclients = 0;
 		numservers = 0;
 		allowAccountCreation = true;
+		
+		// full support = 0x7CFF
+		// 1 << 12 (-4096) = missing echoes of faydwer, disables Fae and Arasai (black portraits) and kelethin as starting city
+		// 1 << 13 (-8192) = disables sarnak (black portraits) and gorowyn as starting city
+		expansionFlag = 0x7CFF; // 0x4CF5 
+		
+		/* dword_1ECBA18 operand for race flag packs (sublevel 0,1,2?) -- (sublevel -1) controls starting zones omission 0xEE vs 0xCF (CF misses halas)
+		1 = city of qeynos
+		2 = city of freeport
+		4 = city of kelethin
+		8 = city of neriak
+		16 = gorowyn
+		32 = new halas
+		64 = queens colony
+		128 = outpost overlord
+		*/
+		citiesFlag = 0xFF;
+		
+		// sub_level 0xFFFFFFFF = blacks out all portraits for class alignments, considered non membership
+		// sub_level > 0 = class alignments still required, but portraits are viewable and race selectable
+		// sub_level = 2 membership, you can 'create characters on time locked servers' vs standard
+		// sub_level = 0 forces popup on close to web browser
+		defaultSubscriptionLevel = 0xFFFFFFFF;
+		
+		// disable extra races FAE(16) ARASAI (17) SARNAK (18) -- with 4096/8192 flags, no visibility of portraits
+		enabledRaces = 0xFFFF; // 0xCFFF
 	}
 	void UpdateWindowTitle(char* iNewTitle = 0);
 	bool Init();
@@ -59,7 +85,11 @@ public:
 	char*	GetUplinkPassword()	{ return uplinkpassword; }
 
 	bool	IsAllowingAccountCreation() { return allowAccountCreation; }
-
+	int32	GetExpansionFlag() { return expansionFlag; }
+	int8	GetCitiesFlag() { return citiesFlag; }
+	int32	GetDefaultSubscriptionLevel() { return defaultSubscriptionLevel; }
+	int32	GetEnabledRaces() { return enabledRaces; }
+	
 	void	WelcomeHeader();
 protected:
 	friend class LWorld;
@@ -73,4 +103,8 @@ private:
 	char	uplinkpassword[300];
 	eServerMode	LoginMode;
 	bool	allowAccountCreation;
+	int32	expansionFlag;
+	int8	citiesFlag;
+	int32	defaultSubscriptionLevel;
+	int32	enabledRaces;
 };

+ 2 - 2
EQ2/source/WorldServer/Bots/BotCommands.cpp

@@ -480,8 +480,8 @@ void Commands::Command_Bot_Customize(Client* client, Seperator* sep) {
 	
 	
 	client->Message(CHANNEL_COLOR_RED, "This command is disabled and requires new implementation.");
-/* this links to OP_SubmitCharCust
-	if (bot && bot->GetOwner() == client->GetPlayer()) {
+
+	/*if (bot && bot->GetOwner() == client->GetPlayer()) {
 		PacketStruct* packet = configReader.getStruct("WS_OpenCharCust", client->GetVersion());
 		if (packet) {
 

+ 55 - 18
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -2115,7 +2115,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 						LogWrite(COMMAND__ERROR, 0, "Command", "Unknown Index: %u", item_index);
 				}
 				else if(strcmp(sep->arg[0], "equipment") == 0){
-					int32 item_index = atol(sep->arg[1]);
+					int32 item_index = client->GetPlayer()->ConvertSlotFromClient(atol(sep->arg[1]), client->GetVersion());
 					Item* item = client->GetPlayer()->GetEquipmentList()->GetItem(item_index);
 					if(item){
 						EQ2Packet* app = item->serialize(client->GetVersion(), true, client->GetPlayer());
@@ -2127,7 +2127,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 						LogWrite(COMMAND__ERROR, 0, "Command", "Unknown Index: %u", item_index);
 				}
 				else if(strcmp(sep->arg[0], "appearance") == 0){
-					int32 item_index = atol(sep->arg[1]);
+					int32 item_index = client->GetPlayer()->ConvertSlotFromClient(atol(sep->arg[1]), client->GetVersion());
 					Item* item = client->GetPlayer()->GetAppearanceEquipmentList()->GetItem(item_index);
 					if(item){
 						EQ2Packet* app = item->serialize(client->GetVersion(), true, client->GetPlayer());
@@ -4631,6 +4631,11 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 						client->Message(CHANNEL_COLOR_YELLOW, "Angle %f between player %s and target %s", spawnAngle, client->GetPlayer()->GetTarget() ? client->GetPlayer()->GetTarget()->GetName() : client->GetPlayer()->GetName(), client->GetPlayer()->GetName());
 						break;
 					}
+					else if (ToLower(string(sep->arg[0])) == "knockback")
+					{
+						cmdTarget->SpawnKnockback(client->GetPlayer(), cmdTarget->GetX(), cmdTarget->GetY(), cmdTarget->GetZ());
+						break;
+					}
 				}
 				if (sep->IsNumber(0))
 				{
@@ -5176,7 +5181,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 				break;
 			}
 		case COMMAND_CANNEDEMOTE:{
-				client->GetCurrentZone()->HandleEmote(client, command_parms->data);
+				client->GetCurrentZone()->HandleEmote(client->GetPlayer(), command_parms->data);
 				break;
 		}
 		case COMMAND_BROADCAST: {
@@ -6702,7 +6707,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
 		else if(sep->arg[4][0] && strncasecmp("move", sep->arg[0], 4) == 0 && sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3) && sep->IsNumber(4))
 		{
 			int16 from_index = atoi(sep->arg[1]);
-			sint16 to_slot = atoi(sep->arg[2]);
+			sint16 to_slot = player->ConvertSlotFromClient(atoi(sep->arg[2]), client->GetVersion());
 			sint32 bag_id = atol(sep->arg[3]);
 			int8 charges = atoi(sep->arg[4]);
 			Item* item = client->GetPlayer()->item_list.GetItemFromIndex(from_index);
@@ -6788,7 +6793,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
 				int8 appearance_equip = 0;
 				
 				if(sep->arg[2][0] && sep->IsNumber(2))
-					slot_id = atoi(sep->arg[2]);
+					slot_id = player->ConvertSlotFromClient(atoi(sep->arg[2]), client->GetVersion());
 				if(sep->arg[3][0] && sep->IsNumber(3))
 					unk3 = atoul(sep->arg[3]);
 				if(sep->arg[4][0] && sep->IsNumber(4))
@@ -6844,7 +6849,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
 				client->SimpleMessage(CHANNEL_COLOR_RED, "You may not unequip items while in combat.");
 			else
 			{
-				int16 index = atoi(sep->arg[1]);
+				int16 index = player->ConvertSlotFromClient(atoi(sep->arg[1]), client->GetVersion());
 				sint32 bag_id = -999;
 				int8 to_slot = 255;
 
@@ -6854,7 +6859,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
 						bag_id = atol(sep->arg[2]);
 
 					if(sep->IsNumber(3))
-						to_slot = atoi(sep->arg[3]);
+						to_slot = player->ConvertSlotFromClient(atoi(sep->arg[3]), client->GetVersion());
 				}
 
 				sint8 unk4 = 0;
@@ -6884,8 +6889,8 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
 				client->SimpleMessage(CHANNEL_COLOR_RED, "You may not swap items while in combat.");
 			}
 			else {
-				int16 index1 = atoi(sep->arg[1]);
-				int16 index2 = atoi(sep->arg[2]);
+				int16 index1 = client->GetPlayer()->ConvertSlotFromClient(atoi(sep->arg[1]), client->GetVersion());
+				int16 index2 = client->GetPlayer()->ConvertSlotFromClient(atoi(sep->arg[2]), client->GetVersion());
 				int8 type = 0;
 				if(sep->IsNumber(3))
 					type = atoul(sep->arg[3]); // type 0 is combat, 3 = appearance
@@ -6903,7 +6908,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
 		}
 		else if (sep->arg[2][0] && strncasecmp("pop", sep->arg[0], 3) == 0 && sep->IsNumber(1) && sep->IsNumber(2)) 
 		{
-			sint16 to_slot = atoi(sep->arg[1]);
+			sint16 to_slot = player->ConvertSlotFromClient(atoi(sep->arg[1]), client->GetVersion());
 			sint32 bag_id = atoi(sep->arg[2]);
 			Item* item = client->GetPlayer()->item_list.GetOverflowItem();
 			if (item) {
@@ -10201,16 +10206,17 @@ void Commands::Command_Test(Client* client, EQ2_16BitString* command_parms) {
 			if (packet2) {
 				packet2->setSubstructDataByName("reward_data", "unknown1", 255);
 				packet2->setSubstructDataByName("reward_data", "reward", "Quest Reward!");
-				packet2->setSubstructDataByName("reward_data", "coin", 150);
-				packet2->setSubstructDataByName("reward_data", "status_points", 5);
-				packet2->setSubstructDataByName("reward_data", "exp_bonus", 10);
-				packet2->setSubstructArrayLengthByName("reward_data", "num_rewards", 4);
-				Item* item = new Item(master_item_list.GetItem(36212));
+				packet2->setSubstructDataByName("reward_data", "max_coin", 0);
+				packet2->setSubstructDataByName("reward_data", "text", "Some custom text to mess things up?");
+				//packet2->setSubstructDataByName("reward_data", "status_points", 5);
+				//packet2->setSubstructDataByName("reward_data", "exp_bonus", 10);
+				packet2->setSubstructArrayLengthByName("reward_data", "num_rewards", 1);
+				Item* item = new Item(master_item_list.GetItem(9357));
 				packet2->setArrayDataByName("reward_id", item->details.item_id);
-				item->stack_count = 20;
+				//item->stack_count = 20;
 				packet2->setItemArrayDataByName("item", item, client->GetPlayer(), 0, 0, -1);
 				safe_delete(item);
-				item = new Item(master_item_list.GetItem(36685));
+				/*item = new Item(master_item_list.GetItem(36685));
 				item->stack_count = 20;
 				packet2->setArrayDataByName("reward_id", item->details.item_id);
 				packet2->setItemArrayDataByName("item", item, client->GetPlayer(), 1, 0, -1);
@@ -10220,7 +10226,7 @@ void Commands::Command_Test(Client* client, EQ2_16BitString* command_parms) {
 				packet2->setItemArrayDataByName("item", item, client->GetPlayer(), 2, 0, -1);
 				item = master_item_list.GetItem(75057);
 				packet2->setArrayDataByName("reward_id", item->details.item_id);
-				packet2->setItemArrayDataByName("item", item, client->GetPlayer(), 3, 0, -1);
+				packet2->setItemArrayDataByName("item", item, client->GetPlayer(), 3, 0, -1);*/
 				EQ2Packet* app = packet2->serialize();
 				if (sep->IsSet(2)) {
 					int16 offset = atoi(sep->arg[1]);
@@ -10544,6 +10550,28 @@ void Commands::Command_Test(Client* client, EQ2_16BitString* command_parms) {
 		else if (atoi(sep->arg[0]) == 28 && sep->IsNumber(1)) {
 			World::newValue = strtoull(sep->arg[1], NULL, 0);
 		}
+		else if (atoi(sep->arg[0]) == 29 && sep->IsNumber(1)) {
+			PacketStruct* packet = configReader.getStruct("WS_HearCastSpell", client->GetVersion());
+			if (packet) {
+				int32 caster_id = client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer());
+				int32 target_id = client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer());
+				
+				packet->setDataByName("spawn_id", caster_id);
+				packet->setArrayLengthByName("num_targets", 1);
+				packet->setArrayDataByName("target", target_id);
+				packet->setDataByName("num_targets", 1);
+				packet->setDataByName("spell_visual", strtoull(sep->arg[1], NULL, 0)); //result
+				packet->setDataByName("cast_time", sep->IsNumber(2) ? strtof(sep->arg[2], NULL)*.01f : 2500); //delay
+				packet->setDataByName("spell_id", 1);
+				packet->setDataByName("spell_level", 1);
+				packet->setDataByName("spell_tier", 1);
+				EQ2Packet* outapp = packet->serialize();
+				
+				DumpPacket(outapp);
+				client->QueuePacket(outapp);
+				safe_delete(packet);
+			}
+		}
 	}
 	else {
 			PacketStruct* packet2 = configReader.getStruct("WS_ExamineSpellInfo", client->GetVersion());
@@ -10977,6 +11005,9 @@ void Commands::Command_Wind(Client* client, Seperator* sep) {
 
 void Commands::Command_SendMerchantWindow(Client* client, Seperator* sep, bool sell) {
 	Spawn* spawn = client->GetPlayer()->GetTarget();
+	if(client->GetVersion() <= 546) {
+		sell = false; // doesn't support in the same way as AoM just open the normal buy/sell window
+	}
 	if(spawn) {
 		client->SetMerchantTransaction(spawn);
 		if (spawn->GetMerchantID() > 0 && spawn->IsClientInMerchantLevelRange(client)){
@@ -11101,6 +11132,12 @@ void Commands::Command_ConsumeFood(Client* client, Seperator* sep) {
 	{
 		Player* player = client->GetPlayer();
 		int8 slot = atoi(sep->arg[0]);
+		if (client->GetVersion() <= 283) {
+			slot += 4;
+		}
+		else if (client->GetVersion() <= 546) {
+			slot += 2;
+		}
 		Item* item = player->GetEquipmentList()->GetItem(slot);
 
 		if(client->CheckConsumptionAllowed(slot)) {

+ 4 - 0
EQ2/source/WorldServer/Entity.cpp

@@ -340,6 +340,8 @@ void Entity::MapInfoStruct()
 	get_int32_funcs["last_claim_time"] = l::bind(&InfoStruct::get_last_claim_time, &info_struct);
 	
 	get_int8_funcs["engaged_encounter"] = l::bind(&InfoStruct::get_engaged_encounter, &info_struct);
+	
+	get_int8_funcs["first_world_login"] = l::bind(&InfoStruct::get_first_world_login, &info_struct);
 
 /** SETS **/
 	set_string_funcs["name"] = l::bind(&InfoStruct::set_name, &info_struct, l::_1);
@@ -525,6 +527,8 @@ void Entity::MapInfoStruct()
 	set_int32_funcs["last_claim_time"] = l::bind(&InfoStruct::set_last_claim_time, &info_struct, l::_1);
 	
 	set_int8_funcs["engaged_encounter"] = l::bind(&InfoStruct::set_engaged_encounter, &info_struct, l::_1);
+	
+	set_int8_funcs["first_world_login"] = l::bind(&InfoStruct::set_first_world_login, &info_struct, l::_1);
 
 }
 

+ 17 - 0
EQ2/source/WorldServer/Entity.h

@@ -275,6 +275,9 @@ struct InfoStruct{
 		last_claim_time_ = 0;
 		
 		engaged_encounter_ = 0;
+		
+		first_world_login_ = 0;
+		reload_player_spells_ = 0;
 	}
 
 
@@ -461,6 +464,9 @@ struct InfoStruct{
 		last_claim_time_ = oldStruct->get_last_claim_time();
 		
 		engaged_encounter_ = oldStruct->get_engaged_encounter();
+		
+		first_world_login_ = oldStruct->get_first_world_login();
+		reload_player_spells_ = oldStruct->get_reload_player_spells();
 
 	}
 	//mutable std::shared_mutex mutex_;
@@ -665,6 +671,10 @@ struct InfoStruct{
 	
 	int8	get_engaged_encounter() { std::lock_guard<std::mutex> lk(classMutex); return engaged_encounter_; }
 	
+	int8	get_first_world_login() { std::lock_guard<std::mutex> lk(classMutex); return first_world_login_; }
+	
+	int8	get_reload_player_spells() { std::lock_guard<std::mutex> lk(classMutex); return reload_player_spells_; }
+	
 	void	set_name(std::string value) { std::lock_guard<std::mutex> lk(classMutex); name_ = value; }
 	
 	void	set_deity(std::string value) { std::lock_guard<std::mutex> lk(classMutex); deity_ = value; }
@@ -950,6 +960,10 @@ struct InfoStruct{
 	void	set_last_claim_time(int32 value) { std::lock_guard<std::mutex> lk(classMutex); last_claim_time_ = value; }
 	
 	void	set_engaged_encounter(int8 value) { std::lock_guard<std::mutex> lk(classMutex); engaged_encounter_ = value; }
+	
+	void	set_first_world_login(int8 value) { std::lock_guard<std::mutex> lk(classMutex); first_world_login_ = value; }
+	
+	void	set_reload_player_spells(int8 value) { std::lock_guard<std::mutex> lk(classMutex); reload_player_spells_ = value; }
 
 	void	ResetEffects(Spawn* spawn)
 	{
@@ -1155,6 +1169,9 @@ private:
 	
 	int8			engaged_encounter_;
 	
+	int8			first_world_login_;
+	int8			reload_player_spells_;
+	
 	// when PacketStruct is fixed for C++17 this should become a shared_mutex and handle read/write lock
 	std::mutex		classMutex;
 };

+ 25 - 66
EQ2/source/WorldServer/Items/Items.cpp

@@ -1800,8 +1800,7 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
 	}
 	if(show_name)
 		packet->setSubstructSubstructDataByName("header", "info_header", "show_name", show_name);
-	if (packet_type == 20)
-		cout << "";
+	
 	if(packet_type == 0)
 		packet->setSubstructSubstructDataByName("header", "info_header", "packettype", GetItemPacketType(packet->GetVersion()));
 	else
@@ -1839,7 +1838,10 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
 	else
 		packet->setSubstructDataByName("header_info", "unique_id", details.unique_id);
 	packet->setSubstructDataByName("header_info", "icon", details.icon);
-	packet->setSubstructDataByName("header_info", "tier", details.tier);
+	
+	if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) {
+		packet->setSubstructDataByName("header_info", "tier", details.tier);
+	}
  	packet->setSubstructDataByName("header_info", "flags", generic_info.item_flags);
 	packet->setSubstructDataByName("header_info", "flags2", generic_info.item_flags2);
 	if(item_stats.size() > 0){
@@ -1858,37 +1860,11 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
 			if (stat->stat_type == 9){
 				bluemod += 1;
 			}
-			if (stat->stat_type == 6 || stat->stat_type == 7){		//Convert stats to proper client
-				//if (client->GetVersion() >= 63137){  //TEST
-				//	tmp_subtype =stat->stat_subtype;
-				//}
-				if(stat->stat_type == 7){
-					tmp_subtype = stat->stat_subtype;
-				}
-				else if ((client->GetVersion() >= 63119) || client->GetVersion() == 61331){  //KA
-					tmp_subtype = world.GetItemStatKAValue(stat->stat_subtype);
-				}
-				else if(client->GetVersion() >= 60085 ) {
-					tmp_subtype = world.GetItemStatAOMValue(stat->stat_subtype);
-				}
-				else if (client->GetVersion() >= 57107){ //TOV
-					tmp_subtype = world.GetItemStatTOVValue(stat->stat_subtype);
-				}
-				else if (client->GetVersion() >= 1193){ //COE
-					tmp_subtype = world.GetItemStatCOEValue(stat->stat_subtype);
-					//tmp_subtype = stat->stat_subtype;
-				}
-				else if (client->GetVersion() >= 1096){ //DOV
-					tmp_subtype = world.GetItemStatDOVValue(stat->stat_subtype);
-					//tmp_subtype = stat->stat_subtype;  //comment for normal use
-				}
-			}
-			else
-				tmp_subtype = stat->stat_subtype;
+			
+			tmp_subtype = world.TranslateSlotSubTypeToClient(client, stat->stat_type, stat->stat_subtype);
+			
 			if (tmp_subtype == 255 ){
-				
 				dropstat += 1;
-				
 			}
 
 		}
@@ -1896,33 +1872,7 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
 		dropstat = 0;
 		for (int32 i = 0; i < item_stats.size(); i++) {
 			ItemStat* stat = item_stats[i];
-			
-			
-			
-			if (stat->stat_type == 6 || stat->stat_type == 7){		//Convert stats to proper client
-				if(stat->stat_type == 7){
-					tmp_subtype = stat->stat_subtype;
-				}
-				else if ((client->GetVersion() >= 63119) || client->GetVersion() == 61331){  //KA
-					tmp_subtype = world.GetItemStatKAValue(stat->stat_subtype);
-				}
-				else if(client->GetVersion() >= 60085 ) {
-					tmp_subtype = world.GetItemStatAOMValue(stat->stat_subtype);
-				}
-				else if (client->GetVersion() >= 57107){ //TOV
-					tmp_subtype = world.GetItemStatTOVValue(stat->stat_subtype);
-				}
-				else if (client->GetVersion() >= 1193){ //COE
-					tmp_subtype = world.GetItemStatCOEValue(stat->stat_subtype);
-					//tmp_subtype = stat->stat_subtype;
-				}
-				else if (client->GetVersion() >= 1096){ //DOV
-					tmp_subtype = world.GetItemStatDOVValue(stat->stat_subtype); //comment out for testing
-					//tmp_subtype = stat->stat_subtype;  //comment for normal use
-				}
-			}
-			else
-				tmp_subtype = stat->stat_subtype;
+			tmp_subtype = world.TranslateSlotSubTypeToClient(client, stat->stat_type, stat->stat_subtype);
 			if (tmp_subtype == 255 ){
 				
 				dropstat += 1;
@@ -3669,7 +3619,7 @@ void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item*
 		item->details.index = i;
 	}
 	packet->setSubstructArrayDataByName("items", "icon", item->details.icon, 0, i);
-	packet->setSubstructArrayDataByName("items", "slot_id", item->details.slot_id, 0, i);
+	packet->setSubstructArrayDataByName("items", "slot_id", player->ConvertSlotToClient(item->details.slot_id, client->GetVersion()), 0, i);
 	if (client->GetVersion() <= 1208) {
 		packet->setSubstructArrayDataByName("items", "count", (std::min)(item->details.count, (int16)255), 0, i);
 	}
@@ -3678,7 +3628,12 @@ void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item*
 	//packet->setSubstructArrayDataByName("items", "unknown4", 5, 0, i);
 	// need item level
 	packet->setSubstructArrayDataByName("items", "item_level", item->details.recommended_level , 0, i);
-	packet->setSubstructArrayDataByName("items", "tier", item->details.tier, 0, i);
+	
+	
+	if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) {
+		packet->setSubstructArrayDataByName("items", "tier", item->details.tier, 0, i);
+	}
+	
 	packet->setSubstructArrayDataByName("items", "num_slots", item->details.num_slots, 0, i);
 	// need empty slots
 	packet->setSubstructArrayDataByName("items", "item_id", item->details.item_id, 0, i);
@@ -4138,8 +4093,6 @@ EQ2Packet* EquipmentItemList::serialize(int16 version, Player* player){
 		packet_size = packet2->GetTotalPacketSize();
 		safe_delete(packet2);
 		int8 num_slots = NUM_SLOTS;
-		if (version <= 564)
-			num_slots = 22;
 		packet->setArrayLengthByName("item_count", num_slots);
 		if(!orig_packet){
 			xor_packet = new uchar[packet_size* num_slots];
@@ -4154,8 +4107,11 @@ EQ2Packet* EquipmentItemList::serialize(int16 version, Player* player){
 		int32 levelsLowered = (effective_level > 0 && effective_level < player->GetLevel()) ? player->GetLevel() - effective_level : 0;
 
 		for(int16 i=0;i<NUM_SLOTS;i++){
+			// override the item slot we currently check as the client has different ordering, we need to match it
+			int16 itemIdx = player->ConvertSlotFromClient(i, version);
+			
 			menu_data = 3;
-			item = items[i];
+			item = items[itemIdx];
 			if(item && item->details.item_id > 0){
 				if(item->slot_data.size() > 0)
 					menu_data -= ITEM_MENU_TYPE_GENERIC;
@@ -4218,10 +4174,13 @@ EQ2Packet* EquipmentItemList::serialize(int16 version, Player* player){
 					menu_data += ITEM_MENU_TYPE_MENTORED;
 				packet->setSubstructArrayDataByName("items", "menu_type", menu_data, 0, i);
 				packet->setSubstructArrayDataByName("items", "icon", item->details.icon, 0, i);
-				packet->setSubstructArrayDataByName("items", "slot_id", item->details.slot_id, 0, i);
+				packet->setSubstructArrayDataByName("items", "slot_id", player->ConvertSlotToClient(item->details.slot_id, version), 0, i);
 				packet->setSubstructArrayDataByName("items", "count", item->details.count, 0, i);
 				// item level needed here
-				packet->setSubstructArrayDataByName("items", "tier", item->details.tier, 0, i);
+				
+				if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) {
+					packet->setSubstructArrayDataByName("items", "tier", item->details.tier, 0, i);
+				}
 				packet->setSubstructArrayDataByName("items", "num_slots", item->details.num_slots, 0, i);
 				//empty slots needed here
 				packet->setSubstructArrayDataByName("items", "item_id", item->details.item_id, 0, i);

+ 1 - 1
EQ2/source/WorldServer/LuaFunctions.cpp

@@ -8291,7 +8291,7 @@ int EQ2Emu_lua_RemoveSkill(lua_State* state) {
 		if (player_spawn && player_spawn->IsPlayer()) {
 			Player* player = (Player*)player_spawn;
 			if (player->skill_list.HasSkill(skill_id)) {
-				player->RemovePlayerSkill(skill_id);
+				player->RemovePlayerSkill(skill_id, true);
 				if (!more_to_remove) {
 					Client* client = player->GetClient();
 					if (client) {

+ 16 - 10
EQ2/source/WorldServer/Player.cpp

@@ -230,15 +230,21 @@ void Player::DestroyQuests(){
 	MPlayerQuests.writelock(__FUNCTION__, __LINE__);
 	map<int32, Quest*>::iterator itr;
 	for(itr = completed_quests.begin(); itr != completed_quests.end(); itr++){
-		safe_delete(itr->second);
+		if(itr->second) {
+			safe_delete(itr->second);
+		}
 	}
 	completed_quests.clear();
 	for(itr = player_quests.begin(); itr != player_quests.end(); itr++){
-		safe_delete(itr->second);
+		if(itr->second) {
+			safe_delete(itr->second);
+		}
 	}
 	player_quests.clear();
 	for(itr = pending_quests.begin(); itr != pending_quests.end(); itr++){
-		safe_delete(itr->second);
+		if(itr->second) {
+			safe_delete(itr->second);
+		}
 	}
 	pending_quests.clear();
 	MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__);
@@ -1336,7 +1342,7 @@ bool Player::DamageEquippedItems(int8 amount, Client* client) {
 	return ret;
 }
 
-int8 Player::ConvertSlotToClient(int8 slot, int16 version) {
+int16 Player::ConvertSlotToClient(int8 slot, int16 version) {
 	if (version <= 283) {
 		if (slot == EQ2_FOOD_SLOT)
 			slot = EQ2_ORIG_FOOD_SLOT;
@@ -1356,7 +1362,7 @@ int8 Player::ConvertSlotToClient(int8 slot, int16 version) {
 	return slot;
 }
 
-int8 Player::ConvertSlotFromClient(int8 slot, int16 version) {
+int16 Player::ConvertSlotFromClient(int8 slot, int16 version) {
 	if (version <= 283) {
 		if (slot == EQ2_ORIG_FOOD_SLOT)
 			slot = EQ2_FOOD_SLOT;
@@ -1447,7 +1453,7 @@ vector<EQ2Packet*>	Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, in
 			to_item = item_list.items[bag_id][BASE_EQUIPMENT][slot];
 
 		bool canEquipToSlot = false;
-		if (to_item && equipList->CanItemBeEquippedInSlot(to_item, ConvertSlotFromClient(item->details.slot_id, version))) {
+		if (to_item && equipList->CanItemBeEquippedInSlot(to_item, item->details.slot_id)) {
 			canEquipToSlot = true;
 		}
 		item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
@@ -1702,7 +1708,7 @@ bool Player::CanEquipItem(Item* item, int8 slot) {
 					client->Message(0, "%s requires a good race.", item->name.c_str());
 			}
 			else if (item->IsArmor() || item->IsWeapon() || item->IsFood() || item->IsRanged() || item->IsShield() || item->IsBauble() || item->IsAmmo() || item->IsThrown()) {
-				if ((item->generic_info.skill_req1 == 0 || item->generic_info.skill_req1 == 0xFFFFFFFF || skill_list.HasSkill(item->generic_info.skill_req1)) && (item->generic_info.skill_req2 == 0 || item->generic_info.skill_req2 == 0xFFFFFFFF || skill_list.HasSkill(item->generic_info.skill_req2))) {
+				if (((item->generic_info.skill_req1 == 0 || item->generic_info.skill_req1 == 0xFFFFFFFF || skill_list.HasSkill(item->generic_info.skill_req1)) && (item->generic_info.skill_req2 == 0 || item->generic_info.skill_req2 == 0xFFFFFFFF || skill_list.HasSkill(item->generic_info.skill_req2)))) {
 					int16 override_level = item->GetOverrideLevel(GetAdventureClass(), GetTradeskillClass());
 					if (override_level > 0 && override_level <= GetLevel())
 						return true;
@@ -1738,7 +1744,6 @@ vector<EQ2Packet*> Player::EquipItem(int16 index, int16 version, int8 appearance
 	}
 	Item* item = item_list.indexed_items[index];
 	int8 orig_slot_id = slot_id;
-	slot_id = ConvertSlotFromClient(slot_id, version);
 	if (item) {
 		if(orig_slot_id == 255 && item->CheckFlag2(APPEARANCE_ONLY)) {
 			appearance_type = 1;
@@ -1847,7 +1852,7 @@ vector<EQ2Packet*> Player::EquipItem(int16 index, int16 version, int8 appearance
 				lua_interface->RunItemScript(item->GetItemScript(), "equipped", item, this);
 
 			item_list.RemoveItem(item);
-			equipList->SetItem(ConvertSlotToClient(slot, version), item);
+			equipList->SetItem(slot, item);
 			item->save_needed = true;
 			packets.push_back(item->serialize(version, false));
 			SetEquipment(item);
@@ -4876,7 +4881,8 @@ int8 Player::CheckQuestFlag(Spawn* spawn){
 	map<int32, Quest*>::iterator itr;
 	MPlayerQuests.readlock(__FUNCTION__, __LINE__);
 	for(itr = player_quests.begin(); itr != player_quests.end(); itr++){
-		if(itr->second->CheckQuestChatUpdate(spawn->GetDatabaseID(), false))
+		// must make sure the quest ptr is alive or nullptr
+		if(itr->second && itr->second->CheckQuestChatUpdate(spawn->GetDatabaseID(), false))
 			ret = 2;
 	}
 	MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__);

+ 2 - 2
EQ2/source/WorldServer/Player.h

@@ -511,8 +511,8 @@ public:
 	bool CanEquipItem(Item* item, int8 slot);
 	void SetEquippedItemAppearances();
 	vector<EQ2Packet*>	UnequipItem(int16 index, sint32 bag_id, int8 slot, int16 version, int8 appearance_type = 0, bool send_item_updates = true);
-	int8 ConvertSlotToClient(int8 slot, int16 version);
-	int8 ConvertSlotFromClient(int8 slot, int16 version);
+	int16 ConvertSlotToClient(int8 slot, int16 version);
+	int16 ConvertSlotFromClient(int8 slot, int16 version);
 	EQ2Packet* SwapEquippedItems(int8 slot1, int8 slot2, int16 version, int16 equiptype);
 	EQ2Packet*	RemoveInventoryItem(int8 bag_slot, int8 slot);
 	EQ2Packet*	SendInventoryUpdate(int16 version);

+ 1 - 0
EQ2/source/WorldServer/Rules/Rules.cpp

@@ -310,6 +310,7 @@ void RuleManager::Init()
 	RULE_INIT(R_World, EnforceRacialAlignment, "1");
 	RULE_INIT(R_World, MemoryCacheZoneMaps, "0");					// 0 disables caching the zone maps in memory, too many individual/unique zones entered may cause a lot of memory build up
 	RULE_INIT(R_World, AutoLockEncounter, "0");						// When set to 0 we require player to attack to lock the encounter, otherwise if 1 then npc can auto lock encounter
+	RULE_INIT(R_World, DisplayItemTiers, "1");						// Display item tiers when set to 1, otherwise do not
 	
 	//INSERT INTO `ruleset_details`(`id`, `ruleset_id`, `rule_category`, `rule_type`, `rule_value`, `description`) VALUES (NULL, '1', 'R_World', '', '', '')
 

+ 1 - 0
EQ2/source/WorldServer/Rules/Rules.h

@@ -163,6 +163,7 @@ enum RuleType {
 	EnforceRacialAlignment,
 	MemoryCacheZoneMaps,
 	AutoLockEncounter,
+	DisplayItemTiers,
 
 	/* ZONE */
 	MinZoneLevelOverrideStatus,

+ 6 - 2
EQ2/source/WorldServer/Spawn.cpp

@@ -2369,7 +2369,7 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
 	if (GetMerchantID() > 0)
 		packet->setDataByName("merchant", 1);
 		
-	packet->setDataByName("effective_level", spawn->IsEntity() && ((Entity*)spawn)->GetInfoStruct()->get_effective_level() != 0 ? (int8)((Entity*)spawn)->GetInfoStruct()->get_effective_level() : (int8)GetLevel());
+	packet->setDataByName("effective_level", IsEntity() && ((Entity*)this)->GetInfoStruct()->get_effective_level() != 0 ? (int8)((Entity*)this)->GetInfoStruct()->get_effective_level() : (int8)GetLevel());
 	packet->setDataByName("level", (int8)GetLevel());
 	packet->setDataByName("unknown4", (int8)GetLevel());
 	packet->setDataByName("difficulty", appearance.encounter_level); //6);
@@ -2690,6 +2690,9 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
 				packet->setDataByName("camping", 1);
 			if ((appearance.activity_status & ACTIVITY_STATUS_LFG) > 0)
 				packet->setDataByName("lfg", 1);
+			if (EngagedInCombat()) {
+				packet->setDataByName("auto_attack", 1);
+			}
 		}
 		if ((appearance.activity_status & ACTIVITY_STATUS_SOLID) > 0)
 			packet->setDataByName("solid_object", 1);
@@ -2712,7 +2715,8 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
 	//	packet->setDataByName("follow_target", 0xFFFFFFFF);
 	//}
 	
-	if (GetTarget() && GetTarget()->GetTargetable())
+	// i think this is used in DoF as a way to make a client say they are in combat with this target and cannot camp, it forces you to stand up if self spawn sends this data
+	if ((version > 546 || spawn != this) && GetTarget() && GetTarget()->GetTargetable())
 		packet->setDataByName("target_id", ((spawn->GetIDWithPlayerSpawn(GetTarget()) * -1) - 1));
 	else
 		packet->setDataByName("target_id", 0xFFFFFFFF);

+ 2 - 2
EQ2/source/WorldServer/Spells.cpp

@@ -1128,7 +1128,7 @@ EQ2Packet* Spell::SerializeAASpell(Client* client, int8 tier, AltAdvanceData* da
 	*/
 }
 
-EQ2Packet* Spell::SerializeSpell(Client* client, bool display, bool trait_display, int8 packet_type, int8 sub_packet_type, const char* struct_name) {
+EQ2Packet* Spell::SerializeSpell(Client* client, bool display, bool trait_display, int8 packet_type, int8 sub_packet_type, const char* struct_name, bool send_partial_packet) {
 	int16 version = 1;
 	if (client)
 		version = client->GetVersion();
@@ -1137,7 +1137,7 @@ EQ2Packet* Spell::SerializeSpell(Client* client, bool display, bool trait_displa
 	if (version <= 546) {
 		if (packet_type == 1)
 			struct_name = "WS_ExamineEffectInfo";
-		else if (!display && version<=283)
+		else if (!display && (version<=283 || send_partial_packet))
 			struct_name = "WS_ExaminePartialSpellInfo";
 		else
 			struct_name = "WS_ExamineSpellInfo";

+ 1 - 1
EQ2/source/WorldServer/Spells.h

@@ -324,7 +324,7 @@ public:
 	Spell();
 	Spell(SpellData* in_spell);
 	Spell(Spell* host_spell, bool unique_spell = true);
-	EQ2Packet* SerializeSpell(Client* client, bool display, bool trait_display = false, int8 packet_type = 0, int8 sub_packet_type = 0, const char* struct_name = 0);
+	EQ2Packet* SerializeSpell(Client* client, bool display, bool trait_display = false, int8 packet_type = 0, int8 sub_packet_type = 0, const char* struct_name = 0, bool send_partial_packet = false);
 	EQ2Packet* SerializeSpecialSpell(Client* client, bool display, int8 packet_type = 0, int8 sub_packet_type = 0);
 	EQ2Packet* SerializeAASpell(Client* client,int8 tier, AltAdvanceData* data, bool display, bool trait_display = false, int8 packet_type = 0, int8 sub_packet_type = 0, const char* struct_name = 0);
 	void AddSpellLevel(int8 adventure_class, int8 tradeskill_class, int16 level);

+ 57 - 23
EQ2/source/WorldServer/World.cpp

@@ -2244,6 +2244,58 @@ sint16 World::GetItemStatKAValue(sint16 subtype) {
 	return (ka_itemstat_conversion[subtype] - 600);
 }
 
+int8 World::TranslateSlotSubTypeToClient(Client* client, int8 stat_type, sint16 sub_type) {
+	int8 new_subtype = (int8)sub_type;
+	switch(stat_type) {
+		case 2: {
+			if(client->GetVersion() <= 546) {
+				if(sub_type == (ITEM_STAT_VS_POISON-200)) // poison
+					new_subtype = 9;
+				if(sub_type == (ITEM_STAT_VS_DISEASE-200)) // disease
+					new_subtype = 8;
+				else if(sub_type == (ITEM_STAT_VS_COLD-200)) // cold
+					new_subtype = 4;
+				else if(sub_type == (ITEM_STAT_VS_HEAT-200) || sub_type == (ITEM_STAT_VS_MAGIC-200))
+					new_subtype += 2;
+				else if(sub_type == (ITEM_STAT_VS_MENTAL-200) || sub_type == (ITEM_STAT_VS_DIVINE-200))
+					new_subtype -= 2;
+			}
+			else if(client->GetVersion() >= 60085) { // AoM era since its the client we support
+				if(sub_type == (ITEM_STAT_VS_MENTAL-200) || sub_type == (ITEM_STAT_VS_DIVINE-200) || sub_type == (ITEM_STAT_VS_COLD-200)) {
+					new_subtype = 255; // omit client cannot properly display
+				}
+			}
+			break;
+		}
+		case 6:
+		case 7: {
+			if(stat_type == 7){
+				new_subtype = sub_type;
+			}
+			else if ((client->GetVersion() >= 63119) || client->GetVersion() == 61331){  //KA
+				new_subtype = world.GetItemStatKAValue(sub_type);
+			}
+			else if(client->GetVersion() >= 60085 ) {
+				new_subtype = world.GetItemStatAOMValue(sub_type);
+			}
+			else if (client->GetVersion() >= 57107){ //TOV
+				new_subtype = world.GetItemStatTOVValue(sub_type);
+			}
+			else if (client->GetVersion() >= 1193){ //COE
+				new_subtype = world.GetItemStatCOEValue(sub_type);
+				//tmp_subtype = stat->stat_subtype;
+			}
+			else if (client->GetVersion() >= 1096){ //DOV
+				new_subtype = world.GetItemStatDOVValue(sub_type); //comment out for testing
+				//tmp_subtype = stat->stat_subtype;  //comment for normal use
+			}
+			break;
+		}
+	}
+	
+	return new_subtype;
+}
+
 bool World::CheckTempBugCRC(char* msg)
 {
 	MBugReport.writelock();
@@ -2292,16 +2344,14 @@ void World::SyncCharacterAbilities(Client* client)
 	int8 secondaryClass = classes.GetSecondaryBaseClass(client->GetPlayer()->GetAdventureClass());
 	int8 actualClass = client->GetPlayer()->GetAdventureClass();
 	int8 baseRace = client->GetPlayer()->GetRace();
-
-	bool reloadSpells = false;
-
+	
 	multimap<int8, multimap<int8, StartingSkill>*>::iterator skill_itr = starting_skills.begin();
 	multimap<int8, multimap<int8, StartingSpell>*>::iterator spell_itr = starting_spells.begin();
-	bool isComplete = false;
+	bool isProcessing = false;
 	int8 wait_iterations = 0; // wait 5 iterations and give up if db takes too long
 	do
 	{
-		bool isProcessing = false;
+		isProcessing = false;
 		if (skill_itr != starting_skills.end())
 		{
 			isProcessing = true;
@@ -2358,32 +2408,16 @@ void World::SyncCharacterAbilities(Client* client)
 								client->GetCharacterID(), child_itr->second.spell_id, child_itr->second.tier, child_itr->second.knowledge_slot);
 
 							// reload spells, we don't know the spellbook or timer info
-							reloadSpells = true;
+							client->GetPlayer()->GetInfoStruct()->set_reload_player_spells(1);
 						}
 					}
 				}
 			}
 			spell_itr++;
 		}
-
-		// poor mans async kill just in case, we don't want a client stuck here, they can get the spells on next zone
-		if (!isProcessing)
-		{
-			bool isDBActive = database.IsActiveQuery(client->GetCharacterID());
-			if (isDBActive && wait_iterations < 5)
-			{
-				wait_iterations++;
-			}
-			else
-				isComplete = true;
-		}
-
-	} while (!isComplete);
+	} while (isProcessing);
 
 	MStartingLists.releasereadlock();
-
-	if (reloadSpells)
-		database.LoadCharacterSpells(client->GetCharacterID(), client->GetPlayer());
 }
 
 void World::LoadStartingLists()

+ 1 - 0
EQ2/source/WorldServer/World.h

@@ -573,6 +573,7 @@ public:
 	sint16 GetItemStatCOEValue(sint16 subtype);
 	sint16 GetItemStatKAValue(sint16 subtype);
 	sint16 GetItemStatTESTValue(sint16 subtype);
+	int8 TranslateSlotSubTypeToClient(Client* client, int8 stat_type, sint16 sub_type);
 
 	vector<string> biography;
 

+ 6 - 3
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -1748,7 +1748,7 @@ bool WorldDatabase::loadCharacter(const char* ch_name, int32 account_id, Client*
 	MYSQL_ROW row, row4;
 	int32 id = 0;
 	query.escaped_name = getEscapeString(ch_name);
-	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, current_zone_id, x, y, z, heading, admin_status, race, model_type, class, deity, level, gender, tradeskill_class, tradeskill_level, wing_type, hair_type, chest_type, legs_type, soga_wing_type, soga_hair_type, soga_chest_type, soga_legs_type, 0xFFFFFFFF - crc32(name), facial_hair_type, soga_facial_hair_type, instance_id, group_id, last_saved, DATEDIFF(curdate(), created_date) as accage, alignment FROM characters where name='%s' and account_id=%i AND deleted = 0", query.escaped_name, account_id);
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, current_zone_id, x, y, z, heading, admin_status, race, model_type, class, deity, level, gender, tradeskill_class, tradeskill_level, wing_type, hair_type, chest_type, legs_type, soga_wing_type, soga_hair_type, soga_chest_type, soga_legs_type, 0xFFFFFFFF - crc32(name), facial_hair_type, soga_facial_hair_type, instance_id, group_id, last_saved, DATEDIFF(curdate(), created_date) as accage, alignment, first_world_login FROM characters where name='%s' and account_id=%i AND deleted = 0", query.escaped_name, account_id);
 	// no character found
 	if ( result == NULL ) {
 		LogWrite(PLAYER__ERROR, 0, "Player", "Error loading character for '%s'", ch_name);
@@ -1821,6 +1821,8 @@ SOGA chars looked ok in LoginServer screen tho... odd.
 			client->GetPlayer()->GetPlayerInfo()->SetAccountAge(atoi(row[29]));
 
 		client->GetPlayer()->GetInfoStruct()->set_alignment(atoi(row[30]));
+		
+		client->GetPlayer()->GetInfoStruct()->set_first_world_login(atoi(row[31]));
 
 		LoadCharacterFriendsIgnoreList(client->GetPlayer());
 		MYSQL_RES* result4 = query4.RunQuery2(Q_SELECT, "SELECT `guild_id` FROM `guild_members` WHERE `char_id`=%u", id);
@@ -2367,7 +2369,7 @@ int32 WorldDatabase::SaveCharacter(PacketStruct* create, int32 loginID){
 		auto_admin_status = 0;
 	}
 
-	string create_char = string("Insert into characters (account_id, server_id, name, race, class, gender, deity, body_size, body_age, soga_wing_type, soga_chest_type, soga_legs_type, soga_hair_type, soga_model_type, legs_type, chest_type, wing_type, hair_type, model_type, facial_hair_type, soga_facial_hair_type, created_date, last_saved, admin_status) values(%i, %i, '%s', %i, %i, %i, %i, %f, %f, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, now(), unix_timestamp(), %i)");
+	string create_char = string("Insert into characters (account_id, server_id, name, race, class, gender, deity, body_size, body_age, soga_wing_type, soga_chest_type, soga_legs_type, soga_hair_type, soga_model_type, legs_type, chest_type, wing_type, hair_type, model_type, facial_hair_type, soga_facial_hair_type, created_date, last_saved, admin_status, first_world_login) values(%i, %i, '%s', %i, %i, %i, %i, %f, %f, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, now(), unix_timestamp(), %i, 1)");
 
 	query.RunQuery2(Q_INSERT, create_char.c_str(), 
 						loginID, 
@@ -3406,7 +3408,7 @@ void WorldDatabase::LoadRevivePoints(vector<RevivePoint*>* revive_points, int32
 		return;
 	}
 	Query query;
-	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT respawn_zone_id, location_name, safe_x, safe_y, safe_z, heading FROM revive_points where zone_id=%u ORDER BY id asc", zone_id);
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT respawn_zone_id, location_name, safe_x, safe_y, safe_z, heading, always_included FROM revive_points where zone_id=%u ORDER BY id asc", zone_id);
 	if(revive_points && result && mysql_num_rows(result) > 0){
 		MYSQL_ROW row;
 		int32 id = 0;
@@ -3420,6 +3422,7 @@ void WorldDatabase::LoadRevivePoints(vector<RevivePoint*>* revive_points, int32
 			point->y = atof(row[3]);
 			point->z = atof(row[4]);
 			point->heading = atof(row[5]);
+			point->always_included = atoul(row[6]);
 			revive_points->push_back(point);
 			id++;
 		}

+ 197 - 61
EQ2/source/WorldServer/client.cpp

@@ -347,22 +347,6 @@ void Client::SendLoginInfo() {
 
 	int32 count = 0;
 
-	if(!GetPlayer()->IsReturningFromLD())
-	{
-		LogWrite(CCLIENT__DEBUG, 0, "Client", "Loading Character Skills for player '%s'...", player->GetName());
-		count = database.LoadCharacterSkills(GetCharacterID(), player);
-
-		LogWrite(CCLIENT__DEBUG, 0, "Client", "Loading Character Spells for player '%s'...", player->GetName());
-		count = database.LoadCharacterSpells(GetCharacterID(), player);
-	}
-	else
-	{
-		LogWrite(CCLIENT__INFO, 0, "Client", "Player is returning from linkdead status (Player does not need reload) thus skipping database loading for '%s'...", player->GetName());
-	}
-	
-	// get the latest character starting skills / spells, may have been updated after character creation
-	world.SyncCharacterAbilities(this);
-
 	if(!GetPlayer()->IsReturningFromLD())
 	{
 		count = database.LoadCharacterTitles(GetCharacterID(), player);
@@ -513,8 +497,6 @@ void Client::SendPlayerDeathWindow()
 				if (point->id == 0xFFFFFFFF)//tmp location
 					safe_delete(point);
 			}
-			// done with the revive points so lets free up the pointer
-			safe_delete(results);
 #if EQDEBUG >= 3
 			LogWrite(CCLIENT__DEBUG, 0, "Client", "WS_DeathWindow Packet:");
 			packet->PrintPacket();
@@ -523,6 +505,8 @@ void Client::SendPlayerDeathWindow()
 			QueuePacket(outapp);
 			safe_delete(packet);
 		}
+		// done with the revive points so lets free up the pointer
+		safe_delete(results);
 	}
 
 }
@@ -762,8 +746,7 @@ void Client::SendCharInfo() {
 
 	ClientPacketFunctions::SendSkillBook(this);
 	if (!IsReloadingZone() && !player->IsResurrecting()) {
-		if(GetVersion() > 546) //we will send this later on for 546 and below
-			ClientPacketFunctions::SendUpdateSpellBook(this);
+		ClientPacketFunctions::SendUpdateSpellBook(this);
 	}
 	else {
 		player->SetResurrecting(false);
@@ -1470,7 +1453,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 		break;
 	}
 	case OP_DoneLoadingUIResourcesMsg: {
-		ClientPacketFunctions::SendUpdateSpellBook(this);
+		//ClientPacketFunctions::SendUpdateSpellBook(this);
 		EQ2Packet* app = new EQ2Packet(OP_DoneLoadingUIResourcesMsg, 0, 0);
 		QueuePacket(app);
 		if(!player_loading_complete)
@@ -2919,7 +2902,7 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
 			lua_interface->FindCustomSpellUnlock();
 		}
 		
-		if (spell && !CountSentSpell(id, tier)) {
+		if (spell && !CountSentSpell(spell->GetSpellID(), spell->GetSpellTier())) {
 			if (!spell->IsCopiedSpell())
 				SetSentSpell(spell->GetSpellID(), spell->GetSpellTier());
 
@@ -2927,6 +2910,11 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
 			//DumpPacket(app);
 			QueuePacket(app);
 		}
+		else if(spell && GetVersion() <=546 && CountSentSpell(spell->GetSpellID(), spell->GetSpellTier())) {
+			EQ2Packet* app = spell->SerializeSpell(this, display, trait_display, GetVersion() <= 546 ? true : false);
+			//DumpPacket(app);
+			QueuePacket(app);
+		}
 	}
 	else if (type == 0) {
 		request = configReader.getStruct("WS_ExamineInfoItemRequest", GetVersion());
@@ -3194,12 +3182,53 @@ bool Client::Process(bool zone_process) {
 	}
 
 	switch(new_client_login) {
-		case NewLoginState::LOGIN_ALLOWED: {
+		case NewLoginState::LOGIN_SEND: {
 			LogWrite(CCLIENT__DEBUG, 0, "Client", "SendLoginInfo to new client...");
 			SendLoginInfo();
+			
 			new_client_login = NewLoginState::LOGIN_NONE;
 			break;
 		}
+		case NewLoginState::LOGIN_INITIAL_LOAD: {
+			bool isDBActive = database.IsActiveQuery(GetCharacterID());
+			
+			// wait for starting skills/spells to load and reload from DB.
+			if(!isDBActive) {
+				if (GetPlayer()->GetInfoStruct()->get_reload_player_spells()) {
+					database.LoadCharacterSpells(GetCharacterID(), GetPlayer());
+					GetPlayer()->GetInfoStruct()->set_reload_player_spells(0);
+				}
+				new_client_login = NewLoginState::LOGIN_SEND;
+			}
+			break;
+		}
+		case NewLoginState::LOGIN_ALLOWED: {
+			int32 count = 0;
+
+			if(!GetPlayer()->IsReturningFromLD())
+			{
+				LogWrite(CCLIENT__DEBUG, 0, "Client", "Loading Character Skills for player '%s'...", player->GetName());
+				count = database.LoadCharacterSkills(GetCharacterID(), player);
+
+				LogWrite(CCLIENT__DEBUG, 0, "Client", "Loading Character Spells for player '%s'...", player->GetName());
+				count = database.LoadCharacterSpells(GetCharacterID(), player);
+			}
+			else
+			{
+				LogWrite(CCLIENT__INFO, 0, "Client", "Player is returning from linkdead status (Player does not need reload) thus skipping database loading for '%s'...", player->GetName());
+			}
+			
+			// get the latest character starting skills / spells, may have been updated after character creation
+			if(GetPlayer()->GetInfoStruct()->get_first_world_login()) {
+				world.SyncCharacterAbilities(this);
+				Query query;
+				query.AddQueryAsync(GetCharacterID(), &database, Q_UPDATE, "UPDATE characters set first_world_login = 0 where id=%u", GetCharacterID());
+				GetPlayer()->GetInfoStruct()->set_first_world_login(0);
+			}
+			
+			new_client_login = NewLoginState::LOGIN_INITIAL_LOAD;
+			break;
+		}
 		case NewLoginState::LOGIN_DELAYED: {
 			if(!delay_msg_timer.Enabled() || delay_msg_timer.Check()) {
 				LogWrite(CCLIENT__INFO, 0, "Client", "Wait for zone %s to load for new client %s...", GetCurrentZone()->GetZoneName(), GetPlayer()->GetName());
@@ -5997,11 +6026,13 @@ void Client::AcceptQuest(int32 quest_id) {
 	MPendingQuestAccept.lock();
 	if (player->pending_quests.count(quest_id) > 0) {
 		Quest* quest = player->pending_quests[quest_id];	
-		player->pending_quests.erase(quest->GetQuestID());
-		AddPlayerQuest(quest);
-		GetCurrentZone()->SendQuestUpdates(this);
+		if(quest) {
+			player->pending_quests.erase(quest->GetQuestID());
+			AddPlayerQuest(quest);
+			GetCurrentZone()->SendQuestUpdates(this);
 
-		GetPlayer()->UpdateQuestCompleteCount(quest_id);
+			GetPlayer()->UpdateQuestCompleteCount(quest_id);
+		}
 	}
 	MPendingQuestAccept.unlock();
 }
@@ -6398,16 +6429,37 @@ void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* reward
 		}
 		if(text)
 			packet2->setSubstructDataByName("reward_data", "text", text);
-		if(rewards){
-			packet2->setSubstructArrayLengthByName("reward_data", "num_rewards", rewards->size());
-			for (int i = 0; i < rewards->size(); i++) {
-				Item* item = rewards->at(i);
+		
+		
+		std::vector<Item*> items;
+		quest->GetTmpRewardItemsByID(&items);
+		if(rewards || items.size() > 0){
+			int32 item_count = items.size();
+			item_count += rewards ? rewards->size() : 0;
+			packet2->setSubstructArrayLengthByName("reward_data", "num_rewards", item_count);
+			int i = 0;
+			if(rewards) {
+				for (i = 0; i < rewards->size(); i++) {
+					Item* item = rewards->at(i);
+					if (item) {
+						packet2->setArrayDataByName("reward_id", item->details.item_id, i);
+						packet2->setItemArrayDataByName("item", item, player, i, 0, -1);
+					}
+					if(!quest) //this entire function is either for version <=546 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards
+						player->AddPendingItemReward(item); //item reference will be deleted after the player accepts it
+				}
+			}
+			
+			for (int j = 0; j < items.size(); j++) {
+				Item* item = items.at(j);
 				if (item) {
 					packet2->setArrayDataByName("reward_id", item->details.item_id, i);
 					packet2->setItemArrayDataByName("item", item, player, i, 0, -1);
 				}
 				if(!quest) //this entire function is either for version <=546 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards
 					player->AddPendingItemReward(item); //item reference will be deleted after the player accepts it
+				
+				i++;
 			}
 		}
 		if (selectable_rewards) {
@@ -7711,7 +7763,9 @@ void Client::SendBuyMerchantList(bool sell) {
 					else
 						tmp_level = item->generic_info.tradeskill_default_level;
 					packet->setArrayDataByName("level", tmp_level, i);
-					packet->setArrayDataByName("tier", item->details.tier, i);
+					if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) {
+						packet->setArrayDataByName("tier", item->details.tier, i);
+					}
 					packet->setArrayDataByName("item_id2", item->details.item_id, i);
 					item_difficulty = player->GetArrowColor(tmp_level);
 					if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY)
@@ -7898,7 +7952,10 @@ void Client::SendSellMerchantList(bool sell) {
 					else
 						tmp_level = item->generic_info.tradeskill_default_level;
 					packet->setArrayDataByName("level", item->details.recommended_level, i);
-					packet->setArrayDataByName("tier", item->details.tier, i);
+					
+					if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) {
+						packet->setArrayDataByName("tier", item->details.tier, i);
+					}
 					packet->setArrayDataByName("item_id2", item->details.item_id, i);
 					item_difficulty = player->GetArrowColor(tmp_level);
 					if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY)
@@ -7975,7 +8032,9 @@ void Client::SendBuyBackList(bool sell) {
 					else
 						tmp_level = master_item->generic_info.tradeskill_default_level;
 					packet->setArrayDataByName("level", tmp_level, i);
-					packet->setArrayDataByName("tier", master_item->details.tier, i);
+					if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) {
+						packet->setArrayDataByName("tier", master_item->details.tier, i);
+					}
 					packet->setArrayDataByName("item_id2", master_item->details.item_id, i);
 					item_difficulty = player->GetArrowColor(tmp_level);
 					if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY)
@@ -8031,18 +8090,23 @@ void Client::SendRepairList() {
 			vector<Item*>::iterator itr;
 			for (itr = repairable_items->begin(); itr != repairable_items->end(); itr++, i++) {
 				item = *itr;
+				
 				packet->setArrayDataByName("item_name", item->name.c_str(), i);
 				packet->setArrayDataByName("price", item->CalculateRepairCost(), i);
 				packet->setArrayDataByName("item_id", item->details.item_id, i);
 				packet->setArrayDataByName("stack_size", item->details.count, i);
 				packet->setArrayDataByName("icon", item->details.icon, i);
+				
 				/*if (item->generic_info.adventure_default_level > 0)
 					tmp_level = item->generic_info.adventure_default_level;
 				else
 					tmp_level = item->generic_info.tradeskill_default_level;
 				packet->setArrayDataByName("level", tmp_level, i);*/
 				packet->setArrayDataByName("level", item->generic_info.adventure_default_level, i);
-				packet->setArrayDataByName("tier", item->details.tier, i);
+				
+				if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) {
+					packet->setArrayDataByName("tier", item->details.tier, i);
+				}
 				packet->setArrayDataByName("item_id2", item->details.item_id, i);
 				item_difficulty = player->GetArrowColor(item->generic_info.adventure_default_level);
 				if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY)
@@ -8060,13 +8124,22 @@ void Client::SendRepairList() {
 					packet->setArrayDataByName("description", item->description.c_str(), i);
 			}
 			if (GetVersion() <= 546) {
-				packet->setDataByName("type", 16);
+				packet->setDataByName("type", 0);
 			}
 			else {
 				packet->setDataByName("type", 96);
 			}
 			EQ2Packet* outapp = packet->serialize();
+			
+			//DumpPacket(outapp);
 			QueuePacket(outapp);
+			
+			if (GetVersion() <= 546) {
+				packet->setDataByName("type", 16);
+				EQ2Packet* outapp2 = packet->serialize();
+				QueuePacket(outapp2);
+			}
+			
 			safe_delete(packet);
 		}
 		safe_delete(repairable_items);
@@ -8112,7 +8185,10 @@ void Client::ShowLottoWindow() {
 			packet->setArrayDataByName("stack_size", item->details.count);
 			packet->setArrayDataByName("icon", item->details.icon);
 			packet->setArrayDataByName("level", item->generic_info.adventure_default_level);
-			packet->setArrayDataByName("tier", item->details.tier);
+			
+			if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) {
+				packet->setArrayDataByName("tier", item->details.tier);
+			}
 			packet->setArrayDataByName("item_id2", item->details.item_id);
 			int8 item_difficulty = player->GetArrowColor(item->generic_info.adventure_default_level);
 			if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY)
@@ -9134,7 +9210,6 @@ void Client::SearchStore(int32 page) {
 			if (search_items->size() > 0) {
 				packet->setArrayLengthByName("num_sellers", 1);
 				packet->setArrayDataByName("seller_seller_id", 1);
-				packet->setArrayDataByName("seller_name", "EQ2EmuDevs");
 				packet->setDataByName("per_page", 8);
 				packet->setDataByName("num_pages", search_items->size() / 8 + 1);
 				packet->setDataByName("page", page);
@@ -9143,10 +9218,17 @@ void Client::SearchStore(int32 page) {
 				for (int32 i = 0; i < limit; i++, x++) {
 					if (x >= search_items->size())
 						break;
+					
 					item = search_items->at(x);
+					std::string teststr("test ");
+					teststr.append(std::to_string(i));
+					packet->setArrayDataByName("string_one", teststr.c_str(), i);
+					packet->setArrayDataByName("string_two", "testtwo", i);
+					packet->setArrayDataByName("seller_name", "EQ2EMuDev", i);
 					packet->setArrayDataByName("item_id", item->details.item_id, i);
 					packet->setArrayDataByName("item_id2", item->details.item_id, i);
 					packet->setArrayDataByName("icon", item->details.icon, i);
+					packet->setArrayDataByName("unknown15", 1, i, World::newValue);
 					//packet->setArrayDataByName("unknown2b", i, i);
 					packet->setArrayDataByName("item_seller_id", 1, i);
 					if (item->stack_count == 0)
@@ -9154,6 +9236,8 @@ void Client::SearchStore(int32 page) {
 					else
 						packet->setArrayDataByName("quantity", item->stack_count, i);
 					packet->setArrayDataByName("stack_size", item->stack_count, i);
+					
+					packet->setArrayDataByName("sell_price", item->sell_price, i);
 
 
 					std::string tmpStr("");
@@ -9167,10 +9251,9 @@ void Client::SearchStore(int32 page) {
 					//QueuePacket(item->serialize(GetVersion(), false, GetPlayer()));
 				}
 			}
-			/*EQ2Packet* outapp = packet->serialize();
+			EQ2Packet* outapp = packet->serialize();
 			DumpPacket(outapp);
-			QueuePacket(outapp);*/
-			QueuePacket(packet->serialize());
+			QueuePacket(outapp);
 			safe_delete(packet);
 		}
 	}
@@ -9422,7 +9505,7 @@ void Client::InspectPlayer(Player* player_to_inspect) {
 		}
 	}
 	
-	if (player_to_inspect) {
+	if (player_to_inspect && player_to_inspect->GetClient()) {
 		PacketStruct* packet = configReader.getStruct("WS_InspectPlayer", GetVersion());
 		if (packet) {
 			packet->setDataByName("unknown", 0);
@@ -9491,22 +9574,34 @@ void Client::InspectPlayer(Player* player_to_inspect) {
 			string biography = player_to_inspect->GetBiography();
 			for (size_t i = 0; i < biography.length(); i++)
 				packet->setArrayDataByName("biography_char", biography[i], i);
-
-			for(int32 s=0;s<NUM_SLOTS;s++) {
-				int32 slot = s*2;
-				
-				char item_slot_name[64], item_slot_name_appearance[64];
-				_snprintf(item_slot_name,64,"slot_%u",slot);
-				int32 slot_appearance = (s*2)+1;
-				_snprintf(item_slot_name_appearance,64,"slot_%u",slot_appearance);
-				Item* pw = player_to_inspect->GetEquipmentList()->GetItem(s);
-				packet->setItemByName(item_slot_name, pw, this->GetPlayer(), 0, 13);
-				if(s <= EQ2_FEET_SLOT || s == EQ2_RANGE_SLOT || s == EQ2_CLOAK_SLOT) {
-					pw = player_to_inspect->GetAppearanceEquipmentList()->GetItem(s);
-					packet->setItemByName(item_slot_name_appearance, pw, this->GetPlayer(), 0, 13);	
+			
+			if(GetVersion() <= 546) {
+				for(int32 s=0;s<22;s++) {
+					int32 slot = s;
+					
+					char item_slot_name[64], item_slot_name_appearance[64];
+					_snprintf(item_slot_name,64,"slot_%u",slot);
+					Item* pw = player_to_inspect->GetEquipmentList()->GetItem(GetPlayer()->ConvertSlotFromClient(s, GetVersion()));
+					packet->setItemByName(item_slot_name, pw, this->GetPlayer(), 0, 5, true, true);
 				}
-				else {
-					packet->setItemByName(item_slot_name_appearance, nullptr, this->GetPlayer(), 0, 13);	
+			}
+			else {
+				for(int32 s=0;s<NUM_SLOTS;s++) {
+					int32 slot = s*2;
+					
+					char item_slot_name[64], item_slot_name_appearance[64];
+					_snprintf(item_slot_name,64,"slot_%u",slot);
+					int32 slot_appearance = (s*2)+1;
+					_snprintf(item_slot_name_appearance,64,"slot_%u",slot_appearance);
+					Item* pw = player_to_inspect->GetEquipmentList()->GetItem(GetPlayer()->ConvertSlotFromClient(s, player_to_inspect->GetClient()->GetVersion()));
+					packet->setItemByName(item_slot_name, pw, this->GetPlayer(), 0, 13, false, true);
+					if(s <= EQ2_FEET_SLOT || s == EQ2_RANGE_SLOT || s == EQ2_CLOAK_SLOT) {
+						pw = player_to_inspect->GetAppearanceEquipmentList()->GetItem(s);
+						packet->setItemByName(item_slot_name_appearance, pw, this->GetPlayer(), 0, 13, false, true);	
+					}
+					else {
+						packet->setItemByName(item_slot_name_appearance, nullptr, this->GetPlayer(), 0, 13, false, true);	
+					}
 				}
 			}
 			QueuePacket(packet->serialize());
@@ -9890,14 +9985,55 @@ void Client::DisplayCollectionComplete(Collection* collection) {
 	int32 i;
 
 	assert(collection);
+	
+	reward_items = collection->GetRewardItems();
+	selectable_reward_items = collection->GetSelectableRewardItems();
 
+	if (GetVersion() <= 546) {
+			int32 source_id = collection->GetID();
+			PacketStruct* packet2 = configReader.getStruct("WS_QuestRewardPackMsg", GetVersion());
+			if (packet2) {
+				packet2->setSubstructDataByName("reward_data", "unknown1", 255);
+				packet2->setSubstructDataByName("reward_data", "reward", "Quest Reward!");
+				packet2->setSubstructDataByName("reward_data", "max_coin", collection->GetRewardCoin());
+				packet2->setSubstructDataByName("reward_data", "exp_bonus", collection->GetRewardXP());
+				
+				if(reward_items){
+					int32 item_count = reward_items->size();
+					packet2->setSubstructArrayLengthByName("reward_data", "num_rewards", item_count);
+					i = 0;
+					if(reward_items) {
+						for (i = 0; i < reward_items->size(); i++) {
+							Item* item = reward_items->at(i)->item;
+							if (item) {
+								packet2->setArrayDataByName("reward_id", item->details.item_id, i);
+								packet2->setItemArrayDataByName("item", item, player, i, 0, -1);
+							}
+						}
+					}
+				}
+				if (selectable_reward_items) {
+					packet2->setSubstructArrayLengthByName("reward_data", "num_select_rewards", selectable_reward_items->size());
+					for (i = 0; i < selectable_reward_items->size(); i++) {
+						Item* item = selectable_reward_items->at(i)->item;
+						if (item) {
+							packet2->setArrayDataByName("select_reward_id", item->details.item_id, i);
+							packet2->setItemArrayDataByName("select_item", item, player, i, 0, -1);
+						}
+					}
+				}
+			}
+
+		EQ2Packet* app = packet2->serialize();
+		QueuePacket(app);
+		safe_delete(packet2);
+		return;
+	}
+	
 	if (!(packet = configReader.getStruct("WS_QuestComplete", version))) {
 		return;
 	}
-
-	reward_items = collection->GetRewardItems();
-	selectable_reward_items = collection->GetSelectableRewardItems();
-
+	
 	packet->setDataByName("title", "Quest Reward!");
 	packet->setDataByName("name", collection->GetName());
 	packet->setDataByName("description", collection->GetCategory());

+ 1 - 1
EQ2/source/WorldServer/client.h

@@ -660,7 +660,7 @@ private:
 	float	zoning_h;
 	bool	firstlogin;
 	
-	enum 	NewLoginState { LOGIN_NONE, LOGIN_DELAYED, LOGIN_ALLOWED };
+	enum 	NewLoginState { LOGIN_NONE, LOGIN_DELAYED, LOGIN_ALLOWED, LOGIN_INITIAL_LOAD, LOGIN_SEND };
 	NewLoginState	new_client_login; // 1 = delayed state, 2 = let client in
 	Timer	underworld_cooldown_timer;
 	Timer	pos_update;

+ 53 - 32
EQ2/source/WorldServer/zoneserver.cpp

@@ -521,7 +521,7 @@ void ZoneServer::DeleteData(bool boot_clients){
 	for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
 		spawn = itr->second;
 		if(spawn){
-			if(!boot_clients && spawn->IsPlayer())
+			if(!boot_clients && (spawn->IsPlayer() || spawn->IsBot()))
 				tmp_player_list.push_back(spawn);
 			else if(spawn->IsPlayer()){
 				Client* client = GetClientBySpawn(spawn);
@@ -674,7 +674,6 @@ vector<RevivePoint*>* ZoneServer::GetRevivePoints(Client* client)
 			test_point = *revive_iter;
 			if(test_point)
 			{
-				LogWrite(ZONE__DEBUG, 0, "Zone", "Test Point Found!");
 				test_closest = client->GetPlayer()->GetDistance(test_point->x, test_point->y, test_point->z);
 
 				// should this be changed to list all revive points within max distance or just the closest
@@ -683,11 +682,19 @@ vector<RevivePoint*>* ZoneServer::GetRevivePoints(Client* client)
 					LogWrite(ZONE__DEBUG, 0, "Zone", "test_closest: %.2f, closest: %.2f", test_closest, closest);
 					closest = test_closest;
 					closest_point = test_point;
-					if(closest <= MAX_REVIVEPOINT_DISTANCE)
-						points->push_back(closest_point);
+				}
+				if(test_point->always_included ) {
+					points->push_back(test_point);
+					if(closest_point == test_point) {
+						closest_point = nullptr;
+						closest = 100000;
+					}
 				}
 			}
 		}
+		if(closest_point) {
+			points->push_back(closest_point);
+		}
 	}
 
 	if(closest_point && points->size() == 0 && closest_point->zone_id == GetZoneID())
@@ -708,6 +715,7 @@ vector<RevivePoint*>* ZoneServer::GetRevivePoints(Client* client)
 		closest_point->x = GetSafeX();
 		closest_point->y = GetSafeY();
 		closest_point->z = GetSafeZ();
+		closest_point->always_included = true;
 		points->push_back(closest_point);
 	}
 	return points;
@@ -778,15 +786,9 @@ void ZoneServer::ProcessDepop(bool respawns_allowed, bool repop) {
 		MSpawnList.readlock(__FUNCTION__, __LINE__);
 		for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
 			spawn = itr->second;
-			if(spawn && !spawn->IsPlayer()){
+			if(spawn && !spawn->IsPlayer() && !spawn->IsBot()){
 				bool dispatched = false;
-				spawn->SetDeletedSpawn(true);
-				if(spawn->IsBot())
-				{
-					((Bot*)spawn)->Camp(true);
-					dispatched = true;
-				}
-				else if(spawn->IsPet())
+				if(spawn->IsPet())
 				{
 					Entity* owner = ((Entity*)spawn)->GetOwner();
 					if(owner)
@@ -796,6 +798,8 @@ void ZoneServer::ProcessDepop(bool respawns_allowed, bool repop) {
 					}
 				}
 				
+				spawn->SetDeletedSpawn(true);
+				
 				if(!dispatched)
 					SendRemoveSpawn(client, spawn, packet);
 			}
@@ -818,7 +822,7 @@ void ZoneServer::ProcessDepop(bool respawns_allowed, bool repop) {
 			if (spawn) {
 				if(spawn->GetRespawnTime() > 0 && spawn->GetSpawnLocationID() > 0)
 					respawn_timers.Put(spawn->GetSpawnLocationID(), Timer::GetCurrentTime2() + spawn->GetRespawnTime()*1000);
-				if(spawn->IsPlayer())
+				if(spawn->IsPlayer() || spawn->IsBot())
 					tmp_player_list.Add(spawn);
 				else {
 				RemoveSpawnSupportFunctions(spawn, true);
@@ -3741,8 +3745,14 @@ void ZoneServer::PlayFlavor(Client* client, Spawn* spawn, const char* mp3, const
 		packet->setMediumStringByName("name", spawn->GetName());
 		if(text)
 			packet->setMediumStringByName("text", text);
-		if(emote)
-			packet->setMediumStringByName("emote", emote);
+		if(emote) {
+			if(client->GetVersion() > 546) {
+				packet->setMediumStringByName("emote", emote);
+			}
+			else {
+				HandleEmote(spawn, std::string(emote));
+			}
+		}
 		if (language != 0)
 			packet->setDataByName("language", language);
 
@@ -5188,6 +5198,9 @@ void ZoneServer::SendSpellFailedPacket(Client* client, int16 error){
 		/*		Temp solution, need to modify the error code before this function and while we still have access to the spell/combat art		*/
 		error = master_spell_list.GetSpellErrorValue(client->GetVersion(), error);
 
+		if(client->GetVersion() <= 546 && error) {
+			error += 1;
+		}
 		packet->setDataByName("error_code", error);
 		//packet->PrintPacket();
 		client->QueuePacket(packet->serialize());
@@ -5239,6 +5252,7 @@ void ZoneServer::SendCastSpellPacket(LuaSpell* spell, Entity* caster){
 		client = *client_itr;
 		if(!client)
 			continue;
+		
 		packet = configReader.getStruct("WS_HearCastSpell", client->GetVersion());
 		if(packet){
 			int32 caster_id = client->GetPlayer()->GetIDWithPlayerSpawn(caster);
@@ -5260,7 +5274,7 @@ void ZoneServer::SendCastSpellPacket(LuaSpell* spell, Entity* caster){
 				}
 			}
 			packet->setDataByName("spell_visual", spell->spell->GetSpellData()->spell_visual); //result
-			packet->setDataByName("cast_time", spell->spell->GetSpellData()->cast_time*.01); //delay
+			packet->setDataByName("cast_time", spell->spell->GetSpellData()->cast_time*.01f); //delay
 			packet->setDataByName("spell_id", spell->spell->GetSpellID());
 			packet->setDataByName("spell_level", 1);
 			packet->setDataByName("spell_tier", spell->spell->GetSpellData()->tier);
@@ -6346,16 +6360,20 @@ void ZoneServer::RemoveSpawnSupportFunctions(Spawn* spawn, bool lock_spell_proce
 		movement_spawns.erase(spawn->GetID());
 }
 
-void ZoneServer::HandleEmote(Client* originator, string name) {
+void ZoneServer::HandleEmote(Spawn* originator, string name) {
 	if (!originator) {
 		LogWrite(ZONE__ERROR, 0, "Zone", "HandleEmote called with an invalid client");
 		return;
 	}
 
+	Client* orig_client = (originator->IsPlayer() && ((Player*)originator)->GetClient()) ? ((Player*)originator)->GetClient() : nullptr;
 	Client* client = 0;
-	Emote* origEmote = visual_states.FindEmote(name, originator->GetVersion());
+	int32 cur_client_version = orig_client ? orig_client->GetVersion() : 546;
+	Emote* origEmote = visual_states.FindEmote(name, cur_client_version);
 	if(!origEmote){
-		originator->Message(CHANNEL_COLOR_YELLOW, "Unable to find emote '%s'.  If this should be a valid emote be sure to submit a /bug report.", name.c_str());
+		if(orig_client) {
+			orig_client->Message(CHANNEL_COLOR_YELLOW, "Unable to find emote '%s'.  If this should be a valid emote be sure to submit a /bug report.", name.c_str());
+		}
 		return;
 	}
 	Emote* emote = origEmote;
@@ -6364,16 +6382,15 @@ void ZoneServer::HandleEmote(Client* originator, string name) {
 	char* emoteResponse = 0;
 	vector<Client*>::iterator client_itr;
 	
-	int32 cur_client_version = originator->GetVersion();
 	map<int32, Emote*> emote_version_range;
 	MClientList.readlock(__FUNCTION__, __LINE__);
 	for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
 		client = *client_itr;
-		if(!client || (client && client->GetPlayer()->IsIgnored(originator->GetPlayer()->GetName())))
+		if(!client || (client && originator->IsPlayer() && client->GetPlayer()->IsIgnored(originator->GetName())))
 			continue;
 
 		// establish appropriate emote for the version used by the client
-		if (client->GetVersion() != originator->GetVersion())
+		if (client->GetVersion() != cur_client_version)
 		{
 			map<int32, Emote*>::iterator rangeitr = emote_version_range.find(client->GetVersion());
 			if (rangeitr == emote_version_range.end())
@@ -6393,45 +6410,49 @@ void ZoneServer::HandleEmote(Client* originator, string name) {
 
 		packet = configReader.getStruct("WS_CannedEmote", client->GetVersion());
 		if(packet){
-			packet->setDataByName("spawn_id" , client->GetPlayer()->GetIDWithPlayerSpawn(originator->GetPlayer()));
+			packet->setDataByName("spawn_id" , client->GetPlayer()->GetIDWithPlayerSpawn(originator));
 			if(!emoteResponse){
 				string message;
-				if(originator->GetPlayer()->GetTarget() && originator->GetPlayer()->GetTarget()->GetID() != originator->GetPlayer()->GetID()){
+				if(originator->GetTarget() && originator->GetTarget()->GetID() != originator->GetID()){
 					message = emote->GetTargetedMessageString();
 					if(message.find("%t") < 0xFFFFFFFF)
-						message.replace(message.find("%t"), 2, originator->GetPlayer()->GetTarget()->GetName());
+						message.replace(message.find("%t"), 2, originator->GetTarget()->GetName());
 				}
 				if(message.length() == 0)
 					message = emote->GetMessageString();
 				if(message.find("%g1") < 0xFFFFFFFF){
-					if(originator->GetPlayer()->GetGender() == 1)
+					if(originator->GetGender() == 1)
 						message.replace(message.find("%g1"), 3, "his");
 					else
 						message.replace(message.find("%g1"), 3, "her");
 				}
 				if(message.find("%g2") < 0xFFFFFFFF){
-					if(originator->GetPlayer()->GetGender() == 1)
+					if(originator->GetGender() == 1)
 						message.replace(message.find("%g2"), 3, "him");
 					else
 						message.replace(message.find("%g2"), 3, "her");
 				}
 				if(message.find("%g3") < 0xFFFFFFFF){
-					if(originator->GetPlayer()->GetGender() == 1)
+					if(originator->GetGender() == 1)
 						message.replace(message.find("%g3"), 3, "he");
 					else
 						message.replace(message.find("%g3"), 3, "she");
 				}
 				if(message.length() > 0){
-					emoteResponse = new char[message.length() + strlen(originator->GetPlayer()->GetName()) + 10];
-					sprintf(emoteResponse,"%s %s", originator->GetPlayer()->GetName(), message.c_str());
+					emoteResponse = new char[message.length() + strlen(originator->GetName()) + 10];
+					sprintf(emoteResponse,"%s %s", originator->GetName(), message.c_str());
 				}
 				else{
-					originator->Message(CHANNEL_COLOR_YELLOW, "%s is not properly configured, be sure to submit a /bug report.", name.c_str());
+					if(orig_client) {
+						orig_client->Message(CHANNEL_COLOR_YELLOW, "%s is not properly configured, be sure to submit a /bug report.", name.c_str());
+					}
 					safe_delete(packet);
 					break;
 				}
 			}
-			packet->setMediumStringByName("emote_msg", emoteResponse);
+			if(originator->IsPlayer()) {
+				packet->setMediumStringByName("emote_msg", emoteResponse);
+			}
 			packet->setDataByName("anim_type", emote->GetVisualState());
 			client->QueuePacket(packet->serialize());
 			safe_delete(packet);

+ 2 - 1
EQ2/source/WorldServer/zoneserver.h

@@ -207,6 +207,7 @@ struct RevivePoint{
 	float	y;
 	float	z;
 	float	heading;
+	bool	always_included;
 };
 
 struct SpawnScriptTimer {
@@ -331,7 +332,7 @@ public:
 	void	SendSpawnVisualState(Spawn* spawn, int16 type);
 	void	SendSpellFailedPacket(Client* client, int16 error);
 	void	SendInterruptPacket(Spawn* interrupted, LuaSpell* spell, bool fizzle=false);
-	void	HandleEmote(Client* originator, string name);
+	void	HandleEmote(Spawn* originator, string name);
 	Client*	GetClientBySpawn(Spawn* spawn);
 	Spawn*	GetSpawnByDatabaseID(int32 id);
 	Spawn*	GetSpawnByID(int32 id, bool spawnListLocked=false);

+ 25 - 14
EQ2/source/common/PacketStruct.cpp

@@ -28,6 +28,7 @@
 #ifdef WORLD
 #include "../WorldServer/Items/Items.h"
 #include "../WorldServer/Player.h"
+#include "../WorldServer/World.h"
 #endif
 
 extern ConfigReader configReader;
@@ -2549,18 +2550,28 @@ void PacketStruct::LoadFromPacketStruct(PacketStruct* packet, char* substruct_na
 }
 
 #ifdef WORLD
-void PacketStruct::setItem(DataStruct* ds, Item* item, Player* player, int32 index, sint8 offset, bool loot_item) {
+void PacketStruct::setItem(DataStruct* ds, Item* item, Player* player, int32 index, sint8 offset, bool loot_item, bool make_empty_item_packet) {
 	if (!ds)
 		return;
 	uchar* ptr = (uchar*)GetStructPointer(ds);
 	
 	if(!item) {
-		if(offset > 12) {
-			// for player inspection this will offset the parts of the packet that have no items
-			uchar bogusItemBuffer[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00 /*0x68 was item flags*/,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x64,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
-			int sizeOfArray = sizeof(bogusItemBuffer) / sizeof(bogusItemBuffer[0]);
-			ds->SetItemSize(sizeOfArray);
-			memcpy(ptr, bogusItemBuffer, sizeOfArray);
+		if(make_empty_item_packet) {
+			if(client_version <= 546) {
+				// for player inspection this will offset the parts of the packet that have no items
+				uchar bogusItemBuffer[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x8C,0x5A,0xF1,0xD2,0x8C,0x5A,0xF1,0xD2,0x01,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00};
+				bogusItemBuffer[World::newValue] = 0x00;
+				int sizeOfArray = sizeof(bogusItemBuffer) / sizeof(bogusItemBuffer[0]);
+				ds->SetItemSize(sizeOfArray);
+				memcpy(ptr, bogusItemBuffer, sizeOfArray);	
+			}
+			else {
+				// for player inspection this will offset the parts of the packet that have no items
+				uchar bogusItemBuffer[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00 /*0x68 was item flags*/,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x64,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+				int sizeOfArray = sizeof(bogusItemBuffer) / sizeof(bogusItemBuffer[0]);
+				ds->SetItemSize(sizeOfArray);
+				memcpy(ptr, bogusItemBuffer, sizeOfArray);
+			}
 		}
 		return;
 	}
@@ -2570,7 +2581,7 @@ void PacketStruct::setItem(DataStruct* ds, Item* item, Player* player, int32 ind
 		item->serialize(packet, true, player, item_version);
 
 		string* generic_string_data = packet->serializeString();
-		int32 size = generic_string_data->length();
+		int32 size = generic_string_data->length(); // had to set to 81
 		int32 actual_length = size;
 
 		if(offset > 12) {
@@ -2604,22 +2615,22 @@ void PacketStruct::setItem(DataStruct* ds, Item* item, Player* player, int32 ind
 			memcpy(out_ptr, (uchar*)generic_string_data->c_str() + (13 + offset), generic_string_data->length() - (13 + offset));
 		}
 		ds->SetItemSize(size);
-		//printf("Item Name: %s\n",item->name.c_str());
-		//DumpPacket(out_data, size);
+	//	printf("Item Name: %s\n",item->name.c_str());
+	//	DumpPacket(out_data, size);
 		memcpy(ptr, out_data, size);
 		safe_delete_array(out_data);
 		delete packet;
 	}
 	//DumpPacket(ptr2, ds->GetItemSize());
 }
-void PacketStruct::setItemByName(const char* name, Item* item, Player* player, int32 index, sint8 offset, bool loot_item) {
-	setItem(findStruct(name, index), item, player, index, offset, loot_item);
+void PacketStruct::setItemByName(const char* name, Item* item, Player* player, int32 index, sint8 offset, bool loot_item, bool make_empty_item_packet) {
+	setItem(findStruct(name, index), item, player, index, offset, loot_item, make_empty_item_packet);
 }
-void PacketStruct::setItemArrayDataByName(const char* name, Item* item, Player* player, int32 index1, int32 index2, sint8 offset, bool loot_item) {
+void PacketStruct::setItemArrayDataByName(const char* name, Item* item, Player* player, int32 index1, int32 index2, sint8 offset, bool loot_item, bool make_empty_item_packet) {
 	char tmp[10] = { 0 };
 	sprintf(tmp, "_%i", index1);
 	string name2 = string(name).append(tmp);
-	setItem(findStruct(name2.c_str(), index1, index2), item, player, index2, offset, loot_item);
+	setItem(findStruct(name2.c_str(), index1, index2), item, player, index2, offset, loot_item, make_empty_item_packet);
 }
 
 void PacketStruct::ResetData() {

+ 3 - 3
EQ2/source/common/PacketStruct.h

@@ -441,9 +441,9 @@ public:
 		}
 	}
 #ifdef WORLD	
-	void setItem(DataStruct* ds, Item* item, Player* player, int32 index, sint8 offset = 0, bool loot_item = false);
-	void setItemByName(const char* name, Item* item, Player* player, int32 index = 0, sint8 offset = 0, bool loot_item = false);
-	void setItemArrayDataByName(const char* name, Item* item, Player* player, int32 index1 = 0, int32 index2 = 0, sint8 offset = 0, bool loot_item = false);
+	void setItem(DataStruct* ds, Item* item, Player* player, int32 index, sint8 offset = 0, bool loot_item = false, bool make_empty_item_packet = false);
+	void setItemByName(const char* name, Item* item, Player* player, int32 index = 0, sint8 offset = 0, bool loot_item = false, bool make_empty_item_packet = false);
+	void setItemArrayDataByName(const char* name, Item* item, Player* player, int32 index1 = 0, int32 index2 = 0, sint8 offset = 0, bool loot_item = false, bool make_empty_item_packet = false);
 #endif
 	void setEquipmentByName(const char* name, EQ2_EquipmentItem data, int32 index = 0) {
 		setEquipmentByName(findStruct(name, index), data, index);

+ 3 - 3
EQ2/source/common/version.h

@@ -38,11 +38,11 @@
 #endif
 
 #if defined(LOGIN)
-#define CURRENT_VERSION	"0.9.5-epsiloncma"
+#define CURRENT_VERSION	"0.9.6-omiscorpii"
 #elif defined(WORLD)
-#define CURRENT_VERSION	"0.9.5-epsiloncma"
+#define CURRENT_VERSION	"0.9.6-omiscorpii"
 #else
-#define CURRENT_VERSION	"0.9.5-epsiloncma"
+#define CURRENT_VERSION	"0.9.6-omiscorpii"
 #endif
 
 #define COMPILE_DATE	__DATE__

+ 30 - 0
server/ItemStructs.xml

@@ -1035,6 +1035,21 @@
 <Data ElementName="item_id" Type="sint32" Size="1" />
 <Data ElementName="name" Type="char" Size="81" />
 </Struct>
+<Struct Name="Substruct_Item" ClientVersion="546" >
+<Data ElementName="unique_id" Type="int32" Size="1" />
+<Data ElementName="bag_id" Type="int32" Size="1" />
+<Data ElementName="inv_slot_id" Type="int32" Size="1" />
+<Data ElementName="menu_type" Type="int32" Size="1" />
+<Data ElementName="slot_id" Type="int8" Size="1" />
+<Data ElementName="index" Type="int16" Size="1" />
+<Data ElementName="icon" Type="int16" Size="1" />
+<Data ElementName="count" Type="int8" Size="1" />
+<Data ElementName="level" Type="int8" Size="1" />
+<Data ElementName="tier" Type="int8" Size="1" />
+<Data ElementName="num_slots" Type="int8" Size="1" />
+<Data ElementName="item_id" Type="sint32" Size="1" />
+<Data ElementName="name" Type="char" Size="81" />
+</Struct>
 <Struct Name="Substruct_Item" ClientVersion="547" >
 <Data ElementName="unique_id" Type="int32" Size="1" />
 <Data ElementName="bag_id" Type="int32" Size="1" />
@@ -4462,6 +4477,21 @@
 <Data ElementName="fence_commission" Type="int16" Size="1" />
 <Data ElementName="footer" Substruct="Substruct_ItemFooter" Size="1" />
 </Struct>
+<Struct Name="WS_ItemHouse" ClientVersion="546" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqExamineInfoCmd">
+<Data ElementName="header" Substruct="Substruct_ItemDescription" Size="1" />
+<Data ElementName="status_rent_reduction" Type="sint32" Size="1" />
+<Data ElementName="footer" Substruct="Substruct_ItemFooter" Size="1" />
+</Struct>
+<Struct Name="WS_ItemHouseContainer" ClientVersion="546" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqExamineInfoCmd">
+<Data ElementName="header" Substruct="Substruct_ItemDescription" Size="1" />
+<Data ElementName="allowed_types" Type="int32" Size="1" />
+<Data ElementName="unknown12" Type="int32" Size="1" />
+<Data ElementName="num_slots" Type="int16" Size="1" />
+<Data ElementName="unknown13" Type="int8" Size="1" />
+<Data ElementName="broker_commission" Type="int16" Size="1" />
+<Data ElementName="fence_commission" Type="int16" Size="1" />
+<Data ElementName="footer" Substruct="Substruct_ItemFooter" Size="1" />
+</Struct>
 <Struct Name="WS_ItemRecipeBook" ClientVersion="547" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqExamineInfoCmd">
 <Data ElementName="header" Substruct="Substruct_ItemDescription" Size="1" />
 <Data ElementName="num_recipes" Type="int16" Size="1" OversizedValue="127" />

+ 7 - 9
server/LoginStructs.xml

@@ -300,7 +300,7 @@ to zero and treated like placeholders." />
 <Data ElementName="hair_color2" Type="sint8" Size="3" />
 <Data ElementName="hair_color3" Type="sint8" Size="3" />
 <Data ElementName="unknown11" Type="int8" Size="10" />
-<Data ElementName="soga_race_type" Type="int16" Size="1" />57
+<Data ElementName="soga_race_type" Type="int16" Size="1" />
 <Data ElementName="soga_skin_color" Type="EQ2_Color" />
 <Data ElementName="soga_eye_color" Type="EQ2_Color" />
 <Data ElementName="Unknown12" Type="int8" Size="3" />
@@ -828,10 +828,8 @@ to zero and treated like placeholders." />
 <Data ElementName="unknown3" Type="EQ2_16Bit_String" Size="1" />
 <Data ElementName="reset_appearance" Type="int8" Size="1" />
 <Data ElementName="do_not_force_soga" Type="int8" Size="1" />
-<Data ElementName="unknown4" Type="int8" Size="1" />
-<Data ElementName="unknown5" Type="int16" Size="1" />
-<Data ElementName="unknown6" Type="int8" Size="5" />
-<Data ElementName="unknown7" Type="int32" Size="1" />
+<Data ElementName="unknown5" Type="int64" Size="1" />
+<Data ElementName="unknown7" Type="int32" Size="1" /> <!-- 80 -->
 <Data ElementName="unknown7a" Type="int16" Size="1" />
 <Data ElementName="race_unknown" Type="int8" Size="1" />
 <Data ElementName="unknown8" Type="int8" Size="3" />
@@ -854,10 +852,10 @@ to zero and treated like placeholders." />
 <Data ElementName="unknown_array2" Type="Array" ArraySizeVariable ="unknown_array2_size" >
   <Data ElementName="array2_unknown" Type="int32" Size="1" />
 </Data>
-<Data ElementName="unknown11" Type="int32" Size="1" />
-<Data ElementName="sub_level" Type="int32" Size="1" />
-<Data ElementName="race_flag" Type="int32" Size="1" />
-<Data ElementName="class_flag" Type="int32" Size="1" />
+<Data ElementName="unknown11" Type="int32" Size="1" /> <!-- 128 -->
+<Data ElementName="sub_level" Type="int32" Size="1" /> <!-- 132 -->
+<Data ElementName="race_flag" Type="int32" Size="1" /> <!-- 136 -->
+<Data ElementName="class_flag" Type="int32" Size="1" /> <!-- 140 -->
 <Data ElementName="password" Type="EQ2_16Bit_String" Size="1" />
 <Data ElementName="username" Type="EQ2_16bit_String" Size="1" />
 <Data ElementName="service" Type="EQ2_16bit_String" Size="1" />

+ 115 - 9
server/WorldStructs.xml

@@ -6662,10 +6662,10 @@ to zero and treated like placeholders." />
 <Data ElementName="target_array" Type="Array" ArraySizeVariable="num_targets">
      <Data ElementName="target" Type="int32" />
 </Data>
-<Data ElementName="invoker_id" Type="int32" />
-<Data ElementName="spell_visual" Type="int32" />
+<Data ElementName="spell_id" Type="int32" />
+<Data ElementName="spell_visual" Type="int16" />
 <Data ElementName="cast_time" Type="float" />
-<Data ElementName="spell_level" Type="int16" />
+<Data ElementName="spell_level" Type="int8" />
 <Data ElementName="spell_tier" Type="int8" />
 </Struct>
 <Struct Name="WS_HearCastSpell" ClientVersion="547" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqHearSpellCastCmd">
@@ -7040,11 +7040,8 @@ to zero and treated like placeholders." />
 	<Data ElementName="race" Type="int8" Size="1" />
 	<Data ElementName="flags" Type="int8" Size="1" />
 	<Data ElementName="unknown5" Type="int32" Size="1" />
-	<Data ElementName="zone" Type="char" Size="77" />
-	<Data ElementName="guild" Type="char" Size="40" />
-	<Data ElementName="unknown7" Type="int8" />
-	<Data ElementName="unknown8" Type="int8" />
-	<Data ElementName="unknown9" Type="int8" />
+	<Data ElementName="zone" Type="char" Size="80" />
+	<Data ElementName="unknown6" Type="int8" Size="28" />
 </Data>
 <Data ElementName="unknown10" Type="int8" />
 </Struct>
@@ -8791,6 +8788,7 @@ to zero and treated like placeholders." />
 	<Data ElementName="item_array" Type="Array" ArraySizeVariable="num_items">
 		<Data ElementName="item_icon" Type="int16" Size="1" />
 		<Data ElementName="item_name" Type="EQ2_16Bit_String" Size="1" />
+		<Data ElementName="item_flag" Type="int8" Size="1" />
 	</Data>
 </Data>
 <Data ElementName="new_collection_flag" Type="int8" Size="1" />
@@ -9351,7 +9349,6 @@ to zero and treated like placeholders." />
 	<Data ElementName="status2" Type="int32" Size="1" />	
 </Data>
 <Data ElementName="type" Type="int8" /> <!-- 0==buy, 1==sell, 16==repair, 128==goblin game -->
-<Data ElementName="unknown" Type="int8" Size="2" />
 </Struct>
 <Struct Name="WS_UpdateMerchant" ClientVersion="547" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqUpdateMerchantCmd">
 <Data ElementName="spawn_id" Type="int32" />
@@ -9777,6 +9774,25 @@ to zero and treated like placeholders." />
 <Data ElementName="per_page" Type="int32" Size="1" />
 <Data ElementName="page" Type="int32" Size="1" />
 </Struct>
+<Struct Name="WS_BrokerItems" ClientVersion="546" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqConsignmentItemsCmd" >    
+<Data ElementName="num_items" Type="int32" Size="1"/>
+<Data ElementName="item_array" Type="Array" ArraySizeVariable="num_items">
+    <Data ElementName="unknown" Type="int32" Size="1" />
+    <Data ElementName="unknowny" Type="int16" Size="1" />
+    <Data ElementName="item_id" Type="int64" Size="1" />
+    <Data ElementName="quantity" Type="int32" Size="1" />
+    <Data ElementName="unknown15x" Type="int8" Size="4" />
+    <Data ElementName="item_name" Type="EQ2_16Bit_String" Size="1" />
+    <Data ElementName="string_one" Type="EQ2_16Bit_String" Size="1" />
+    <Data ElementName="unknown15y" Type="int8" Size="28" />
+    <Data ElementName="sell_price" Type="int64" Size="1" />
+    <Data ElementName="unknown15spacex" Type="int8" Size="28" />
+    <Data ElementName="seller_name" Type="EQ2_16Bit_String" Size="1" />
+    <Data ElementName="icon" Type="int16" Size="1" />
+    <Data ElementName="unknown" Type="int8" Size="10" />
+</Data>
+</Struct>
+
 <Struct Name="WS_BrokerItems" ClientVersion="972" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqConsignmentItemsCmd" >
 <Data ElementName="unknown" Type="int8" />
 <Data ElementName="num_items" Type="int32" />
@@ -15296,6 +15312,96 @@ to zero and treated like placeholders." />
 <Data ElementName="primary" Type="EQ2_Item" Size="1" />
 <Data ElementName="secondary" Type="EQ2_Item" Size="1" />
 </Struct>
+<Struct Name="WS_InspectPlayer" ClientVersion="546" OpcodeName="OP_InspectPlayerMsg">
+<Data ElementName="unknown" Type="int8" />
+<Data ElementName="name" Type="EQ2_8Bit_String" />
+<Data ElementName="race" Type="int8" />
+<Data ElementName="gender" Type="int8" />
+<Data ElementName="adventure_level" Type="int8" />
+<Data ElementName="adventure_level_effective" Type="int8" />
+<Data ElementName="adventure_class" Type="int8" />
+<Data ElementName="tradeskill_level" Type="int8" />
+<Data ElementName="tradeskill_class" Type="int8" />
+<Data ElementName="health" Type="sint32" />
+<Data ElementName="health_max" Type="sint32" />
+<Data ElementName="health_base" Type="sint32" />
+<Data ElementName="power" Type="sint32" />
+<Data ElementName="power_max" Type="sint32" />
+<Data ElementName="power_base" Type="sint32" />
+<Data ElementName="unknown1" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="avoidancex" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="mitigation" Type="sint16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="unknown2" Type="int16"/>
+<Data ElementName="mitigation_percentage" Type="int8" />
+<Data ElementName="unknown3" Type="int8" />
+<Data ElementName="strength" Type="sint16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="strength_base" Type="int8" />
+<Data ElementName="stamina" Type="sint16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="stamina_base" Type="int8" />
+<Data ElementName="agility" Type="sint16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="agility_base" Type="int8" />
+<Data ElementName="wisdom" Type="sint16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="wisdom_base" Type="int8" />
+<Data ElementName="intelligence" Type="sint16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="intelligence_base" Type="int8" />
+<Data ElementName="unknown4" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="unknown5" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="unknown6" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="unknown7" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="unknown8" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="unknown9" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="unknown10" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="unknown11" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="unknown12" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="heat_resist" Type="sint16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="heat_resist_base" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="heat_resist_percentage" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="cold_resist" Type="sint16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="cold_resist_base" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="cold_resist_percentage" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="magic_resist" Type="sint16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="magic_resist_base" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="magic_resist_percentage" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="mental_resist" Type="sint16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="mental_resist_base" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="mental_resist_percentage" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="divine_resist" Type="sint16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="divine_resist_base" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="divine_resist_percentage" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="disease_resist" Type="sint16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="disease_resist_base" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="disease_resist_percentage" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="poison_resist" Type="sint16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="poison_resist_base" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="poison_resist_percentage" Type="int16" OversizedValue="127" OversizedByte="127" />
+<Data ElementName="num_chars" Type="int16" Size="1" />
+<Data ElementName="biography_array" Type="Array" ArraySizeVariable="num_chars">
+	<Data ElementName="biography_char" Type="char" />
+</Data>
+<Data ElementName="slot_0" Type="EQ2_Item" />
+<Data ElementName="slot_1" Type="EQ2_Item" />
+<Data ElementName="slot_2" Type="EQ2_Item" />
+<Data ElementName="slot_3" Type="EQ2_Item" />
+<Data ElementName="slot_4" Type="EQ2_Item" />
+<Data ElementName="slot_5" Type="EQ2_Item" />
+<Data ElementName="slot_6" Type="EQ2_Item" />
+<Data ElementName="slot_7" Type="EQ2_Item" />
+<Data ElementName="slot_8" Type="EQ2_Item" />
+<Data ElementName="slot_9" Type="EQ2_Item" />
+<Data ElementName="slot_10" Type="EQ2_Item" />
+<Data ElementName="slot_11" Type="EQ2_Item" />
+<Data ElementName="slot_12" Type="EQ2_Item" />
+<Data ElementName="slot_13" Type="EQ2_Item" />
+<Data ElementName="slot_14" Type="EQ2_Item" />
+<Data ElementName="slot_15" Type="EQ2_Item" />
+<Data ElementName="slot_16" Type="EQ2_Item" />
+<Data ElementName="slot_17" Type="EQ2_Item" />
+<Data ElementName="slot_18" Type="EQ2_Item" />
+<Data ElementName="slot_19" Type="EQ2_Item" />
+<Data ElementName="slot_20" Type="EQ2_Item" />
+<Data ElementName="slot_21" Type="EQ2_Item" />
+<Data ElementName="slot_22" Type="EQ2_Item" />
+</Struct>
 <Struct Name="WS_InspectPlayer" ClientVersion="1096" OpcodeName="OP_InspectPlayerMsg">
 <Data ElementName="unknown" Type="int8" />
 <Data ElementName="name" Type="EQ2_8Bit_String" />