Browse Source

- Fix #549 - DoF inventory can now remove/add items freely. The lower bounds index <=255 is assigned to existing items on zone-in or replacing items in that existing index. Indexed items 256+ will represent new items that have to grow the clients item index array. This will distinguish properly what the client is trying to examine when requests come in (versus getting no index).
- Fixed server crashing trying to send spawn packets when handling a bad version of the client entering zone (or malformed packets causing bad versioning).
- Character History is now only saved on requirement, reduced DB load.
- Avoidance of reuse for Query class as it can cause crashes
- Fixed a potential issue with AoM/DoF inventory, in which it would continually build up the array instead of re-using the first slot, eventually causing the client to crash

Emagi 5 months ago
parent
commit
e10aedb79d

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

@@ -257,7 +257,7 @@ void MasterAAList::DisplayAA(Client* client,int8 newtemplate,int8 changemode) {
 	if (TreeNodeList.size() == 0)
 		return;
 	vector<vector<vector<AAEntry> > > AAEntryList ;
-	Query query;
+	Query query, query2;
 	MYSQL_ROW row;
 	int32 Pid = client->GetCharacterID();
 
@@ -282,7 +282,7 @@ void MasterAAList::DisplayAA(Client* client,int8 newtemplate,int8 changemode) {
 	}
 	LogWrite(SPELL__INFO, 0, "AA", "Loaded %u AA Tree Nodes", AAEntryList.size());
 	// load tmplates 4-6 Server
-	MYSQL_RES* result2 = query.RunQuery2(Q_SELECT, "SELECT `template_id`,`tab_id`,`aa_id`,`order`,treeid FROM character_aa where char_id = %i order by `order`", client->GetCharacterID());
+	MYSQL_RES* result2 = query2.RunQuery2(Q_SELECT, "SELECT `template_id`,`tab_id`,`aa_id`,`order`,treeid FROM character_aa where char_id = %i order by `order`", client->GetCharacterID());
 
 	while (result2 && (row = mysql_fetch_row(result2))) {
 		AAEntry newentry;

+ 13 - 3
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -2095,9 +2095,19 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 			if(sep && sep->arg[1][0] && sep->IsNumber(1)){
 				if(strcmp(sep->arg[0], "inventory") == 0){
 					int32 item_index = atol(sep->arg[1]);
-					//printf("Index provided: %u\n",item_index);
-					if(client->GetVersion() <= 546 && item_index <= 255) {
-						item_index = 255 - item_index;
+					if(client->GetVersion() <= 546) {
+						if(item_index <= 255) {
+							item_index = 255 - item_index;
+						}
+						else {
+							if(item_index == 256) { // first "new" item to inventory is assigned index 256 by client
+								item_index = client->GetPlayer()->item_list.GetFirstNewItem();
+							}
+							else {
+								// otherwise the slot has to be mapped out depending on the amount of new items + index sent in
+								item_index = client->GetPlayer()->item_list.GetNewItemByIndex((int16)item_index - 255);
+							}
+						}
 					}
 					Item* item = client->GetPlayer()->item_list.GetItemFromIndex(item_index);
 					if(item){

+ 28 - 34
EQ2/source/WorldServer/Commands/CommandsDB.cpp

@@ -118,7 +118,7 @@ bool WorldDatabase::RemoveSpawnTemplate(int32 template_id)
 
 int32 WorldDatabase::CreateSpawnFromTemplateByID(Client* client, int32 template_id)
 {
-	Query query;
+	Query query, query2, query3, query4, query5, query6;
 	MYSQL_ROW row;
 	int32 spawn_location_id = 0;
 	float new_x = client->GetPlayer()->GetX();
@@ -130,8 +130,7 @@ int32 WorldDatabase::CreateSpawnFromTemplateByID(Client* client, int32 template_
 	LogWrite(COMMAND__DEBUG, 5, "Command", "\tCoords: %.2f %.2f %.2f...", new_x, new_y, new_z);
 
 	// find the spawn_location_id in the template we plan to duplicate
-	Query query1;
-	MYSQL_RES* result = query1.RunQuery2(Q_SELECT, "SELECT spawn_location_id FROM spawn_templates WHERE id = %u", template_id);
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT spawn_location_id FROM spawn_templates WHERE id = %u", template_id);
 	if (result && (row = mysql_fetch_row(result))) {
 		if (row[0])
 			spawn_location_id = atoi(row[0]);
@@ -143,26 +142,25 @@ int32 WorldDatabase::CreateSpawnFromTemplateByID(Client* client, int32 template_
 
 		// insert a new spawn_location_name record
 		string name = "TemplateGenerated";
-		query.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_name (name) VALUES ('%s')", name.c_str());
-		if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
-			LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query.GetQuery(), query.GetError());
+		query2.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_name (name) VALUES ('%s')", name.c_str());
+		if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF){
+			LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query2.GetQuery(), query2.GetError());
 			return 0;
 		}
-		int32 new_location_id = query.GetLastInsertedID();
+		int32 new_location_id = query2.GetLastInsertedID();
 		LogWrite(COMMAND__DEBUG, 5, "Command", "Created new Spawn Location: '%s' (%u)", name.c_str(), new_location_id);
 
 		// get all spawn_location_entries that match the templates spawn_location_id value and insert as new
 		LogWrite(COMMAND__DEBUG, 5, "Command", "Finding existing spawn_location_entry(s) for location_id %u", spawn_location_id);
-		Query query2;
-		MYSQL_RES* result2 = query2.RunQuery2(Q_SELECT, "SELECT spawn_id, spawnpercentage FROM spawn_location_entry WHERE spawn_location_id = %u", spawn_location_id);
+		MYSQL_RES* result2 = query3.RunQuery2(Q_SELECT, "SELECT spawn_id, spawnpercentage FROM spawn_location_entry WHERE spawn_location_id = %u", spawn_location_id);
 		if(result2 && mysql_num_rows(result2) > 0){
 			MYSQL_ROW row2;
 			while(result2 && (row2 = mysql_fetch_row(result2)) && row2[0])
 			{
-				query.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_entry (spawn_id, spawn_location_id, spawnpercentage) VALUES (%u, %u, %i)", 
+				query4.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_entry (spawn_id, spawn_location_id, spawnpercentage) VALUES (%u, %u, %i)", 
 					atoul(row2[0]), new_location_id, atoi(row2[1]));
-				if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
-					LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query.GetQuery(), query.GetError());
+				if(query4.GetErrorNumber() && query4.GetError() && query4.GetErrorNumber() < 0xFFFFFFFF){
+					LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query4.GetQuery(), query4.GetError());
 					return 0;
 				}
 				LogWrite(COMMAND__DEBUG, 5, "Command", "Insert Entry for spawn_id %u, location_id %u, percentage %i", atoul(row2[0]), new_location_id, atoi(row2[1]));
@@ -172,16 +170,15 @@ int32 WorldDatabase::CreateSpawnFromTemplateByID(Client* client, int32 template_
 		// get all spawn_location_placements that match the templates spawn_location_id value and insert as new
 		// Note: /spawn templates within current zone_id only, because of spawn_id issues (cannot template an Antonic spawn in Commonlands)
 		LogWrite(COMMAND__DEBUG, 5, "Command", "Finding existing spawn_location_placement(s) for location_id %u", spawn_location_id);
-		Query query3;
-		MYSQL_RES* result3 = query3.RunQuery2(Q_SELECT, "SELECT zone_id, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id FROM spawn_location_placement WHERE spawn_location_id = %u", spawn_location_id);
+		MYSQL_RES* result3 = query5.RunQuery2(Q_SELECT, "SELECT zone_id, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id FROM spawn_location_placement WHERE spawn_location_id = %u", spawn_location_id);
 		if(result3 && mysql_num_rows(result3) > 0){
 			MYSQL_ROW row3;
 			while(result3 && (row3 = mysql_fetch_row(result3)) && row3[0])
 			{
-				query.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_placement (zone_id, spawn_location_id, x, y, z, heading, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id) VALUES (%i, %u, %2f, %2f, %2f, %2f, %2f, %2f, %2f, %i, %i, %i, %u)", 
+				query6.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_placement (zone_id, spawn_location_id, x, y, z, heading, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id) VALUES (%i, %u, %2f, %2f, %2f, %2f, %2f, %2f, %2f, %i, %i, %i, %u)", 
 					atoi(row3[0]), new_location_id, new_x, new_y, new_z, new_heading, atof(row3[1]), atof(row3[2]), atof(row3[3]), atoi(row3[4]), atoi(row3[5]), atoi(row3[6]), atoul(row3[7]));
-				if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
-					LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query.GetQuery(), query.GetError());
+				if(query6.GetErrorNumber() && query6.GetError() && query6.GetErrorNumber() < 0xFFFFFFFF){
+					LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query6.GetQuery(), query6.GetError());
 					return 0;
 				}
 				LogWrite(COMMAND__DEBUG, 5, "Command", "Insert Placement at new coords for location_id %u", new_location_id);
@@ -197,7 +194,7 @@ int32 WorldDatabase::CreateSpawnFromTemplateByID(Client* client, int32 template_
 
 int32 WorldDatabase::CreateSpawnFromTemplateByName(Client* client, const char* template_name)
 {
-	Query query;
+	Query query, query1, query2, query3, query4, query5, query6;
 	MYSQL_ROW row;
 	int32 template_id = 0;
 	int32 spawn_location_id = 0;
@@ -210,8 +207,7 @@ int32 WorldDatabase::CreateSpawnFromTemplateByName(Client* client, const char* t
 	LogWrite(COMMAND__DEBUG, 5, "Command", "\tCoords: %.2f %.2f %.2f...", new_x, new_y, new_z);
 
 	// find the spawn_location_id in the template we plan to duplicate
-	Query query1;
-	MYSQL_RES* result = query1.RunQuery2(Q_SELECT, "SELECT id, spawn_location_id FROM spawn_templates WHERE name = '%s'", template_name);
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, spawn_location_id FROM spawn_templates WHERE name = '%s'", template_name);
 	if (result && (row = mysql_fetch_row(result))) {
 		if (row[0])
 		{
@@ -226,26 +222,25 @@ int32 WorldDatabase::CreateSpawnFromTemplateByName(Client* client, const char* t
 
 		// insert a new spawn_location_name record
 		string name = "TemplateGenerated";
-		query.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_name (name) VALUES ('%s')", name.c_str());
-		if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
-			LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query.GetQuery(), query.GetError());
+		query2.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_name (name) VALUES ('%s')", name.c_str());
+		if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF){
+			LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query2.GetQuery(), query2.GetError());
 			return 0;
 		}
-		int32 new_location_id = query.GetLastInsertedID();
+		int32 new_location_id = query2.GetLastInsertedID();
 		LogWrite(COMMAND__DEBUG, 5, "Command", "Created new Spawn Location: '%s' (%u)", name.c_str(), new_location_id);
 
 		// get all spawn_location_entries that match the templates spawn_location_id value and insert as new
 		LogWrite(COMMAND__DEBUG, 5, "Command", "Finding existing spawn_location_entry(s) for location_id %u", spawn_location_id);
-		Query query2;
-		MYSQL_RES* result2 = query2.RunQuery2(Q_SELECT, "SELECT spawn_id, spawnpercentage FROM spawn_location_entry WHERE spawn_location_id = %u", spawn_location_id);
+		MYSQL_RES* result2 = query3.RunQuery2(Q_SELECT, "SELECT spawn_id, spawnpercentage FROM spawn_location_entry WHERE spawn_location_id = %u", spawn_location_id);
 		if(result2 && mysql_num_rows(result2) > 0){
 			MYSQL_ROW row2;
 			while(result2 && (row2 = mysql_fetch_row(result2)) && row2[0])
 			{
-				query.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_entry (spawn_id, spawn_location_id, spawnpercentage) VALUES (%u, %u, %i)", 
+				query4.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_entry (spawn_id, spawn_location_id, spawnpercentage) VALUES (%u, %u, %i)", 
 					atoul(row2[0]), new_location_id, atoi(row2[1]));
-				if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
-					LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query.GetQuery(), query.GetError());
+				if(query4.GetErrorNumber() && query4.GetError() && query4.GetErrorNumber() < 0xFFFFFFFF){
+					LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query4.GetQuery(), query4.GetError());
 					return 0;
 				}
 				LogWrite(COMMAND__DEBUG, 5, "Command", "Insert Entry for spawn_id %u, location_id %u, percentage %i", atoul(row2[0]), new_location_id, atoi(row2[1]));
@@ -255,16 +250,15 @@ int32 WorldDatabase::CreateSpawnFromTemplateByName(Client* client, const char* t
 		// get all spawn_location_placements that match the templates spawn_location_id value and insert as new
 		// Note: /spawn templates within current zone_id only, because of spawn_id issues (cannot template an Antonic spawn in Commonlands)
 		LogWrite(COMMAND__DEBUG, 5, "Command", "Finding existing spawn_location_placement(s) for location_id %u", spawn_location_id);
-		Query query3;
-		MYSQL_RES* result3 = query3.RunQuery2(Q_SELECT, "SELECT zone_id, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id FROM spawn_location_placement WHERE spawn_location_id = %u", spawn_location_id);
+		MYSQL_RES* result3 = query5.RunQuery2(Q_SELECT, "SELECT zone_id, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id FROM spawn_location_placement WHERE spawn_location_id = %u", spawn_location_id);
 		if(result3 && mysql_num_rows(result3) > 0){
 			MYSQL_ROW row3;
 			while(result3 && (row3 = mysql_fetch_row(result3)) && row3[0])
 			{
-				query.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_placement (zone_id, spawn_location_id, x, y, z, heading, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id) VALUES (%i, %u, %2f, %2f, %2f, %2f, %2f, %2f, %2f, %i, %i, %i, %u)", 
+				query6.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_placement (zone_id, spawn_location_id, x, y, z, heading, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id) VALUES (%i, %u, %2f, %2f, %2f, %2f, %2f, %2f, %2f, %i, %i, %i, %u)", 
 					atoi(row3[0]), new_location_id, new_x, new_y, new_z, new_heading, atof(row3[1]), atof(row3[2]), atof(row3[3]), atoi(row3[4]), atoi(row3[5]), atoi(row3[6]), atoul(row3[7]));
-				if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
-					LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query.GetQuery(), query.GetError());
+				if(query6.GetErrorNumber() && query6.GetError() && query6.GetErrorNumber() < 0xFFFFFFFF){
+					LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query6.GetQuery(), query6.GetError());
 					return 0;
 				}
 				LogWrite(COMMAND__DEBUG, 5, "Command", "Insert Placement at new coords for location_id %u", new_location_id);

+ 54 - 5
EQ2/source/WorldServer/Items/Items.cpp

@@ -2946,13 +2946,20 @@ bool PlayerItemList::AddItem(Item* item){ //is called with a slot already set
 		if(itr->first > max_index) //just grab the highest index val for next loop
 			max_index = itr->first;
 	}
+	
+	bool doNotOverrideIndex = false;
 	for(int32 i=0;i<max_index;i++){
 		if(!indexed_items[i]){
 			new_index = i;
+			LogWrite(ITEM__DEBUG, 9, "Item %s assigned to %u",item->name.c_str(), i);
+			item->details.new_item = false;
+			item->details.new_index = 0;
+			doNotOverrideIndex = true;
 			break;
-		}	
+		}
 	}
-	if(new_index == 0 && max_index > 0)
+	// may break non DoF clients
+	if(!doNotOverrideIndex && new_index == 0 && max_index > 0)
 		new_index = max_index;
 
 	indexed_items[new_index] = item;
@@ -3257,6 +3264,8 @@ bool PlayerItemList::AssignItemToFreeSlot(Item* item){
 						if(items[bag->details.bag_id][BASE_EQUIPMENT].count(x) == 0){
 							item->details.inv_slot_id = bag->details.bag_id;
 							item->details.slot_id = x;
+							item->details.new_item = true;
+							item->details.new_index = 0;
 							MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
 							bool ret = AddItem(item);
 							return ret;
@@ -3270,6 +3279,8 @@ bool PlayerItemList::AssignItemToFreeSlot(Item* item){
 			if(items[0][BASE_EQUIPMENT].count(i) == 0){
 				item->details.inv_slot_id = 0;
 				item->details.slot_id = i;
+				item->details.new_item = true;
+				item->details.new_index = 0;
 				MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
 				bool ret = AddItem(item);
 				return ret;
@@ -3505,10 +3516,14 @@ EQ2Packet* PlayerItemList::serialize(Player* player, int16 version){
 		
 		packet_count = size;
 		
+		int16 new_index = 0;
 		for(int16 i = 0; i < indexed_items.size(); i++){
 			item = indexed_items[i];
+			if(item && item->details.new_item)
+				new_index++;
+			
 			if (item && item->details.item_id > 0)
-				AddItemToPacket(packet, player, item, i);
+				AddItemToPacket(packet, player, item, i, false, new_index);
 		}
 
 		if (overflowItems.size() > 0) {
@@ -3535,7 +3550,34 @@ EQ2Packet* PlayerItemList::serialize(Player* player, int16 version){
 	return app;
 }
 
-void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item* item, int16 i, bool overflow){
+int16 PlayerItemList::GetFirstNewItem() {
+	int16 new_item_slot = 0;
+	for(int16 i = 0; i < indexed_items.size(); i++){
+		Item* item = indexed_items[i];
+		if(item && item->details.new_item) {
+			return i;
+		}
+	}
+	return 0xFFFF;
+}
+
+int16 PlayerItemList::GetNewItemByIndex(int16 in_index) {
+	int16 new_item_slot = 0;
+	for(int16 i = 0; i < indexed_items.size(); i++){
+		Item* item = indexed_items[i];
+		if(item && item->details.new_item) {
+			new_item_slot++;
+			int16 actual_index = in_index - new_item_slot;
+			LogWrite(ITEM__DEBUG, 9, "In index: %u new index %u actual %u and %u, new slot num %u", in_index, item->details.new_index, actual_index, i, new_item_slot);
+			if(actual_index == i) {
+				return i;
+			}
+		}
+	}
+	return 0xFFFF;
+}
+
+void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item* item, int16 i, bool overflow, int16 new_index){
 	Client *client;
 	if (!packet || !player)
 		return;
@@ -3667,7 +3709,14 @@ void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item*
 				/* DoF client and earlier side automatically assigns indexes
 				** we have to send 0xFF or else all index is set to 255 on client
 				** and then examine inventory won't work */
-				packet->setSubstructArrayDataByName("items", "index", 0xFF, 0, i);
+				LogWrite(ITEM__DEBUG, 9, "%s Offset index %u bag id %u (new index %u, set index %u)",item->name.c_str(),i, item->details.bag_id, new_index, item->details.new_index);
+				if(item->details.new_item) {
+					item->details.new_index = new_index + i; // we have to offset in this way to get consistent indexes for the client to send back
+					packet->setSubstructArrayDataByName("items", "index", 0xFF+item->details.new_index, 0, i);
+				}
+				else {
+					packet->setSubstructArrayDataByName("items", "index", 0xFF, 0, i);
+				}
 		}
 		else {
 				packet->setSubstructArrayDataByName("items", "index", i, 0, i);

+ 6 - 1
EQ2/source/WorldServer/Items/Items.h

@@ -706,6 +706,8 @@ struct ItemCore{
 	int8	num_free_slots;
 	int16	recommended_level;
 	bool	item_locked;
+	bool	new_item;
+	int16	new_index;
 };
 #pragma pack()
 struct ItemStat{
@@ -1139,9 +1141,12 @@ public:
 	
 	int32   GetItemCountInBag(Item* bag);
 
+	int16	GetFirstNewItem();
+	int16	GetNewItemByIndex(int16 in_index);
+	
 	Mutex MPlayerItems;
 private:
-	void AddItemToPacket(PacketStruct* packet, Player* player, Item* item, int16 i, bool overflow = false);
+	void AddItemToPacket(PacketStruct* packet, Player* player, Item* item, int16 i, bool overflow = false, int16 new_index = 0);
 	void Stack(Item* orig_item, Item* item);
 	int16 packet_count;
 	vector<Item*> overflowItems;

+ 7 - 3
EQ2/source/WorldServer/Items/ItemsDB.cpp

@@ -1250,7 +1250,6 @@ void WorldDatabase::SaveItem(int32 account_id, int32 char_id, Item* item, const
 
 void WorldDatabase::DeleteItem(int32 char_id, Item* item, const char* type) 
 {
-	Query query;
 	string delete_item;
 
 	if(type)
@@ -1258,6 +1257,7 @@ void WorldDatabase::DeleteItem(int32 char_id, Item* item, const char* type)
 		LogWrite(ITEM__DEBUG, 1, "Items", "Deleting item_id %u (Type: %s) for player %u", item->details.item_id, type, char_id);
 
 		delete_item = string("DELETE FROM character_items WHERE char_id = %u AND (id = %u OR bag_id = %u) AND type='%s'");
+		Query query;
 		query.RunQuery2(Q_DELETE, delete_item.c_str(), char_id, item->details.unique_id, item->details.unique_id, type);
 	}
 	else
@@ -1265,12 +1265,14 @@ void WorldDatabase::DeleteItem(int32 char_id, Item* item, const char* type)
 		LogWrite(ITEM__DEBUG, 0, "Items", "Deleting item_id %u for player %u", item->details.item_id, char_id);
 
 		delete_item = string("DELETE FROM character_items WHERE char_id = %u AND (id = %u OR bag_id = %u)");
-		query.RunQuery2(Q_DELETE, delete_item.c_str(), char_id, item->details.unique_id, item->details.unique_id);
+		Query query2;
+		query2.RunQuery2(Q_DELETE, delete_item.c_str(), char_id, item->details.unique_id, item->details.unique_id);
 	}
 	
 	if(item->CheckFlag2(HEIRLOOM)) {
 		delete_item = string("DELETE FROM character_items_group_members WHERE unique_id = %u");
-		query.RunQuery2(Q_DELETE, delete_item.c_str(), item->details.unique_id);
+		Query query3;
+		query3.RunQuery2(Q_DELETE, delete_item.c_str(), item->details.unique_id);
 	}
 }
 
@@ -1352,6 +1354,8 @@ void WorldDatabase::LoadCharacterItemList(int32 account_id, int32 char_id, Playe
 				}
 
 				item->details.inv_slot_id = atol(row[10]); //bag_id
+				item->details.new_item = false;
+				item->details.new_index = 0;
 				item->details.count = atoi(row[11]); //count
 				item->SetMaxSellValue(atoul(row[12])); //max sell value
 				item->no_sale = (atoul(row[13]) == 1);

+ 7 - 1
EQ2/source/WorldServer/Player.cpp

@@ -6306,6 +6306,7 @@ void Player::HandleHistoryDiscovery(int8 subtype, int32 value, int32 value2) {
 		hd->Value2 = value2;
 		hd->EventDate = Timer::GetUnixTimeStamp();
 		strcpy(hd->Location, GetZone()->GetZoneName());
+		hd->needs_save = true;
 
 		m_characterHistory[HISTORY_TYPE_DISCOVERY][HISTORY_SUBTYPE_LOCATION].push_back(hd);
 		break;
@@ -6326,6 +6327,7 @@ void Player::HandleHistoryXP(int8 subtype, int32 value, int32 value2) {
 		hd->Value2 = value2;
 		hd->EventDate = Timer::GetUnixTimeStamp();
 		strcpy(hd->Location, GetZone()->GetZoneName());
+		hd->needs_save = true;
 
 		m_characterHistory[HISTORY_TYPE_XP][HISTORY_SUBTYPE_ADVENTURE].push_back(hd);
 	}
@@ -6359,7 +6361,11 @@ void Player::SaveHistory() {
 	for (itr = m_characterHistory.begin(); itr != m_characterHistory.end(); itr++) {
 		for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) {
 			for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++) {
-				database.SaveCharacterHistory(this, itr->first, itr2->first, (*itr3)->Value, (*itr3)->Value2, (*itr3)->Location, (*itr3)->EventDate);
+				
+				if((*itr3)->needs_save) {
+					database.SaveCharacterHistory(this, itr->first, itr2->first, (*itr3)->Value, (*itr3)->Value2, (*itr3)->Location, (*itr3)->EventDate);
+					(*itr3)->needs_save = false;
+				}
 			}
 		}
 	}

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

@@ -154,6 +154,7 @@ struct HistoryData {
 	char		Location[200];
 	int32		EventID;
 	int32		EventDate;
+	bool		needs_save;
 };
 
 /// <summary>History set through the LUA system</summary>

+ 21 - 8
EQ2/source/WorldServer/Spawn.cpp

@@ -685,12 +685,15 @@ EQ2Packet* Spawn::spawn_serialize(Player* player, int16 version, int16 offset, i
 	ptr += sizeof(oversized_packet);
 
 	int16 opcode = 0;
-	if(IsWidget())
-		opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqCreateWidgetCmd);
-	else if(IsSign())
-		opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqCreateSignWidgetCmd);
-	else
-		opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqCreateGhostCmd);
+	
+	if (EQOpcodeManager.count(GetOpcodeVersion(version)) != 0) {	
+		if(IsWidget())
+			opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqCreateWidgetCmd);
+		else if(IsSign())
+			opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqCreateSignWidgetCmd);
+		else
+			opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqCreateGhostCmd);
+	}
 	memcpy(ptr, &opcode, sizeof(opcode));
 	ptr += sizeof(opcode);
 
@@ -931,7 +934,13 @@ EQ2Packet* Spawn::player_position_update_packet(Player* player, int16 version){
 		size += 2;
 	}
 	static const int8 oversized = 255;
-	int16 opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd);
+	
+	
+	int16 opcode_val = 0;
+	
+	if (EQOpcodeManager.count(GetOpcodeVersion(version)) != 0) {	
+		opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd);
+	}
 	uchar* tmp = new uchar[size];
 	memset(tmp, 0, size);
 	uchar* ptr = tmp;
@@ -991,7 +1000,11 @@ EQ2Packet* Spawn::spawn_update_packet(Player* player, int16 version, bool overri
 	uchar* pos_changes = 0;
 	uchar* vis_changes = 0;
 	static const int8 oversized = 255;
-	int16 opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd);
+	int16 opcode_val = 0;
+	
+	if (EQOpcodeManager.count(GetOpcodeVersion(version)) != 0) {
+		opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd);
+	}
 
 	//We need to lock these variables up to make this thread safe
 	m_Update.writelock(__FUNCTION__, __LINE__);

+ 36 - 34
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -359,16 +359,16 @@ void WorldDatabase::UpdateCharacterMacro(int32 char_id, int8 number, const char*
 
 //we use our timestamp just in case db is on another server, otherwise times might be off
 void WorldDatabase::UpdateVitality(int32 timestamp, float amount){ 
-	Query query;
+	Query query, query2, query3;
 
 	LogWrite(PLAYER__DEBUG, 3, "Player", "Reset Vitality > 100: %f", amount);
 	query.RunQuery2(Q_UPDATE, "update character_details set xp_vitality=100 where (xp_vitality + %f) > 100", amount);
 
 	LogWrite(PLAYER__DEBUG, 3, "Player", "Update Vitality <= 100: %f", amount);
-	query.RunQuery2(Q_UPDATE, "update character_details set xp_vitality=(xp_vitality+%f) where (xp_vitality + %f) <= 100", amount, amount);
+	query2.RunQuery2(Q_UPDATE, "update character_details set xp_vitality=(xp_vitality+%f) where (xp_vitality + %f) <= 100", amount, amount);
 
 	LogWrite(PLAYER__DEBUG, 3, "Player", "Update Vitality Timer: %u", timestamp);
-	query.RunQuery2(Q_UPDATE, "update variables set variable_value=%u where variable_name='vitalitytimer'", timestamp);
+	query3.RunQuery2(Q_UPDATE, "update variables set variable_value=%u where variable_name='vitalitytimer'", timestamp);
 }
 
 void WorldDatabase::SaveVariable(const char* name, const char* value, const char* comment){
@@ -2030,14 +2030,14 @@ bool WorldDatabase::UpdateCharacterTimeStamp(int32 account_id, int32 character_i
 }
 
 bool WorldDatabase::insertCharacterProperty(Client* client, char* propName, char* propValue) {
-	Query query;
+	Query query, query2;
 
 	string update_status = string("update character_properties set propvalue='%s' where charid=%i and propname='%s'");
 	query.RunQuery2(Q_UPDATE, update_status.c_str(), propValue, client->GetCharacterID(), propName);
 	if (!query.GetAffectedRows())
 	{
-		query.RunQuery2(Q_UPDATE, "insert into character_properties (charid, propname, propvalue) values(%i, '%s', '%s')", client->GetCharacterID(), propName, propValue);
-		if (query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) {
+		query2.RunQuery2(Q_UPDATE, "insert into character_properties (charid, propname, propvalue) values(%i, '%s', '%s')", client->GetCharacterID(), propName, propValue);
+		if (query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF) {
 			LogWrite(WORLD__ERROR, 0, "World", "Error in insertCharacterProperty query '%s': %s", query.GetQuery(), query.GetError());
 			return false;
 		}
@@ -2722,7 +2722,6 @@ void WorldDatabase::SaveCharRepeatableQuest(Client* client, int32 quest_id, int1
 }
 
 void WorldDatabase::SaveCharacterQuestProgress(Client* client, Quest* quest){
-	Query query;
 	vector<QuestStep*>* steps = quest->GetQuestSteps();
 	vector<QuestStep*>::iterator itr;
 	QuestStep* step = 0;
@@ -2730,14 +2729,16 @@ void WorldDatabase::SaveCharacterQuestProgress(Client* client, Quest* quest){
 	if(steps){
 		for(itr = steps->begin(); itr != steps->end(); itr++){
 			step = *itr;
-			if(step && step->GetQuestCurrentQuantity() > 0)
+			if(step && step->GetQuestCurrentQuantity() > 0) {
+				Query query;
 				query.RunQuery2(Q_REPLACE, "replace into character_quest_progress (char_id, quest_id, step_id, progress) values(%u, %u, %u, %i)", client->GetCharacterID(), quest->GetQuestID(), step->GetStepID(), step->GetQuestCurrentQuantity());
+							
+				if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF)
+					LogWrite(WORLD__ERROR, 0, "World", "Error in SaveCharacterQuestProgress query '%s': %s", query.GetQuery(), query.GetError());
+			}
 		}
 	}
 	quest->MQuestSteps.releasereadlock(__FUNCTION__, __LINE__);
-	
-	if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF)
-		LogWrite(WORLD__ERROR, 0, "World", "Error in SaveCharacterQuestProgress query '%s': %s", query.GetQuery(), query.GetError());
 }
 
 void WorldDatabase::LoadCharacterQuestProgress(Client* client){
@@ -4312,15 +4313,15 @@ void WorldDatabase::LoadFactionAlliances()
 bool WorldDatabase::UpdateSpawnScriptData(int32 spawn_id, int32 spawn_location_id, int32 spawnentry_id, const char* name){
 	bool ret = false;
 	if((spawn_id > 0 || spawn_location_id > 0 || spawnentry_id > 0) && name){
-		Query query;
+		Query query, query2;
 		int32 row_id = 0;
 		if(spawn_id > 0){
 			query.RunQuery2(Q_DELETE, "DELETE FROM spawn_scripts where spawn_id=%u", spawn_id);
-			query.RunQuery2(Q_INSERT, "INSERT into spawn_scripts (spawn_id, lua_script) values(%u, '%s')", spawn_id, getSafeEscapeString(name).c_str());
-			if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF)
-				LogWrite(LUA__ERROR, 0, "LUA", "Error in UpdateSpawnScriptData, Query: %s, Error: %s", query.GetQuery(), query.GetError());
+			query2.RunQuery2(Q_INSERT, "INSERT into spawn_scripts (spawn_id, lua_script) values(%u, '%s')", spawn_id, getSafeEscapeString(name).c_str());
+			if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF)
+				LogWrite(LUA__ERROR, 0, "LUA", "Error in UpdateSpawnScriptData, Query: %s, Error: %s", query2.GetQuery(), query2.GetError());
 			else{
-				row_id = query.GetLastInsertedID();
+				row_id = query2.GetLastInsertedID();
 				if(row_id > 0)
 					world.AddSpawnScript(row_id, name);
 				ret = true;
@@ -4328,11 +4329,11 @@ bool WorldDatabase::UpdateSpawnScriptData(int32 spawn_id, int32 spawn_location_i
 		}
 		else if(spawn_location_id > 0){
 			query.RunQuery2(Q_DELETE, "DELETE FROM spawn_scripts where spawn_location_id=%u", spawn_location_id);
-			query.RunQuery2(Q_INSERT, "INSERT into spawn_scripts (spawn_location_id, lua_script) values(%u, '%s')", spawn_location_id, getSafeEscapeString(name).c_str());
-			if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF)
-				LogWrite(LUA__ERROR, 0, "LUA", "Error in UpdateSpawnScriptData, Query: %s, Error: %s", query.GetQuery(), query.GetError());
+			query2.RunQuery2(Q_INSERT, "INSERT into spawn_scripts (spawn_location_id, lua_script) values(%u, '%s')", spawn_location_id, getSafeEscapeString(name).c_str());
+			if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF)
+				LogWrite(LUA__ERROR, 0, "LUA", "Error in UpdateSpawnScriptData, Query: %s, Error: %s", query2.GetQuery(), query2.GetError());
 			else{
-				row_id = query.GetLastInsertedID();
+				row_id = query2.GetLastInsertedID();
 				if(row_id > 0)
 					world.AddSpawnLocationScript(row_id, name);
 				ret = true;
@@ -4340,11 +4341,11 @@ bool WorldDatabase::UpdateSpawnScriptData(int32 spawn_id, int32 spawn_location_i
 		}
 		else if(spawnentry_id > 0){
 			query.RunQuery2(Q_DELETE, "DELETE FROM spawn_scripts where spawnentry_id=%u", spawnentry_id);
-			query.RunQuery2(Q_INSERT, "INSERT into spawn_scripts (spawnentry_id, lua_script) values(%u, '%s')", spawnentry_id, getSafeEscapeString(name).c_str());
-			if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF)
-				LogWrite(LUA__ERROR, 0, "LUA", "Error in UpdateSpawnScriptData, Query: %s, Error: %s", query.GetQuery(), query.GetError());
+			query2.RunQuery2(Q_INSERT, "INSERT into spawn_scripts (spawnentry_id, lua_script) values(%u, '%s')", spawnentry_id, getSafeEscapeString(name).c_str());
+			if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF)
+				LogWrite(LUA__ERROR, 0, "LUA", "Error in UpdateSpawnScriptData, Query: %s, Error: %s", query2.GetQuery(), query2.GetError());
 			else{
-				row_id = query.GetLastInsertedID();
+				row_id = query2.GetLastInsertedID();
 				if(row_id > 0)
 					world.AddSpawnEntryScript(row_id, name);
 				ret = true;
@@ -5021,16 +5022,15 @@ bool WorldDatabase::DeleteCharacter(int32 account_id, int32 character_id){
 	if((guild = guild_list.GetGuild(GetGuildIDByCharacterID(character_id))))
 		guild->RemoveGuildMember(character_id);
 
-	Query query;
-	Query query2;
+	Query query, query2, query3, query4, query5;
 	query.RunQuery2(Q_DELETE, "DELETE FROM characters WHERE id=%u AND account_id=%u", character_id, account_id);
 	//delete languages
 	query2.RunQuery2(Q_DELETE, "DELETE FROM character_languages WHERE char_id=%u", character_id);
 	//delete quest rewards
-	query2.RunQuery2(Q_DELETE, "DELETE FROM character_quest_rewards WHERE char_id=%u", character_id);
-	query2.RunQuery2(Q_DELETE, "DELETE FROM character_quest_temporary_rewards WHERE char_id=%u", character_id);
+	query3.RunQuery2(Q_DELETE, "DELETE FROM character_quest_rewards WHERE char_id=%u", character_id);
+	query4.RunQuery2(Q_DELETE, "DELETE FROM character_quest_temporary_rewards WHERE char_id=%u", character_id);
 	//delete character claims
-	query2.RunQuery2(Q_DELETE, "DELETE FROM character_claim_items where char_id=%u", character_id);
+	query5.RunQuery2(Q_DELETE, "DELETE FROM character_claim_items where char_id=%u", character_id);
 
 	if(!query.GetAffectedRows())
 	{
@@ -6377,6 +6377,7 @@ void WorldDatabase::LoadCharacterHistory(int32 char_id, Player *player)
 		strcpy(hd->Location, result.GetString(4));
 		// skipped event id as use for it has not been determined yet
 		hd->EventDate = result.GetInt32(6);
+		hd->needs_save = false;
 
 		player->LoadPlayerHistory(type, subtype, hd);
 	}
@@ -8186,7 +8187,8 @@ void WorldDatabase::UpdateStartingLanguage(int32 char_id, uint8 race_id, int32 s
 		if (mysql_num_rows(result) > 0)	{
 			while (result && (row = mysql_fetch_row(result))){
 			//add custom languages to the character_languages db.
-				query.RunQuery2(Q_INSERT, "INSERT IGNORE INTO character_languages (char_id, language_id) VALUES (%u,%u)",char_id, atoul(row[0]));
+				Query query2;
+				query2.RunQuery2(Q_INSERT, "INSERT IGNORE INTO character_languages (char_id, language_id) VALUES (%u,%u)",char_id, atoul(row[0]));
 			}
 		}
 	}
@@ -8223,8 +8225,8 @@ void WorldDatabase::LoadClaimItems(int32 char_id)
 					max_claim	= 1;
 					curr_claim	= 1;
 				}
-
-				MYSQL_RES* res = query.RunQuery2(Q_INSERT, "insert ignore into character_claim_items (char_id, item_id, max_claim, curr_claim, one_per_char, veteran_reward_time, account_id) values  (%i, %i, %i, %i, %i, %I64i, %i)", char_id, item_id, max_claim, curr_claim, one_per_char, vet_reward_time, acct_id);
+				Query query2;
+				MYSQL_RES* res = query2.RunQuery2(Q_INSERT, "insert ignore into character_claim_items (char_id, item_id, max_claim, curr_claim, one_per_char, veteran_reward_time, account_id) values  (%i, %i, %i, %i, %i, %I64i, %i)", char_id, item_id, max_claim, curr_claim, one_per_char, vet_reward_time, acct_id);
 				total++;
 			}
 		}
@@ -8386,8 +8388,8 @@ void WorldDatabase::ClaimItem(int32 char_id, int32 item_id, Client* client) {
 					}
 				}
 				
-				Query query2;
-				query2.RunQuery2(Q_UPDATE, "UPDATE `character_claim_items` SET `curr_claim`=0 , `last_claim`=%u WHERE char_id=%i and item_id=%i", curr_time, char_id, item_id);
+				Query query4;
+				query4.RunQuery2(Q_UPDATE, "UPDATE `character_claim_items` SET `curr_claim`=0 , `last_claim`=%u WHERE char_id=%i and item_id=%i", curr_time, char_id, item_id);
 				//give the item to the player, and update last claim time.
 				//client->AddItem(item_id);
 				bool item_added = client->AddItem(item_id);

+ 38 - 8
EQ2/source/WorldServer/client.cpp

@@ -1107,6 +1107,15 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 				if (EQOpcodeManager.count(GetOpcodeVersion(version)) == 0) {
 					LogWrite(WORLD__ERROR, 0, "World", "Incompatible version: %i", version);
 					ClientPacketFunctions::SendLoginDenied(this);
+					
+					/* reset version and protect server from trying to send packets out to a bad client
+					** cause of Dec 6th/Dec 7th 2023 crash
+					** Client::MakeSpawnChangePacket
+					**	int16 opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd); <-- crashes pulling opcode with bad version
+					*/
+					version = 546;
+					ready_for_updates = false;
+					ready_for_spawns = false;
 					return false;
 				}
 
@@ -2732,6 +2741,12 @@ bool Client::HandleLootItem(Spawn* entity, Item* item) {
 				lua_interface->RunItemScript(item->GetItemScript(), "obtained", item, player);
 			
 			CheckPlayerQuestsItemUpdate(item);
+			
+			if(GetVersion() <= 546) {
+				EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
+				if (outapp)
+					QueuePacket(outapp);
+			}
 			return true;
 		}
 		else
@@ -2777,8 +2792,15 @@ void Client::HandleLoot(EQApplicationPacket* app) {
 								item = new Item(master_item);
 								if (item) {
 									loot_all = HandleLootItem(0, item);
-									if (loot_all)
+									if (loot_all) {
 										player->RemovePendingLootItem(loot_id, item->details.item_id);
+										
+										if(GetVersion() <= 546) {
+											EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
+											if (outapp)
+												QueuePacket(outapp);
+										}
+									}
 								}
 							}
 						}
@@ -2807,9 +2829,11 @@ void Client::HandleLoot(EQApplicationPacket* app) {
 						safe_delete(packet);
 					}
 				}
-				EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
-				if (outapp)
-					QueuePacket(outapp);
+				if(GetVersion() > 546) {
+					EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
+					if (outapp)
+						QueuePacket(outapp);
+				}
 				Loot(0, player->GetPendingLootItems(loot_id), spawn);
 			}
 			else {
@@ -2829,9 +2853,11 @@ void Client::HandleLoot(EQApplicationPacket* app) {
 							safe_delete(packet);
 						}
 					}
-					EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
-					if (outapp)
-						QueuePacket(outapp);
+					if(GetVersion() > 546) {
+						EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
+						if (outapp)
+							QueuePacket(outapp);
+					}
 					Loot(spawn);
 					if (!spawn->HasLoot()) {
 						CloseLoot(loot_id);
@@ -11056,7 +11082,11 @@ void Client::SendSpawnChanges(set<Spawn*>& spawns) {
 void Client::MakeSpawnChangePacket(map<int32, SpawnData> info_changes, map<int32, SpawnData> pos_changes, map<int32, SpawnData> vis_changes, int32 info_size, int32 pos_size, int32 vis_size)
 {
 	static const int8 oversized = 255;
-	int16 opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd);
+	int16 opcode_val = 0;
+	
+	if (EQOpcodeManager.count(GetOpcodeVersion(version)) != 0) {	
+		opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd);
+	}
 	int32 size = info_size + pos_size + vis_size + 8;
 	if (version > 283) //version 283 and below uses an overload for size, not always 4 bytes
 		size += 3;