浏览代码

Prototype placement of objects/items in homes

Support for object placement in player homes to support issue #124

We need to populate item_appearances with an equip_type to match the model type id for each item->spawn model

query updates required:

alter table spawn_location_placement add column instance_id int(10) unsigned not null default 0;
update commands set handler=512 where command='place_house_item';
create table spawn_instance_data(
   spawn_id int(10) unsigned not null default 0,
   spawn_location_id int(10) unsigned not null default 0,
   pickup_item_id int(10) unsigned not null default 0
);
alter table spawn add column is_instanced_spawn tinyint(3) unsigned not null default 0;
Image 4 年之前
父节点
当前提交
4d83bb766f

+ 59 - 19
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -1535,13 +1535,11 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 									 }
 		case COMMAND_SPAWN_MOVE:{
 			if(cmdTarget && cmdTarget->IsPlayer() == false){
-				PacketStruct* packet = configReader.getStruct("WS_MoveObjectMode", client->GetVersion());
-				if(packet){
 					float unknown2_3 = 0;
 					int8 placement_mode = 0;
 					client->SetSpawnPlacementMode(Client::ServerSpawnPlacementMode::DEFAULT);
-					if(sep && sep->arg[0][0]){
-						if(strcmp(sep->arg[0], "wall") == 0){
+					if (sep && sep->arg[0][0]) {
+						if (strcmp(sep->arg[0], "wall") == 0) {
 							placement_mode = 2;
 							unknown2_3 = 150;
 						}
@@ -1561,7 +1559,6 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 						{
 							if (cmdTarget->GetSpawnLocationPlacementID() < 1) {
 								client->Message(CHANNEL_COLOR_YELLOW, "[PlacementMode] Spawn %s cannot be moved it is not assigned a spawn location placement id.", cmdTarget->GetName());
-								safe_delete(packet);
 								break;
 							}
 
@@ -1572,23 +1569,12 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 
 							if (database.UpdateSpawnLocationSpawns(cmdTarget))
 								client->Message(CHANNEL_COLOR_YELLOW, "[PlacementMode] Spawn %s placed at your location.  Updated spawn_location_placement for spawn.", cmdTarget->GetName());
-							safe_delete(packet);
 							break;
 						}
+
+
+						client->SendMoveObjectMode(cmdTarget, placement_mode, unknown2_3);
 					}
-					packet->setDataByName("placement_mode", placement_mode);
-					packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(cmdTarget));
-					packet->setDataByName("model_type", cmdTarget->GetModelType());
-					packet->setDataByName("unknown", 1); //size
-					packet->setDataByName("unknown2", 1); //size 2
-					packet->setDataByName("unknown2", .5, 1); //size 3
-					packet->setDataByName("unknown2", 3, 2);
-					packet->setDataByName("unknown2", unknown2_3, 3);
-					packet->setDataByName("max_distance", 500);
-					packet->setDataByName("CoEunknown", 0xFFFFFFFF);
-					client->QueuePacket(packet->serialize());
-					safe_delete(packet);
-				}
 			}
 			else{
 				client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /spawn move (wall OR ceiling)");
@@ -2721,6 +2707,60 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 			}
 			break;
 		}
+		case COMMAND_PLACE_HOUSE_ITEM: {
+			if (sep && sep->IsNumber(0))
+			{
+				int32 uniqueid = atoi(sep->arg[0]);
+
+				Item* item = client->GetPlayer()->item_list.GetItemFromUniqueID(uniqueid);
+				//Item* item = player->GetEquipmentList()->GetItem(slot);
+				if (item && item->IsHouseItem())
+				{
+					if (!client->HasOwnerOrEditAccess())
+					{
+						client->SimpleMessage(CHANNEL_COLOR_RED, "This is not your home!");
+						break;
+					}
+					else if (!item->generic_info.appearance_id)
+					{
+						client->Message(CHANNEL_COLOR_RED, "This item has not been configured in the database, %s (%u) needs an entry where ?? has the model type id, eg. insert into item_appearances set item_id=%u,equip_type=??;", item->name.c_str(), item->details.item_id, item->details.item_id);
+						break;
+					}
+
+					if (client->GetTempPlacementSpawn())
+					{
+						Spawn* tmp = client->GetTempPlacementSpawn();
+						client->GetCurrentZone()->RemoveSpawn(false, tmp);
+						delete tmp;
+						client->SetTempPlacementSpawn(nullptr);
+					}
+
+					Spawn* spawn = new Object();
+					memset(&spawn->appearance, 0, sizeof(spawn->appearance));
+					spawn->SetID(Spawn::NextID());
+					spawn->SetX(client->GetPlayer()->GetX());
+					spawn->SetY(client->GetPlayer()->GetY());
+					spawn->SetZ(client->GetPlayer()->GetZ());
+					spawn->SetHeading(client->GetPlayer()->GetHeading());
+					spawn->SetSpawnOrigX(spawn->GetX());
+					spawn->SetSpawnOrigY(spawn->GetY());
+					spawn->SetSpawnOrigZ(spawn->GetZ());
+					spawn->SetSpawnOrigHeading(spawn->GetHeading());
+					spawn->appearance.targetable = 1;
+					spawn->appearance.race = item && item->generic_info.appearance_id ? item->generic_info.appearance_id : 1472;
+					spawn->appearance.pos.grid_id = client->GetPlayer()->appearance.pos.grid_id;
+					spawn->SetModelType(item && item->generic_info.appearance_id ? item->generic_info.appearance_id : 1472);
+					spawn->SetZone(client->GetCurrentZone());
+					client->GetCurrentZone()->SendSpawn(spawn, client);
+
+					client->SetTempPlacementSpawn(spawn);
+					client->SetPlacementUniqueItemID(uniqueid);
+
+					client->SendMoveObjectMode(spawn, 0);
+				}
+			}
+			break;
+		}
 		case COMMAND_ATTACK:
 		case COMMAND_AUTO_ATTACK:{
 			int8 type = 1;

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

@@ -874,6 +874,7 @@ private:
 #define COMMAND_CASTSPELL				509
 #define COMMAND_DISARM					510
 #define COMMAND_KNOWLEDGEWINDOWSORT		511
+#define COMMAND_PLACE_HOUSE_ITEM		512
 
 #define GET_AA_XML						751
 #define ADD_AA							752

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

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

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

@@ -1046,6 +1046,13 @@ public:
 
 	Mutex	MCommandMutex;
 	bool	has_spawn_proximities;
+
+	void	SetPickupItemID(int32 itemid)
+	{
+		pickup_item_id = itemid;
+	}
+
+	int32	GetPickupItemID() { return pickup_item_id; }
 protected:
 
 	bool	send_spawn_changes;
@@ -1071,6 +1078,7 @@ protected:
 	int32			merchant_id;
 	int8			merchant_type;
 	int32			transporter_id;
+	int32			pickup_item_id;
 	map<int32, vector<int16>* > required_quests;
 	map<int32, LUAHistory> required_history;
 	EquipmentItemList equipment_list;

+ 46 - 7
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -889,9 +889,9 @@ void WorldDatabase::LoadNPCs(ZoneServer* zone){
 													"ON npc.spawn_id = le.spawn_id\n"
 													"INNER JOIN spawn_location_placement lp\n"
 													"ON le.spawn_location_id = lp.spawn_location_id\n"
-													"WHERE lp.zone_id = %u\n"
+													"WHERE lp.zone_id = %u and (lp.instance_id = 0 or lp.instance_id = %u)\n"
 													"GROUP BY s.id",
-													zone->GetZoneID());
+													zone->GetZoneID(), zone->GetInstanceID());
 	while(result && (row = mysql_fetch_row(result))){
 		/*npc->SetAppearanceID(atoi(row[12]));
 		AppearanceData* appearance = world.GetNPCAppearance(npc->GetAppearanceID());
@@ -1212,9 +1212,9 @@ void WorldDatabase::LoadObjects(ZoneServer* zone){
 												  "ON so.spawn_id = le.spawn_id\n"
 												  "INNER JOIN spawn_location_placement lp\n"
 												  "ON le.spawn_location_id = lp.spawn_location_id\n"
-												  "WHERE lp.zone_id = %u\n"
+												  "WHERE lp.zone_id = %u and (lp.instance_id = 0 or lp.instance_id = %u)\n"
 												  "GROUP BY s.id",
-												  zone->GetZoneID());
+												  zone->GetZoneID(), zone->GetInstanceID());
 
 	while(result && (row = mysql_fetch_row(result))){
 
@@ -2862,6 +2862,7 @@ int32 WorldDatabase::ProcessSpawnLocations(ZoneServer* zone, const char* sql_que
 			spawn_location->grid_id = strtoul(row[12], NULL, 0);
 			spawn_location->placement_id = strtoul(row[13], NULL, 0);
 			spawn_location->AddSpawn(entry);
+
 		}
 		if(spawn_location){
 			zone->AddSpawnLocation(spawn_location_id, spawn_location);
@@ -3027,10 +3028,12 @@ bool WorldDatabase::SaveSpawnInfo(Spawn* spawn){
 	string last_name = getSafeEscapeString(spawn->GetLastName());
 	if(spawn->GetDatabaseID() == 0){
 
+		int8 isInstanceType = (spawn->GetZone()->GetInstanceType() == Instance_Type::PERSONAL_HOUSE_INSTANCE);
+
 		int32 new_spawn_id = GetNextSpawnIDInZone(spawn->GetZone()->GetZoneID());
 
-		query.RunQuery2(Q_INSERT, "insert into spawn (id, name, race, model_type, size, targetable, show_name, command_primary, command_secondary, visual_state, attackable, show_level, show_command_icon, display_hand_icon, faction_id, collision_radius, hp, power, prefix, suffix, last_name) values(%u, '%s', %i, %i, %i, %i, %i, %u, %u, %i, %i, %i, %i, %i, %u, %i, %u, %u, '%s', '%s', '%s')",
-			new_spawn_id, name.c_str(), spawn->GetRace(), spawn->GetModelType(), spawn->GetSize(), spawn->appearance.targetable, spawn->appearance.display_name, spawn->GetPrimaryCommandListID(), spawn->GetSecondaryCommandListID(), spawn->GetVisualState(), spawn->appearance.attackable, spawn->appearance.show_level, spawn->appearance.show_command_icon, spawn->appearance.display_hand_icon, 0, spawn->appearance.pos.collision_radius, spawn->GetTotalHP(), spawn->GetTotalPower(), prefix.c_str(), suffix.c_str(), last_name.c_str());
+		query.RunQuery2(Q_INSERT, "insert into spawn (id, name, race, model_type, size, targetable, show_name, command_primary, command_secondary, visual_state, attackable, show_level, show_command_icon, display_hand_icon, faction_id, collision_radius, hp, power, prefix, suffix, last_name, is_instanced_spawn) values(%u, '%s', %i, %i, %i, %i, %i, %u, %u, %i, %i, %i, %i, %i, %u, %i, %u, %u, '%s', '%s', '%s', %u)",
+			new_spawn_id, name.c_str(), spawn->GetRace(), spawn->GetModelType(), spawn->GetSize(), spawn->appearance.targetable, spawn->appearance.display_name, spawn->GetPrimaryCommandListID(), spawn->GetSecondaryCommandListID(), spawn->GetVisualState(), spawn->appearance.attackable, spawn->appearance.show_level, spawn->appearance.show_command_icon, spawn->appearance.display_hand_icon, 0, spawn->appearance.pos.collision_radius, spawn->GetTotalHP(), spawn->GetTotalPower(), prefix.c_str(), suffix.c_str(), last_name.c_str(), isInstanceType);
 
 		if( new_spawn_id > 0 )
 			spawn->SetDatabaseID(new_spawn_id); // use the new zone_id range
@@ -3186,7 +3189,7 @@ bool WorldDatabase::SaveSpawnEntry(Spawn* spawn, const char* spawn_location_name
 		return false;
 	}
 	if(save_zonespawn){
-		query2.RunQuery2(Q_INSERT, "insert into spawn_location_placement (zone_id, spawn_location_id, x, y, z, x_offset, y_offset, z_offset, heading, grid_id) values(%u, %u, %f, %f, %f, %f, %f, %f, %f, %u)", spawn->GetZone()->GetZoneID(), spawn->GetSpawnLocationID(), spawn->GetX(), spawn->GetY(), spawn->GetZ(),x_offset, y_offset, z_offset, spawn->GetHeading(), spawn->appearance.pos.grid_id);
+		query2.RunQuery2(Q_INSERT, "insert into spawn_location_placement (zone_id, instance_id, spawn_location_id, x, y, z, x_offset, y_offset, z_offset, heading, grid_id) values(%u, %u, %u, %f, %f, %f, %f, %f, %f, %f, %u)", spawn->GetZone()->GetZoneID(), spawn->GetZone()->GetInstanceID(), spawn->GetSpawnLocationID(), spawn->GetX(), spawn->GetY(), spawn->GetZ(),x_offset, y_offset, z_offset, spawn->GetHeading(), spawn->appearance.pos.grid_id);
 		if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF){
 			LogWrite(SPAWN__ERROR, 0, "Spawn", "Error in SaveSpawnEntry query '%s': %s", query2.GetQuery(), query2.GetError());
 			return false;
@@ -6683,4 +6686,40 @@ bool WorldDatabase::CheckExpansionFlags(ZoneServer* zone, int32 spawnXpackFlag)
 		return false;
 
 	return true;
+}
+
+void WorldDatabase::GetHouseSpawnInstanceData(ZoneServer* zone, Spawn* spawn)
+{
+	if (!spawn)
+		return;
+
+	if (zone->house_object_database_lookup.count(spawn->GetModelType()) < 1)
+		zone->house_object_database_lookup.Put(spawn->GetModelType(), spawn->GetDatabaseID());
+
+	DatabaseResult result;
+
+	database_new.Select(&result, "SELECT pickup_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));
+	}
+}
+
+int32 WorldDatabase::FindHouseInstanceSpawn(Spawn* spawn)
+{
+	DatabaseResult result;
+
+	database_new.Select(&result, "SELECT id\n"
+		" FROM spawn\n"
+		" WHERE model_type = %u limit 1",
+		spawn->GetModelType());
+
+	if (result.GetNumRows() > 0 && result.Next()) {
+		return result.GetInt32(0);
+	}
+
+	return 0;
 }

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

@@ -575,6 +575,8 @@ public:
 	void				LoadChestTraps();
 
 	bool				CheckExpansionFlags(ZoneServer* zone, int32 spawnXpackFlag);
+	void				GetHouseSpawnInstanceData(ZoneServer* zone, Spawn* spawn);
+	int32				FindHouseInstanceSpawn(Spawn* spawn);
 private:
 	DatabaseNew			database_new;
 	map<int32, string>	zone_names;

+ 197 - 10
EQ2/source/WorldServer/client.cpp

@@ -191,6 +191,9 @@ Client::Client(EQStream* ieqs) : pos_update(125), quest_pos_timer(2000), lua_deb
 	delayedAccountID = 0;
 	delayedAccessKey = 0;
 	delayTimer.Disable();
+	tempPlacementSpawn = nullptr;
+	placement_unique_item_id = 0;
+	SetHasOwnerOrEditAccess(false);
 }
 
 Client::~Client() {
@@ -233,6 +236,12 @@ Client::~Client() {
 	safe_delete(pending_last_name);
 	safe_delete_array(incoming_paperdoll.image_bytes);
 
+	if (GetTempPlacementSpawn())
+	{
+		Spawn* tmp = GetTempPlacementSpawn();
+		delete tmp;
+		SetTempPlacementSpawn(nullptr);
+	}
 	UpdateWindowTitle(0);
 }
 
@@ -1129,15 +1138,44 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 		}
 		break;
 	}
+	case OP_CancelMoveObjectModeMsg: {
+		SetSpawnPlacementMode(Client::ServerSpawnPlacementMode::DEFAULT);
+		if (GetTempPlacementSpawn())
+		{
+			Spawn* tmp = GetTempPlacementSpawn();
+			GetCurrentZone()->RemoveSpawn(false, tmp);
+			delete tmp;
+			SetTempPlacementSpawn(nullptr);
+			SetPlacementUniqueItemID(0);
+			break; // break out early if we are tied to a temp spawn
+		}
+
+		// if we are moving some other object?  other use-cases not covered
+		break;
+	}
 	case OP_PositionMoveableObject: {
 		LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_PositionMoveableObject", opcode, opcode);
 		PacketStruct* place_object = configReader.getStruct("WS_PlaceMoveableObject", GetVersion());
 		if (place_object && place_object->LoadPacketData(app->pBuffer, app->size)) {
-			Spawn* spawn = GetPlayer()->GetSpawnWithPlayerID(place_object->getType_int32_ByName("spawn_id"));
+			Spawn* spawn = 0;
+
+			if (GetTempPlacementSpawn())
+				spawn = GetTempPlacementSpawn();
+			else
+				spawn = GetPlayer()->GetSpawnWithPlayerID(place_object->getType_int32_ByName("spawn_id"));
+
 			if (!spawn) {
 				SimpleMessage(CHANNEL_COLOR_YELLOW, "Unable to find spawn.");
 				break;
 			}
+			else if (GetCurrentZone()->GetInstanceType() == PERSONAL_HOUSE_INSTANCE && !HasOwnerOrEditAccess())
+			{
+				SimpleMessage(CHANNEL_COLOR_RED, "This is not your home!");
+				break;
+			}
+
+			// handles instantiation logic + adding to zone of a new house object
+			PopulateHouseSpawn(place_object);
 
 			float newHeading = place_object->getType_float_ByName("heading") + 180;
 
@@ -1203,6 +1241,10 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 			}
 			}
 
+			PopulateHouseSpawnFinalize();
+
+			SetSpawnPlacementMode(Client::ServerSpawnPlacementMode::DEFAULT);
+
 			safe_delete(place_object);
 		}
 		break;
@@ -1439,15 +1481,19 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 			Spawn* spawn = player->GetTarget();
 			if (spawn && !spawn->IsNPC() && !spawn->IsPlayer()) {
 				string command = packet->getType_EQ2_16BitString_ByName("command").data;
-				if (EntityCommandPrecheck(spawn, command.c_str())) {
-					if (spawn->IsGroundSpawn())
-						((GroundSpawn*)spawn)->HandleUse(this, command);
-					else if (spawn->IsObject())
-						((Object*)spawn)->HandleUse(this, command);
-					else if (spawn->IsWidget())
-						((Widget*)spawn)->HandleUse(this, command);
-					else if (spawn->IsSign())
-						((Sign*)spawn)->HandleUse(this, command);
+
+				if (!HandleHouseEntityCommands(spawn, spawn_id, command))
+				{
+					if (EntityCommandPrecheck(spawn, command.c_str())) {
+						if (spawn->IsGroundSpawn())
+							((GroundSpawn*)spawn)->HandleUse(this, command);
+						else if (spawn->IsObject())
+							((Object*)spawn)->HandleUse(this, command);
+						else if (spawn->IsWidget())
+							((Widget*)spawn)->HandleUse(this, command);
+						else if (spawn->IsSign())
+							((Sign*)spawn)->HandleUse(this, command);
+					}
 				}
 			}
 			else {
@@ -8266,6 +8312,18 @@ bool Client::HandleNewLogin(int32 account_id, int32 access_code)
 				client_list.Remove(this); //remove from master client list
 				new_client_login = true;
 				GetCurrentZone()->AddClient(this); //add to zones client list
+
+				if (GetCurrentZone()->GetInstanceID())
+				{
+					PlayerHouse* ph = world.GetPlayerHouseByInstanceID(GetCurrentZone()->GetInstanceID());
+					if (ph) {
+						//HouseZone* hz = world.GetHouseZone(ph->house_id);
+						string name = string(GetPlayer()->GetName());
+						if (name.compare(ph->player_name) == 0)
+							SetHasOwnerOrEditAccess(true);
+					}
+				}
+
 				world.RejoinGroup(this);
 				zone_list.AddClientToMap(player->GetName(), this);
 			}
@@ -8519,4 +8577,133 @@ void Client::SendDefaultCommand(Spawn* spawn, const char* command, float distanc
 			safe_delete(packet);
 		}
 	}
+}
+
+bool Client::HandleHouseEntityCommands(Spawn* spawn, int32 spawnid, string command)
+{
+	if (GetCurrentZone()->GetInstanceType() != PERSONAL_HOUSE_INSTANCE)
+		return false;
+
+	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;
+}
+
+bool Client::PopulateHouseSpawn(PacketStruct* place_object)
+{
+	if (GetTempPlacementSpawn())
+	{
+		Spawn* tmp = GetTempPlacementSpawn();
+
+		int32 spawn_group_id = database.GetNextSpawnLocation();
+		tmp->SetSpawnLocationID(spawn_group_id);
+
+		float newHeading = place_object->getType_float_ByName("heading") + 180;
+
+		int32 spawnDBID = 0;
+		if (GetCurrentZone()->house_object_database_lookup.count(tmp->GetModelType()) > 0)
+		{
+			spawnDBID = GetCurrentZone()->house_object_database_lookup.Get(tmp->GetModelType());
+			tmp->SetDatabaseID(spawnDBID);
+		}
+		else
+		{
+			spawnDBID = database.FindHouseInstanceSpawn(tmp);
+			if (spawnDBID)
+			{
+				GetCurrentZone()->house_object_database_lookup.Put(tmp->GetModelType(), spawnDBID);
+				tmp->SetDatabaseID(spawnDBID);
+			}
+		}
+
+		tmp->SetX(place_object->getType_float_ByName("x"));
+		tmp->SetY(place_object->getType_float_ByName("y"));
+		tmp->SetZ(place_object->getType_float_ByName("z"));
+		tmp->SetHeading(newHeading);
+		tmp->SetSpawnOrigX(tmp->GetX());
+		tmp->SetSpawnOrigY(tmp->GetY());
+		tmp->SetSpawnOrigZ(tmp->GetZ());
+		tmp->SetSpawnOrigHeading(tmp->GetHeading());
+		database.SaveSpawnInfo(tmp);
+		database.SaveSpawnEntry(tmp, "houseplacement", 100, 0, 0, 0);
+
+		if (!spawnDBID)
+		{
+			GetCurrentZone()->house_object_database_lookup.Put(tmp->GetModelType(), tmp->GetDatabaseID());
+			GetCurrentZone()->AddObject(tmp->GetDatabaseID(), (Object*)tmp);
+		}
+
+		return true;
+	}
+
+	return false;
+}
+
+bool Client::PopulateHouseSpawnFinalize()
+{
+	if (GetTempPlacementSpawn())
+	{
+		Spawn* tmp = GetTempPlacementSpawn();
+		GetCurrentZone()->AddSpawn(tmp);
+		GetCurrentZone()->SendSpawnChanges(tmp, this);
+		SetTempPlacementSpawn(nullptr);
+		int32 uniqueID = GetPlacementUniqueItemID();
+		Item* uniqueItem = GetPlayer()->item_list.GetItemFromUniqueID(uniqueID);
+		tmp->SetPickupItemID(uniqueItem->details.item_id);
+
+		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());
+			}
+
+			database.DeleteItem(GetCharacterID(), uniqueItem, 0);
+			GetPlayer()->item_list.RemoveItem(uniqueItem, true);
+			QueuePacket(GetPlayer()->SendInventoryUpdate(GetVersion()));
+			SetPlacementUniqueItemID(0);
+		}
+		return true;
+	}
+
+	return false;
+}
+
+void Client::SendMoveObjectMode(Spawn* spawn, uint8 placementMode, float unknown2_3)
+{
+	PacketStruct* packet = configReader.getStruct("WS_MoveObjectMode", GetVersion());
+	packet->setDataByName("placement_mode", placementMode);
+	packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(spawn));
+	packet->setDataByName("model_type", spawn->GetModelType());
+	packet->setDataByName("unknown", 1); //size
+	packet->setDataByName("unknown2", 1); //size 2
+	packet->setDataByName("unknown2", .5, 1); //size 3
+	packet->setDataByName("unknown2", 3, 2);
+	packet->setDataByName("unknown2", unknown2_3, 3);
+	packet->setDataByName("max_distance", 500);
+	packet->setDataByName("CoEunknown", 0xFFFFFFFF);
+	QueuePacket(packet->serialize());
+	safe_delete(packet);
 }

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

@@ -404,6 +404,24 @@ public:
 
 	void SendHailCommand(Spawn* target);
 	void SendDefaultCommand(Spawn* spawn, const char* command, float distance);
+
+	void SetTempPlacementSpawn(Spawn* tmp) { tempPlacementSpawn = tmp; }
+	Spawn* GetTempPlacementSpawn() { return tempPlacementSpawn; }
+
+	void SetPlacementUniqueItemID(int32 id) { placement_unique_item_id = id; }
+	int32 GetPlacementUniqueItemID() { return placement_unique_item_id; }
+
+	void SetHasOwnerOrEditAccess(bool val) { hasOwnerOrEditAccess = val; }
+	bool HasOwnerOrEditAccess() { return hasOwnerOrEditAccess; }
+
+	bool HandleHouseEntityCommands(Spawn* spawn, int32 spawnid, string command);
+	// find an appropriate spawn to use for the house object, save spawn location/entry data to DB
+	bool PopulateHouseSpawn(PacketStruct* place_object);
+	
+	// finalize the spawn-in of the object in world, remove the item from player inventory, set the spawned in object item id (for future pickup)
+	bool PopulateHouseSpawnFinalize();
+
+	void SendMoveObjectMode(Spawn* spawn, uint8 placementMode, float unknown2_3=0.0f);
 private:
 	void    SavePlayerImages();
 	void	SkillChanged(Skill* skill, int16 previous_value, int16 new_value);
@@ -500,6 +518,9 @@ private:
 	int32 delayedAccountID;
 	int32 delayedAccessKey;
 	Timer delayTimer;
+	Spawn* tempPlacementSpawn;
+	int32 placement_unique_item_id;
+	bool hasOwnerOrEditAccess;
 };
 
 class ClientList {

+ 16 - 0
EQ2/source/WorldServer/zoneserver.cpp

@@ -2124,6 +2124,9 @@ Spawn* ZoneServer::ProcessSpawnLocation(SpawnLocation* spawnlocation, bool respa
 			else if (spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_SIGN)
 				spawn = AddSignSpawn(spawnlocation, spawnlocation->entities[i]);
 
+			if (GetInstanceType() == PERSONAL_HOUSE_INSTANCE)
+				database.GetHouseSpawnInstanceData(this, spawn);
+
 			if (!spawn)
 			{
 				LogWrite(ZONE__ERROR, 0, "Zone", "Error adding spawn to zone");
@@ -2185,6 +2188,9 @@ Spawn* ZoneServer::ProcessInstanceSpawnLocation(SpawnLocation* spawnlocation, ma
 				(spawnTime = database.CheckSpawnRemoveInfo(instSignSpawns,spawnlocation->entities[i]->spawn_location_id)) > 0)
 				spawn = AddSignSpawn(spawnlocation, spawnlocation->entities[i]);
 
+			if (GetInstanceType() == PERSONAL_HOUSE_INSTANCE)
+				database.GetHouseSpawnInstanceData(this, spawn);
+
 			const char* script = 0;
 
 			for(int x=0;x<3;x++)
@@ -2892,6 +2898,16 @@ void ZoneServer::AddSpawn(Spawn* spawn) {
 		((Player*)spawn)->SetReturningFromLD(false);
 	spawn_range.Trigger();
 	spawn_check_add.Trigger();
+
+	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("Pack in Moving Crate", 20, "house_spawn_pack_in_moving_crate", "", 0, 0);
+		spawn->AddSecondaryEntityCommand("Pick Up", 20, "house_spawn_pickup", "", 0, 0);
+		spawn->SetShowCommandIcon(1);
+	}
+
 	if(spawn->IsNPC())
 		AddEnemyList((NPC*)spawn);
 	if(spawn->IsPlayer() && ((Player*)spawn)->GetGroupMemberInfo())

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

@@ -618,6 +618,8 @@ public:
 	bool IsLoading() {
 		return LoadingData;
 	}
+
+	MutexMap<int32, int32>							house_object_database_lookup;						// 1st int32 = model type, 2nd int32 = spawn id
 private:
 	/* Private Functions */
 	void	AddTransporter(LocationTransportDestination* loc);