Browse Source

Appearance equipment, content feature requests, fixed ability to delete mail and see item icons in the main list if stack is 0

Fix #300 - appearance inventory saved to database, can equip and overrides normal equipment.  Also fixed appearance properly updating (it wasn't sent other than in zone in primarily)

Fix #322 - rule to disable house alignment requirements
RULE_INIT(R_Player, DisableHouseAlignmentRequirement, "1");

Fix #311 - defines for root/snare as spell types
#define SPELL_TYPE_ROOT         15
#define SPELL_TYPE_SNARE        16

Fix #301 - loot_global needs a lua_script to access the ZoneScript and call function loot_criteria(Spawn)

loot_criteria_zone(Zone, Spawn, LootTableID, MinLevel, MaxLevel)
loot_criteria_racial(Zone, Spawn, LootTableID, MinLevel, MaxLevel)
loot_criteria_level(Zone, Spawn, LootTableID, MinLevel, MaxLevel)

    Spawn: the target of the loot table to be added
    LootTableID: the current loot table id we are checking to add to the Spawn
    MinLevel/MaxLevel: Criteria from the global loot table that is handled via code (eg. always add if minlevel/maxlevel is 0, otherwise it is based on the Spawn's level)

    return value of 0 will skip the provided LootTableID(int32) despite the database MinLevel/MaxLevel checks
    return value of 1 will always include the loot table id on the Spawn list, even overriding if the min/max level check fails in the code

Issue #231 partially addressed, added support to override item_difficulty in the item script based on the arrow color (3 is white):
function item_difficulty(Item, Spawn)
	return 3
end
Image 3 years ago
parent
commit
ad58cd85ec

+ 35 - 9
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -1611,6 +1611,18 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 					else
 						LogWrite(COMMAND__ERROR, 0, "Command", "Unknown Index: %u", item_index);
 				}
+				else if(strcmp(sep->arg[0], "appearance") == 0){
+					int32 item_index = atol(sep->arg[1]);
+					Item* item = client->GetPlayer()->GetAppearanceEquipmentList()->GetItem(item_index);
+					if(item){
+						EQ2Packet* app = item->serialize(client->GetVersion(), true, client->GetPlayer());
+						client->QueuePacket(app);
+						if(item->GetItemScript() && lua_interface)
+							lua_interface->RunItemScript(item->GetItemScript(), "examined", item, client->GetPlayer());
+					}
+					else
+						LogWrite(COMMAND__ERROR, 0, "Command", "Unknown Index: %u", item_index);
+				}
 				else if(strcmp(sep->arg[0], "item") == 0 || strcmp(sep->arg[0], "merchant") == 0 || strcmp(sep->arg[0], "store") == 0 || strcmp(sep->arg[0], "buyback") == 0 || strcmp(sep->arg[0], "consignment") == 0){
 					int32 item_id = atoul(sep->arg[1]);
 					Item* item = master_item_list.GetItem(item_id);
@@ -2157,7 +2169,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 			break;
 									  }
 		case COMMAND_SET_MAIL_ITEM: {
-			if(sep && sep->IsNumber(0))
+			if(sep && sep->IsNumber(0) && sep->IsNumber(2))
 			{
 				Item* item = client->GetPlayer()->item_list.GetItemFromIndex(atoul(sep->arg[0]));
 				if(item)
@@ -2401,7 +2413,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 				}
 				else
 				{
-					vector<int32> loot_list = client->GetCurrentZone()->GetSpawnLootList(spawn->GetDatabaseID(), spawn->GetZone()->GetZoneID(), spawn->GetLevel(), race_types_list.GetRaceType(spawn->GetModelType()));
+					vector<int32> loot_list = client->GetCurrentZone()->GetSpawnLootList(spawn->GetDatabaseID(), spawn->GetZone()->GetZoneID(), spawn->GetLevel(), race_types_list.GetRaceType(spawn->GetModelType()), spawn);
 					if (loot_list.size() > 0) {
 						client->Message(CHANNEL_COLOR_YELLOW, "%s belongs to the following loot lists: ", spawn->GetName());
 						vector<int32>::iterator list_itr;
@@ -2455,7 +2467,8 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 		case COMMAND_LOOT_REMOVEITEM:{
 			Spawn* spawn = cmdTarget;
 			if(spawn && spawn->IsEntity() && sep && sep->arg[0] && sep->IsNumber(0)){
-				spawn->LootItem(atoul(sep->arg[0]));
+				Item* item = spawn->LootItem(atoul(sep->arg[0]));
+				safe_delete(item);
 				client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully removed item.");
 			}
 			else
@@ -5967,7 +5980,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
 
 	if(sep && sep->arg[0][0])
 	{
-		LogWrite(COMMAND__INFO, 0, "Command", "command: %s", command->command.data.c_str());
+		LogWrite(COMMAND__INFO, 0, "Command", "command: %s", sep->argplus[0]);
 
 		if(client->GetPlayer()->GetHP() == 0)
 			client->SimpleMessage(CHANNEL_COLOR_RED,"You cannot do that right now.");
@@ -6033,7 +6046,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
 				}
 			}
 
-			EQ2Packet* outapp = client->GetPlayer()->MoveInventoryItem(bag_id, from_index, (int8)to_slot, charges, client->GetVersion());
+			EQ2Packet* outapp = client->GetPlayer()->MoveInventoryItem(bag_id, from_index, (int8)to_slot, charges, 0, client->GetVersion());
 			client->QueuePacket(outapp);
 
 			//removed from bag send update
@@ -6060,11 +6073,17 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
 			{
 				int16 index = atoi(sep->arg[1]);
 				int8 slot_id = 255;
-
+				int8 unk3 = 0;
+				int8 appearance_equip = 0;
+				
 				if(sep->arg[2][0] && sep->IsNumber(2))
 					slot_id = atoi(sep->arg[2]);
+				if(sep->arg[3][0] && sep->IsNumber(3))
+					unk3 = atoul(sep->arg[3]);
+				if(sep->arg[4][0] && sep->IsNumber(4))
+					appearance_equip = atoul(sep->arg[4]);
 
-				vector<EQ2Packet*> packets = client->GetPlayer()->EquipItem(index, client->GetVersion(), slot_id);
+				vector<EQ2Packet*> packets = client->GetPlayer()->EquipItem(index, client->GetVersion(), appearance_equip, slot_id);
 				EQ2Packet* outapp = 0;
 
 				for(int32 i=0;i<packets.size();i++)
@@ -6124,7 +6143,14 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
 						to_slot = atoi(sep->arg[3]);
 				}
 
-				vector<EQ2Packet*> packets = client->GetPlayer()->UnequipItem(index, bag_id, to_slot, client->GetVersion());
+				sint8 unk4 = 0;
+				int8 appearance_equip = 0;
+				if(sep->arg[4][0] && sep->IsNumber(4))
+					unk4 = atoi(sep->arg[4]);
+				if(sep->arg[5][0] && sep->IsNumber(5))
+					appearance_equip = atoul(sep->arg[5]);
+
+				vector<EQ2Packet*> packets = client->GetPlayer()->UnequipItem(index, bag_id, to_slot, client->GetVersion(), appearance_equip);
 				EQ2Packet* outapp = 0;
 
 				for(int32 i=0;i<packets.size();i++)
@@ -10521,7 +10547,7 @@ void Commands::Command_Attune_Inv(Client* client, Seperator* sep) {
 
 			client->QueuePacket(item->serialize(client->GetVersion(), false, client->GetPlayer()));
 
-			vector<EQ2Packet*> packets = client->GetPlayer()->EquipItem(index, client->GetVersion(), -1);
+			vector<EQ2Packet*> packets = client->GetPlayer()->EquipItem(index, client->GetVersion(), 0, -1); // appearance type??
 			EQ2Packet* outapp = 0;
 
 			for (int32 i=0;i<packets.size();i++) {

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

@@ -1391,6 +1391,10 @@ EquipmentItemList* Entity::GetEquipmentList(){
 	return &equipment_list;
 }
 
+EquipmentItemList* Entity::GetAppearanceEquipmentList(){
+	return &appearance_equipment_list;
+}
+
 void Entity::SetEquipment(Item* item, int8 slot){
 	std::lock_guard<std::mutex> lk(MEquipment);
 	if(!item && slot < NUM_SLOTS){

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

@@ -1115,6 +1115,7 @@ public:
 	}
 	int8	GetDeity(){ return deity; }
 	EquipmentItemList* GetEquipmentList();
+	EquipmentItemList* GetAppearanceEquipmentList();
 
 	bool IsEntity(){ return true; }
 	float CalculateSkillStatChance(char* skill, int16 item_stat, float max_cap = 0.0f, float modifier = 0.0f, bool add_to_skill = false);

+ 5 - 2
EQ2/source/WorldServer/Housing/HousingPackets.cpp

@@ -2,14 +2,17 @@
 #include "../World.h"
 #include "../client.h"
 #include "../WorldDatabase.h"
+#include "../Rules/Rules.h"
 
 extern ConfigReader configReader;
 extern World world;
 extern WorldDatabase database;
+extern RuleManager rule_manager;
 
 void ClientPacketFunctions::SendHousePurchase(Client* client, HouseZone* hz, int32 spawnID) {
 	PacketStruct* packet = configReader.getStruct("WS_PlayerHousePurchase", client->GetVersion());
 	if (packet) {
+		int8 disable_alignment_req = rule_manager.GetGlobalRule(R_Player, DisableHouseAlignmentRequirement)->GetInt8();
 		packet->setDataByName("house_name", hz->name.c_str());
 		packet->setDataByName("house_id", hz->id);
 		packet->setDataByName("spawn_id", spawnID);
@@ -19,7 +22,7 @@ void ClientPacketFunctions::SendHousePurchase(Client* client, HouseZone* hz, int
 		packet->setDataByName("upkeep_status", hz->upkeep_status);
 		packet->setDataByName("vendor_vault_slots", hz->vault_slots);
 		string req;
-		if (hz->alignment > 0) {
+		if (hz->alignment > 0 && !disable_alignment_req) {
 			req = "You must be of ";
 			if (hz->alignment == 1)
 				req.append("Good");
@@ -53,7 +56,7 @@ void ClientPacketFunctions::SendHousePurchase(Client* client, HouseZone* hz, int
 		packet->setDataByName("additional_reqs", req.c_str());
 
 		bool enable_buy = true;
-		if (hz->alignment > 0 && client->GetPlayer()->GetAlignment() != hz->alignment)
+		if (hz->alignment > 0 && client->GetPlayer()->GetAlignment() != hz->alignment && !disable_alignment_req)
 			enable_buy = false;
 		if (hz->guild_level > 0 && (!client->GetPlayer()->GetGuild() || (client->GetPlayer()->GetGuild() && client->GetPlayer()->GetGuild()->GetLevel() < hz->guild_level)))
 			enable_buy = false;

+ 102 - 67
EQ2/source/WorldServer/Items/Items.cpp

@@ -2670,10 +2670,13 @@ PlayerItemList::PlayerItemList(){
 PlayerItemList::~PlayerItemList(){
 	safe_delete_array(xor_packet);
 	safe_delete_array(orig_packet);
-	map<sint32, map<int16, Item*> >::iterator bag_iter;
+	map<sint32, map<int8, map<int16, Item*>> >::iterator bag_iter;
 	map<int16, Item*>::iterator itr;
 	for(bag_iter = items.begin(); bag_iter != items.end(); bag_iter++){
-		for(itr = bag_iter->second.begin(); itr != bag_iter->second.end(); itr++){
+		for(itr = bag_iter->second[0].begin(); itr != bag_iter->second[0].end(); itr++){
+			safe_delete(itr->second);
+		}
+		for(itr = bag_iter->second[1].begin(); itr != bag_iter->second[1].end(); itr++){
 			safe_delete(itr->second);
 		}
 		bag_iter->second.clear();
@@ -2702,11 +2705,11 @@ Item* PlayerItemList::GetItemFromIndex(int32 index){
 	return ret;
 }
 
-Item* PlayerItemList::GetItem(sint32 bag_slot, int16 slot){
+Item* PlayerItemList::GetItem(sint32 bag_slot, int16 slot, int8 appearance_type){
 	Item* ret = 0;
 	MPlayerItems.readlock(__FUNCTION__, __LINE__);
-	if(items.count(bag_slot) > 0 && items[bag_slot].count(slot) > 0)
-		ret = items[bag_slot][slot];
+	if(items.count(bag_slot) > 0 && items[bag_slot][appearance_type].count(slot) > 0)
+		ret = items[bag_slot][appearance_type][slot];
 	MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
 	return ret;
 }
@@ -2746,7 +2749,7 @@ void PlayerItemList::AddItem(Item* item){ //is called with a slot already set
 		new_index = max_index;
 
 	indexed_items[new_index] = item;
-	items[item->details.inv_slot_id][item->details.slot_id] = item;
+	items[item->details.inv_slot_id][item->details.appearance_type][item->details.slot_id] = item;
 	MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
 }
 
@@ -2754,8 +2757,8 @@ Item* PlayerItemList::GetBag(int8 inventory_slot, bool lock){
 	Item* bag = 0;
 	if(lock)
 		MPlayerItems.readlock(__FUNCTION__, __LINE__);
-	if(items.count(0) > 0 && items[0].count(inventory_slot) > 0 && items[0][inventory_slot]->IsBag())
-		bag = items[0][inventory_slot];
+	if(items.count(0) > 0 && items[0][BASE_EQUIPMENT].count(inventory_slot) > 0 && items[0][BASE_EQUIPMENT][inventory_slot]->IsBag())
+		bag = items[0][BASE_EQUIPMENT][inventory_slot];
 	if(lock)
 		MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
 	return bag;
@@ -2765,8 +2768,8 @@ Item* PlayerItemList::GetBankBag(int8 inventory_slot, bool lock){
 	Item* bag = 0;
 	if(lock)
 		MPlayerItems.readlock(__FUNCTION__, __LINE__);
-	if(items.count(-3) > 0 && items[-3].count(inventory_slot) > 0 && items[-3][inventory_slot]->IsBag())
-		bag = items[-3][inventory_slot];
+	if(items.count(-3) > 0 && items[-3][BASE_EQUIPMENT].count(inventory_slot) > 0 && items[-3][BASE_EQUIPMENT][inventory_slot]->IsBag())
+		bag = items[-3][BASE_EQUIPMENT][inventory_slot];
 	if(lock)
 		MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
 	return bag;
@@ -2785,7 +2788,7 @@ int16 PlayerItemList::GetNumberOfFreeSlots(){
 		if(bag && bag->details.num_slots > 0){
 			if(items.count(bag->details.bag_id) > 0){
 				for(int16 x=0;x<bag->details.num_slots;x++){
-					if(items[bag->details.bag_id].count(x) == 0)
+					if(items[bag->details.bag_id][BASE_EQUIPMENT].count(x) == 0)
 						count++;
 				}
 			}
@@ -2802,7 +2805,7 @@ bool PlayerItemList::HasFreeBagSlot(){
 	MPlayerItems.readlock(__FUNCTION__, __LINE__);
 	if(items.count(0) > 0){
 		for(int8 i=0;i<NUM_INV_SLOTS;i++){
-			if(items[0].count(i) == 0){
+			if(items[0][BASE_EQUIPMENT].count(i) == 0){
 				ret = true;
 				break;
 			}
@@ -2819,7 +2822,7 @@ bool PlayerItemList::HasFreeSlot(){
 	MPlayerItems.readlock(__FUNCTION__, __LINE__);
 	if(items.count(0) > 0){
 		for(int8 i=0;i<NUM_INV_SLOTS;i++){
-			if(items[0].count(i) == 0){
+			if(items[0][BASE_EQUIPMENT].count(i) == 0){
 				ret = true;
 				break;
 			}
@@ -2834,7 +2837,7 @@ bool PlayerItemList::HasFreeSlot(){
 			if(bag && bag->details.num_slots > 0){
 				if(items.count(bag->details.bag_id) > 0){
 					for(int16 x=0;x<bag->details.num_slots;x++){
-						if(items[bag->details.bag_id].count(x) == 0){
+						if(items[bag->details.bag_id][BASE_EQUIPMENT].count(x) == 0){
 							ret = true;
 							break;
 						}
@@ -2856,7 +2859,7 @@ bool PlayerItemList::GetFirstFreeBankSlot(sint32* bag_id, sint16* slot) {
 	MPlayerItems.readlock(__FUNCTION__, __LINE__);
 	if (items.count(-3) > 0) {
 		for (int8 i = 0; i < NUM_BANK_SLOTS; i++) {
-			if (items[-3].count(i) == 0) {
+			if (items[-3][BASE_EQUIPMENT].count(i) == 0) {
 				*bag_id = -3;
 				*slot = i;
 				ret = true;
@@ -2880,7 +2883,7 @@ bool PlayerItemList::GetFirstFreeBankSlot(sint32* bag_id, sint16* slot) {
 				// Item was a bag so lets loop through the slots and try to find an empty one
 				if(items.count(bag->details.bag_id) > 0) {
 					for(int16 x = 0; x < bag->details.num_slots; x++) {
-						if(items[bag->details.bag_id].count(x) == 0) {
+						if(items[bag->details.bag_id][BASE_EQUIPMENT].count(x) == 0) {
 							// Found a free slot, get the bag id of this bag
 							*bag_id = bag->details.bag_id;
 							// Get the slot
@@ -2912,7 +2915,7 @@ bool PlayerItemList::GetFirstFreeSlot(sint32* bag_id, sint16* slot) {
 	MPlayerItems.readlock(__FUNCTION__, __LINE__);
 	if(items.count(0) > 0){
 		for(int8 i=0; i < NUM_INV_SLOTS; i++) {
-			if(items[0].count(i) == 0) {
+			if(items[0][BASE_EQUIPMENT].count(i) == 0) {
 				// Found an empty slot, store the slot id and set the return value
 				*bag_id = 0;
 				*slot = i;
@@ -2938,7 +2941,7 @@ bool PlayerItemList::GetFirstFreeSlot(sint32* bag_id, sint16* slot) {
 				// Item was a bag so lets loop through the slots and try to find an empty one
 				if(items.count(bag->details.bag_id) > 0) {
 					for(int16 x = 0; x < bag->details.num_slots; x++) {
-						if(items[bag->details.bag_id].count(x) == 0) {
+						if(items[bag->details.bag_id][BASE_EQUIPMENT].count(x) == 0) {
 							// Found a free slot, get the bag id of this bag
 							*bag_id = bag->details.bag_id;
 							// Get the slot
@@ -2967,12 +2970,18 @@ Item* PlayerItemList::CanStack(Item* item, bool include_bank){
 	if(!item)
 		return 0;
 	Item* ret = 0;
-	map<sint32, map<int16, Item*> >::iterator itr;
+	map<sint32, map<int8, map<int16, Item*>> >::iterator itr;
 	map<int16, Item*>::iterator slot_itr;
 	MPlayerItems.readlock(__FUNCTION__, __LINE__);
 	for(itr = items.begin(); itr != items.end(); itr++){
 		if(include_bank || (!include_bank && itr->first >= 0)){
-			for(slot_itr=itr->second.begin();slot_itr!=itr->second.end(); slot_itr++){
+			for(slot_itr=itr->second[0].begin();slot_itr!=itr->second[0].end(); slot_itr++){
+				if(slot_itr->second && slot_itr->second->details.item_id == item->details.item_id && ((slot_itr->second->details.count + item->details.count) <= slot_itr->second->stack_count)){
+					ret = slot_itr->second;
+					break;
+				}
+			}
+			for(slot_itr=itr->second[1].begin();slot_itr!=itr->second[1].end(); slot_itr++){
 				if(slot_itr->second && slot_itr->second->details.item_id == item->details.item_id && ((slot_itr->second->details.count + item->details.count) <= slot_itr->second->stack_count)){
 					ret = slot_itr->second;
 					break;
@@ -3010,7 +3019,7 @@ bool PlayerItemList::AssignItemToFreeSlot(Item* item){
 				bag = GetBag(i, false);
 				if(bag && bag->details.num_slots > 0){
 					for(int16 x=0;x<bag->details.num_slots;x++){
-						if(items[bag->details.bag_id].count(x) == 0){
+						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;
 							MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
@@ -3023,7 +3032,7 @@ bool PlayerItemList::AssignItemToFreeSlot(Item* item){
 		}
 		//bags full, check inventory slots
 		for(int8 i=0;i<NUM_INV_SLOTS;i++){
-			if(items[0].count(i) == 0){
+			if(items[0][BASE_EQUIPMENT].count(i) == 0){
 				item->details.inv_slot_id = 0;
 				item->details.slot_id = i;
 				MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
@@ -3038,13 +3047,13 @@ bool PlayerItemList::AssignItemToFreeSlot(Item* item){
 
 void PlayerItemList::RemoveItem(Item* item, bool delete_item){
 	MPlayerItems.writelock(__FUNCTION__, __LINE__);
-	if(items.count(item->details.inv_slot_id) > 0 && items[item->details.inv_slot_id].count(item->details.slot_id) > 0){
-		items[item->details.inv_slot_id].erase(item->details.slot_id);
+	if(items.count(item->details.inv_slot_id) > 0 && items[item->details.inv_slot_id][item->details.appearance_type].count(item->details.slot_id) > 0){
+		items[item->details.inv_slot_id][item->details.appearance_type].erase(item->details.slot_id);
 		indexed_items[item->details.index] = 0;
 	}
 	if(item->IsBag() && item->details.inv_slot_id == 0 && item->details.slot_id < NUM_INV_SLOTS && items.count(item->details.bag_id) > 0){
 		map<int16, Item*>::iterator itr;
-		for(itr = items[item->details.bag_id].begin(); itr != items[item->details.bag_id].end(); itr++){
+		for(itr = items[item->details.bag_id][item->details.appearance_type].begin(); itr != items[item->details.bag_id][item->details.appearance_type].end(); itr++){
 			indexed_items[itr->second->details.index] = 0;
 			if(delete_item){
 				safe_delete(itr->second);
@@ -3063,7 +3072,7 @@ void PlayerItemList::DestroyItem(int16 index){
 	Item* item = indexed_items[index];
 	map<int16, Item*>::iterator itr;
 	if(item && item->IsBag() && item->details.inv_slot_id == 0 && item->details.slot_id < NUM_INV_SLOTS && items.count((sint32)item->details.bag_id) > 0){ //inventory
-		map<int16, Item*>* tmp_map = &(items[(sint32)item->details.bag_id]);
+		map<int16, Item*>* tmp_map = &(items[(sint32)item->details.bag_id][item->details.appearance_type]);
 		for(itr = tmp_map->begin(); itr != tmp_map->end(); itr++){
 			indexed_items[itr->second->details.index] = 0;
 			if(itr->second != item){
@@ -3072,19 +3081,20 @@ void PlayerItemList::DestroyItem(int16 index){
 		}
 		items.erase(item->details.bag_id);
 	}
-	if(items.count(item->details.inv_slot_id) > 0 && items[item->details.inv_slot_id].count(item->details.slot_id) > 0)
-		items[item->details.inv_slot_id].erase(item->details.slot_id);
+	if(items.count(item->details.inv_slot_id) > 0 && items[item->details.inv_slot_id][item->details.appearance_type].count(item->details.slot_id) > 0)
+		items[item->details.inv_slot_id][item->details.appearance_type].erase(item->details.slot_id);
 	indexed_items[index] = 0;
 	safe_delete(item);
 	MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
 }
 
-void PlayerItemList::MoveItem(Item* item, sint32 inv_slot, int16 slot, bool erase_old){
-	if(erase_old && items.count(item->details.inv_slot_id) > 0)
-		items[item->details.inv_slot_id].erase(item->details.slot_id);
-	items[inv_slot][slot] = item;
+void PlayerItemList::MoveItem(Item* item, sint32 inv_slot, int16 slot, int8 appearance_type, bool erase_old){
+	if(erase_old && items.count(item->details.inv_slot_id) > 0 && items[item->details.inv_slot_id][BASE_EQUIPMENT].count(item->details.slot_id))
+		items[item->details.inv_slot_id][BASE_EQUIPMENT].erase(item->details.slot_id);
+	items[inv_slot][BASE_EQUIPMENT][slot] = item;
 	item->details.inv_slot_id = inv_slot;
 	item->details.slot_id = slot;
+	item->details.appearance_type = 0;
 	item->save_needed = true;
 }
 
@@ -3092,19 +3102,19 @@ int16 PlayerItemList::GetNumberOfItems(){
 	int16 ret = 0;
 	MPlayerItems.readlock(__FUNCTION__, __LINE__);
 	if(items.size() > 0){
-		map<sint32, map<int16, Item*> >::iterator itr;
+		map<sint32, map<int8, map<int16, Item*>> >::iterator itr;
 		sint32 bag_id = 0;
 		for(itr = items.begin(); itr != items.end(); itr++){
 			bag_id = itr->first;
-			if(items.count(bag_id) > 0)
-				ret += items[bag_id].size();
+			if(items[bag_id].count(0))
+				ret += items[bag_id][BASE_EQUIPMENT].size();
 		}
 	}
 	MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
 	return ret;
 }
 
-bool PlayerItemList::MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8 charges){
+bool PlayerItemList::MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8 appearance_type, int8 charges){
 	MPlayerItems.writelock(__FUNCTION__, __LINE__);
 	Item* item_from = indexed_items[from_index];
 	Item* item_to = 0;
@@ -3112,14 +3122,14 @@ bool PlayerItemList::MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8
 		if(to_bag_id > 0){  //bag item
 			Item* bag = GetItemFromUniqueID(to_bag_id, true, false);
 			if(bag && bag->details.num_slots > to)
-				item_to = items[to_bag_id][to];
+				item_to = items[to_bag_id][BASE_EQUIPMENT][to];
 			else{
 				MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
 				return false;
 			}
 		}
 		else
-			item_to = items[to_bag_id][to];
+			item_to = items[to_bag_id][BASE_EQUIPMENT][to];
 		if(charges > 0) {
 			if (item_to && item_from->details.item_id == item_to->details.item_id){
 				if(item_to->details.count > 0 && item_to->details.count < item_to->stack_count){
@@ -3128,7 +3138,7 @@ bool PlayerItemList::MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8
 						total_tmp_price = (item_to->GetMaxSellValue()*item_to->details.count) + (item_from->GetMaxSellValue()*item_from->details.count);
 						item_to->details.count += item_from->details.count;
 						indexed_items[from_index] = 0;
-						items[item_from->details.inv_slot_id].erase(item_from->details.slot_id);
+						items[item_from->details.inv_slot_id][BASE_EQUIPMENT].erase(item_from->details.slot_id);
 						item_from->needs_deletion = true;
 						item_to->save_needed = true;
 					}
@@ -3147,9 +3157,9 @@ bool PlayerItemList::MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8
 			else {
 				if (item_from->details.count == charges) {
 					if (item_to) 
-						MoveItem(item_to, item_from->details.inv_slot_id, item_from->details.slot_id);
+						MoveItem(item_to, item_from->details.inv_slot_id, item_from->details.slot_id, BASE_EQUIPMENT, true);
 
-					MoveItem(item_from, to_bag_id, to, item_to ? false:true);
+					MoveItem(item_from, to_bag_id, to, BASE_EQUIPMENT, item_to ? false:true);
 					MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
 				}
 				else {
@@ -3162,6 +3172,7 @@ bool PlayerItemList::MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8
 					new_item->details.count = charges;
 					new_item->details.slot_id = to;
 					new_item->details.inv_slot_id = to_bag_id;
+					new_item->details.appearance_type = 0;
 					MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
 					new_item->save_needed = true;
 					AddItem(new_item);
@@ -3176,7 +3187,7 @@ bool PlayerItemList::MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8
 			if (item_from->IsBag() && item_from->details.num_slots > 0) {
 				for (int8 i = 0; i < item_from->details.num_slots; i++) {
 					// if there is something in the bag return, can't put bags with items into other bags
-					if (items[item_from->details.bag_id].count(i) != 0) {
+					if (items[item_from->details.bag_id][BASE_EQUIPMENT].count(i) != 0) {
 						MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
 						return false;
 					}
@@ -3184,23 +3195,23 @@ bool PlayerItemList::MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8
 			}
 			if(items.count(item_to->details.bag_id) > 0){
 				for(int8 i=0;i<item_to->details.num_slots;i++){
-					if(items[item_to->details.bag_id].count(i) == 0){
-						MoveItem(item_from, item_to->details.bag_id, i);
+					if(items[item_to->details.bag_id][BASE_EQUIPMENT].count(i) == 0){
+						MoveItem(item_from, item_to->details.bag_id, i, 0, true);
 						MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
 						return true;
 					}
 				}
 			}
 			else{
-				MoveItem(item_from, item_to->details.bag_id, 0);
+				MoveItem(item_from, item_to->details.bag_id, 0, BASE_EQUIPMENT, true);
 				MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
 				return true;
 			}
 		}
 		if (item_to) 
-			MoveItem(item_to, item_from->details.inv_slot_id, item_from->details.slot_id);
+			MoveItem(item_to, item_from->details.inv_slot_id, item_from->details.slot_id, BASE_EQUIPMENT, true);
 
-		MoveItem(item_from, to_bag_id, to, item_to ? false:true);
+		MoveItem(item_from, to_bag_id, to, BASE_EQUIPMENT, item_to ? false:true);
 		
 		MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
 		return true;
@@ -3284,7 +3295,7 @@ void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item*
 	
 	int32 menu_data = 3;
 	if(item->slot_data.size() > 0)
-		menu_data -= ITEM_MENU_TYPE_GENERIC;	
+		menu_data -= ITEM_MENU_TYPE_GENERIC;
 
 	if (item->details.num_slots > 0) {
 		if (packet->GetVersion() <= 283 && item->details.num_slots > CLASSIC_EQ_MAX_BAG_SLOTS)
@@ -3456,12 +3467,18 @@ vector<Item*>* PlayerItemList::GetOverflowItemList() {
 }
 
 bool PlayerItemList::HasItem(int32 id, bool include_bank){
-	map<sint32, map<int16, Item*> >::iterator itr;
+	map<sint32, map<int8, map<int16, Item*>> >::iterator itr;
 	map<int16, Item*>::iterator slot_itr;
 	MPlayerItems.readlock(__FUNCTION__, __LINE__);
 	for(itr = items.begin(); itr != items.end(); itr++){
 		if(include_bank || (!include_bank && itr->first >= 0)){
-			for(slot_itr=itr->second.begin();slot_itr!=itr->second.end(); slot_itr++){
+			for(slot_itr=itr->second[BASE_EQUIPMENT].begin();slot_itr!=itr->second[BASE_EQUIPMENT].end(); slot_itr++){
+				if(slot_itr->second && slot_itr->second->details.item_id == id){
+					MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
+					return true;
+				}
+			}
+			for(slot_itr=itr->second[APPEARANCE_EQUIPMENT].begin();slot_itr!=itr->second[APPEARANCE_EQUIPMENT].end(); slot_itr++){
 				if(slot_itr->second && slot_itr->second->details.item_id == id){
 					MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
 					return true;
@@ -3479,7 +3496,7 @@ bool PlayerItemList::SharedBankAddAllowed(Item* item){
 	MPlayerItems.readlock(__FUNCTION__, __LINE__);
 	if(item->IsBag() && items.count(item->details.bag_id) > 0){
 		map<int16, Item*>::iterator itr;
-		for(itr = items[item->details.bag_id].begin(); itr != items[item->details.bag_id].end(); itr++){
+		for(itr = items[item->details.bag_id][BASE_EQUIPMENT].begin(); itr != items[item->details.bag_id][BASE_EQUIPMENT].end(); itr++){
 			if(itr->second->CheckFlag(NO_TRADE)){
 				MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
 				return false;
@@ -3497,7 +3514,7 @@ vector<Item*>* PlayerItemList::GetItemsFromBagID(sint32 bag_id){
 		map<int16, Item*>::iterator itr;
 		map<int16, Item*>::iterator itr2;
 		Item* item = 0;
-		for(itr = items[bag_id].begin(); itr != items[bag_id].end(); itr++){
+		for(itr = items[bag_id][BASE_EQUIPMENT].begin(); itr != items[bag_id][BASE_EQUIPMENT].end(); itr++){
 			item = itr->second;
 			if(item)
 				ret->push_back(item);
@@ -3512,7 +3529,7 @@ vector<Item*>* PlayerItemList::GetItemsInBag(Item* bag){
 	MPlayerItems.readlock(__FUNCTION__, __LINE__);
 	if(bag && bag->IsBag() && items.count(bag->details.bag_id) > 0){
 		map<int16, Item*>::iterator itr;
-		for(itr = items[bag->details.bag_id].begin(); itr != items[bag->details.bag_id].end(); itr++){
+		for(itr = items[bag->details.bag_id][BASE_EQUIPMENT].begin(); itr != items[bag->details.bag_id][BASE_EQUIPMENT].end(); itr++){
 			ret_items->push_back(itr->second);
 		}
 	}
@@ -3522,17 +3539,20 @@ vector<Item*>* PlayerItemList::GetItemsInBag(Item* bag){
 
 Item* PlayerItemList::GetItemFromID(int32 id, int8 count, bool include_bank, bool lock){
 	//first check for an exact count match
-	map<sint32, map<int16, Item*> >::iterator itr;
+	map<sint32, map<int8, map<int16, Item*>> >::iterator itr;
 	map<int16, Item*>::iterator slot_itr;
 	if(lock)
 		MPlayerItems.readlock(__FUNCTION__, __LINE__);
 	for(itr = items.begin(); itr != items.end(); itr++){
 		if(include_bank || (!include_bank && itr->first >= 0)){
-			for(slot_itr=itr->second.begin();slot_itr!=itr->second.end(); slot_itr++){
-				if(slot_itr->second && slot_itr->second->details.item_id == id && (count == 0 || slot_itr->second->details.count == count)){
-					if(lock)
-						MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
-					return slot_itr->second;
+			for(int8 i=0;i<MAX_EQUIPMENT;i++)
+			{
+				for(slot_itr=itr->second[i].begin();slot_itr!=itr->second[i].end(); slot_itr++){
+					if(slot_itr->second && slot_itr->second->details.item_id == id && (count == 0 || slot_itr->second->details.count == count)){
+						if(lock)
+							MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
+						return slot_itr->second;
+					}
 				}
 			}
 		}
@@ -3542,9 +3562,12 @@ Item* PlayerItemList::GetItemFromID(int32 id, int8 count, bool include_bank, boo
 	Item* closest = 0;
 	for(itr = items.begin(); itr != items.end(); itr++){
 		if(include_bank || (!include_bank && itr->first >= 0)){
-			for(slot_itr=itr->second.begin();slot_itr!=itr->second.end(); slot_itr++){
-				if(slot_itr->second && slot_itr->second->details.item_id == id && slot_itr->second->details.count > count && (closest == 0 || slot_itr->second->details.count < closest->details.count))
-					closest = slot_itr->second;
+			for(int8 i=0;i<MAX_EQUIPMENT;i++)
+			{
+				for(slot_itr=itr->second[i].begin();slot_itr!=itr->second[i].end(); slot_itr++){
+					if(slot_itr->second && slot_itr->second->details.item_id == id && slot_itr->second->details.count > count && (closest == 0 || slot_itr->second->details.count < closest->details.count))
+						closest = slot_itr->second;
+				}
 			}
 		}
 	}
@@ -3554,13 +3577,20 @@ Item* PlayerItemList::GetItemFromID(int32 id, int8 count, bool include_bank, boo
 }
 
 Item* PlayerItemList::GetItemFromUniqueID(int32 id, bool include_bank, bool lock){
-	map<sint32, map<int16, Item*> >::iterator itr;
+	map<sint32, map<int8, map<int16, Item*>> >::iterator itr;
 	map<int16, Item*>::iterator slot_itr;
 	if(lock)
 		MPlayerItems.readlock(__FUNCTION__, __LINE__);
 	for(itr = items.begin(); itr != items.end(); itr++){
 		if(include_bank || (!include_bank && itr->first >= 0)){
-			for(slot_itr=itr->second.begin();slot_itr!=itr->second.end(); slot_itr++){
+			for(slot_itr=itr->second[0].begin();slot_itr!=itr->second[0].end(); slot_itr++){
+				if(slot_itr->second && slot_itr->second->details.unique_id == id){
+					if(lock)
+						MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
+					return slot_itr->second;
+				}
+			}
+			for(slot_itr=itr->second[1].begin();slot_itr!=itr->second[1].end(); slot_itr++){
 				if(slot_itr->second && slot_itr->second->details.unique_id == id){
 					if(lock)
 						MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
@@ -3577,7 +3607,7 @@ Item* PlayerItemList::GetItemFromUniqueID(int32 id, bool include_bank, bool lock
 bool PlayerItemList::HasFreeBankSlot() {
 	bool ret = false;
 	MPlayerItems.readlock(__FUNCTION__, __LINE__);
-	if (items[-3].size() < 12) //12 slots in the bank
+	if (items[-3][BASE_EQUIPMENT].size() < 12) //12 slots in the bank
 		ret = true;
 	MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
 	return ret;
@@ -3587,7 +3617,7 @@ int8 PlayerItemList::FindFreeBankSlot() {
 	int8 ret = 0;
 	MPlayerItems.readlock(__FUNCTION__, __LINE__);
 	for (int8 i = 0; i < 12; i++) { //12 slots in the bank
-		if (items[-3].count(i) == 0) {
+		if (items[-3][BASE_EQUIPMENT].count(i) == 0) {
 			ret = i;
 			break;
 		}
@@ -3602,6 +3632,7 @@ EquipmentItemList::EquipmentItemList(){
 	for(int8 i=0;i<NUM_SLOTS;i++)
 		items[i] = 0;
 	MEquipmentItems.SetName("EquipmentItemList::MEquipmentItems");
+	AppearanceType = 0;
 }
 
 EquipmentItemList::EquipmentItemList(const EquipmentItemList& list){
@@ -3660,6 +3691,7 @@ void EquipmentItemList::SetItem(int8 slot_id, Item* item, bool locked){
 	item->details.inv_slot_id = 0;
 	item->details.slot_id = slot_id;
 	item->details.index = slot_id;
+	item->details.appearance_type = GetAppearanceType();
 	items[slot_id] = item;
 
 	if(!locked)
@@ -3782,7 +3814,7 @@ EQ2Packet* EquipmentItemList::serialize(int16 version, Player* player){
 			packet->setSubstructArrayDataByName("items", "index", i, 0, i);
 		}
 		MEquipmentItems.unlock();
-		packet->setDataByName("equip_flag", 1);
+		packet->setDataByName("equip_flag", GetAppearanceType() ? 2 : 1);
 		app = packet->serializeCountPacket(version, 1, orig_packet, xor_packet);
 		safe_delete(packet);
 	}
@@ -3817,6 +3849,9 @@ bool EquipmentItemList::HasItem(int32 id){
 void EquipmentItemList::RemoveItem(int8 slot, bool delete_item){
 	if(slot < NUM_SLOTS){
 		MEquipmentItems.lock();
+		if(items[slot] && items[slot]->details.appearance_type)
+			items[slot]->details.appearance_type = 0;
+		
 		if(delete_item){
 			safe_delete(items[slot]);
 		}

+ 14 - 4
EQ2/source/WorldServer/Items/Items.h

@@ -32,6 +32,11 @@ class MasterItemList;
 class Player;
 class Entity;
 extern MasterItemList master_item_list;
+
+#define BASE_EQUIPMENT 		 0
+#define APPEARANCE_EQUIPMENT 1
+#define MAX_EQUIPMENT 2 // max iterations for equipment (base is 0, appearance is 1, so this is 2)
+
 #define EQ2_PRIMARY_SLOT 0
 #define EQ2_SECONDARY_SLOT 1
 #define EQ2_HEAD_SLOT 2
@@ -652,6 +657,7 @@ struct ItemCore{
 	int32	bag_id;
 	sint32	inv_slot_id;
 	sint16	slot_id;
+	sint16	appearance_type; // 0 for combat armor, 1 for appearance armor
 	int8	index;
 	int16	icon;
 	int16	count;
@@ -1001,7 +1007,7 @@ public:
 	~PlayerItemList();
 //	int16 number;
 	map<int32, Item*> indexed_items;
-	map<sint32, map<int16, Item*> > items;
+	map<sint32, map<int8, map<int16, Item*>> > items;
 //	map< int8, Item* > inv_items;
 //	map< int8, Item* > bank_items;
 	bool  SharedBankAddAllowed(Item* item);
@@ -1010,8 +1016,8 @@ public:
 	Item* GetBag(int8 inventory_slot, bool lock = true);
 	bool  HasItem(int32 id, bool include_bank = false);
 	Item* GetItemFromIndex(int32 index);
-	void  MoveItem(Item* item, sint32 inv_slot, int16 slot, bool erase_old = true);
-	bool  MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8 charges);
+	void  MoveItem(Item* item, sint32 inv_slot, int16 slot, int8 appearance_type, bool erase_old); // erase old was true
+	bool  MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8 appearance_type, int8 charges);
 	Item* GetItemFromUniqueID(int32 item_id, bool include_bank = false, bool lock = true);
 	Item* GetItemFromID(int32 item_id, int8 count = 0, bool include_bank = false, bool lock = true);
 	bool  AssignItemToFreeSlot(Item* item);
@@ -1025,7 +1031,7 @@ public:
 	void RemoveItem(Item* item, bool delete_item = false);
 	void AddItem(Item* item);
 
-	Item* GetItem(sint32 bag_slot, int16 slot);
+	Item* GetItem(sint32 bag_slot, int16 slot, int8 appearance_type = 0);
 	
 	EQ2Packet*	serialize(Player* player, int16 version);
 	uchar* xor_packet;
@@ -1090,8 +1096,12 @@ public:
 	EQ2Packet* serialize(int16 version, Player* player);
 	uchar* xor_packet;
 	uchar* orig_packet;
+
+	void	SetAppearanceType(int8 type) { AppearanceType = type; }
+	int8	GetAppearanceType() { return AppearanceType; }
 private:
 	Mutex MEquipmentItems;
+	int8 AppearanceType; // 0 for normal equip, 1 for appearance
 };
 
 #endif

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

@@ -1083,12 +1083,30 @@ void WorldDatabase::SaveItems(Client* client)
 
 		if(item && item->save_needed)
 		{
-			SaveItem(client->GetAccountID(), client->GetCharacterID(), item, "EQUIPPED");
+			if(item->details.appearance_type)
+				SaveItem(client->GetAccountID(), client->GetCharacterID(), item, "APPEARANCE");
+			else
+				SaveItem(client->GetAccountID(), client->GetCharacterID(), item, "EQUIPPED");
 			item->save_needed = false;
 		}
 	}
 	safe_delete(equipped_list);
 
+	
+	vector<Item*>* appearance_equipped_list = client->GetPlayer()->GetAppearanceEquippedItemList();
+
+	for(int32 i=0;i<appearance_equipped_list->size();i++)
+	{
+		item = appearance_equipped_list->at(i);
+
+		if(item && item->save_needed)
+		{
+			SaveItem(client->GetAccountID(), client->GetCharacterID(), item, "APPEARANCE");
+			item->save_needed = false;
+		}
+	}
+	safe_delete(appearance_equipped_list);
+
 	vector<Item*>* overflow = client->GetPlayer()->item_list.GetOverflowItemList();
 	for (int32 i = 0; i < overflow->size(); i++){
 		item = overflow->at(i);
@@ -1183,12 +1201,16 @@ void WorldDatabase::LoadCharacterItemList(int32 account_id, int32 char_id, Playe
 				item->details.count = atoi(row[11]); //count
 				item->SetMaxSellValue(atoul(row[12])); //max sell value
 				item->no_sale = (atoul(row[13]) == 1);
+				item->details.appearance_type = 0;
 
 				
 				if(strncasecmp(row[0], "EQUIPPED", 8)==0)
 					ret = player->GetEquipmentList()->AddItem(item->details.slot_id, item);
-				else if (strncasecmp(row[0], "APPEARANCE", 10) == 2)
-					ret = player->GetEquipmentList()->AddItem(item->details.slot_id, item);
+				else if (strncasecmp(row[0], "APPEARANCE", 10) == 0)
+				{
+					item->details.appearance_type = 1;
+					ret = player->GetAppearanceEquipmentList()->AddItem(item->details.slot_id, item);
+				}
 				else {
 					if (version < 1209 && item->details.count > 255) {
 						int stacks = item->details.count / 255;

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

@@ -785,7 +785,8 @@ int EQ2Emu_lua_RemoveLootItem(lua_State* state) {
 	Spawn* spawn = lua_interface->GetSpawn(state);
 	if (spawn && spawn->IsEntity()) {
 		int32 item_id = lua_interface->GetInt32Value(state, 2);
-		spawn->LootItem(item_id);
+		Item* item = spawn->LootItem(item_id);
+		safe_delete(item);
 	}
 	return 0;
 }

+ 75 - 6
EQ2/source/WorldServer/LuaInterface.cpp

@@ -629,10 +629,10 @@ void LuaInterface::RemoveSpawnScript(const char* name) {
 	MSpawnScripts.releasewritelock(__FUNCTION__, __LINE__);
 }
 
-bool LuaInterface::CallItemScript(lua_State* state, int8 num_parameters) {
+bool LuaInterface::CallItemScript(lua_State* state, int8 num_parameters, sint64* returnValue) {
 	if(shutting_down)
 		return false;
-	if(!state || lua_pcall(state, num_parameters, 0, 0) != 0){
+	if(!state || lua_pcall(state, num_parameters, 1, 0) != 0){
 		if (state){
 			const char* err = lua_tostring(state, -1);
 			LogError("%s: %s", GetScriptName(state), err);
@@ -640,6 +640,18 @@ bool LuaInterface::CallItemScript(lua_State* state, int8 num_parameters) {
 		}
 		return false;
 	}
+
+	sint64 result = 0;
+	
+	if (lua_isnumber(state, -1))
+	{
+		result = (sint64)lua_tonumber(state, -1);
+		lua_pop(state, 1);
+	}
+	
+	if(returnValue)
+		*returnValue = result;
+	
 	return true;
 }
 
@@ -657,10 +669,10 @@ bool LuaInterface::CallSpawnScript(lua_State* state, int8 num_parameters) {
 	return true;
 }
 
-bool LuaInterface::CallZoneScript(lua_State* state, int8 num_parameters) {
+bool LuaInterface::CallZoneScript(lua_State* state, int8 num_parameters, int32* returnValue) {
 	if(shutting_down)
 		return false;
-	if (!state || lua_pcall(state, num_parameters, 0, 0) != 0) {
+	if (!state || lua_pcall(state, num_parameters, 1, 0) != 0) {
 		if (state){
 			const char* err = lua_tostring(state, -1);
 			LogError("%s: %s", GetScriptName(state), err);
@@ -668,6 +680,18 @@ bool LuaInterface::CallZoneScript(lua_State* state, int8 num_parameters) {
 		}
 		return false;
 	}
+	
+	int32 result = 0;
+	
+	if (lua_isnumber(state, -1))
+	{
+		result = (int32)lua_tonumber(state, -1);
+		lua_pop(state, 1);
+	}
+	
+	if(returnValue)
+		*returnValue = result;
+	
 	return true;
 }
 
@@ -2023,7 +2047,7 @@ lua_State* LuaInterface::GetRegionScript(const char* name, bool create_new, bool
 	return ret;
 }
 
-bool LuaInterface::RunItemScript(string script_name, const char* function_name, Item* item, Spawn* spawn) {
+bool LuaInterface::RunItemScript(string script_name, const char* function_name, Item* item, Spawn* spawn, sint64* returnValue) {
 	if(!item)
 		return false;
 	lua_State* state = GetItemScript(script_name.c_str(), true, true);
@@ -2049,7 +2073,7 @@ bool LuaInterface::RunItemScript(string script_name, const char* function_name,
 			SetSpawnValue(state, spawn);
 			num_parms++;
 		}
-		if(!CallItemScript(state, num_parms)){
+		if(!CallItemScript(state, num_parms, returnValue)){
 			if(mutex)
 				mutex->releasereadlock(__FUNCTION__, __LINE__);
 			UseItemScript(script_name.c_str(), state, false);
@@ -2187,6 +2211,51 @@ bool LuaInterface::RunZoneScript(string script_name, const char* function_name,
 		return false;
 }
 
+bool LuaInterface::RunZoneScriptWithReturn(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn, int32 int32_arg1, int32 int32_arg2, int32 int32_arg3, int32* returnValue) {
+	if (!zone)
+		return false;
+	lua_State* state = GetZoneScript(script_name.c_str(), true, true);
+	if (state) {
+		Mutex* mutex = GetZoneScriptMutex(script_name.c_str());
+		if (mutex)
+			mutex->readlock(__FUNCTION__, __LINE__);
+		else {
+			LogError("Error getting lock for '%s'", script_name.c_str());
+			UseZoneScript(script_name.c_str(), state, false);
+			return false;
+		}
+		lua_getglobal(state, function_name);
+		if (!lua_isfunction(state, lua_gettop(state))) {
+			lua_pop(state, 1);
+			mutex->releasereadlock(__FUNCTION__);
+			UseZoneScript(script_name.c_str(), state, false);
+			return false;
+		}
+		SetZoneValue(state, zone);
+		int8 num_params = 1;
+		SetSpawnValue(state, spawn);
+		num_params++;
+		SetInt32Value(state, int32_arg1);
+		num_params++;
+		SetInt32Value(state, int32_arg2);
+		num_params++;
+		SetInt32Value(state, int32_arg3);
+		num_params++;
+		if (!CallZoneScript(state, num_params, returnValue)) {
+			if (mutex)
+				mutex->releasereadlock(__FUNCTION__, __LINE__);
+			UseZoneScript(script_name.c_str(), state, false);
+			return false;
+		}
+		if (mutex)
+			mutex->releasereadlock(__FUNCTION__, __LINE__);
+		UseZoneScript(script_name.c_str(), state, false);
+		return true;
+	}
+	else
+		return false;
+}
+
 
 bool LuaInterface::RunRegionScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn, sint32 int32_arg1, int32* returnValue) {
 	if (!zone)

+ 4 - 3
EQ2/source/WorldServer/LuaInterface.h

@@ -250,12 +250,13 @@ public:
 	const char*		GetScriptName(lua_State* state);
 
 	void			RemoveSpawnScript(const char* name);
-	bool			RunItemScript(string script_name, const char* function_name, Item* item, Spawn* spawn = 0);
-	bool			CallItemScript(lua_State* state, int8 num_parameters);
+	bool			RunItemScript(string script_name, const char* function_name, Item* item, Spawn* spawn = 0, sint64* returnValue = 0);
+	bool			CallItemScript(lua_State* state, int8 num_parameters, sint64* returnValue = 0);
 	bool			RunSpawnScript(string script_name, const char* function_name, Spawn* npc, Spawn* spawn = 0, const char* message = 0, bool is_door_open = false);
 	bool			CallSpawnScript(lua_State* state, int8 num_parameters);
 	bool			RunZoneScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn = 0, int32 int32_arg1 = 0, const char* str_arg1 = 0, Spawn* spawn_arg1 = 0, int32 int32_arg2 = 0, const char* str_arg2 = 0, Spawn* spawn_arg2 = 0);
-	bool			CallZoneScript(lua_State* state, int8 num_parameters);
+	bool			RunZoneScriptWithReturn(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn, int32 int32_arg1, int32 int32_arg2, int32 int32_arg3, int32* returnValue = 0);
+	bool			CallZoneScript(lua_State* state, int8 num_parameters, int32* returnValue = 0);
 	bool			RunRegionScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn = 0, sint32 int32_arg1 = 0, int32* returnValue = 0);
 	bool			CallRegionScript(lua_State* state, int8 num_parameters, int32* returnValue);
 	void			ResetFunctionStack(lua_State* state);

+ 93 - 33
EQ2/source/WorldServer/Player.cpp

@@ -1769,13 +1769,21 @@ int8 Player::ConvertSlotFromClient(int8 slot, int16 version) {
 	return slot;
 }
 
-vector<EQ2Packet*>	Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, int16 version) {
+vector<EQ2Packet*>	Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, int16 version, int8 appearance_type) {
 	vector<EQ2Packet*>	packets;
-	Item* item = equipment_list.items[index];
+	EquipmentItemList* equipList = &equipment_list;
+	
+	if(appearance_type)
+		equipList = &appearance_equipment_list;
+		
+	Item* item = equipList->items[index];
 	if (item && bag_id == -999) {
 		int8 old_slot = item->details.slot_id;
 		if (item_list.AssignItemToFreeSlot(item)) {
-			database.DeleteItem(GetCharacterID(), item, "EQUIPPED");
+			if(appearance_type)
+				database.DeleteItem(GetCharacterID(), item, "APPEARANCE");
+			else
+				database.DeleteItem(GetCharacterID(), item, "EQUIPPED");
 
 			if (item->GetItemScript() && lua_interface)
 				lua_interface->RunItemScript(item->GetItemScript(), "unequipped", item, this);
@@ -1791,8 +1799,9 @@ vector<EQ2Packet*>	Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, in
 				if (bag_packet)
 					packets.push_back(bag_packet);
 			}
-			equipment_list.RemoveItem(index);
-			packets.push_back(equipment_list.serialize(version, this));
+			equipList->RemoveItem(index);
+			SetEquippedItemAppearances();
+			packets.push_back(equipList->serialize(version, this));
 			SetCharSheetChanged(true);
 			SetEquipment(0, old_slot);
 		}
@@ -1809,11 +1818,25 @@ vector<EQ2Packet*>	Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, in
 	}
 	else if (item) {
 		Item* to_item = 0;
-		if (item_list.items.count(bag_id) > 0 && item_list.items[bag_id].count(slot) > 0)
-			to_item = item_list.items[bag_id][slot];
-		if (to_item && GetEquipmentList()->CanItemBeEquippedInSlot(to_item, ConvertSlotFromClient(item->details.slot_id, version))) {
-			equipment_list.RemoveItem(index);
-			database.DeleteItem(GetCharacterID(), item, "EQUIPPED");
+		if(appearance_type && slot == 255)
+		{
+			sint16 tmpSlot = 0;
+			item_list.GetFirstFreeSlot(&bag_id, &tmpSlot);
+			if(tmpSlot >= 0 && tmpSlot < 255)
+				slot = tmpSlot;
+			else
+				bag_id = 0;
+		}
+
+		if (item_list.items.count(bag_id) > 0 && item_list.items[bag_id][appearance_type].count(slot) > 0)
+			to_item = item_list.items[bag_id][appearance_type][slot];
+		if (to_item && equipList->CanItemBeEquippedInSlot(to_item, ConvertSlotFromClient(item->details.slot_id, version))) {
+			equipList->RemoveItem(index);
+			if(item->details.appearance_type)
+				database.DeleteItem(GetCharacterID(), item, "APPEARANCE");
+			else
+				database.DeleteItem(GetCharacterID(), item, "EQUIPPED");
+			
 			database.DeleteItem(GetCharacterID(), to_item, "NOT-EQUIPPED");
 
 			if (item->GetItemScript() && lua_interface)
@@ -1823,34 +1846,42 @@ 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);
-			equipment_list.SetItem(item->details.slot_id, to_item);
+			equipList->SetItem(item->details.slot_id, to_item);
 			to_item->save_needed = true;
 			packets.push_back(to_item->serialize(version, false));
 			SetEquipment(to_item);
 			item->details.inv_slot_id = bag_id;
 			item->details.slot_id = slot;
+			item->details.appearance_type = 0;
 			item_list.AddItem(item);
 			item->save_needed = true;
+			SetEquippedItemAppearances();
 			packets.push_back(item->serialize(version, false));
-			packets.push_back(equipment_list.serialize(version, this));
+			packets.push_back(equipList->serialize(version, this));
 			packets.push_back(item_list.serialize(this, version));
 		}
 		else if (to_item && to_item->IsBag() && to_item->details.num_slots > 0) {
 			bool free_slot = false;
 			for (int8 i = 0; i < to_item->details.num_slots; i++) {
-				if (item_list.items[to_item->details.bag_id].count(i) == 0) {
+				if (item_list.items[to_item->details.bag_id][appearance_type].count(i) == 0) {
 					SetEquipment(0, item->details.slot_id);
-					database.DeleteItem(GetCharacterID(), item, "EQUIPPED");
+
+					if(item->details.appearance_type)
+						database.DeleteItem(GetCharacterID(), item, "APPEARANCE");
+					else
+						database.DeleteItem(GetCharacterID(), item, "EQUIPPED");
 
 					if (item->GetItemScript() && lua_interface)
 						lua_interface->RunItemScript(item->GetItemScript(), "unequipped", item, this);
 
-					equipment_list.RemoveItem(index);
+					equipList->RemoveItem(index);
 					item->details.inv_slot_id = to_item->details.bag_id;
 					item->details.slot_id = i;
+					item->details.appearance_type = to_item->details.appearance_type;
 					item_list.AddItem(item);
 					item->save_needed = true;
-					packets.push_back(equipment_list.serialize(version, this));
+					SetEquippedItemAppearances();
+					packets.push_back(equipList->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));
@@ -1892,18 +1923,24 @@ vector<EQ2Packet*>	Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, in
 					}
 				}
 				else {
+					// need to check if appearance slot vs equipped
 					SetEquipment(0, item->details.slot_id);
-					database.DeleteItem(GetCharacterID(), item, "EQUIPPED");
+					if(item->details.appearance_type)
+						database.DeleteItem(GetCharacterID(), item, "APPEARANCE");
+					else
+						database.DeleteItem(GetCharacterID(), item, "EQUIPPED");
 
 					if (item->GetItemScript() && lua_interface)
 						lua_interface->RunItemScript(item->GetItemScript(), "unequipped", item, this);
 
-					equipment_list.RemoveItem(index);
+					equipList->RemoveItem(index);
 					item->details.inv_slot_id = bag_id;
 					item->details.slot_id = slot;
+					item->details.appearance_type = 0;
 					item_list.AddItem(item);
 					item->save_needed = true;
-					packets.push_back(equipment_list.serialize(version, this));
+					SetEquippedItemAppearances();
+					packets.push_back(equipList->serialize(version, this));
 					packets.push_back(item->serialize(version, false));
 					packets.push_back(item_list.serialize(this, version));
 				}
@@ -1912,17 +1949,22 @@ vector<EQ2Packet*>	Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, in
 				Item* bag = item_list.GetItemFromUniqueID(bag_id, true);
 				if (bag && bag->IsBag() && slot < bag->details.num_slots) {
 					SetEquipment(0, item->details.slot_id);
-					database.DeleteItem(GetCharacterID(), item, "EQUIPPED");
+					if(item->details.appearance_type)
+						database.DeleteItem(GetCharacterID(), item, "APPEARANCE");
+					else
+						database.DeleteItem(GetCharacterID(), item, "EQUIPPED");
 
 					if (item->GetItemScript() && lua_interface)
 						lua_interface->RunItemScript(item->GetItemScript(), "unequipped", item, this);
 
-					equipment_list.RemoveItem(index);
+					equipList->RemoveItem(index);
 					item->details.inv_slot_id = bag_id;
 					item->details.slot_id = slot;
+					item->details.appearance_type = 0;
 					item_list.AddItem(item);
 					item->save_needed = true;
-					packets.push_back(equipment_list.serialize(version, this));
+					SetEquippedItemAppearances();
+					packets.push_back(equipList->serialize(version, this));
 					packets.push_back(item->serialize(version, false));
 					packets.push_back(item_list.serialize(this, version));
 				}
@@ -1943,6 +1985,10 @@ vector<Item*>* Player::GetEquippedItemList(){
 	return equipment_list.GetAllEquippedItems();
 }
 
+vector<Item*>* Player::GetAppearanceEquippedItemList(){
+	return appearance_equipment_list.GetAllEquippedItems();
+}
+
 EQ2Packet*	Player::SendBagUpdate(int32 bag_unique_id, int16 version){
 	Item* bag = 0;
 	if(bag_unique_id > 0)
@@ -1955,11 +2001,19 @@ EQ2Packet*	Player::SendBagUpdate(int32 bag_unique_id, int16 version){
 
 void Player::SetEquippedItemAppearances(){
 	vector<Item*>* items = GetEquipmentList()->GetAllEquippedItems();
+	vector<Item*>* appearance_items = GetAppearanceEquipmentList()->GetAllEquippedItems();
 	if(items){
 		for(int32 i=0;i<items->size();i++)
 			SetEquipment(items->at(i));
+		
+		// just have appearance items brute force replace the slots after the fact
+		for(int32 i=0;i<appearance_items->size();i++)
+			SetEquipment(appearance_items->at(i));
 	}
 	safe_delete(items);
+	safe_delete(appearance_items);
+	info_changed = true;
+	GetZone()->SendSpawnChanges(this);
 }
 
 EQ2Packet* Player::SwapEquippedItems(int8 slot1, int8 slot2, int16 version){
@@ -2005,7 +2059,12 @@ bool Player::CanEquipItem(Item* item) {
 	return false;
 }
 
-vector<EQ2Packet*> Player::EquipItem(int16 index, int16 version, int8 slot_id) {
+vector<EQ2Packet*> Player::EquipItem(int16 index, int16 version, int8 appearance_type, int8 slot_id) {
+
+	EquipmentItemList* equipList = &equipment_list;
+	if(appearance_type)
+		equipList = &appearance_equipment_list;
+
 	vector<EQ2Packet*>	packets;
 	if (item_list.indexed_items.count(index) == 0)
 		return packets;
@@ -2014,7 +2073,7 @@ vector<EQ2Packet*> Player::EquipItem(int16 index, int16 version, int8 slot_id) {
 	if (item) {
 		if (slot_id != 255 && !item->HasSlot(slot_id))
 			return packets;
-		int8 slot = equipment_list.GetFreeSlot(item, slot_id);
+		int8 slot = equipList->GetFreeSlot(item, slot_id);
 		bool canEquip = CanEquipItem(item);
 		if (canEquip && item->CheckFlag(ATTUNEABLE)) {
 			PacketStruct* packet = configReader.getStruct("WS_ChoiceWindow", version);
@@ -2039,18 +2098,18 @@ vector<EQ2Packet*> Player::EquipItem(int16 index, int16 version, int8 slot_id) {
 				slot = item->slot_data.at(0);
 			else
 				slot = slot_id;
-			packets = UnequipItem(slot, item->details.inv_slot_id, item->details.slot_id, version);
+			packets = UnequipItem(slot, item->details.inv_slot_id, item->details.slot_id, version, appearance_type);
 			// If item is a 2handed weapon and something is in the secondary, unequip the secondary
-			if (item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND && equipment_list.GetItem(EQ2_SECONDARY_SLOT) != 0) {
-				vector<EQ2Packet*> tmp_packets = UnequipItem(EQ2_SECONDARY_SLOT, -999, 0, version);
+			if (item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND && equipList->GetItem(EQ2_SECONDARY_SLOT) != 0) {
+				vector<EQ2Packet*> tmp_packets = UnequipItem(EQ2_SECONDARY_SLOT, -999, 0, version, appearance_type);
 				//packets.reserve(packets.size() + tmp_packets.size());
 				packets.insert(packets.end(), tmp_packets.begin(), tmp_packets.end());
 			}
 		}
 		else if (canEquip && slot < 255) {
 			// If item is a 2handed weapon and something is in the secondary, unequip the secondary
-			if (item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND && equipment_list.GetItem(EQ2_SECONDARY_SLOT) != 0) {
-				vector<EQ2Packet*> tmp_packets = UnequipItem(EQ2_SECONDARY_SLOT, -999, 0, version);
+			if (item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND && equipList->GetItem(EQ2_SECONDARY_SLOT) != 0) {
+				vector<EQ2Packet*> tmp_packets = UnequipItem(EQ2_SECONDARY_SLOT, -999, 0, version, appearance_type);
 				//packets.reserve(packets.size() + tmp_packets.size());
 				packets.insert(packets.end(), tmp_packets.begin(), tmp_packets.end());
 			}
@@ -2061,7 +2120,7 @@ vector<EQ2Packet*> Player::EquipItem(int16 index, int16 version, int8 slot_id) {
 				lua_interface->RunItemScript(item->GetItemScript(), "equipped", item, this);
 
 			item_list.RemoveItem(item);
-			equipment_list.SetItem(ConvertSlotToClient(slot, version), item);
+			equipList->SetItem(ConvertSlotToClient(slot, version), item);
 			item->save_needed = true;
 			packets.push_back(item->serialize(version, false));
 			SetEquipment(item);
@@ -2078,7 +2137,8 @@ vector<EQ2Packet*> Player::EquipItem(int16 index, int16 version, int8 slot_id) {
 					client->Message(CHANNEL_COLOR_RED, "Your %s is worn out and will not be effective until repaired.", item->CreateItemLink(client->GetVersion(), true).c_str());
 				}
 			}
-			packets.push_back(equipment_list.serialize(version, this));
+			SetEquippedItemAppearances();
+			packets.push_back(equipList->serialize(version, this));
 			EQ2Packet* outapp = item_list.serialize(this, version);
 			if (outapp) {
 				packets.push_back(outapp);
@@ -2124,9 +2184,9 @@ bool Player::AddItemToBank(Item* item) {
 EQ2Packet* Player::SendInventoryUpdate(int16 version) {
 	return item_list.serialize(this, version);
 }
-EQ2Packet* Player::MoveInventoryItem(sint32 to_bag_id, int16 from_index, int8 new_slot, int8 charges, int16 version) {
+EQ2Packet* Player::MoveInventoryItem(sint32 to_bag_id, int16 from_index, int8 new_slot, int8 charges, int8 appearance_type, int16 version) {
 	Item* item = item_list.GetItemFromIndex(from_index);
-	int8 result = item_list.MoveItem(to_bag_id, from_index, new_slot, charges);
+	int8 result = item_list.MoveItem(to_bag_id, from_index, new_slot, appearance_type, charges);
 	if (result == 1) {
 		if (item) {
 			if (!item->needs_deletion)

+ 4 - 3
EQ2/source/WorldServer/Player.h

@@ -447,10 +447,10 @@ public:
 	Skill*	GetSkillByID(int32 skill_id, bool check_update = false);
 	PlayerSkillList* GetSkills();
 	bool DamageEquippedItems(int8 amount = 10, Client* client = 0);
-	vector<EQ2Packet*>	EquipItem(int16 index, int16 version, int8 slot_id = 255);
+	vector<EQ2Packet*>	EquipItem(int16 index, int16 version, int8 appearance_type, int8 slot_id = 255);
 	bool CanEquipItem(Item* item);
 	void SetEquippedItemAppearances();
-	vector<EQ2Packet*>	UnequipItem(int16 index, sint32 bag_id, int8 slot, int16 version);
+	vector<EQ2Packet*>	UnequipItem(int16 index, sint32 bag_id, int8 slot, int16 version, int8 appearance_type = 0);
 	int8 ConvertSlotToClient(int8 slot, int16 version);
 	int8 ConvertSlotFromClient(int8 slot, int16 version);
 	EQ2Packet* SwapEquippedItems(int8 slot1, int8 slot2, int16 version);
@@ -462,6 +462,7 @@ public:
 	map<int32, Item*>* GetItemList();
 	map<int32, Item*>* GetBankItemList();
 	vector<Item*>* GetEquippedItemList();
+	vector<Item*>* GetAppearanceEquippedItemList();
 	Quest* SetStepComplete(int32 quest_id, int32 step);
 	Quest* AddStepProgress(int32 quest_id, int32 step, int32 progress);
 	int32  GetStepProgress(int32 quest_id, int32 step_id);
@@ -505,7 +506,7 @@ public:
 	/// <param name='skill_id'>The id of the skill to check</param>
 	/// <returns>A vector of int32's of the spell id's</returns>
 	vector<int32> GetSpellBookSpellIDBySkill(int32 skill_id);
-	EQ2Packet* MoveInventoryItem(sint32 to_bag_id, int16 from_index, int8 new_slot, int8 charges, int16 version = 1);
+	EQ2Packet* MoveInventoryItem(sint32 to_bag_id, int16 from_index, int8 new_slot, int8 charges, int8 appearance_type, int16 version = 1);
 	bool IsPlayer(){ return true; }
 	MaintainedEffects* GetFreeMaintainedSpellSlot();
 	MaintainedEffects* GetMaintainedSpell(int32 id);

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

@@ -210,6 +210,7 @@ void RuleManager::Init()
 	RULE_INIT(R_Player, MinLastNameLevel, "20");
 	RULE_INIT(R_Player, MaxLastNameLength, "20");
 	RULE_INIT(R_Player, MinLastNameLength, "4");
+	RULE_INIT(R_Player, DisableHouseAlignmentRequirement, "1");
 
 	/* PVP */
 	RULE_INIT(R_PVP, AllowPVP, "0");

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

@@ -70,6 +70,7 @@ enum RuleType {
 	MinLastNameLevel,
 	MaxLastNameLength,
 	MinLastNameLength,
+	DisableHouseAlignmentRequirement,
 
 	/* PVP */
 	AllowPVP,

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

@@ -124,6 +124,7 @@ Spawn::Spawn(){
 	RegionMutex.SetName("Spawn::RegionMutex");
 	pause_timer.Disable();
 	m_SpawnMutex.SetName("Spawn::SpawnMutex");
+	appearance_equipment_list.SetAppearanceType(1);
 }
 
 Spawn::~Spawn(){

+ 5 - 0
EQ2/source/WorldServer/Spawn.h

@@ -872,6 +872,10 @@ public:
 			loot_items.push_back(item);
 		}
 	}
+	void AddLootItem(Item* item) {
+		if(item)
+			loot_items.push_back(item);
+	}
 	bool HasLoot() {
 		if (loot_items.size() == 0 && loot_coins == 0)
 			return false;
@@ -1236,6 +1240,7 @@ protected:
 	map<int32, vector<int16>* > required_quests;
 	map<int32, LUAHistory*> required_history;
 	EquipmentItemList equipment_list;
+	EquipmentItemList appearance_equipment_list;
 
 	MutexList<SpawnProximity*> spawn_proximities;
 

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

@@ -187,6 +187,8 @@
 #define SPELL_TYPE_CURE			12
 #define SPELL_TYPE_FOOD			13
 #define SPELL_TYPE_DRINK		14
+#define SPELL_TYPE_ROOT			15
+#define SPELL_TYPE_SNARE		16
 
 
 struct LUAData{

+ 59 - 29
EQ2/source/WorldServer/client.cpp

@@ -724,6 +724,11 @@ void Client::SendCharInfo() {
 		QueuePacket(app);
 	}
 
+	app = player->GetAppearanceEquipmentList()->serialize(GetVersion(), player);
+	if (app) {
+		QueuePacket(app);
+	}
+
 	vector<Item*>* items = player->item_list.GetItemsFromBagID(-3); // bank items
 	if (items && items->size() > 0) {
 		for (int32 i = 0; i < items->size(); i++) {
@@ -1960,6 +1965,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 			packet->LoadPacketData(app->pBuffer, app->size);
 			HouseZone* hz = world.GetHouseZone(packet->getType_int64_ByName("house_id"));
 			if (hz) {
+				int8 disable_alignment_req = rule_manager.GetGlobalRule(R_Player, DisableHouseAlignmentRequirement)->GetInt8();
 				std::vector<PlayerHouse*> houses = world.GetAllPlayerHouses(GetCharacterID());
 				if (houses.size() > 24)
 				{
@@ -1967,6 +1973,18 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 					safe_delete(packet);
 					break;
 				}
+				if(disable_alignment_req && hz->alignment > 0 && hz->alignment != GetPlayer()->GetAlignment())
+				{
+					std::string req = "You must be of ";
+					if (hz->alignment == 1)
+						req.append("Good");
+					else
+						req.append("Evil");
+					req.append(" alignment to purchase this house");
+					SimpleMessage(CHANNEL_COLOR_YELLOW, req.c_str());
+					safe_delete(packet);
+					break;
+				}
 				int32 status_req = hz->upkeep_status + hz->cost_status;
 				int32 available_status = player->GetInfoStruct()->get_status_points();
 				if (status_req <= available_status && 
@@ -2416,8 +2434,13 @@ bool Client::HandleLootItem(Spawn* entity, int32 item_id) {
 	if (!entity) {
 		return false;
 	}
-
-	return HandleLootItem(entity, entity->LootItem(item_id));
+	Item* item = entity->LootItem(item_id);
+	bool success = false;
+	success = HandleLootItem(entity, item);
+	if(!success)
+		entity->AddLootItem(item);
+	
+	return success;
 }
 
 void Client::HandleLoot(EQApplicationPacket* app) {
@@ -2670,6 +2693,8 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
 			item = GetPlayer()->item_list.GetItemFromUniqueID(id, true);
 		if (!item)
 			item = GetPlayer()->GetEquipmentList()->GetItemFromUniqueID(id);
+		if (!item)
+			item = GetPlayer()->GetAppearanceEquipmentList()->GetItemFromUniqueID(id);
 		if (!item)
 			item = master_item_list.GetItem(id);
 		if (item) {// && sent_item_details.count(id) == 0){
@@ -2697,6 +2722,8 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
 		Item* item = GetPlayer()->item_list.GetItemFromUniqueID(unique_id, true);
 		if (!item)
 			item = GetPlayer()->GetEquipmentList()->GetItemFromUniqueID(unique_id);
+		if (!item)
+			item = GetPlayer()->GetAppearanceEquipmentList()->GetItemFromUniqueID(unique_id);
 		if (!item)
 			item = master_item_list.GetItem(id);
 		if (item) {
@@ -6979,9 +7006,18 @@ void Client::SendBuyMerchantList(bool sell) {
 					item_difficulty = player->GetArrowColor(tmp_level);
 					if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY)
 						item_difficulty = ARROW_COLOR_WHITE;
+
+					sint64 overrideValue = 0;
+					if (item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "item_difficulty", item, player, &overrideValue))
+					{
+						item_difficulty = (sint8)overrideValue;
+						printf("Override difficulty: %i\n",item_difficulty);
+					}
+
 					item_difficulty -= 6;
 					if (item_difficulty < 0)
 						item_difficulty *= -1;
+					
 					packet->setArrayDataByName("item_difficulty", item_difficulty, i);
 					packet->setArrayDataByName("quantity", ItemInfo.quantity, i);
 					packet->setArrayDataByName("unknown5", 255, i);
@@ -7152,9 +7188,18 @@ void Client::SendSellMerchantList(bool sell) {
 					item_difficulty = player->GetArrowColor(tmp_level);
 					if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY)
 						item_difficulty = ARROW_COLOR_WHITE;
+					
+					sint64 overrideValue = 0;
+					if (item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "item_difficulty", item, player, &overrideValue))
+					{
+						item_difficulty = (sint8)overrideValue;
+						printf("Override difficulty: %i\n",item_difficulty);
+					}
+					
 					item_difficulty -= 6;
 					if (item_difficulty < 0)
 						item_difficulty *= -1;
+
 					packet->setArrayDataByName("item_difficulty", item_difficulty, i);
 					if (item->details.count == 1)
 						packet->setArrayDataByName("quantity", 0xFFFF, i);
@@ -7223,6 +7268,14 @@ void Client::SendBuyBackList(bool sell) {
 					item_difficulty = player->GetArrowColor(tmp_level);
 					if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY)
 						item_difficulty = ARROW_COLOR_WHITE;
+					
+					sint64 overrideValue = 0;
+					if (master_item->GetItemScript() && lua_interface && lua_interface->RunItemScript(master_item->GetItemScript(), "item_difficulty", master_item, player, &overrideValue))
+					{
+						item_difficulty = (sint8)overrideValue;
+						printf("Override difficulty: %i\n",item_difficulty);
+					}
+
 					item_difficulty -= 6;
 					if (item_difficulty < 0)
 						item_difficulty *= -1;
@@ -7566,7 +7619,7 @@ void Client::SendMailList() {
 				//p->setArrayDataByName("unknown2", 0, i);
 
 				bool successItemAdd = false;
-				if(mail->stack && mail->char_item_id)
+				if(mail->stack || mail->char_item_id)
 				{
 					Item* item = master_item_list.GetItem(mail->char_item_id);
 					if(item)
@@ -7581,30 +7634,13 @@ void Client::SendMailList() {
 						
 						successItemAdd = true;
 					}
-					// 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);
 				}
-				
+
 				if(!successItemAdd)
 				{
-					//p->setArrayDataByName("packettype", GetItemPacketType(GetVersion()), i);
-					//p->setArrayDataByName("packetsubtype", 0xFF, i);
-					p->setArrayDataByName("end_tag1", 0xFFFFFFFF, i);
 					p->setArrayDataByName("end_tag2", GetItemPacketType(GetVersion()), i);
 					p->setArrayDataByName("end_tag3", 0xFF, i);
 				}
-				//p->setArrayDataByName("item_id", 5, i, 0);
-				//Item* item = master_item_list.GetItem(5);
-				//if (item)
-				//	printf("%s\n", item->name.c_str());
-				//else
-				//	printf("no item\n");
-				//p->setItemArrayDataByName("item", master_item_list.GetItem(5), i, 0);
 				i++;
 			}
 
@@ -7675,14 +7711,8 @@ void Client::DisplayMailMessage(int32 mail_id) {
 				}
 				else
 				{
-					packet->setDataByName("end_tag", 0xFFFFFFFF);
-
-					/*packet->setDataByName("packettype", GetItemPacketType(GetVersion()));
-					packet->setDataByName("packetsubtype", 0xFF);
-					packet->setDataByName("unknown4", 0);
-					packet->setDataByName("unknown5", 0);
-					packet->setDataByName("unknown6", 0);
-					packet->setDataByName("unknown7", 0);*/
+					packet->setDataByName("end_tag2", GetItemPacketType(GetVersion()));
+					packet->setDataByName("end_tag3", 0xFF);
 				}
 				mail->already_read = true;
 				mail->save_needed = true;

+ 51 - 10
EQ2/source/WorldServer/zoneserver.cpp

@@ -175,7 +175,7 @@ ZoneServer::~ZoneServer() {
 		Sleep(10);
 	}
 	LogWrite(ZONE__INFO, 0, "Zone", "Initiating zone shutdown of '%s'", zone_name);
-	changed_spawns.clear();
+	changed_spawns.clear(true);
 	transport_spawns.clear();
 	safe_delete(tradeskillMgr);
 	MMasterZoneLock->lock();
@@ -1878,17 +1878,23 @@ void ZoneServer::SendSpawnChanges(){
 
 	MSpawnList.readlock(__FUNCTION__, __LINE__);
 
+	int32 max_updates = 100;
+	
 	MutexList<int32>::iterator spawn_iter = changed_spawns.begin();
 	int count = 0;
 	while(spawn_iter.Next()){		
 		spawn = GetSpawnByID(spawn_iter->value);
 		if(spawn){
 			spawns_to_send.insert(spawn);
+			count++;
 		}
 		if (!spawn)
 			changed_spawns.Remove(spawn_iter->value);
+
+		if(count >= max_updates)
+			break;
 	}
-	changed_spawns.clear();
+	//changed_spawns.clear() is not thread safe, advise we rely on what was removed and continue on, get any others in next batch
 
 	vector<Client*>::iterator client_itr;
 	Client* client = 0;
@@ -2320,7 +2326,7 @@ void ZoneServer::ProcessSpawnLocations()
 }
 
 void ZoneServer::AddLoot(NPC* npc){
-	vector<int32> loot_tables = GetSpawnLootList(npc->GetDatabaseID(), GetZoneID(), npc->GetLevel(), race_types_list.GetRaceType(npc->GetModelType()));
+	vector<int32> loot_tables = GetSpawnLootList(npc->GetDatabaseID(), GetZoneID(), npc->GetLevel(), race_types_list.GetRaceType(npc->GetModelType()), npc);
 	if(loot_tables.size() > 0){
 		vector<LootDrop*>* loot_drops = 0;
 		vector<LootDrop*>::iterator loot_drop_itr;
@@ -7221,8 +7227,10 @@ void ZoneServer::ClearLootTables(){
 	zone_loot_list.clear();
 }
 
-vector<int32> ZoneServer::GetSpawnLootList(int32 spawn_id, int32 zone_id, int8 spawn_level, int16 racial_id) {
+vector<int32> ZoneServer::GetSpawnLootList(int32 spawn_id, int32 zone_id, int8 spawn_level, int16 racial_id, Spawn* spawn) {
 	vector<int32> ret;
+	int32 returnValue = 0;
+
 	if(reloading)
 		return ret;
 
@@ -7233,12 +7241,23 @@ vector<int32> ZoneServer::GetSpawnLootList(int32 spawn_id, int32 zone_id, int8 s
 		vector<GlobalLoot*>::iterator itr;
 		for (itr = level_loot_list.begin(); itr != level_loot_list.end(); itr++) {
 			GlobalLoot* loot = *itr;
-			if (loot->minLevel == 0 && loot->maxLevel == 0)
+			const char* zone_script = world.GetZoneScript(this->GetZoneID());
+			returnValue = 0; // reset since this can override the database setting
+			if(zone_script)
+			{
+				if(lua_interface->RunZoneScriptWithReturn(zone_script, "loot_criteria_level", spawn->GetZone(), spawn, loot->table_id, loot->minLevel, loot->maxLevel, &returnValue) && returnValue == 0)
+					continue;
+			}
+			bool entryAdded = false;
+			if (loot->minLevel == 0 && loot->maxLevel == 0 && (entryAdded = true)) // successful plan to add set entryAdded to true
 				ret.push_back(loot->table_id);
 			else {
-				if (spawn_level >= loot->minLevel && spawn_level <= loot->maxLevel)
+				if (spawn_level >= loot->minLevel && spawn_level <= loot->maxLevel && (entryAdded = true)) // successful plan to add set entryAdded to true
 					ret.push_back(loot->table_id);
 			}
+			
+			if(!entryAdded && returnValue) // DB override via LUA scripting
+				ret.push_back(loot->table_id);
 		}
 	}
 
@@ -7246,12 +7265,23 @@ vector<int32> ZoneServer::GetSpawnLootList(int32 spawn_id, int32 zone_id, int8 s
 		vector<GlobalLoot*>::iterator itr;
 		for (itr = racial_loot_list[racial_id].begin(); itr != racial_loot_list[racial_id].end(); itr++) {
 			GlobalLoot* loot = *itr;
-			if (loot->minLevel == 0 && loot->maxLevel == 0)
+			const char* zone_script = world.GetZoneScript(this->GetZoneID());
+			returnValue = 0; // reset since this can override the database setting
+			if(zone_script)
+			{
+				if(lua_interface->RunZoneScriptWithReturn(zone_script, "loot_criteria_racial", spawn->GetZone(), spawn, loot->table_id, loot->minLevel, loot->maxLevel, &returnValue) && returnValue == 0)
+					continue;
+			}
+			bool entryAdded = false;
+			if (loot->minLevel == 0 && loot->maxLevel == 0 && (entryAdded = true)) // successful plan to add set entryAdded to true
 				ret.push_back(loot->table_id);
 			else {
-				if (spawn_level >= loot->minLevel && spawn_level <= loot->maxLevel)
+				if (spawn_level >= loot->minLevel && spawn_level <= loot->maxLevel && (entryAdded = true)) // successful plan to add set entryAdded to true
 					ret.push_back(loot->table_id);
 			}
+
+			if(!entryAdded && returnValue) // DB override via LUA scripting
+				ret.push_back(loot->table_id);
 		}
 	}
 
@@ -7259,12 +7289,23 @@ vector<int32> ZoneServer::GetSpawnLootList(int32 spawn_id, int32 zone_id, int8 s
 		vector<GlobalLoot*>::iterator itr;
 		for (itr = zone_loot_list[zone_id].begin(); itr != zone_loot_list[zone_id].end(); itr++) {
 			GlobalLoot* loot = *itr;
-			if (loot->minLevel == 0 && loot->maxLevel == 0)
+			const char* zone_script = world.GetZoneScript(this->GetZoneID());
+			returnValue = 0; // reset since this can override the database setting
+			if(zone_script)
+			{
+				if(lua_interface->RunZoneScriptWithReturn(zone_script, "loot_criteria_zone", spawn->GetZone(), spawn, loot->table_id, loot->minLevel, loot->maxLevel, &returnValue) && returnValue == 0)
+					continue;
+			}
+			bool entryAdded = false;
+			if (loot->minLevel == 0 && loot->maxLevel == 0 && (entryAdded = true)) // successful plan to add set entryAdded to true
 				ret.push_back(loot->table_id);
 			else {
-				if (spawn_level >= loot->minLevel && spawn_level <= loot->maxLevel)
+				if (spawn_level >= loot->minLevel && spawn_level <= loot->maxLevel && (entryAdded = true)) // successful plan to add set entryAdded to true
 					ret.push_back(loot->table_id);
 			}
+
+			if(!entryAdded && returnValue) // DB override via LUA scripting
+				ret.push_back(loot->table_id);
 		}
 	}
 

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

@@ -1046,7 +1046,7 @@ public:
 	void				AddRacialLootList(int16 racial_id, GlobalLoot* loot);
 	void				AddZoneLootList(int32 zone, GlobalLoot* loot);
 	void				ClearLootTables();
-	vector<int32>		GetSpawnLootList(int32 spawn_id, int32 zone_id, int8 spawn_level, int16 racial_id);
+	vector<int32>		GetSpawnLootList(int32 spawn_id, int32 zone_id, int8 spawn_level, int16 racial_id, Spawn* spawn = 0);
 	vector<LootDrop*>*	GetLootDrops(int32 table_id);
 	LootTable*			GetLootTable(int32 table_id);
 

+ 6 - 2
server/WorldStructs.xml

@@ -14996,6 +14996,7 @@ to zero and treated like placeholders." />
 <Data ElementName="location_z" Type="float" Size="1" />
 </Data>
 </Struct>
+
 <Struct Name="WS_GetMailHeader" ClientVersion="1" OpcodeName="OP_MailGetHeadersReplyMsg">
 <Data ElementName="kiosk_id" Type="int32" />
 <Data ElementName="num_messages" Type="int16" Size="1" />
@@ -15091,8 +15092,11 @@ to zero and treated like placeholders." />
 <Data ElementName="coin_silver" Type="int32" />
 <Data ElementName="coin_gold" Type="int32" />
 <Data ElementName="coin_plat" Type="int32" />
-<Data ElementName="item" Type="EQ2_Item" />
-<Data ElementName="end_tag" Type="int32" IfVariableNotSet="item"/>
+<Data ElementName="item" Type="EQ2_Item" Optional="TRUE"/>
+<Data ElementName="end_tagx" Type="int16" IfVariableNotSet="item"/>
+<Data ElementName="end_tag2" Type="int16" IfVariableNotSet="item"/>
+<Data ElementName="end_tag3" Type="int8" IfVariableNotSet="item"/>
+<Data ElementName="end_tag4" Type="int32"/>
 </Struct>
 <Struct Name="WS_MailSendMessage" ClientVersion="1" OpcodeName="OP_MailSendMessageMsg">
 <Data ElementName="player_to" Type="EQ2_16Bit_String" />