Browse Source

End of Sirius Feb 2021 Stage

Fix #250 - Added a caching of state to send a single shoot of visual state.  When setting visual states for chests it was causing opening/closing of the chest if you were not already in range of it.  See SendStateCommand and ProcessStateCommands in client.cpp for more

Fix #302 - Integrated GiveQuestItem into the Client::DisplayQuestComplete / Client::AcceptQuestReward process.  There is support for temp rewards/status/coin.
LUA Functions added, ONLY CALLED BEFORE GiveQuestItem, NOT to be used with GiveQuestReward
SetStatusTmpReward(quest, status)
SetCoinTmpReward(quest, coin)

- Also addressed quests not updating properly in the journal where sub tasks would completely disappear

Fix #299 - AoM client Addressed hot swapping a bagged item to equipment slots poofing the item.  Note: DoF client seems to have inventory issues of its own, those are not addressed here

Fix #207 - Chests have new rules for exposure times
	RULE_INIT(R_Loot, ChestUnlockedTimeDrop, "1200"); // time in seconds, 20 minutes by default, triggers only if AllowChestUnlockByDropTime is 1
	RULE_INIT(R_Loot, AllowChestUnlockByDropTime, "1"); // when set to 1 we will start a countdown timer to allow anyone to loot once ChestUnlockedTimeDrop elapsed
	RULE_INIT(R_Loot, ChestUnlockedTimeTrap, "600"); // time in seconds, 10 minutes by default
	RULE_INIT(R_Loot, AllowChestUnlockByTrapTime, "1"); // when set to 1 we will allow unlocking the chest to all players after the trap is triggered (or chest is open) and period ChestUnlockedTimeTrap elapsed

Fix #297 - Prevent stacking of food / drink effects.  Also added spell_type Food and Drink to enumeration.  When these are set it tells the server these are unique effects to food/drink and cannot stack with other consumed items.

In conjunction with this fix, auto consume is implemented (while in zone).  Flips the auto consume on/off option as well as 'yellow tints' the background behind the auto consume options when 'on'.

Issue #305 has been created to allow 'cross zone' of maintained effects / effects on the player.

- Few additional fixes:
  *  if you remove an item from inventory and there is a dialog screen, you can now close it (see last parchment in Taint quest before main boss mob, it wasnt letting you put the parchment away).
  * equipment serialized to the player now always includes the player pointer, this was causing menu item information for food/drink to be omitted on zone-in
  *

- Few crash fixes, Deleting spells on players from lua_interface when zoning, protection on QuestStep instantiation, we recreate all the ids and locations so as to not be dependent on the prior step should it be deleted
Image 3 years ago
parent
commit
80bb342256

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

@@ -383,7 +383,7 @@ void ClientPacketFunctions::SendZoneChange(Client* client, char* zone_ip, int16
 }
 
 void ClientPacketFunctions::SendStateCommand(Client* client, int32 spawn_id, int32 state) {
-	if (!client) {
+	if (!client || !spawn_id) {
 		return;
 	}
 

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

@@ -306,7 +306,7 @@ void Entity::RangeAttack(Spawn* victim, float distance, Item* weapon, Item* ammo
 					((Player*)this)->equipment_list.RemoveItem(ammo->details.slot_id, true);
 
 				Client* client = GetZone()->GetClientBySpawn(this);
-				EQ2Packet* outapp = ((Player*)this)->GetEquipmentList()->serialize(client->GetVersion());
+				EQ2Packet* outapp = ((Player*)this)->GetEquipmentList()->serialize(client->GetVersion(), (Player*)this);
 				if(outapp)
 					client->QueuePacket(outapp);
 			}

+ 24 - 20
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -8284,7 +8284,10 @@ void Commands::Command_Toggle_AutoConsume(Client* client, Seperator* sep)
 			if (flag == 1)
 				client->Message(CHANNEL_NARRATIVE, "You decide to eat immediately whenever you become hungry.");
 			else
+			{
 				client->Message(CHANNEL_NARRATIVE, "You decide to ignore the hunger.");
+				return;
+			}
 		}
 		else
 		{
@@ -8294,8 +8297,20 @@ void Commands::Command_Toggle_AutoConsume(Client* client, Seperator* sep)
 			if (flag == 1)
 				client->Message(CHANNEL_NARRATIVE, "You decide to drink immediately whenever you become thirsty.");
 			else
+			{
 				client->Message(CHANNEL_NARRATIVE, "You decide to ignore the thirst.");
+				return;
+			}
 		}
+
+
+		if(slot == 22 && player->GetSpellEffectBySpellType(SPELL_TYPE_FOOD))
+			return;
+		else if (player->GetSpellEffectBySpellType(SPELL_TYPE_DRINK))
+			return;
+		Item* item = player->GetEquipmentList()->GetItem(slot);
+		if(item)
+			client->ConsumeFoodDrink(item, slot);
 	}
 }
 
@@ -10343,28 +10358,17 @@ void Commands::Command_ConsumeFood(Client* client, Seperator* sep) {
 		Player* player = client->GetPlayer();
 		int8 slot = atoi(sep->arg[0]);
 		Item* item = player->GetEquipmentList()->GetItem(slot);
-
-		if(item) {
-			LogWrite(MISC__INFO, 1, "Command", "ItemID: %u, ItemName: %s ItemCount: %i ", item->details.item_id, item->name.c_str(), item->details.count);
-			if(item->GetItemScript() && lua_interface){
-				lua_interface->RunItemScript(item->GetItemScript(), "cast", item, client->GetPlayer());
-				if (slot == 22)
-					client->Message(CHANNEL_NARRATIVE, "You eat a %s.", item->name.c_str());
-				else
-					client->Message(CHANNEL_NARRATIVE, "You drink a %s.", item->name.c_str());
-			}
-		}
-
-		if (item->details.count > 1) {
-			item->details.count -= 1;
-			item->save_needed = true;
+		if(slot == 22 && player->GetSpellEffectBySpellType(SPELL_TYPE_FOOD))
+		{
+			client->Message(CHANNEL_NARRATIVE, "If you ate anymore you would explode!");
+			return;
 		}
-		else {
-			player->GetEquipmentList()->RemoveItem(slot, true);
-			database.DeleteItem(player->GetCharacterID(), item, "EQUIPPED");
+		else if (player->GetSpellEffectBySpellType(SPELL_TYPE_DRINK))
+		{
+			client->Message(CHANNEL_NARRATIVE, "If you drank anymore you would explode!");
+			return;
 		}
-		client->GetPlayer()->SetCharSheetChanged(true);
-		client->QueuePacket(player->GetEquipmentList()->serialize(client->GetVersion(), player));
+		client->ConsumeFoodDrink(item, slot);
 	}
 }
 

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

@@ -30,6 +30,7 @@
 #include "ClientPacketFunctions.h"
 #include "Skills.h"
 #include "Rules/Rules.h"
+#include "LuaInterface.h"
 
 extern World world;
 extern MasterItemList master_item_list;
@@ -37,6 +38,7 @@ extern MasterSpellList master_spell_list;
 extern MasterSkillList master_skill_list;
 extern RuleManager rule_manager;
 extern Classes classes;
+extern LuaInterface* lua_interface;
 
 Entity::Entity(){
 	MapInfoStruct();	
@@ -103,6 +105,35 @@ Entity::~Entity(){
 	for (itr4 = immunities.begin(); itr4 != immunities.end(); itr4++)
 		safe_delete(itr4->second);
 	immunities.clear();
+	DeleteSpellEffects();
+}
+
+void Entity::DeleteSpellEffects()
+{
+	map<LuaSpell*,bool> deletedPtrs;
+
+	for(int i=0;i<45;i++){
+		if(i<30){
+			if(GetInfoStruct()->maintained_effects[i].spell_id != 0xFFFFFFFF)
+			{
+				lua_interface->RemoveSpell(GetInfoStruct()->maintained_effects[i].spell);
+				if (IsPlayer())
+					GetInfoStruct()->maintained_effects[i].icon = 0xFFFF;
+
+				deletedPtrs[GetInfoStruct()->maintained_effects[i].spell] = true;
+				GetInfoStruct()->maintained_effects[i].spell = nullptr;
+			}
+		}
+		if(GetInfoStruct()->spell_effects[i].spell_id != 0xFFFFFFFF)
+		{
+			if(deletedPtrs.find(GetInfoStruct()->spell_effects[i].spell) == deletedPtrs.end())
+			{
+				lua_interface->RemoveSpell(GetInfoStruct()->spell_effects[i].spell);
+				deletedPtrs[GetInfoStruct()->spell_effects[i].spell] = true;
+				GetInfoStruct()->spell_effects[i].spell = nullptr;
+			}
+		}
+	}
 }
 
 void Entity::MapInfoStruct()
@@ -931,6 +962,20 @@ SpellEffects* Entity::GetSpellEffect(int32 id, Entity* caster) {
 	return ret;
 }
 
+SpellEffects* Entity::GetSpellEffectBySpellType(int8 spell_type) {
+	SpellEffects* ret = 0;
+	InfoStruct* info = GetInfoStruct();
+	MSpellEffects.readlock(__FUNCTION__, __LINE__);
+	for(int i = 0; i < 45; i++) {
+		if(info->spell_effects[i].spell_id != 0xFFFFFFFF && info->spell_effects[i].spell->spell->GetSpellData()->spell_type == spell_type) {
+			ret = &info->spell_effects[i];
+			break;
+		}
+	}
+	MSpellEffects.releasereadlock(__FUNCTION__, __LINE__);
+	return ret;
+}
+
 SpellEffects* Entity::GetSpellEffectWithLinkedTimer(int32 id, int32 linked_timer, sint32 type_group_spell_id, Entity* caster) {
 	SpellEffects* ret = 0;
 	InfoStruct* info = GetInfoStruct();

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

@@ -1086,6 +1086,7 @@ public:
 	Entity();
 	virtual ~Entity();
 
+	void DeleteSpellEffects();
 	void MapInfoStruct();
 	virtual float GetDodgeChance();
 	virtual void AddMaintainedSpell(LuaSpell* spell);
@@ -1157,6 +1158,7 @@ public:
 	MaintainedEffects* GetFreeMaintainedSpellSlot();
 	SpellEffects* GetFreeSpellEffectSlot();
 	SpellEffects* GetSpellEffect(int32 id, Entity* caster = 0);
+	SpellEffects* GetSpellEffectBySpellType(int8 spell_type);
 	SpellEffects* GetSpellEffectWithLinkedTimer(int32 id, int32 linked_timer = 0, sint32 type_group_spell_id = 0, Entity* caster = 0);
 	LuaSpell* HasLinkedTimerID(LuaSpell* spell, Spawn* target = nullptr,  bool stackWithOtherPlayers = true);
 

+ 8 - 2
EQ2/source/WorldServer/Items/Items.cpp

@@ -2716,7 +2716,8 @@ void PlayerItemList::AddItem(Item* item){ //is called with a slot already set
 			Item* bag = GetItemFromUniqueID(item->details.inv_slot_id, true);
 			if(bag && bag->IsBag()){
 				if(item->details.slot_id > bag->details.num_slots){
-					LogWrite(ITEM__ERROR, 0, "Item", "Error Adding Item: Invalid slot for item unique id: %u", item->details.unique_id);
+					LogWrite(ITEM__ERROR, 0, "Item", "Error Adding Item: Invalid slot for item unique id: %u (%s - %i), InvSlotID: %u, slotid: %u, numslots: %u", item->details.unique_id, item->name.c_str(), 
+					item->details.item_id, item->details.inv_slot_id, item->details.slot_id, bag->details.num_slots);
 					safe_delete(item);
 					return;
 				}
@@ -3739,9 +3740,14 @@ EQ2Packet* EquipmentItemList::serialize(int16 version, Player* player){
 							menu_data += ORIG_ITEM_MENU_TYPE_FOOD;
 					}
 					else {
-						menu_data += ITEM_MENU_TYPE_CONSUME;
+							menu_data += ITEM_MENU_TYPE_CONSUME;
 						if (player && ((item->IsFoodFood() && player->get_character_flag(CF_FOOD_AUTO_CONSUME)) || (item->IsFoodDrink() && player->get_character_flag(CF_DRINK_AUTO_CONSUME))))
+						{
+							// needs all 3 to display 'auto consume' off option as well as set the yellowish tint in the background
 							menu_data += ITEM_MENU_TYPE_CONSUME_OFF;
+							menu_data += ORIG_ITEM_MENU_TYPE_DRINK;
+							menu_data += ORIG_ITEM_MENU_TYPE_FOOD;
+						}
 					}
 				}
 				packet->setSubstructArrayDataByName("items", "unique_id", item->details.unique_id, 0, i);

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

@@ -1080,7 +1080,7 @@ public:
 	int8	GetFreeSlot(Item* tmp, int8 slot_id = 255);
 	int8	GetSlotByItem(Item* item);
 	ItemStatsValues*	CalculateEquipmentBonuses(Entity* entity = 0);
-	EQ2Packet* serialize(int16 version, Player* player = 0);
+	EQ2Packet* serialize(int16 version, Player* player);
 	uchar* xor_packet;
 	uchar* orig_packet;
 private:

+ 1 - 1
EQ2/source/WorldServer/Items/Loot.cpp

@@ -431,7 +431,7 @@ NPC* Entity::DropChest() {
 	chest->SetShowName(1);
 	chest->SetTargetable(1);
 	chest->SetLevel(GetLevel());
-
+	chest->SetChestDropTime();
 	// Set the brain to a blank brain so it does nothing
 	chest->SetBrain(new BlankBrain(chest));
 	// Set the x, y, z, heading, location (grid id) to that of the dead spawn

+ 85 - 63
EQ2/source/WorldServer/LuaFunctions.cpp

@@ -370,10 +370,38 @@ int EQ2Emu_lua_SendStateCommand(lua_State* state) {
 		return 0;
 	Spawn* spawn = lua_interface->GetSpawn(state);
 	int32 new_state = lua_interface->GetInt32Value(state, 2);
+	Spawn* player = lua_interface->GetSpawn(state, 3);
+
+	lua_interface->ResetFunctionStack(state);
+
 	if (spawn) {
-		spawn->GetZone()->SendStateCommand(spawn, new_state);
+		if(player)
+		{
+			if(player->IsPlayer())
+			{
+				Client* client = ((Player*)player)->GetClient();
+				if(client)
+				{
+					ClientPacketFunctions::SendStateCommand(client, client->GetPlayer()->GetIDWithPlayerSpawn(spawn), new_state);		
+					lua_interface->SetBooleanValue(state, true);
+					return 1;
+				}
+				else
+					LogWrite(LUA__ERROR, 0, "LUA", "Spawn %s Error in SendStateCommand,attempted to pass player value in argument 3, but argument does not have active client.", spawn->GetName());
+			}
+			else
+				LogWrite(LUA__ERROR, 0, "LUA", "Spawn %s Error in SendStateCommand,attempted to pass player value in argument 3, but argument is NOT a player.", spawn->GetName());
+		}
+		else
+		{
+			spawn->GetZone()->SendStateCommand(spawn, new_state);
+			lua_interface->SetBooleanValue(state, true);
+			return 1;
+		}
 	}
-	return 0;
+
+	lua_interface->SetBooleanValue(state, false);
+	return 1;
 }
 
 int EQ2Emu_lua_SpawnSet(lua_State* state) {
@@ -3595,6 +3623,30 @@ int EQ2Emu_lua_SetQuestRewardStatus(lua_State* state) {
 	return 0;
 }
 
+int EQ2Emu_lua_SetStatusTmpReward(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Quest* quest = lua_interface->GetQuest(state);
+	if (quest) {
+		int32 status = lua_interface->GetInt32Value(state, 2);
+		quest->SetStatusTmpReward(status);
+	}
+	return 0;
+}
+
+
+int EQ2Emu_lua_SetCoinTmpReward(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Quest* quest = lua_interface->GetQuest(state);
+	if (quest) {
+		int64 coins = lua_interface->GetInt64Value(state, 2);
+		quest->SetCoinTmpReward(coins);
+	}
+	return 0;
+}
+
+
 int EQ2Emu_lua_SetQuestRewardComment(lua_State* state) {
 	if (!lua_interface)
 		return 0;
@@ -3698,7 +3750,8 @@ int EQ2Emu_lua_AddQuestStepChat(lua_State* state) {
 			quest_step->SetIcon(icon);
 		if (quest->GetPlayer()) {
 			Client* client = quest->GetPlayer()->GetZone()->GetClientBySpawn(quest->GetPlayer());
-			quest->GetPlayer()->GetZone()->SendQuestUpdates(client);
+			if(client)
+				quest->GetPlayer()->GetZone()->SendQuestUpdates(client);
 		}
 	}
 	return 0;
@@ -3979,11 +4032,11 @@ int EQ2Emu_lua_UpdateQuestTaskGroupDescription(lua_State* state) {
 	bool display_bullets = (lua_interface->GetInt8Value(state, 4) == 1);
 	if (quest && step > 0 && description.length() > 0) {
 		quest->SetTaskGroupDescription(step, description, display_bullets);
-		if (quest->GetPlayer()) {
+	/*	if (quest->GetPlayer()) {
 			Client* client = quest->GetPlayer()->GetZone()->GetClientBySpawn(quest->GetPlayer());
 			if (client)
 				client->SendQuestUpdateStep(quest, step, false);
-		}
+		}*/
 	}
 	return 0;
 }
@@ -3996,11 +4049,11 @@ int EQ2Emu_lua_UpdateQuestStepDescription(lua_State* state) {
 	string description = lua_interface->GetStringValue(state, 3);
 	if (quest && step > 0 && description.length() > 0) {
 		quest->SetStepDescription(step, description);
-		if (quest->GetPlayer()) {
+		/*if (quest->GetPlayer()) {
 			Client* client = quest->GetPlayer()->GetZone()->GetClientBySpawn(quest->GetPlayer());
 			if (client)
 				client->SendQuestUpdateStepImmediately(quest, step);
-		}
+		}*/
 	}
 	return 0;
 }
@@ -4073,11 +4126,16 @@ int EQ2Emu_lua_GiveQuestReward(lua_State* state) {
 		return 0;
 	Quest* quest = lua_interface->GetQuest(state);
 	Spawn* spawn = lua_interface->GetSpawn(state, 2);
+
+	lua_interface->ResetFunctionStack(state);
 	if (quest && spawn) {
 		if (spawn->IsPlayer()) {
 			Client* client = spawn->GetZone()->GetClientBySpawn(spawn);
 			if (client)
+			{
+				client->AddPendingQuestAcceptReward(quest);
 				client->AddPendingQuestReward(quest);
+			}
 		}
 	}
 	return 0;
@@ -5624,67 +5682,31 @@ int EQ2Emu_lua_GiveQuestItem(lua_State* state)
 		lua_interface->SetBooleanValue(state, false);
 		return 1;
 	}
+	
+	Item* firstItem = new Item(item);
+	quest->AddTmpRewardItem(firstItem);
 
-	PacketStruct* packet = configReader.getStruct("WS_QuestComplete", client->GetVersion());
-	if (packet) {
-		packet->setDataByName("title", "Quest Reward!");
-		packet->setDataByName("name", quest->GetName());
-		packet->setDataByName("description", description.c_str());
-		packet->setDataByName("level", quest->GetLevel());
-
-		// if there are any additional optional items to add we will verify them and append
-		int8 num_args = (int8)lua_interface->GetNumberOfArgs(state);
-		vector<Item*> additionalItems;
-		if(num_args > 4)
+	int8 num_args = (int8)lua_interface->GetNumberOfArgs(state);
+	bool itemsAddedSuccessfully = true;
+	if(num_args > 4)
+	{
+		for(int8 n=5;n<num_args+1;n++)
 		{
-			for(int8 n=5;n<num_args+1;n++)
+			int32 new_item = lua_interface->GetInt32Value(state, n);
+			Item* tmpItem = master_item_list.GetItem(new_item);
+			if(tmpItem)
 			{
-				int32 new_item = lua_interface->GetInt32Value(state, n);
-				Item* tmpItem = master_item_list.GetItem(new_item);
-				if(tmpItem)
-					additionalItems.push_back(tmpItem);
+				Item* newTmpItem = new Item(tmpItem);
+				quest->AddTmpRewardItem(newTmpItem);
 			}
+			else
+				itemsAddedSuccessfully = false;
 		}
-		
-		packet->setArrayLengthByName("num_rewards", 1+additionalItems.size());
-
-		sint8 offset = 2; // all new clients
-
-		if (client->GetVersion() < 860)
-			offset = -1;
-		else if (client->GetVersion() < 1193)
-			offset = 0;
-
-		packet->setArrayDataByName("reward_id", item->details.item_id, 0);
-		packet->setItemArrayDataByName("item", item, (Player*)spawn, 0, 0, offset);
-
-		bool itemsAddedSuccessfully = true;
-
-		itemsAddedSuccessfully = client->AddItem(item_id, 1);
-		client->Message(CHANNEL_COLOR_YELLOW, "You receive %s.", item->CreateItemLink(client->GetVersion()).c_str());
-
-		for(int8 n=0;n<additionalItems.size();n++)
-		{
-			packet->setArrayDataByName("reward_id", additionalItems[n]->details.item_id, n+1);
-			packet->setItemArrayDataByName("item", additionalItems[n], (Player*)spawn, n+1, 0, offset);
-
-			// run until we hit a failure then don't update the boolean since its false
-			if(itemsAddedSuccessfully)
-				itemsAddedSuccessfully = client->AddItem(additionalItems[n]->details.item_id, 1);
-			else // we already failed to add an item somewhere
-				client->AddItem(additionalItems[n]->details.item_id, 1);
-			
-			client->Message(CHANNEL_COLOR_YELLOW, "You receive %s.", additionalItems[n]->CreateItemLink(client->GetVersion()).c_str());
-		}
-		
-		lua_interface->SetBooleanValue(state, itemsAddedSuccessfully);
-
-		client->QueuePacket(packet->serialize());
-		safe_delete(packet);
-		return 1;
 	}
-
-	lua_interface->SetBooleanValue(state, false);
+	client->AddPendingQuestAcceptReward(quest);
+	client->DisplayQuestComplete(quest, true, description);
+	
+	lua_interface->SetBooleanValue(state, itemsAddedSuccessfully);
 	return 1;
 }
 
@@ -8049,7 +8071,7 @@ int EQ2Emu_lua_SetItemCount(lua_State* state) {
 
 	((Player*)owner)->SendInventoryUpdate(client->GetVersion());
 
-	EQ2Packet* app = ((Player*)owner)->GetEquipmentList()->serialize(client->GetVersion());
+	EQ2Packet* app = ((Player*)owner)->GetEquipmentList()->serialize(client->GetVersion(), client->GetPlayer());
 	if (app)
 		client->QueuePacket(app);
 

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

@@ -257,6 +257,8 @@ int EQ2Emu_lua_AddQuestSelectableRewardItem(lua_State* state);
 int EQ2Emu_lua_AddQuestRewardCoin(lua_State* state);
 int EQ2Emu_lua_AddQuestRewardFaction(lua_State* state);
 int EQ2Emu_lua_SetQuestRewardStatus(lua_State* state);
+int EQ2Emu_lua_SetStatusTmpReward(lua_State* state);
+int EQ2Emu_lua_SetCoinTmpReward(lua_State* state);
 int EQ2Emu_lua_SetQuestRewardComment(lua_State* state);
 int EQ2Emu_lua_SetQuestRewardExp(lua_State* state);
 int EQ2Emu_lua_AddQuestStep(lua_State* state);

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

@@ -750,6 +750,31 @@ void LuaInterface::RemoveSpell(LuaSpell* spell, bool call_remove_function, bool
 		spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(spell, false);
 		spell->caster->RemoveProc(0, spell);
 		spell->caster->RemoveMaintainedSpell(spell);
+
+		int8 spell_type = spell->spell->GetSpellData()->spell_type;
+		if(spell->caster->IsPlayer())
+		{
+			Player* player = (Player*)spell->caster;
+			switch(spell_type)
+			{
+				case SPELL_TYPE_FOOD:
+					if(player->get_character_flag(CF_FOOD_AUTO_CONSUME))
+					{
+						Item* item = player->GetEquipmentList()->GetItem(EQ2_FOOD_SLOT);
+						if(item && player->GetClient())
+							player->GetClient()->ConsumeFoodDrink(item, EQ2_FOOD_SLOT);
+					}
+				break;
+				case SPELL_TYPE_DRINK:
+					if(player->get_character_flag(CF_DRINK_AUTO_CONSUME))
+					{
+						Item* item = player->GetEquipmentList()->GetItem(EQ2_DRINK_SLOT);
+						if(item && player->GetClient())
+							player->GetClient()->ConsumeFoodDrink(item, EQ2_DRINK_SLOT);
+					}
+				break;
+			}
+		}
 	}
 }
 
@@ -980,6 +1005,8 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "AddQuestRewardCoin", EQ2Emu_lua_AddQuestRewardCoin);
 	lua_register(state, "AddQuestRewardFaction", EQ2Emu_lua_AddQuestRewardFaction);
 	lua_register(state, "SetQuestRewardStatus", EQ2Emu_lua_SetQuestRewardStatus);
+	lua_register(state, "SetStatusTmpReward", EQ2Emu_lua_SetStatusTmpReward);
+	lua_register(state, "SetCoinTmpReward", EQ2Emu_lua_SetCoinTmpReward);
 	lua_register(state, "SetQuestRewardComment", EQ2Emu_lua_SetQuestRewardComment);
 	lua_register(state, "SetQuestRewardExp", EQ2Emu_lua_SetQuestRewardExp);
 	lua_register(state, "AddQuestStepKill", EQ2Emu_lua_AddQuestStepKill);

+ 9 - 0
EQ2/source/WorldServer/NPC_AI.cpp

@@ -466,6 +466,15 @@ bool Brain::CheckLootAllowed(Entity* entity) {
 	bool ret = false;
 	vector<int32>::iterator itr;
 
+	if(m_body)
+	{
+		if(rule_manager.GetGlobalRule(R_Loot, AllowChestUnlockByDropTime)->GetInt8()
+		&& m_body->GetChestDropTime() > 0 && Timer::GetCurrentTime2() >= m_body->GetChestDropTime()+(rule_manager.GetGlobalRule(R_Loot, ChestUnlockedTimeDrop)->GetInt32()*1000))
+			return true;
+		if(rule_manager.GetGlobalRule(R_Loot, AllowChestUnlockByTrapTime)->GetInt8() 
+		&& m_body->GetTrapOpenedTime() > 0 && Timer::GetCurrentTime2() >= m_body->GetChestDropTime()+(rule_manager.GetGlobalRule(R_Loot, ChestUnlockedTimeTrap)->GetInt32()*1000))
+			return true;
+	}
 	// Check the encounter list to see if the given entity is in it, if so return true.
 	MEncounter.readlock(__FUNCTION__, __LINE__);
 	if (entity->IsPlayer())

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

@@ -1821,8 +1821,7 @@ vector<EQ2Packet*>	Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, in
 				lua_interface->RunItemScript(item->GetItemScript(), "equipped", to_item, this);
 
 			item_list.RemoveItem(to_item);
-			slot = item->details.slot_id;
-			equipment_list.SetItem(slot, to_item);
+			equipment_list.SetItem(item->details.slot_id, to_item);
 			to_item->save_needed = true;
 			packets.push_back(to_item->serialize(version, false));
 			SetEquipment(to_item);
@@ -1831,7 +1830,7 @@ vector<EQ2Packet*>	Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, in
 			item_list.AddItem(item);
 			item->save_needed = true;
 			packets.push_back(item->serialize(version, false));
-			packets.push_back(equipment_list.serialize(version));
+			packets.push_back(equipment_list.serialize(version, this));
 			packets.push_back(item_list.serialize(this, version));
 		}
 		else if (to_item && to_item->IsBag() && to_item->details.num_slots > 0) {
@@ -1849,7 +1848,7 @@ vector<EQ2Packet*>	Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, in
 					item->details.slot_id = i;
 					item_list.AddItem(item);
 					item->save_needed = true;
-					packets.push_back(equipment_list.serialize(version));
+					packets.push_back(equipment_list.serialize(version, this));
 					packets.push_back(item->serialize(version, false));
 					packets.push_back(to_item->serialize(version, false, this));
 					packets.push_back(item_list.serialize(this, version));
@@ -1902,7 +1901,7 @@ vector<EQ2Packet*>	Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, in
 					item->details.slot_id = slot;
 					item_list.AddItem(item);
 					item->save_needed = true;
-					packets.push_back(equipment_list.serialize(version));
+					packets.push_back(equipment_list.serialize(version, this));
 					packets.push_back(item->serialize(version, false));
 					packets.push_back(item_list.serialize(this, version));
 				}
@@ -1921,7 +1920,7 @@ vector<EQ2Packet*>	Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, in
 					item->details.slot_id = slot;
 					item_list.AddItem(item);
 					item->save_needed = true;
-					packets.push_back(equipment_list.serialize(version));
+					packets.push_back(equipment_list.serialize(version, this));
 					packets.push_back(item->serialize(version, false));
 					packets.push_back(item_list.serialize(this, version));
 				}
@@ -1973,7 +1972,7 @@ EQ2Packet* Player::SwapEquippedItems(int8 slot1, int8 slot2, int16 version){
 		}
 		item_from->save_needed = true;
 		item_from->details.slot_id = slot2;
-		return equipment_list.serialize(version);
+		return equipment_list.serialize(version, this);
 	}
 	return 0;
 }
@@ -3643,13 +3642,13 @@ void Player::toggle_character_flag(int flag){
 	if (flag > CF_MAXIMUM_FLAG) return;
 	if (flag < 32)
 	{
-		int8 origflag = GetInfoStruct()->get_flags();
-		GetInfoStruct()->set_flags(origflag ^= ~(1 << flag));
+		int32 origflag = GetInfoStruct()->get_flags();
+		GetInfoStruct()->set_flags(origflag ^= (1 << flag));
 	}
 	else
 	{
-		int8 flag2 = GetInfoStruct()->get_flags2();
-		GetInfoStruct()->set_flags2(flag2  ^= ~(1 << (flag - 32)));
+		int32 flag2 = GetInfoStruct()->get_flags2();
+		GetInfoStruct()->set_flags2(flag2  ^= (1 << (flag - 32)));
 	}
 	charsheet_changed = true;
 	info_changed = true;
@@ -4248,6 +4247,7 @@ PacketStruct* Player::GetQuestJournalPacket(bool all_quests, int16 version, int3
 					packet->setArrayDataByName("turned_in", 1, i);
 					packet->setArrayDataByName("completed", 1, i);
 					packet->setArrayDataByName("visible", 1, i);
+					packet->setArrayDataByName("unknown3", 1, i);
 					display_status += QUEST_DISPLAY_STATUS_COMPLETED;					
 				}
 				if (updated) {
@@ -4341,7 +4341,8 @@ PacketStruct* Player::GetQuestJournalPacket(Quest* quest, int16 version, int32 c
 			packet->setArrayDataByName("completed", 1);
 		if(quest->GetTurnedIn()) {
 			packet->setArrayDataByName("turned_in", 1);
-			packet->setArrayDataByName("completed", 1);			
+			packet->setArrayDataByName("completed", 1);
+			packet->setArrayDataByName("visible", 1);	
 			display_status += QUEST_DISPLAY_STATUS_COMPLETED;
 		}		
 		packet->setArrayDataByName("quest_id", quest->GetQuestID());
@@ -4391,7 +4392,9 @@ PacketStruct* Player::GetQuestJournalPacket(Quest* quest, int16 version, int32 c
 		if (updated) {
 			packet->setArrayDataByName("quest_updated", 1);
 			packet->setArrayDataByName("journal_updated", 1);
-		}
+		}		
+		if(version >= 546)
+			packet->setDataByName("unknown3", 1);
 		packet->setDataByName("visible_quest_id", quest->GetQuestID());
 		packet->setDataByName("player_crc", crc);
 		packet->setDataByName("player_name", GetName());

+ 58 - 2
EQ2/source/WorldServer/Quests.cpp

@@ -40,7 +40,26 @@ QuestStep::QuestStep(int32 in_id, int8 in_type, string in_description, vector<in
 	ids = in_ids;
 	if(in_task_group)
 		task_group = string(in_task_group);
-	locations = in_locations;
+	if(type != QUEST_STEP_TYPE_LOCATION) {
+		locations = 0;
+		if (in_ids){
+			in_ids = new vector<int32>;
+			for(int32 i=0;i<in_ids->size();i++)
+				ids->push_back(in_ids->at(i));
+		}
+		else
+			ids = 0;
+	}
+	else { // location step
+		ids = 0;
+		if (in_locations) {
+			locations = new vector<Location>;
+			for(int32 i=0; i < in_locations->size(); i++)
+				locations->push_back(in_locations->at(i));
+		}
+		else
+			locations = 0;
+	}
 	max_variation = in_max_variation;
 	quantity = in_quantity;
 	step_progress = 0;
@@ -58,6 +77,8 @@ QuestStep::QuestStep(QuestStep* old_step){
 	quantity = old_step->quantity;
 	max_variation = old_step->max_variation;
 	step_progress = 0;
+	ids = 0;
+	locations = 0;
 	if(type != QUEST_STEP_TYPE_LOCATION) {
 		locations = 0;
 		if (old_step->ids){
@@ -318,6 +339,9 @@ Quest::Quest(int32 in_id){
 	MCompletedActions.SetName("Quest::MCompletedActions");
 	MProgressActions.SetName("Quest::MProgressActions");
 	MFailedActions.SetName("Quest::failed_Actions");
+	quest_state_temporary = false;
+	tmp_reward_status = 0;
+	tmp_reward_coins = 0;
 }
 
 Quest::Quest(Quest* old_quest){
@@ -388,6 +412,9 @@ Quest::Quest(Quest* old_quest){
 	MQuestSteps.SetName("Quest::MQuestSteps");
 	MProgressActions.SetName("Quest::MProgressActions");
 	MCompletedActions.SetName("Quest::MCompletedActions");
+	quest_state_temporary = false;
+	tmp_reward_status = 0;
+	tmp_reward_coins = 0;
 }
 
 Quest::~Quest(){
@@ -397,6 +424,8 @@ Quest::~Quest(){
 		safe_delete(prereq_items[i]);
 	for(int32 i=0;i<reward_items.size();i++)
 		safe_delete(reward_items[i]);
+	for(int32 i=0;i<tmp_reward_items.size();i++)
+		safe_delete(tmp_reward_items[i]);
 	for(int32 i=0;i<selectable_reward_items.size();i++)
 		safe_delete(selectable_reward_items[i]);
 	quest_step_map.clear();
@@ -1456,6 +1485,10 @@ void Quest::AddRewardItem(Item* item){
 	reward_items.push_back(item);
 }
 
+void Quest::AddTmpRewardItem(Item* item){
+	tmp_reward_items.push_back(item);
+}
+
 void Quest::AddRewardCoins(int32 copper, int32 silver, int32 gold, int32 plat){
 	reward_coins = copper + (silver*100) + (gold*10000) + ((int64)plat*1000000);
 }
@@ -1520,6 +1553,10 @@ vector<Item*>* Quest::GetRewardItems(){
 	return &reward_items;
 }
 
+vector<Item*>* Quest::GetTmpRewardItems(){
+	return &tmp_reward_items;
+}
+
 vector<Item*>* Quest::GetSelectableRewardItems(){
 	return &selectable_reward_items;
 }
@@ -1679,7 +1716,9 @@ int32 Quest::GetExpReward(){
 void Quest::GiveQuestReward(Player* player){
 	if(reward_coins > 0)
 		player->AddCoins(reward_coins);
-	reward_items.clear();
+
+	if(!GetQuestTemporaryState())
+		reward_items.clear();
 }
 
 bool Quest::GetDeleted(){
@@ -1754,3 +1793,20 @@ void MasterQuestList::LockQuests(){
 void MasterQuestList::UnlockQuests(){
 	MQuests.unlock();
 }
+
+void Quest::SetQuestTemporaryState(bool tempState, std::string customDescription)
+{
+	if(!tempState)
+	{
+		tmp_reward_coins = 0;
+		tmp_reward_status = 0;
+		
+		for(int32 i=0;i<tmp_reward_items.size();i++)
+			safe_delete(tmp_reward_items[i]);
+
+		tmp_reward_items.clear();
+	}
+
+	quest_state_temporary = tempState;
+	quest_temporary_description = customDescription;
+}

+ 16 - 0
EQ2/source/WorldServer/Quests.h

@@ -139,6 +139,7 @@ public:
 	void				AddPrereqItem(Item* item);
 
 	void				AddRewardItem(Item* item);
+	void				AddTmpRewardItem(Item* item);
 	void				AddSelectableRewardItem(Item* item);
 	void				AddRewardCoins(int32 copper, int32 silver, int32 gold, int32 plat);
 	void                AddRewardCoinsMax(int64 coins);
@@ -180,6 +181,7 @@ public:
 	vector<int32>*		GetPrereqQuests();
 	vector<Item*>*		GetPrereqItems();
 	vector<Item*>*		GetRewardItems();
+	vector<Item*>*		GetTmpRewardItems();
 	vector<Item*>*		GetSelectableRewardItems();
 	map<int32, sint32>*	GetRewardFactions();
 	void				GiveQuestReward(Player* player);
@@ -193,6 +195,11 @@ public:
 	void				SetStepDescription(int32 step, string desc);
 	void				SetTaskGroupDescription(int32 step, string desc, bool display_bullets);
 
+	void				SetStatusTmpReward(int32 status) { tmp_reward_status = status; }
+	int64				GetStatusTmpReward() { return tmp_reward_status; }
+
+	void				SetCoinTmpReward(int64 coins) { tmp_reward_coins = coins; }
+	int64				GetCoinTmpReward() { return tmp_reward_coins; }
 	int64				GetCoinsReward();
 	int64               GetCoinsRewardMax();
 	int64               GetGeneratedCoin();
@@ -305,6 +312,9 @@ public:
 	void				SetCompleteCount(int16 val) { m_completeCount = val; }
 	void				IncrementCompleteCount() { m_completeCount += 1; }
 
+	void				SetQuestTemporaryState(bool tempState, std::string customDescription = string(""));
+	bool				GetQuestTemporaryState() { return quest_state_temporary; }
+	std::string			GetQuestTemporaryDescription() { return quest_temporary_description; }
 protected:
 	bool				needs_save;
 	Mutex				MQuestSteps;
@@ -349,8 +359,11 @@ protected:
 	vector<Item*>		prereq_items;
 	vector<Item*>		reward_items;
 	vector<Item*>		selectable_reward_items;
+	vector<Item*>		tmp_reward_items;
 	int64				reward_coins;
 	int64               reward_coins_max;
+	int32				tmp_reward_status;
+	int64				tmp_reward_coins;
 	int64               generated_coin;
 	map<int32, sint32>	reward_factions;
 	int32				reward_status;
@@ -379,6 +392,9 @@ protected:
 	int32				m_timerStep;	// used for the fail action when timer expires
 
 	int16				m_completeCount;
+
+	bool				quest_state_temporary;
+	std::string			quest_temporary_description;
 };
 
 class MasterQuestList{

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

@@ -318,6 +318,10 @@ void RuleManager::Init()
 	RULE_INIT(R_Loot, LootRadius, "5.0");
 	RULE_INIT(R_Loot, AutoDisarmChest, "1");
 	RULE_INIT(R_Loot, ChestTriggerRadiusGroup, "10.0"); // radius at which chest will trigger against group members
+	RULE_INIT(R_Loot, ChestUnlockedTimeDrop, "1200"); // time in seconds, 20 minutes by default, triggers only if AllowChestUnlockByDropTime is 1
+	RULE_INIT(R_Loot, AllowChestUnlockByDropTime, "1"); // when set to 1 we will start a countdown timer to allow anyone to loot once ChestUnlockedTimeDrop elapsed
+	RULE_INIT(R_Loot, ChestUnlockedTimeTrap, "600"); // time in seconds, 10 minutes by default
+	RULE_INIT(R_Loot, AllowChestUnlockByTrapTime, "1"); // when set to 1 we will allow unlocking the chest to all players after the trap is triggered (or chest is open) and period ChestUnlockedTimeTrap elapsed
 
 	RULE_INIT(R_Spells, NoInterruptBaseChance, "50");
 

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

@@ -166,6 +166,10 @@ enum RuleType {
 	LootRadius,
 	AutoDisarmChest, // if enabled disarm only works if you right click and disarm, clicking and opening chest won't attempt auto disarm
 	ChestTriggerRadiusGroup,
+	ChestUnlockedTimeDrop,
+	AllowChestUnlockByDropTime,
+	ChestUnlockedTimeTrap,
+	AllowChestUnlockByTrapTime,
 	
 	/* SPELLS */
 	NoInterruptBaseChance,

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

@@ -41,6 +41,9 @@ extern World world;
 Spawn::Spawn(){ 
 	loot_coins = 0;
 	trap_triggered = false;
+	trap_state = 0;
+	chest_drop_time = 0;
+	trap_opened_time = 0;
 	group_id = 0;
 	size_offset = 0;
 	merchant_id = 0;

+ 21 - 1
EQ2/source/WorldServer/Spawn.h

@@ -843,8 +843,25 @@ public:
 	bool HasTrapTriggered() {
 		return trap_triggered;
 	}
-	void SetTrapTriggered(bool triggered) {
+	int32 GetTrapState() {
+		return trap_state;
+	}
+	void SetChestDropTime() {
+		chest_drop_time = Timer::GetCurrentTime2();
+		trap_opened_time = 0;
+	}
+	void SetTrapTriggered(bool triggered, int32 state) {
+		if(!trap_triggered && triggered)
+			trap_opened_time = Timer::GetCurrentTime2();
+		
 		trap_triggered = triggered;
+		trap_state = state;
+	}
+	int32 GetChestDropTime() {
+		return chest_drop_time;
+	}
+	int32 GetTrapOpenedTime() {
+		return trap_opened_time;
 	}
 	void AddLootItem(int32 id, int16 charges = 1) {
 		Item* master_item = master_item_list.GetItem(id);
@@ -1227,6 +1244,9 @@ private:
 	vector<Item*>	loot_items;
 	int32			loot_coins;
 	bool			trap_triggered;
+	int32			trap_state;
+	int32			chest_drop_time;
+	int32			trap_opened_time;
 	deque<MovementLocation*>* movement_locations;
 	Mutex			MLootItems;
 	Mutex*			MMovementLocations;

+ 2 - 0
EQ2/source/WorldServer/Spells.h

@@ -185,6 +185,8 @@
 #define SPELL_TYPE_DETAUNT		10
 #define SPELL_TYPE_REZ			11
 #define SPELL_TYPE_CURE			12
+#define SPELL_TYPE_FOOD			13
+#define SPELL_TYPE_DRINK		14
 
 
 struct LUAData{

+ 320 - 112
EQ2/source/WorldServer/client.cpp

@@ -200,6 +200,8 @@ Client::Client(EQStream* ieqs) : pos_update(125), quest_pos_timer(2000), lua_deb
 	regionDebugMessaging = false;
 	client_reloading_zone = false;
 	last_saved_timestamp = 0;
+	MQueueStateCmds.SetName("Client::MQueueStateCmds");
+	MConversation.SetName("Client::MConversation");
 }
 
 Client::~Client() {
@@ -714,7 +716,7 @@ void Client::SendCharInfo() {
 		//DumpPacket(app);
 		QueuePacket(app);
 	}
-	app = player->GetEquipmentList()->serialize(GetVersion());
+	app = player->GetEquipmentList()->serialize(GetVersion(), player);
 	if (app) {
 		QueuePacket(app);
 	}
@@ -1003,6 +1005,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 		LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_SysClient", opcode, opcode);
 		LogWrite(CCLIENT__DEBUG, 0, "Client", "Client '%s' (%u) is ready for spawn updates.", GetPlayer()->GetName(), GetPlayer()->GetCharacterID());
 		SetReadyForUpdates();
+		ProcessStateCommands();
 		break;
 	}
 	case OP_MapRequest: {
@@ -1209,13 +1212,17 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 			int32 conversation_id = packet->getType_int32_ByName("conversation_id");
 			int32 response_index = packet->getType_int32_ByName("response");
 			if (GetCurrentZone()) {
+				MConversation.readlock();
 				Spawn* spawn = conversation_spawns[conversation_id];
 				Item* item = conversation_items[conversation_id];
+				MConversation.releasereadlock();
 				if (conversation_map.count(conversation_id) > 0 && conversation_map[conversation_id].count(response_index) > 0) {
 					if (spawn)
 						GetCurrentZone()->CallSpawnScript(spawn, SPAWN_SCRIPT_CONVERSATION, player, conversation_map[conversation_id][response_index].c_str());
 					else if (item && lua_interface && item->GetItemScript())
 						lua_interface->RunItemScript(item->GetItemScript(), conversation_map[conversation_id][response_index].c_str(), item, player);
+					else
+						CloseDialog(conversation_id);
 				}
 				else
 					CloseDialog(conversation_id);
@@ -2914,6 +2921,8 @@ bool Client::Process(bool zone_process) {
 	}
 	if (pos_update.Check())
 	{
+		ProcessStateCommands();
+
 		if(GetPlayer()->GetRegionMap())
 			GetPlayer()->GetRegionMap()->TicRegionsNearSpawn(this->GetPlayer(), regionDebugMessaging ? this : nullptr);
 		
@@ -4842,7 +4851,7 @@ void Client::OpenChest(Spawn* entity, bool attemptDisarm)
 
 		Skill* disarmSkill = GetPlayer()->GetSkillByName("Disarm Trap", false);
 		firstChestOpen = true;
-		entity->SetTrapTriggered(true);
+		entity->SetTrapTriggered(true, state);
 		if (ret)
 		{
 			if (disarmSkill && attemptDisarm)
@@ -4872,17 +4881,15 @@ void Client::OpenChest(Spawn* entity, bool attemptDisarm)
 	else if (!entity->HasTrapTriggered())
 	{
 		firstChestOpen = true;
-		entity->SetTrapTriggered(true);
+		entity->SetTrapTriggered(true, state);
 	}
 
 	// We set the visual state with out updating so those not in range will see it opened when it is finally sent to them,
 	// for those in range the SendStateCommand will cause it to animate open.
 
-	// TODO: when player enters radius that does not have visual state, update visual state
+	// players not currently in radius will have it queued with client->QueueStateCommand when SendSpawn takes place
 	if (firstChestOpen)
-		entity->SetVisualState(state, false);
-
-	GetCurrentZone()->SendStateCommand(entity, state);
+		GetCurrentZone()->SendStateCommand(entity, state);
 }
 
 void Client::CastGroupOrSelf(Entity* source, uint32 spellID, uint32 spellTier, float restrictiveRadius)
@@ -5129,10 +5136,17 @@ void Client::BankDeposit(int64 amount) {
 
 }
 
-void Client::AddPendingQuestReward(Quest* quest) {
+void Client::AddPendingQuestAcceptReward(Quest* quest)
+{
+	MPendingQuestAccept.lock();
+	pending_quest_accept.push_back(quest);
+	MPendingQuestAccept.unlock();
+}
+
+void Client::AddPendingQuestReward(Quest* quest, bool update) {
 	MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__);
 	quest_pending_reward.push_back(quest);
-	quest_updates = true;
+	quest_updates = update;
 	MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__);
 
 }
@@ -5162,6 +5176,10 @@ void Client::ProcessQuestUpdates() {
 				}
 				else
 					AddStepProgress(quest_itr->first, step_itr->first, step_itr->second);
+				
+				Quest* tmpQuest = GetPlayer()->GetQuest(quest_itr->first);
+				if(tmpQuest)
+					SendQuestJournalUpdate(tmpQuest);
 			}
 		}
 	}
@@ -5465,18 +5483,22 @@ void Client::SendQuestUpdate(Quest* quest) {
 		for (int32 i = 0; i < updates->size(); i++) {
 			step = updates->at(i);
 			if (lua_interface && step->Complete() && quest->GetCompleteAction(step->GetStepID()))
+			{
 				lua_interface->CallQuestFunction(quest, quest->GetCompleteAction(step->GetStepID()), player);
+				SendQuestUpdateStep(quest, step->GetStepID());
+			}
 			if (step->WasUpdated()) {
 				// reversing the order of SendQuestJournal and QueuePacket QuestJournalReply causes AoM client to crash!
 				SendQuestJournal(false, 0, true);
 				QueuePacket(quest->QuestJournalReply(GetVersion(), GetNameCRC(), player, step));
+				updated = true;
 			}
 			LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal...");
 
 		}
 		if (lua_interface && quest->GetCompleted() && quest->GetCompleteAction()) {
 			lua_interface->CallQuestFunction(quest, quest->GetCompleteAction(), player);
-			SendQuestJournalUpdate(quest, false);
+			SendQuestJournalUpdate(quest, true);
 		}
 		if (quest->GetCompleted()) {
 			if (quest->GetQuestReturnNPC() > 0)
@@ -5531,7 +5553,11 @@ Quest* Client::GetPendingQuestAcceptance(int32 item_id) {
 	MPendingQuestAccept.lock();
 	for (itr = pending_quest_accept.begin(); itr != pending_quest_accept.end(); itr++) {
 		quest = *itr;
-		items = quest->GetRewardItems();
+
+		if(quest->GetQuestTemporaryState())
+			items = quest->GetTmpRewardItems();
+		else
+			items = quest->GetRewardItems();
 		if (item_id == 0 && items && items->size() > 0) {
 			found_quest = true;
 		}
@@ -5566,12 +5592,40 @@ void Client::AcceptQuestReward(Quest* quest, int32 item_id) {
 		num_slots_needed++;
 		master_item = master_item_list.GetItem(item_id);
 	}
-	vector<Item*>* items = quest->GetRewardItems();
-	if (items && items->size() > 0)
-		num_slots_needed += items->size();
-	if (free_slots >= num_slots_needed || (player->item_list.HasFreeBagSlot() && master_item && master_item->IsBag() && master_item->bag_info->num_slots >= items->size())) {
+
+	int32 totalItems = 0;
+
+	vector<Item*>* items = 0;
+	vector<Item*>* tmpItems = 0;
+	
+	bool isTempState = quest->GetQuestTemporaryState();
+	
+	if(isTempState)
+	{
+		tmpItems = quest->GetTmpRewardItems();
+		if (tmpItems && tmpItems->size() > 0)
+		{
+			num_slots_needed += tmpItems->size();
+			totalItems += tmpItems->size();
+		}
+	}
+	else
+	{
+		items = quest->GetRewardItems();
+		if (items && items->size() > 0)
+		{
+			num_slots_needed += items->size();
+			totalItems += items->size();
+		}
+	}
+
+	if (free_slots >= num_slots_needed || (player->item_list.HasFreeBagSlot() && master_item && master_item->IsBag() && master_item->bag_info->num_slots >= totalItems)) {
 		if (master_item)
 			AddItem(item_id);
+		if (tmpItems && tmpItems->size() > 0) {
+			for (int32 i = 0; i < tmpItems->size(); i++)
+				AddItem(new Item(tmpItems->at(i)));
+		}
 		if (items && items->size() > 0) {
 			for (int32 i = 0; i < items->size(); i++)
 				AddItem(new Item(items->at(i)));
@@ -5589,17 +5643,31 @@ void Client::AcceptQuestReward(Quest* quest, int32 item_id) {
 			else
 				player->GetFactions()->DecreaseFaction(faction_id, (amount * -1));
 		}
-		if (player->GetGuild()) {
-			player->GetInfoStruct()->add_status_points(quest->GetStatusPoints());
-			player->SetCharSheetChanged(true);
+
+		player->GetInfoStruct()->add_status_points(quest->GetStatusPoints());
+		player->SetCharSheetChanged(true);
+		
+		if(quest->GetQuestTemporaryState())
+		{
+			int64 total_coins = quest->GetCoinTmpReward();
+			if (total_coins > 0)
+				AwardCoins(total_coins, std::string("for completing ").append(quest->GetName()));
+
+			player->GetInfoStruct()->add_status_points(quest->GetStatusTmpReward());
+			
+			quest->SetQuestTemporaryState(false);
 		}
+		else
+			player->GetInfoStruct()->add_status_points(quest->GetStatusPoints());
+		
+		player->SetCharSheetChanged(true);
 	}
 	else {
 		MPendingQuestAccept.lock();
 		pending_quest_accept.push_back(quest);
 		MPendingQuestAccept.unlock();
 		SimpleMessage(CHANNEL_COLOR_RED, "You do not have enough free slots!  Free some slots and try again.");
-		DisplayQuestComplete(quest);
+		DisplayQuestComplete(quest, quest->GetQuestTemporaryState(), quest->GetQuestTemporaryDescription());
 	}
 
 }
@@ -5699,9 +5767,14 @@ void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* reward
 	}
 }
 
-void Client::DisplayQuestComplete(Quest* quest) {	
+void Client::DisplayQuestComplete(Quest* quest, bool tempReward, std::string customDescription) {	
 	if (!quest)
 		return;
+	
+	quest->SetQuestTemporaryState(tempReward, customDescription);
+	
+	AddPendingQuestAcceptReward(quest);
+	
 	if (GetVersion() <= 546) {
 		DisplayQuestRewards(quest, 0, quest->GetRewardItems(), quest->GetSelectableRewardItems(), quest->GetRewardFactions(), "Quest Complete!", quest->GetStatusPoints());
 		return;
@@ -5710,7 +5783,13 @@ void Client::DisplayQuestComplete(Quest* quest) {
 	if (packet) {
 		packet->setDataByName("title", "Quest Reward!");
 		packet->setDataByName("name", quest->GetName());
-		packet->setDataByName("description", quest->GetDescription());
+		if(customDescription.size() > 0)
+		{
+			packet->setDataByName("description", customDescription.c_str());
+		}
+		else
+			packet->setDataByName("description", quest->GetDescription());
+		
 		packet->setDataByName("level", quest->GetLevel());
 		packet->setDataByName("encounter_level", quest->GetEncounterLevel());
 		int8 difficulty = 0;
@@ -5719,57 +5798,88 @@ void Client::DisplayQuestComplete(Quest* quest) {
 		else
 			difficulty = player->GetArrowColor(quest->GetLevel());
 		packet->setDataByName("difficulty", difficulty);
-		int64 rewarded_coin = 0;
-		if (quest->GetCoinsReward() > 0) {
-			if (quest->GetCoinsRewardMax() > 0)
-				rewarded_coin = MakeRandomInt(quest->GetCoinsReward(), quest->GetCoinsRewardMax());
-			else
-				rewarded_coin = quest->GetCoinsReward();
-		}
-		quest->SetGeneratedCoin(rewarded_coin);
-		packet->setDataByName("max_coin", rewarded_coin);
-		packet->setDataByName("min_coin", rewarded_coin);
-		packet->setDataByName("status_points", quest->GetStatusPoints());
-		vector<Item*>* items2 = quest->GetSelectableRewardItems();
-		vector<Item*>* items = quest->GetRewardItems();
-		if (items) {
-			packet->setArrayLengthByName("num_rewards", items->size());
-			for (int32 i = 0; i < items->size(); i++) {
-				packet->setArrayDataByName("reward_id", items->at(i)->details.item_id, i);
-				if (version < 860)
-					packet->setItemArrayDataByName("item", items->at(i), player, i, 0, -1);
-				else if (version < 1193)
-					packet->setItemArrayDataByName("item", items->at(i), player, i);
+
+		if(tempReward)
+		{
+			packet->setDataByName("max_coin", quest->GetCoinTmpReward());
+			packet->setDataByName("min_coin", quest->GetCoinTmpReward());
+			packet->setDataByName("status_points", quest->GetStatusPoints());
+		}
+		else
+		{
+			int64 rewarded_coin = 0;
+			if (quest->GetCoinsReward() > 0) {
+				if (quest->GetCoinsRewardMax() > 0)
+					rewarded_coin = MakeRandomInt(quest->GetCoinsReward(), quest->GetCoinsRewardMax());
 				else
-					packet->setItemArrayDataByName("item", items->at(i), player, i, 0, 2);
+					rewarded_coin = quest->GetCoinsReward();
 			}
+			quest->SetGeneratedCoin(rewarded_coin);
+			packet->setDataByName("max_coin", rewarded_coin);
+			packet->setDataByName("min_coin", rewarded_coin);
+			packet->setDataByName("status_points", quest->GetStatusPoints());
 		}
-		if (items2 && items2->size() > 0) {
-			packet->setArrayLengthByName("num_select_rewards", items2->size());
-			for (int32 i = 0; i < items2->size(); i++) {
-				packet->setArrayDataByName("select_reward_id", items2->at(i)->details.item_id, i);
-				if (version < 860)
-					packet->setItemArrayDataByName("select_item", items2->at(i), player, i, 0, -1);
-				else if (version < 1193)
-					packet->setItemArrayDataByName("select_item", items2->at(i), player, i);
-				else
-					packet->setItemArrayDataByName("select_item", items2->at(i), player, i, 0, 2);
+
+		vector<Item*>* items = quest->GetTmpRewardItems();
+
+		if(tempReward)
+		{
+			if (items) {
+				packet->setArrayLengthByName("num_rewards", items->size());
+				for (int32 i = 0; i < items->size(); i++) {
+					packet->setArrayDataByName("reward_id", items->at(i)->details.item_id, i);
+					if (version < 860)
+						packet->setItemArrayDataByName("item", items->at(i), player, i, 0, -1);
+					else if (version < 1193)
+						packet->setItemArrayDataByName("item", items->at(i), player, i);
+					else
+						packet->setItemArrayDataByName("item", items->at(i), player, i, 0, 2);
+				}
 			}
 		}
-		map<int32, sint32>* reward_factions = quest->GetRewardFactions();
-		if (reward_factions && reward_factions->size() > 0) {
-			packet->setArrayLengthByName("num_factions", reward_factions->size());
-			map<int32, sint32>::iterator itr;
-			int16 index = 0;
-			for (itr = reward_factions->begin(); itr != reward_factions->end(); itr++) {
-				int32 faction_id = itr->first;
-				sint32 amount = itr->second;
-				const char* faction_name = master_faction_list.GetFactionNameByID(faction_id);
-				if (faction_name) {
-					packet->setArrayDataByName("faction_name", const_cast<char*>(faction_name), index);
-					packet->setArrayDataByName("amount", amount, index);
+		else
+		{
+			vector<Item*>* items2 = quest->GetSelectableRewardItems();
+			vector<Item*>* items = quest->GetRewardItems();
+			if (items) {
+				packet->setArrayLengthByName("num_rewards", items->size());
+				for (int32 i = 0; i < items->size(); i++) {
+					packet->setArrayDataByName("reward_id", items->at(i)->details.item_id, i);
+					if (version < 860)
+						packet->setItemArrayDataByName("item", items->at(i), player, i, 0, -1);
+					else if (version < 1193)
+						packet->setItemArrayDataByName("item", items->at(i), player, i);
+					else
+						packet->setItemArrayDataByName("item", items->at(i), player, i, 0, 2);
+				}
+			}
+			if (items2 && items2->size() > 0) {
+				packet->setArrayLengthByName("num_select_rewards", items2->size());
+				for (int32 i = 0; i < items2->size(); i++) {
+					packet->setArrayDataByName("select_reward_id", items2->at(i)->details.item_id, i);
+					if (version < 860)
+						packet->setItemArrayDataByName("select_item", items2->at(i), player, i, 0, -1);
+					else if (version < 1193)
+						packet->setItemArrayDataByName("select_item", items2->at(i), player, i);
+					else
+						packet->setItemArrayDataByName("select_item", items2->at(i), player, i, 0, 2);
+				}
+			}
+			map<int32, sint32>* reward_factions = quest->GetRewardFactions();
+			if (reward_factions && reward_factions->size() > 0) {
+				packet->setArrayLengthByName("num_factions", reward_factions->size());
+				map<int32, sint32>::iterator itr;
+				int16 index = 0;
+				for (itr = reward_factions->begin(); itr != reward_factions->end(); itr++) {
+					int32 faction_id = itr->first;
+					sint32 amount = itr->second;
+					const char* faction_name = master_faction_list.GetFactionNameByID(faction_id);
+					if (faction_name) {
+						packet->setArrayDataByName("faction_name", const_cast<char*>(faction_name), index);
+						packet->setArrayDataByName("amount", amount, index);
+					}
+					index++;
 				}
-				index++;
 			}
 		}
 		EQ2Packet* outapp = packet->serialize();
@@ -5841,16 +5951,20 @@ void Client::DisplayRandomizeFeatures(int32 flags) {
 
 void Client::GiveQuestReward(Quest* quest) {
 	current_quest_id = 0;
-	MPendingQuestAccept.lock();
-	pending_quest_accept.push_back(quest);
-	MPendingQuestAccept.unlock();
 
-	quest->IncrementCompleteCount();
-	player->AddCompletedQuest(quest);
+	if(!quest->GetQuestTemporaryState())
+	{
+		quest->IncrementCompleteCount();
+		player->AddCompletedQuest(quest);
+	}
 	
-	DisplayQuestComplete(quest);
+	DisplayQuestComplete(quest, quest->GetQuestTemporaryState(), quest->GetQuestTemporaryDescription());
 	LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal...");
 	SendQuestJournal();
+	
+	if(quest->GetQuestTemporaryState())
+		return;
+	
 	player->RemoveQuest(quest->GetQuestID(), false);
 	if (quest->GetExpReward() > 0) {
 		int16 level = player->GetLevel();
@@ -5873,43 +5987,12 @@ void Client::GiveQuestReward(Quest* quest) {
 		}
 	}
 	int64 total_coins = quest->GetGeneratedCoin();
-	if (total_coins > 0) {
-		player->AddCoins(total_coins);
-		PlaySound("coin_cha_ching");
-		char tmp[64] = { 0 };
-		string message = "You receive ";
-		int32 val = 0;
-		if (total_coins >= 1000000) {
-			val = total_coins / 1000000;
-			total_coins -= 1000000 * val;
-			sprintf(tmp, "%u Platinum ", val);
-			message.append(tmp);
-			memset(tmp, 0, 64);
-		}
-		if (total_coins >= 10000) {
-			val = total_coins / 10000;
-			total_coins -= 10000 * val;
-			sprintf(tmp, "%u Gold ", val);
-			message.append(tmp);
-			memset(tmp, 0, 64);
-		}
-		if (total_coins >= 100) {
-			val = total_coins / 100;
-			total_coins -= 100 * val;
-			sprintf(tmp, "%u Silver ", val);
-			message.append(tmp);
-			memset(tmp, 0, 64);
-		}
-		if (total_coins > 0) {
-			sprintf(tmp, "%u Copper ", (int32)total_coins);
-			message.append(tmp);
-		}
-		message.append("for completing ").append(quest->GetName());
-		int8 type = CHANNEL_LOOT;
-		SimpleMessage(type, message.c_str());
-	}
+	if (total_coins > 0)
+		AwardCoins(total_coins, std::string("for completing ").append(quest->GetName()));
+	
 	if (quest->GetQuestGiver() > 0)
 		GetCurrentZone()->SendSpawnChangesByDBID(quest->GetQuestGiver(), this, false, true);
+	
 	RemovePlayerQuest(quest->GetQuestID(), true, false);	
 }
 
@@ -5949,7 +6032,9 @@ void Client::DisplayConversation(Item* item, vector<ConversationOption>* convers
 		next_conversation_id++;
 		conversation_id = next_conversation_id;
 	}
+	MConversation.writelock();
 	conversation_items[conversation_id] = item;
+	MConversation.releasewritelock();
 	if (type == 4)
 		DisplayConversation(conversation_id, player->GetIDWithPlayerSpawn(player), conversations, text, mp3, key1, key2);
 	else
@@ -5966,7 +6051,9 @@ void Client::DisplayConversation(Spawn* src, int8 type, vector<ConversationOptio
 		next_conversation_id++;
 		conversation_id = next_conversation_id;
 	}
+	MConversation.writelock();
 	conversation_spawns[conversation_id] = src;
+	MConversation.releasewritelock();
 
 	/* Spawns can start two different types of conversations.
 	 * Type 1: The chat type with bubbles.
@@ -5987,13 +6074,27 @@ void Client::CloseDialog(int32 conversation_id) {
 		QueuePacket(packet->serialize());
 		safe_delete(packet);
 	}
-	conversation_items.erase(conversation_id);
-	conversation_spawns.erase(conversation_id);
+
+	MConversation.writelock();
+	std::map<int32, Item*>::iterator itr;
+	while((itr = conversation_items.find(conversation_id)) != conversation_items.end())
+	{
+		conversation_items.erase(itr);
+	}
+	
+	std::map<int32, Spawn*>::iterator itr2 = conversation_spawns.find(conversation_id);
+
+	while((itr2 = conversation_spawns.find(conversation_id)) != conversation_spawns.end())
+	{
+		conversation_spawns.erase(itr2);
+	}
+	MConversation.releasewritelock();
 
 }
 
 int32 Client::GetConversationID(Spawn* spawn, Item* item) {
 	int32 conversation_id = 0;
+	MConversation.readlock();
 	if (spawn) {
 		map<int32, Spawn*>::iterator itr;
 		for (itr = conversation_spawns.begin(); itr != conversation_spawns.end(); itr++) {
@@ -6012,6 +6113,7 @@ int32 Client::GetConversationID(Spawn* spawn, Item* item) {
 			}
 		}
 	}
+	MConversation.releasereadlock();
 
 	return conversation_id;
 }
@@ -6211,7 +6313,10 @@ bool Client::RemoveItem(Item* item, int16 quantity) {
 		if (item->GetItemScript() && lua_interface)
 			lua_interface->RunItemScript(item->GetItemScript(), "removed", item, player);
 		if (delete_item)
+		{
+			PurgeItem(item);
 			safe_delete(item);
+		}
 		return true;
 	}
 
@@ -6623,7 +6728,7 @@ void Client::RepairItem(int32 item_id) {
 			if (player->RemoveCoins((int32)repair_cost)) {
 				item->generic_info.condition = 100;
 				item->save_needed = true;
-				QueuePacket(player->GetEquipmentList()->serialize(GetVersion()));
+				QueuePacket(player->GetEquipmentList()->serialize(GetVersion(), player));
 				QueuePacket(player->SendInventoryUpdate(GetVersion()));
 				QueuePacket(item->serialize(version, false, player));
 				Message(CHANNEL_MERCHANT, "You give %s %s to repair your %s.", spawn->GetName(), GetCoinMessage(repair_cost).c_str(), item->CreateItemLink(GetVersion()).c_str());
@@ -6661,7 +6766,7 @@ void Client::RepairAllItems() {
 						Message(CHANNEL_COLOR_YELLOW, "Repaired: %s.", item->CreateItemLink(GetVersion()).c_str());
 					}
 				}
-				QueuePacket(player->GetEquipmentList()->serialize(GetVersion()));
+				QueuePacket(player->GetEquipmentList()->serialize(GetVersion(), player));
 				QueuePacket(player->SendInventoryUpdate(GetVersion()));
 				PlaySound("coin_cha_ching");
 				if (spawn->GetMerchantType() & MERCHANT_TYPE_REPAIR)
@@ -10002,7 +10107,7 @@ void Client::SendEquipOrInvUpdateBySlot(int8 slot)
 {
 	if(slot < NUM_SLOTS)
 	{
-		EQ2Packet* app = GetPlayer()->GetEquipmentList()->serialize(GetVersion());
+		EQ2Packet* app = GetPlayer()->GetEquipmentList()->serialize(GetVersion(), GetPlayer());
 		if (app)
 			QueuePacket(app);
 	}
@@ -10012,4 +10117,107 @@ void Client::SendEquipOrInvUpdateBySlot(int8 slot)
 			if (outapp)
 				QueuePacket(outapp);
 	}
+}
+
+void Client::QueueStateCommand(int32 spawn_player_id, int32 state)
+{
+	if(spawn_player_id < 1)
+		return;
+
+	MQueueStateCmds.writelock();
+	queued_state_commands.insert(make_pair(spawn_player_id, state));
+	MQueueStateCmds.releasewritelock();
+}
+
+void Client::ProcessStateCommands()
+{
+	if(!IsReadyForUpdates())
+		return;
+
+	MQueueStateCmds.writelock();
+	map<int32, int32>::iterator itr = queued_state_commands.begin();
+	for(; itr != queued_state_commands.end(); itr++)
+		ClientPacketFunctions::SendStateCommand(this, itr->first, itr->second);
+
+	queued_state_commands.clear();
+	MQueueStateCmds.releasewritelock();
+}
+
+void Client::PurgeItem(Item* item)
+{
+	MConversation.writelock();
+	map<int32, Item*>::iterator itr;
+	for(itr = conversation_items.begin(); itr != conversation_items.end(); itr++)
+	{
+		if ( itr->second == item )
+		{
+			conversation_items.erase(itr);
+			break;
+		}
+	}
+	MConversation.releasewritelock();
+}
+
+void Client::ConsumeFoodDrink(Item* item, int32 slot)
+{
+		if(item) {
+			LogWrite(MISC__INFO, 1, "Command", "ItemID: %u, ItemName: %s ItemCount: %i ", item->details.item_id, item->name.c_str(), item->details.count);
+			if(item->GetItemScript() && lua_interface){
+				lua_interface->RunItemScript(item->GetItemScript(), "cast", item, GetPlayer());
+				if (slot == 22)
+					Message(CHANNEL_NARRATIVE, "You eat a %s.", item->name.c_str());
+				else
+					Message(CHANNEL_NARRATIVE, "You drink a %s.", item->name.c_str());
+			}
+
+		if (item->details.count > 1) {
+			item->details.count -= 1;
+			item->save_needed = true;
+		}
+		else {
+			GetPlayer()->GetEquipmentList()->RemoveItem(slot, true);
+			database.DeleteItem(GetPlayer()->GetCharacterID(), item, "EQUIPPED");
+		}
+		GetPlayer()->SetCharSheetChanged(true);
+		QueuePacket(player->GetEquipmentList()->serialize(GetVersion(), player));
+	}
+}
+
+void Client::AwardCoins(int64 total_coins, std::string reason)
+{
+		if (total_coins > 0) {
+		player->AddCoins(total_coins);
+		PlaySound("coin_cha_ching");
+		char tmp[64] = { 0 };
+		string message = "You receive ";
+		int32 val = 0;
+		if (total_coins >= 1000000) {
+			val = total_coins / 1000000;
+			total_coins -= 1000000 * val;
+			sprintf(tmp, "%u Platinum ", val);
+			message.append(tmp);
+			memset(tmp, 0, 64);
+		}
+		if (total_coins >= 10000) {
+			val = total_coins / 10000;
+			total_coins -= 10000 * val;
+			sprintf(tmp, "%u Gold ", val);
+			message.append(tmp);
+			memset(tmp, 0, 64);
+		}
+		if (total_coins >= 100) {
+			val = total_coins / 100;
+			total_coins -= 100 * val;
+			sprintf(tmp, "%u Silver ", val);
+			message.append(tmp);
+			memset(tmp, 0, 64);
+		}
+		if (total_coins > 0) {
+			sprintf(tmp, "%u Copper ", (int32)total_coins);
+			message.append(tmp);
+		}
+		message.append(reason);
+		int8 type = CHANNEL_LOOT;
+		SimpleMessage(type, message.c_str());
+		}
 }

+ 14 - 2
EQ2/source/WorldServer/client.h

@@ -272,7 +272,7 @@ public:
 	void	SendQuestUpdateStep(Quest* quest, int32 step, bool display_quest_helper = true);
 	void	SendQuestUpdateStepImmediately(Quest* quest, int32 step, bool display_quest_helper = true);
 	void	DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* rewards=0, vector<Item*>* selectable_rewards=0, map<int32, sint32>* factions=0, const char* header="Quest Reward!", int32 status_points=0, const char* text=0);
-	void	DisplayQuestComplete(Quest* quest);
+	void	DisplayQuestComplete(Quest* quest, bool tempReward = false, std::string customDescription = string(""));
 	void	DisplayRandomizeFeatures(int32 features);
 	void	AcceptQuestReward(Quest* quest, int32 item_id);
 	Quest*	GetPendingQuestAcceptance(int32 item_id);
@@ -338,7 +338,9 @@ public:
 		player = new_player;
 		player->SetClient(this);
 	}
-	void	AddPendingQuestReward(Quest* quest);
+
+	void	AddPendingQuestAcceptReward(Quest* quest);
+	void	AddPendingQuestReward(Quest* quest, bool update=true);
 	void	AddPendingQuestUpdate(int32 quest_id, int32 step_id, int32 progress = 0xFFFFFFFF);
 	void	ProcessQuestUpdates();	
 	void	AddWaypoint(const char* waypoint_name, int8 waypoint_category, int32 spawn_id);
@@ -470,6 +472,12 @@ public:
 
 	void SetReloadingZone(bool val) { client_reloading_zone = val; }
 	bool IsReloadingZone() { return client_reloading_zone; }
+
+	void QueueStateCommand(int32 spawn_player_id, int32 state);
+	void ProcessStateCommands();
+	void PurgeItem(Item* item);
+	void ConsumeFoodDrink(Item* item, int32 slot);
+	void AwardCoins(int64 total_coins, std::string reason = string(""));
 private:
 	void    SavePlayerImages();
 	void	SkillChanged(Skill* skill, int16 previous_value, int16 new_value);
@@ -500,6 +508,7 @@ private:
 	int32	next_conversation_id;
 	map<int32, Spawn*> conversation_spawns;
 	map<int32, Item*> conversation_items;
+	Mutex MConversation;
 	map<int32, map<int8, string> > conversation_map;
 	int32	current_quest_id;
 	Spawn*	banker;
@@ -581,6 +590,9 @@ private:
 	bool regionDebugMessaging;
 
 	bool client_reloading_zone;
+
+	map<int32, int32> queued_state_commands;
+	Mutex MQueueStateCmds;
 };
 
 class ClientList {

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

@@ -177,7 +177,6 @@ ZoneServer::~ZoneServer() {
 	LogWrite(ZONE__INFO, 0, "Zone", "Initiating zone shutdown of '%s'", zone_name);
 	changed_spawns.clear();
 	transport_spawns.clear();
-	safe_delete(spellProcess);
 	safe_delete(tradeskillMgr);
 	MMasterZoneLock->lock();
 	MMasterSpawnLock.writelock(__FUNCTION__, __LINE__);
@@ -208,6 +207,9 @@ ZoneServer::~ZoneServer() {
 	if (movementMgr != nullptr)
 		delete movementMgr;
 
+	// moved to the bottom as we want spawns deleted first, this used to be above Spawn deletion which is a big no no
+	safe_delete(spellProcess);
+	
 	LogWrite(ZONE__INFO, 0, "Zone", "Completed zone shutdown of '%s'", zone_name);
 	--numzones;
 	UpdateWindowTitle(0);
@@ -3299,6 +3301,8 @@ void ZoneServer::SendSpawn(Spawn* spawn, Client* client){
 	2 = update and new quest
 	3 = update
 	*/
+	if(spawn->IsEntity() && spawn->HasTrapTriggered())
+		client->QueueStateCommand(client->GetPlayer()->GetIDWithPlayerSpawn(spawn), spawn->GetTrapState());
 }
 
 Client*	ZoneServer::GetClientBySpawn(Spawn* spawn){
@@ -4220,7 +4224,7 @@ void ZoneServer::KillSpawn(bool spawnListLocked, Spawn* dead, Spawn* killer, boo
 			if(client) {
 
 				if(client->GetPlayer()->DamageEquippedItems(10, client))
-					client->QueuePacket(client->GetPlayer()->GetEquipmentList()->serialize(client->GetVersion()));
+					client->QueuePacket(client->GetPlayer()->GetEquipmentList()->serialize(client->GetVersion(), client->GetPlayer()));
 
 				client->DisplayDeadWindow();
 			}