Browse Source

Some equipment lock fixes (not all resolved yet) plus LUA equip functions, regular mail without items works

Partial address issue #246 - normal mail can be sent/received without corrupting the packet.  'Double' 0x80 item packets need serialize support, LE was working on it for crafting, holding off fully addressing 246 with that addition.

Fix #253

bool return = SetEquippedItemByID(Entity, slot, item_id)
bool return = SetEquippedItem(Entity, slot, item)
return = false if it fails to set the item, means the slot is in use already, have to unequip first

UnequipSlot(Entity, slot, no_delete_item) -- does delete the item upon unequipping by default, otherwise set no_delete_item = true
SetEquipment(Entity, slot, type, r, g, b, h_r, h_g, h_b) -- (r = red, g = green, b = blue, h_ = highlights)
Image 3 years ago
parent
commit
529a260d3e

+ 10 - 7
EQ2/source/WorldServer/Entity.cpp

@@ -1110,6 +1110,7 @@ EquipmentItemList* Entity::GetEquipmentList(){
 }
 
 void Entity::SetEquipment(Item* item, int8 slot){
+	std::lock_guard<std::mutex> lk(MEquipment);
 	if(!item && slot < NUM_SLOTS){
 		SetInfo(&equipment.equip_id[slot], 0);
 		SetInfo(&equipment.color[slot].red, 0);
@@ -1120,13 +1121,15 @@ void Entity::SetEquipment(Item* item, int8 slot){
 		SetInfo(&equipment.highlight[slot].blue, 0);
 	}
 	else{
-		SetInfo(&equipment.equip_id[item->details.slot_id], item->generic_info.appearance_id);
-		SetInfo(&equipment.color[item->details.slot_id].red, item->generic_info.appearance_red);
-		SetInfo(&equipment.color[item->details.slot_id].green, item->generic_info.appearance_green);
-		SetInfo(&equipment.color[item->details.slot_id].blue, item->generic_info.appearance_blue);
-		SetInfo(&equipment.highlight[item->details.slot_id].red, item->generic_info.appearance_highlight_red);
-		SetInfo(&equipment.highlight[item->details.slot_id].green, item->generic_info.appearance_highlight_green);
-		SetInfo(&equipment.highlight[item->details.slot_id].blue, item->generic_info.appearance_highlight_blue);
+		if ( slot >= NUM_SLOTS ) 
+			slot = item->details.slot_id;
+		SetInfo(&equipment.equip_id[slot], item->generic_info.appearance_id);
+		SetInfo(&equipment.color[slot].red, item->generic_info.appearance_red);
+		SetInfo(&equipment.color[slot].green, item->generic_info.appearance_green);
+		SetInfo(&equipment.color[slot].blue, item->generic_info.appearance_blue);
+		SetInfo(&equipment.highlight[slot].red, item->generic_info.appearance_highlight_red);
+		SetInfo(&equipment.highlight[slot].green, item->generic_info.appearance_highlight_green);
+		SetInfo(&equipment.highlight[slot].blue, item->generic_info.appearance_highlight_blue);
 	}
 }
 

+ 5 - 1
EQ2/source/WorldServer/Entity.h

@@ -1201,6 +1201,7 @@ public:
 
 	void SetEquipment(Item* item, int8 slot = 255);
 	void SetEquipment(int8 slot, int16 type, int8 red, int8 green, int8 blue, int8 h_r, int8 h_g, int8 h_b){
+		std::lock_guard<std::mutex> lk(MEquipment);
 		SetInfo(&equipment.equip_id[slot], type);
 		SetInfo(&equipment.color[slot].red, red);
 		SetInfo(&equipment.color[slot].green, green);
@@ -1314,6 +1315,7 @@ public:
 	EQ2_Color* GetMountColor(){
 		return &features.mount_color;
 	}	
+	// should only be accessed through MEquipment mutex
 	EQ2_Equipment	equipment;
 	CharFeatures	features;	
 
@@ -1531,6 +1533,9 @@ public:
 	bool SetInfoStructUInt(std::string field, int64 value);
 	bool SetInfoStructSInt(std::string field, sint64 value);
 	bool SetInfoStructFloat(std::string field, float value);
+
+	// when PacketStruct is fixed for C++17 this should become a shared_mutex and handle read/write lock
+	std::mutex		MEquipment;
 protected:
 	bool	in_combat;
 
@@ -1614,7 +1619,6 @@ private:
 	map<string, boost::function<void(sint8)> > set_sint8_funcs;
 	
 	map<string, boost::function<void(std::string)> > set_string_funcs;
-
 };
 
 #endif

+ 7 - 0
EQ2/source/WorldServer/Items/Items.cpp

@@ -3553,6 +3553,11 @@ EquipmentItemList::~EquipmentItemList(){
 bool EquipmentItemList::AddItem(int8 slot, Item* item){
 	if(item){
 		MEquipmentItems.lock();
+		Item* curItem = GetItem(slot);
+
+		if (curItem) // existing item in slot
+			return false;
+		
 		SetItem(slot, item);
 		if (item->details.unique_id == 0) {
 			GetItem(slot)->details.unique_id = MasterItemList::NextUniqueID();
@@ -3577,10 +3582,12 @@ int8 EquipmentItemList::GetNumberOfItems(){
 }
 
 void EquipmentItemList::SetItem(int8 slot_id, Item* item){
+	MEquipmentItems.lock();
 	item->details.inv_slot_id = 0;
 	item->details.slot_id = slot_id;
 	item->details.index = slot_id;
 	items[slot_id] = item;
+	MEquipmentItems.unlock();
 }
 
 vector<Item*>* EquipmentItemList::GetAllEquippedItems(){

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

@@ -5995,6 +5995,8 @@ int EQ2Emu_lua_HasItemEquipped(lua_State* state) {
 		return 0;
 	Spawn* player = lua_interface->GetSpawn(state);
 	int32 item_id = lua_interface->GetInt32Value(state, 2);
+	
+	lua_interface->ResetFunctionStack(state);
 	if (!player->IsPlayer()) {
 		lua_interface->LogError("%s: LUA HasItemEquipped command error: spawn is not player", lua_interface->GetScriptName(state));
 		return 0;
@@ -6009,6 +6011,7 @@ int	EQ2Emu_lua_GetEquippedItemBySlot(lua_State* state) {
 	Spawn* player = lua_interface->GetSpawn(state);
 	int8 slot = lua_interface->GetInt8Value(state, 2);
 
+	lua_interface->ResetFunctionStack(state);
 	if (!player) {
 		lua_interface->LogError("%s: LUA GetEquippedItemBySlot command error: spawn is not valid", lua_interface->GetScriptName(state));
 		return 0;
@@ -6032,6 +6035,7 @@ int	EQ2Emu_lua_GetEquippedItemByID(lua_State* state) {
 	Spawn* player = lua_interface->GetSpawn(state);
 	int32 id = lua_interface->GetInt32Value(state, 2);
 
+	lua_interface->ResetFunctionStack(state);
 	if (!player) {
 		lua_interface->LogError("%s: LUA GetEquippedItemByID command error: spawn is not valid", lua_interface->GetScriptName(state));
 		return 0;
@@ -6049,6 +6053,141 @@ int	EQ2Emu_lua_GetEquippedItemByID(lua_State* state) {
 	return 1;
 }
 
+int	EQ2Emu_lua_SetEquippedItemByID(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	int8 slot = lua_interface->GetInt8Value(state, 2);
+	int32 item_id = lua_interface->GetInt32Value(state, 3);
+
+	lua_interface->ResetFunctionStack(state);
+	if (!spawn) {
+		lua_interface->LogError("%s: LUA SetEquippedItemByID command error: spawn is not valid", lua_interface->GetScriptName(state));
+		return 0;
+	}
+	if (!spawn->IsEntity()) {
+		lua_interface->LogError("%s: LUA SetEquippedItemByID command error: spawn is not an entity", lua_interface->GetScriptName(state));
+		return 0;
+	}
+	
+	Item* item = master_item_list.GetItem(item_id);
+	if (!item) {
+		lua_interface->LogError("%s: LUA SetEquippedItemByID command error: equipped item with used id %u not found", item_id, lua_interface->GetScriptName(state));
+		return 0;
+	}
+	
+	Item* copy = new Item(item);
+	bool result = ((Entity*)spawn)->GetEquipmentList()->AddItem(slot, copy);
+
+	if(result)
+	{
+		((Entity*)spawn)->SetEquipment(copy, slot);
+		spawn->vis_changed = true;
+
+		if(spawn->IsPlayer())
+			((Player*)spawn)->GetClient()->SendEquipOrInvUpdateBySlot(slot);
+	}
+	else
+	{
+		safe_delete(copy);
+	}
+	
+
+	lua_interface->SetBooleanValue(state, result);
+	return 1;
+}
+
+int	EQ2Emu_lua_SetEquippedItem(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	int8 slot = lua_interface->GetInt8Value(state, 2);
+	Item* item = lua_interface->GetItem(state, 3);
+
+	lua_interface->ResetFunctionStack(state);
+	if (!spawn) {
+		lua_interface->LogError("%s: LUA SetEquippedItem command error: spawn is not valid", lua_interface->GetScriptName(state));
+		return 0;
+	}
+	if (!spawn->IsEntity()) {
+		lua_interface->LogError("%s: LUA SetEquippedItem command error: spawn is not an entity", lua_interface->GetScriptName(state));
+		return 0;
+	}
+	if (!item) {
+		lua_interface->LogError("%s: LUA SetEquippedItem command error: passed item not found", lua_interface->GetScriptName(state));
+		return 0;
+	}
+	
+	bool result = ((Entity*)spawn)->GetEquipmentList()->AddItem(slot, item);
+	if(result)
+	{
+		((Entity*)spawn)->SetEquipment(item, slot);
+		spawn->vis_changed = true;
+
+		if(spawn->IsPlayer())
+			((Player*)spawn)->GetClient()->SendEquipOrInvUpdateBySlot(slot);
+	}
+	lua_interface->SetBooleanValue(state, result);
+	return 1;
+}
+
+int	EQ2Emu_lua_UnequipSlot(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	int8 slot = lua_interface->GetInt8Value(state, 2);
+	bool no_delete_item = (lua_interface->GetBooleanValue(state, 2) == false); // if not set then we default to deleting it, otherwise if set to true we don't delete
+
+	lua_interface->ResetFunctionStack(state);
+	if (!spawn) {
+		lua_interface->LogError("%s: LUA UnequipSlot command error: spawn is not valid", lua_interface->GetScriptName(state));
+		return 0;
+	}
+	if (!spawn->IsEntity()) {
+		lua_interface->LogError("%s: LUA UnequipSlot command error: spawn is not an entity", lua_interface->GetScriptName(state));
+		return 0;
+	}
+	
+	((Entity*)spawn)->GetEquipmentList()->RemoveItem(slot, no_delete_item);
+	((Entity*)spawn)->SetEquipment(nullptr, slot);
+	spawn->vis_changed = true;
+
+	if(spawn->IsPlayer())
+		((Player*)spawn)->GetClient()->SendEquipOrInvUpdateBySlot(slot);
+
+	lua_interface->SetBooleanValue(state, true);
+	return 1;
+}
+
+int	EQ2Emu_lua_SetEquipment(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	int8 slot = lua_interface->GetInt8Value(state, 2);
+	int16 type = lua_interface->GetInt16Value(state, 3);
+	int8 r = lua_interface->GetInt8Value(state, 4);
+	int8 g = lua_interface->GetInt8Value(state, 5);
+	int8 b = lua_interface->GetInt8Value(state, 6);
+	int8 h_r = lua_interface->GetInt8Value(state, 7);
+	int8 h_g = lua_interface->GetInt8Value(state, 8);
+	int8 h_b = lua_interface->GetInt8Value(state, 9);
+
+	lua_interface->ResetFunctionStack(state);
+	if (!spawn) {
+		lua_interface->LogError("%s: LUA SetEquipment command error: spawn is not valid", lua_interface->GetScriptName(state));
+		return 0;
+	}
+	if (!spawn->IsEntity()) {
+		lua_interface->LogError("%s: LUA SetEquipment command error: spawn is not an entity", lua_interface->GetScriptName(state));
+		return 0;
+	}
+	
+	((Entity*)spawn)->SetEquipment(slot, type, r, g, b, h_r, h_g, h_b);
+	spawn->vis_changed = true;
+	lua_interface->SetBooleanValue(state, true);
+	return 1;
+}
+
 int	EQ2Emu_lua_GetItemByID(lua_State* state) {
 	if (!lua_interface)
 		return 0;
@@ -6057,6 +6196,7 @@ int	EQ2Emu_lua_GetItemByID(lua_State* state) {
 	int8 count = lua_interface->GetInt8Value(state, 3);
 	bool include_bank = lua_interface->GetInt8Value(state, 4);
 
+	lua_interface->ResetFunctionStack(state);
 	if (!player) {
 		lua_interface->LogError("%s: LUA GetItemByID command error: spawn is not valid", lua_interface->GetScriptName(state));
 		return 0;
@@ -11538,7 +11678,6 @@ int EQ2Emu_lua_AddPlayerMail(lua_State* state) {
 	}
 
 	int32 time_sent = sent_time > 0 ? sent_time : Timer::GetUnixTimeStamp();
-	int32 char_id = ((Player*)spawn)->GetCharacterID();
 
 	((Player*)spawn)->GetClient()->CreateAndUpdateMail(fromName, subjectName, mailBody, mailType, copper, silver, gold, platinum, item_id, stack_size, time_sent, expire_time);
 	lua_interface->SetBooleanValue(state, true);

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

@@ -134,6 +134,10 @@ int EQ2Emu_lua_HasFreeSlot(lua_State* state);
 int EQ2Emu_lua_HasItemEquipped(lua_State* state);
 int EQ2Emu_lua_GetEquippedItemBySlot(lua_State* state);
 int EQ2Emu_lua_GetEquippedItemByID(lua_State* state);
+int EQ2Emu_lua_SetEquippedItemByID(lua_State* state);
+int EQ2Emu_lua_SetEquippedItem(lua_State* state);
+int EQ2Emu_lua_UnequipSlot(lua_State* state);
+int EQ2Emu_lua_SetEquipment(lua_State* state);
 int EQ2Emu_lua_GetItemByID(lua_State* state);
 int EQ2Emu_lua_GetItemType(lua_State* state);
 int EQ2Emu_lua_GetSpellName(lua_State* state);

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

@@ -808,6 +808,10 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "HasFreeSlot", EQ2Emu_lua_HasFreeSlot);
 	lua_register(state, "HasItemEquipped", EQ2Emu_lua_HasItemEquipped);
 	lua_register(state, "GetEquippedItemByID", EQ2Emu_lua_GetEquippedItemByID);
+	lua_register(state, "SetEquippedItemByID", EQ2Emu_lua_SetEquippedItemByID);
+	lua_register(state, "SetEquippedItem", EQ2Emu_lua_SetEquippedItem);
+	lua_register(state, "UnequipSlot", EQ2Emu_lua_UnequipSlot);
+	lua_register(state, "SetEquipment", EQ2Emu_lua_SetEquipment);
 	lua_register(state, "GetEquippedItemBySlot", EQ2Emu_lua_GetEquippedItemBySlot);
 	lua_register(state, "GetItemByID", EQ2Emu_lua_GetItemByID);
 	lua_register(state, "GetItemType", EQ2Emu_lua_GetItemType);

+ 3 - 0
EQ2/source/WorldServer/Spawn.cpp

@@ -2369,9 +2369,12 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
 					}
 				}
 			}
+			entity->MEquipment.lock();
 			packet->setDataByName("equipment_types", entity->equipment.equip_id[i], i);
 			packet->setColorByName("equipment_colors", entity->equipment.color[i], i);
 			packet->setColorByName("equipment_highlights", entity->equipment.highlight[i], i);
+			entity->MEquipment.unlock();
+			
 		}
 		packet->setDataByName("mount_type", entity->GetMount());
 

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

@@ -2003,15 +2003,9 @@ int32 WorldDatabase::LoadNPCAppearanceEquipmentData(ZoneServer* zone){
 				count++;
 			spawn_id = new_spawn_id;
 		}
-		slot = atoi(row[1]);
+		slot = atoul(row[1]);
 		if(slot < NUM_SLOTS){
-			npc->equipment.equip_id[slot] = atoi(row[2]);
-			npc->equipment.color[slot].red = atoi(row[3]);
-			npc->equipment.color[slot].green = atoi(row[4]);
-			npc->equipment.color[slot].blue = atoi(row[5]);
-			npc->equipment.highlight[slot].red = atoi(row[6]);
-			npc->equipment.highlight[slot].green = atoi(row[7]);
-			npc->equipment.highlight[slot].blue = atoi(row[8]);
+			npc->SetEquipment(slot, atoul(row[2]), atoul(row[3]), atoul(row[4]), atoul(row[5]), atoul(row[6]), atoul(row[7]), atoul(row[8]));
 		}
 	}
 	if(query.GetError() && strlen(query.GetError()) > 0)
@@ -6859,13 +6853,7 @@ void WorldDatabase::LoadNPCAppearanceEquipmentData(ZoneServer* zone, int32 spawn
 	while (result.Next()) {
 		slot = result.GetInt8(0);
 		if(slot < NUM_SLOTS) {
-			npc->equipment.equip_id[slot] = result.GetInt16(1);
-			npc->equipment.color[slot].red = result.GetInt8(2);
-			npc->equipment.color[slot].green = result.GetInt8(3);
-			npc->equipment.color[slot].blue = result.GetInt8(4);
-			npc->equipment.highlight[slot].red = result.GetInt8(5);
-			npc->equipment.highlight[slot].green = result.GetInt8(6);
-			npc->equipment.highlight[slot].blue = result.GetInt8(7);
+			npc->SetEquipment(slot, result.GetInt16(1), result.GetInt8(2), result.GetInt8(3), result.GetInt8(4), result.GetInt8(5), result.GetInt8(6), result.GetInt8(7));
 		}
 	}
 }

+ 39 - 6
EQ2/source/WorldServer/client.cpp

@@ -7296,7 +7296,7 @@ void Client::SendMailList() {
 		if (p) {
 			MutexMap<int32, Mail*>* mail_list = player->GetMail();
 			MutexMap<int32, Mail*>::iterator itr = mail_list->begin();
-			int16 i = 0;
+			int32 i = 0;
 			p->setDataByName("kiosk_id", kiosk_id);
 			p->setArrayLengthByName("num_messages", (int16)mail_list->size());
 			while (itr.Next()) {
@@ -7319,11 +7319,26 @@ void Client::SendMailList() {
 				p->setArrayDataByName("coin_silver", mail->coin_silver, i);
 				p->setArrayDataByName("coin_gold", mail->coin_gold, i);
 				p->setArrayDataByName("coin_plat", mail->coin_plat, i);
-				p->setArrayDataByName("num_items", mail->stack, i);
-				p->setArrayDataByName("packettype", GetItemPacketType(GetVersion()));
-				p->setArrayDataByName("packetsubtype", 0xFF, i);
+				if(mail->stack)
+					p->setArrayDataByName("num_items", mail->stack, i);
+
+				//p->setArrayDataByName("unknown2", 0, i);
 
-				p->setArrayDataByName("unknown2", 0, i);
+				if(mail->stack)
+				{
+					// need double item packet support for serialize, LE was working on it for crafting so don't want to double down the work
+					//Item* item = master_item_list.GetItem(mail->char_item_id);
+					//EQ2Packet* pack = item->serialize(GetVersion(), true, GetPlayer(), true, GetItemPacketType(GetVersion()), 0, false, false);
+					//p->setArrayLengthByName("item", pack->size, i);
+					//p->setArrayDataByName("item", pack->pBuffer, i, 0);
+					//p->setItemArrayDataByName("item", item, GetPlayer(), i, 0, 2, true, true);
+					//DumpPacket(pack);
+				}
+				else
+				{
+					p->setArrayDataByName("packettype", GetItemPacketType(GetVersion()), i);
+					p->setArrayDataByName("packetsubtype", 0xFF, i);
+				}
 				//p->setArrayDataByName("item_id", 5, i, 0);
 				//Item* item = master_item_list.GetItem(5);
 				//if (item)
@@ -7347,7 +7362,9 @@ void Client::SendMailList() {
 			}
 			p->setDataByName("unknown3", 0x01F4);
 			p->setDataByName("unknown4", 0x01000000);
-			QueuePacket(p->serialize());
+			EQ2Packet* pack = p->serialize();
+			//DumpPacket(pack->pBuffer, pack->size);
+			QueuePacket(pack);
 			safe_delete(p);
 		}
 	}
@@ -9902,4 +9919,20 @@ int8 mailType, int32 copper, int32 silver, int32 gold, int32 platinum, int32 ite
 	mail->already_read = 0;
 	database.SavePlayerMail(mail);
 	GetPlayer()->AddMail(mail);
+}
+
+void Client::SendEquipOrInvUpdateBySlot(int8 slot)
+{
+	if(slot < NUM_SLOTS)
+	{
+		EQ2Packet* app = GetPlayer()->GetEquipmentList()->serialize(GetVersion());
+		if (app)
+			QueuePacket(app);
+	}
+	else
+	{
+		EQ2Packet* outapp = GetPlayer()->SendInventoryUpdate(GetVersion());
+			if (outapp)
+				QueuePacket(outapp);
+	}
 }

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

@@ -466,6 +466,7 @@ public:
 	void CreateAndUpdateMail(std::string fromName, std::string subjectName, std::string mailBody, 
 		int8 mailType, int32 copper, int32 silver, int32 gold, int32 platinum, int32 item_id, int16 stack_size, int32 time_sent, int32 expire_time);
 
+	void SendEquipOrInvUpdateBySlot(int8 slot);
 private:
 	void    SavePlayerImages();
 	void	SkillChanged(Skill* skill, int16 previous_value, int16 new_value);