Browse Source

Additional housing features (UI items panel)

Furthering issue #124
- spawn_instance_data now tracks unique item id
- inside door widget now lists all items in the house, can move or pickup from UI
- Support for examine item by unique item id in house instance
Image 3 years ago
parent
commit
313b060328

+ 54 - 2
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -2721,9 +2721,61 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 			}
 			break;
 		}
+		case COMMAND_MOVE_ITEM:
+		{
+			int32 id = 0;
+
+			if (sep->IsNumber(0))
+				id = atoul(sep->arg[0]);
+
+			Spawn* spawn = 0;
+			if (id == 0)
+			{
+				spawn = cmdTarget;
+			}
+			else
+				spawn = client->GetCurrentZone()->GetSpawnFromUniqueItemID(id);
+
+			if (!spawn || !client->HasOwnerOrEditAccess() || !spawn->GetPickupItemID())
+				break;
+
+			client->SendMoveObjectMode(spawn, 0);
+			break;
+		}
+		case COMMAND_PICKUP:
+		{
+			int32 id = 0;
+			if (sep->IsNumber(0))
+				id = atoul(sep->arg[0]);
+
+			Spawn* spawn = 0;
+			if (id == 0)
+			{
+				spawn = cmdTarget;
+			}
+			else
+				spawn = client->GetCurrentZone()->GetSpawnFromUniqueItemID(id);
+
+			if (!spawn || !client->HasOwnerOrEditAccess() || !spawn->GetPickupItemID())
+				break;
+
+			client->AddItem(spawn->GetPickupItemID(), 1);
+
+			Query query;
+			query.RunQuery2(Q_INSERT, "delete from spawn_instance_data where spawn_id = %u and spawn_location_id = %u and pickup_item_id = %u", spawn->GetDatabaseID(), spawn->GetSpawnLocationID(), spawn->GetPickupItemID());
+
+			if (database.RemoveSpawnFromSpawnLocation(spawn)) {
+				client->GetCurrentZone()->RemoveSpawn(false, spawn, true, true);
+			}
+
+			// we had a UI Window displayed, update the house items
+			if ( id > 0 )
+				client->GetCurrentZone()->SendHouseItems(client);
+
+			break;
+		}
 		case COMMAND_HOUSE:
 		{
-			PrintSep(sep, "COMMAND_HOUSE");
 			if (sep && sep->IsNumber(0))
 			{
 				int32 unique_id = atoi(sep->arg[0]);
@@ -2745,12 +2797,12 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 					hz = world.GetHouseZone(ph->house_id);
 
 				ClientPacketFunctions::SendBaseHouseWindow(client, hz, ph, client->GetPlayer()->GetID());
+				client->GetCurrentZone()->SendHouseItems(client);
 			}
 			break;
 		}
 		case COMMAND_HOUSE_UI:
 		{
-			PrintSep(sep, "COMMAND_HOUSEUI");
 			if (sep && sep->IsNumber(0) && client->GetCurrentZone()->GetInstanceType() == Instance_Type::NONE)
 			{
 				int32 unique_id = atoi(sep->arg[0]);

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

@@ -878,6 +878,8 @@ private:
 #define COMMAND_GM						513
 #define COMMAND_HOUSE_UI				514
 #define COMMAND_HOUSE					515
+#define COMMAND_MOVE_ITEM				516
+#define COMMAND_PICKUP					517
 
 #define GET_AA_XML						751
 #define ADD_AA							752

+ 1 - 0
EQ2/source/WorldServer/Housing/HousingPackets.cpp

@@ -95,6 +95,7 @@ void ClientPacketFunctions::SendHousingList(Client* client) {
 			packet->setArrayDataByName("unknown2", 1, i);
 	}
 	client->QueuePacket(packet->serialize());
+	safe_delete(packet);
 }
 
 void ClientPacketFunctions::SendBaseHouseWindow(Client* client, HouseZone* hz, PlayerHouse* ph, int32 spawnID) {

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

@@ -105,6 +105,7 @@ Spawn::Spawn(){
 	MCommandMutex.SetName("Entity::MCommandMutex");
 	has_spawn_proximities = false;
 	pickup_item_id = 0;
+	pickup_unique_item_id = 0;
 }
 
 Spawn::~Spawn(){

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

@@ -1052,7 +1052,13 @@ public:
 		pickup_item_id = itemid;
 	}
 
+	void	SetPickupUniqueItemID(int32 uniqueid)
+	{
+		pickup_unique_item_id = uniqueid;
+	}
+
 	int32	GetPickupItemID() { return pickup_item_id; }
+	int32	GetPickupUniqueItemID() { return pickup_unique_item_id; }
 protected:
 
 	bool	send_spawn_changes;
@@ -1079,6 +1085,7 @@ protected:
 	int8			merchant_type;
 	int32			transporter_id;
 	int32			pickup_item_id;
+	int32			pickup_unique_item_id;
 	map<int32, vector<int16>* > required_quests;
 	map<int32, LUAHistory> required_history;
 	EquipmentItemList equipment_list;

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

@@ -393,6 +393,7 @@ void Widget::HandleUse(Client* client, string command, int8 overrideWidgetType){
 				ClientPacketFunctions::SendHouseVisitWindow(client, world.GetAllPlayerHousesByHouseID(m_houseID));
 
 			ClientPacketFunctions::SendBaseHouseWindow(client, hz, ph, id);
+			client->GetCurrentZone()->SendHouseItems(client);
 		}
 		else {
 			if (hz)

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

@@ -6708,13 +6708,14 @@ void WorldDatabase::GetHouseSpawnInstanceData(ZoneServer* zone, Spawn* spawn)
 
 	DatabaseResult result;
 
-	database_new.Select(&result, "SELECT pickup_item_id\n"
+	database_new.Select(&result, "SELECT pickup_item_id, pickup_unique_item_id\n"
 		" FROM spawn_instance_data\n"
 		" WHERE spawn_id = %u and spawn_location_id = %u",
 		spawn->GetDatabaseID(),spawn->GetSpawnLocationID());
 
 	if (result.GetNumRows() > 0 && result.Next()) {
 		spawn->SetPickupItemID(result.GetInt32(0));
+		spawn->SetPickupUniqueItemID(result.GetInt32(1));
 	}
 }
 

+ 26 - 25
EQ2/source/WorldServer/client.cpp

@@ -1862,7 +1862,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 
 					if (upkeep_due > (Timer::GetUnixTimeStamp() + 7257600)) // 84 days max upkeep to pay https://eq2.zam.com/wiki/Housing_%28EQ2%29#Upkeep
 					{
-						Message(CHANNEL_COLOR_YELLOW, "You cannot pay more than 1 month of upkeep.");
+						Message(CHANNEL_COLOR_YELLOW, "You cannot pay more than 3 months of upkeep.");
 						break;
 					}
 				}
@@ -2381,8 +2381,28 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
 			return;
 		}
 		request->LoadPacketData(app->pBuffer, app->size);
+
 		int32 id = request->getType_int32_ByName("id");
-		Item* item = GetPlayer()->item_list.GetItemFromUniqueID(id, true);
+
+		Item* item = 0;
+
+		// translate from unique id to spawn id for houses
+		Spawn* spawn = this->GetCurrentZone()->GetSpawnFromUniqueItemID(id);
+
+		bool wasSpawn = false;
+		if (spawn)
+		{
+			item = master_item_list.GetItem(spawn->GetPickupItemID());
+			if (item)
+			{
+				wasSpawn = true;
+				item = new Item(item);
+				item->details.unique_id = spawn->GetPickupUniqueItemID();
+			}
+		}
+
+		if (!item)
+			item = GetPlayer()->item_list.GetItemFromUniqueID(id, true);
 		if (!item)
 			item = GetPlayer()->GetEquipmentList()->GetItemFromUniqueID(id);
 		if (!item)
@@ -2392,6 +2412,8 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
 			EQ2Packet* app = item->serialize(GetVersion(), false, GetPlayer());
 			//DumpPacket(app);
 			QueuePacket(app);
+			if (wasSpawn)
+				delete item;
 		}
 		else {
 			LogWrite(WORLD__ERROR, 0, "World", "HandleExamineInfoRequest: Unknown Item ID = %u", id);
@@ -8654,28 +8676,6 @@ bool Client::HandleHouseEntityCommands(Spawn* spawn, int32 spawnid, string comma
 		}
 		return true;
 	}
-	else if (!HasOwnerOrEditAccess())
-	{
-		//SimpleMessage(CHANNEL_COLOR_RED, "This is not your home!");
-		return false;
-	}
-	else if (command == "house_spawn_move")
-	{
-		SendMoveObjectMode(spawn, 0);
-		return true;
-	}
-	else if (command == "house_spawn_pickup" && spawn->GetPickupItemID())
-	{
-		AddItem(spawn->GetPickupItemID(), 1);
-
-		Query query;
-		query.RunQuery2(Q_INSERT, "delete from spawn_instance_data where spawn_id = %u and spawn_location_id = %u and pickup_item_id = %u", spawn->GetDatabaseID(), spawn->GetSpawnLocationID(), spawn->GetPickupItemID());
-
-		if (database.RemoveSpawnFromSpawnLocation(spawn)) {
-			GetCurrentZone()->RemoveSpawn(false, spawn, true, true);
-		}
-		return true;
-	}
 
 	return false;
 }
@@ -8741,13 +8741,14 @@ bool Client::PopulateHouseSpawnFinalize()
 		int32 uniqueID = GetPlacementUniqueItemID();
 		Item* uniqueItem = GetPlayer()->item_list.GetItemFromUniqueID(uniqueID);
 		tmp->SetPickupItemID(uniqueItem->details.item_id);
+		tmp->SetPickupUniqueItemID(uniqueID);
 
 		if (uniqueItem)
 		{
 			if (GetCurrentZone()->GetInstanceType() == PERSONAL_HOUSE_INSTANCE)
 			{
 				Query query;
-				query.RunQuery2(Q_INSERT, "insert into spawn_instance_data set spawn_id = %u, spawn_location_id = %u, pickup_item_id = %u", tmp->GetDatabaseID(), tmp->GetSpawnLocationID(), tmp->GetPickupItemID());
+				query.RunQuery2(Q_INSERT, "insert into spawn_instance_data set spawn_id = %u, spawn_location_id = %u, pickup_item_id = %u, pickup_unique_item_id = %u", tmp->GetDatabaseID(), tmp->GetSpawnLocationID(), tmp->GetPickupItemID(), uniqueID);
 			}
 
 			database.DeleteItem(GetCharacterID(), uniqueItem, 0);

+ 110 - 3
EQ2/source/WorldServer/zoneserver.cpp

@@ -799,7 +799,7 @@ bool ZoneServer::AddCloseSpawnsToSpawnGroup(Spawn* spawn, float radius){
 
 void ZoneServer::RepopSpawns(Client* client, Spawn* in_spawn){
 	vector<Spawn*>* spawns = in_spawn->GetSpawnGroup();
-	PacketStruct* packet = configReader.getStruct("WS_DestroyGhostCmd", client->GetVersion());;
+	PacketStruct* packet = configReader.getStruct("WS_DestroyGhostCmd", client->GetVersion());
 	if(spawns){
 		if(!packet)
 			return;
@@ -2903,9 +2903,9 @@ void ZoneServer::AddSpawn(Spawn* spawn) {
 	if (GetInstanceType() == PERSONAL_HOUSE_INSTANCE && spawn->IsObject())
 	{
 		spawn->AddSecondaryEntityCommand("Examine", 20, "house_spawn_examine", "", 0, 0);
-		spawn->AddSecondaryEntityCommand("Move", 20, "house_spawn_move", "", 0, 0);
+		spawn->AddSecondaryEntityCommand("Move", 20, "move_item", "", 0, 0);
 		spawn->AddSecondaryEntityCommand("Pack in Moving Crate", 20, "house_spawn_pack_in_moving_crate", "", 0, 0);
-		spawn->AddSecondaryEntityCommand("Pick Up", 20, "house_spawn_pickup", "", 0, 0);
+		spawn->AddSecondaryEntityCommand("Pick Up", 20, "pickup", "", 0, 0);
 		spawn->SetShowCommandIcon(1);
 	}
 
@@ -7338,4 +7338,111 @@ void ZoneServer::SetSpawnScript(SpawnEntry* entry, Spawn* spawn)
 			break;
 		}
 	}
+}
+
+vector<HouseItem> ZoneServer::GetHouseItems(Client* client)
+{
+	if (!client->GetCurrentZone()->GetInstanceID() || !client->HasOwnerOrEditAccess())
+		return std::vector<HouseItem>();
+
+	PacketStruct* packet = configReader.getStruct("WS_HouseItemsList", client->GetVersion());
+
+	std::vector<HouseItem> items;
+	map<int32, Spawn*>::iterator itr;
+	Spawn* spawn = 0;
+	MSpawnList.readlock(__FUNCTION__, __LINE__);
+	for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
+		spawn = itr->second;
+		if (spawn && spawn->IsObject() && spawn->GetPickupItemID())
+		{
+			HouseItem tmpItem;
+			tmpItem.item_id = spawn->GetPickupItemID();
+			tmpItem.unique_id = spawn->GetPickupUniqueItemID();
+			tmpItem.spawn_id = spawn->GetID();
+			tmpItem.item = master_item_list.GetItem(spawn->GetPickupItemID());
+
+			if (!tmpItem.item)
+				continue;
+
+			items.push_back(tmpItem);
+		}
+	}
+	MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
+
+	return items;
+}
+
+void ZoneServer::SendHouseItems(Client* client)
+{
+	if (!client->GetCurrentZone()->GetInstanceID() || !client->HasOwnerOrEditAccess())
+		return;
+
+	PacketStruct* packet = configReader.getStruct("WS_HouseItemsList", client->GetVersion());
+
+	std::vector<HouseItem> items = GetHouseItems(client);
+
+	// setting this to 1 puts it on the door widget
+	packet->setDataByName("is_widget_door", 1);
+	packet->setArrayLengthByName("num_items", items.size());
+	for (int i = 0; i < items.size(); i++)
+	{
+		HouseItem tmpItem = items[i];
+		packet->setArrayDataByName("unique_id", tmpItem.unique_id, i); // unique_id is in fact the item_id...
+		packet->setArrayDataByName("item_name", tmpItem.item->name.c_str(), i);
+		packet->setArrayDataByName("status_reduction", tmpItem.item->houseitem_info->status_rent_reduction, i);
+
+		// location, 0 = floor, 1 = ceiling
+		//packet->setArrayDataByName("location", 1, i, 0);
+
+		// item_state int8
+		// 0 = normal (cannot pick up item / move item / toggle visibility)
+		// 1 = virtual (toggle visibility available, no move item)
+		// 2 = hidden (cannot pick up item / move item / toggle visibility)
+		// 3 = virtual/hidden/toggle visibility
+		// 4 = none (cannot pick up item / move item / toggle visibility)
+		// 5 = none, toggle visibility (cannot pick up item / move item)
+		// 8 = none (cannot pick up item / move item / toggle visibility)
+		//packet->setArrayDataByName("item_state", tmpvalue, i, 0);
+
+		// makes it so we don't have access to move item/retrieve item
+		// cannot use in conjunction with ui_tab_flag1/ui_tab_flag2
+		//packet->setArrayDataByName("tradeable", 1, i);
+		//packet->setArrayDataByName("item_description", "failboat", i);
+
+		// access to move item/retrieve item, do not use in conjunction with tradeable
+		packet->setArrayDataByName("ui_tab_flag1", 1, i, 0);
+		packet->setArrayDataByName("ui_tab_flag2", 1, i, 0);
+
+		// both of these can serve as description fields (only one should be used they populate the same area below the item name)
+		//packet->setArrayDataByName("first_item_description", "test", i);
+		//packet->setArrayDataByName("second_item_description", "Description here!", i);
+
+		packet->setArrayDataByName("icon", tmpItem.item->details.icon, i);
+	}
+
+	EQ2Packet* pack = packet->serialize();
+	client->QueuePacket(pack);
+	safe_delete(packet);
+}
+
+Spawn* ZoneServer::GetSpawnFromUniqueItemID(int32 unique_id)
+{
+	if (!GetInstanceID() || GetInstanceType() != Instance_Type::PERSONAL_HOUSE_INSTANCE)
+		return nullptr;
+
+	map<int32, Spawn*>::iterator itr;
+	Spawn* spawn = 0;
+	MSpawnList.readlock(__FUNCTION__, __LINE__);
+	for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
+		spawn = itr->second;
+		if (spawn && spawn->IsObject() && spawn->GetPickupUniqueItemID() == unique_id)
+		{
+			Spawn* tmpSpawn = spawn;
+			MSpawnList.releasereadlock();
+			return tmpSpawn;
+		}
+	}
+	MSpawnList.releasereadlock();
+
+	return nullptr;
 }

+ 11 - 0
EQ2/source/WorldServer/zoneserver.h

@@ -154,6 +154,13 @@ struct TrackedSpawn {
 	float distance;
 };
 
+struct HouseItem {
+	int32 spawn_id;
+	int32 item_id;
+	int32 unique_id;
+	Item* item;
+};
+
 class Widget;
 class Client;
 class Sign;
@@ -625,6 +632,10 @@ public:
 		return LoadingData;
 	}
 
+	vector<HouseItem> GetHouseItems(Client* client);
+	Spawn* GetSpawnFromUniqueItemID(int32 unique_id);
+	void SendHouseItems(Client* client);
+
 	MutexMap<int32, int32>							house_object_database_lookup;						// 1st int32 = model type, 2nd int32 = spawn id
 private:
 	/* Private Functions */

+ 24 - 0
server/WorldStructs.xml

@@ -32975,6 +32975,30 @@ to zero and treated like placeholders." />
 </Data>
 <Data ElementName="unknown7" Type="int16" />
 </Struct>
+<Struct Name="WS_HouseItemsList" ClientVersion="60114" OpcodeName="OP_HouseItemsList">
+<Data ElementName="num_items" Type="int32" />
+<Data ElementName="items_array" Type="Array" ArraySizeVariable="num_items">
+  <Data ElementName="unique_id" Type="int32" />
+  <Data ElementName="item_name" Type="EQ2_16Bit_String" />
+  <Data ElementName="status_reduction" Type="int32" />
+  <Data ElementName="unknown1" Type="int32" />
+  <Data ElementName="unknown2" Type="int32" />
+  <Data ElementName="tradeable" Type="int8" /> <!-- when 0 should must? item_description -->
+  <Data ElementName="is_notrade" Type="EQ2_16Bit_String" IfVariableNotEquals="tradeable_%i"/>
+  <Data ElementName="unknown5" Type="int8"/>
+  <Data ElementName="ui_tab_flag1" Type="int8"/>
+  <Data ElementName="first_item_description" Type="EQ2_16Bit_String" IfVariableNotSet="ui_tab_flag1_%i"/>
+  <Data ElementName="ui_tab_flag2" Type="int8"/>
+  <Data ElementName="second_item_description" Type="EQ2_16Bit_String" IfVariableNotSet="ui_tab_flag2_%i"/>
+  <Data ElementName="icon" Type="int16" />
+  <Data ElementName="location" Type="int8" />
+  <Data ElementName="item_state" Type="int8"/>
+  <Data ElementName="item_state_extended" Type="int8" size="3" /> <!-- could be more of the item_state -->
+</Data>
+<Data ElementName="unknown7" Type="int8" />
+<!-- setting to 1 causes it to populate on the items tab with the widget door aka /house command. 0 its a popup (moving crate). -->
+<Data ElementName="is_widget_door" Type="int16" />
+</Struct>
 <Struct Name="WS_HouseItemsList" ClientVersion="63119" OpcodeName="OP_HouseItemsList">
 <Data ElementName="num_items" Type="int32" />
 <Data ElementName="items_array" Type="Array" ArraySizeVariable="num_items">