Browse Source

Housing functionality (outside widget door, accessing house, pay upkeep)

In support of issue #124

- General housing fixes:
 * Pay Upkeep button is now functional for upkeep cost (player status is not implemented yet)
 * /house_ui is now functional in conjunction with this my houses list: update commands set handler=514 where command='house_ui';

- Outer door Widget fixes:
 * My Houses list is now working (lists all player houses from all zones and lets you see upkeep due or not)
 * Visitor panel will automatically populate when clicking on a door widget -- there are some circumstances that need to be addressed to clear it when accessing the house ui window outside of a door widget

- Character housing fixes:
 * Character Profile->Housing is now populated, clicking Access will allow you to access the house UI
Image 3 years ago
parent
commit
9ac85ecff8

+ 0 - 19
EQ2/source/WorldServer/ClientPacketFunctions.cpp

@@ -125,25 +125,6 @@ void ClientPacketFunctions::SendTraitList ( Client* client ){
 
 }
 
-void ClientPacketFunctions::SendClassAA( Client* client ){
-	int16 version = 1;
-	if(client)
-		version = client->GetVersion();
-
-	PacketStruct* packet = configReader.getStruct("WS_DisplayHouseStatus", version);
-	
-	packet->setDataByName("spawn_id", client->GetCharacterID());
-	packet->setDataByName("house_name", client->GetPlayer()->GetName());
-	packet->setDataByName("house_location", "Qeynos Village Inn");
-
-	packet->PrintPacket();
-	EQ2Packet* data = packet->serialize();
-	EQ2Packet* app = new EQ2Packet(OP_PlayerHouseDisplayStatusMsg, data->pBuffer, data->size);
-	safe_delete(packet);
-	safe_delete(data);
-	client->QueuePacket(app);
-}
-
 void ClientPacketFunctions::SendAbilities ( Client* client ){
 	LogWrite(MISC__TODO, 1, "TODO", " Add SendAbilities functionality\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__);
 	// this is the featherfall ability data

+ 2 - 1
EQ2/source/WorldServer/ClientPacketFunctions.h

@@ -41,7 +41,6 @@ public:
 	static void SendSkillBook ( Client* client );
 	static void SendTraitList ( Client* client );
 	static void SendAbilities ( Client* client );
-	static void SendClassAA ( Client* client );
 
 	static void SendCommandNamePacket ( Client* client );
 
@@ -79,8 +78,10 @@ public:
 
 	/* Housing (/Housing/HousingPackets.cpp) */
 	static void SendHousePurchace(Client* client, HouseZone* hz, int32 spawnID);
+	static void SendHousingList(Client* client);
 	static void SendBaseHouseWindow(Client* client, HouseZone* hz, PlayerHouse* ph, int32 spawnID);
 	static void SendHouseVisitWindow(Client* client, vector<PlayerHouse*> houses);
+	static void SendLocalizedTextMessage(Client* client);
 
 	/* Heroic OP's (/HeroicOp/HeroicOpPackets.cpp) */
 	static void SendHeroicOPUpdate(Client* client, HeroicOP* ho);

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

@@ -2721,6 +2721,22 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 			}
 			break;
 		}
+		case COMMAND_HOUSE_UI:
+		{
+			PrintSep(sep, "COMMAND_HOUSEUI");
+			if (sep && sep->IsNumber(0))
+			{
+				int32 unique_id = atoi(sep->arg[0]);
+				PlayerHouse* ph = world.GetPlayerHouseByUniqueID(unique_id);
+				HouseZone* hz = 0;
+
+				if ( ph )
+					hz = world.GetHouseZone(ph->house_id);
+				// there is a arg[1] that is true/false, but not sure what it is for investigate more later
+				ClientPacketFunctions::SendBaseHouseWindow(client, hz, ph, cmdTarget ? cmdTarget->GetID() : 0);
+			}
+			break;
+		}
 		case COMMAND_PLACE_HOUSE_ITEM: {
 			if (sep && sep->IsNumber(0))
 			{

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

@@ -876,6 +876,7 @@ private:
 #define COMMAND_KNOWLEDGEWINDOWSORT		511
 #define COMMAND_PLACE_HOUSE_ITEM		512
 #define COMMAND_GM						513
+#define COMMAND_HOUSE_UI				514
 
 #define GET_AA_XML						751
 #define ADD_AA							752

+ 7 - 0
EQ2/source/WorldServer/Housing/HousingDB.cpp

@@ -24,6 +24,13 @@ int64 WorldDatabase::AddPlayerHouse(int32 char_id, int32 house_id, int32 instanc
 	return unique_id;
 }
 
+void WorldDatabase::SetHouseUpkeepDue(int32 char_id, int32 house_id, int32 instance_id, int32 upkeep_due) {
+	Query query;
+	string update = string("UPDATE character_houses set upkeep_due=%u where char_id = %u and house_id = %u and instance_id = %u");
+	query.RunQuery2(Q_UPDATE, update.c_str(), upkeep_due, char_id, house_id, instance_id);
+}
+
+
 void WorldDatabase::RemovePlayerHouse(int32 char_id, int32 house_id) {
 }
 

+ 240 - 7
EQ2/source/WorldServer/Housing/HousingPackets.cpp

@@ -1,9 +1,11 @@
 #include "../ClientPacketFunctions.h"
 #include "../World.h"
 #include "../client.h"
+#include "../WorldDatabase.h"
 
 extern ConfigReader configReader;
 extern World world;
+extern WorldDatabase database;
 
 void ClientPacketFunctions::SendHousePurchace(Client* client, HouseZone* hz, int32 spawnID) {
 	PacketStruct* packet = configReader.getStruct("WS_PlayerHousePurchase", client->GetVersion());
@@ -64,20 +66,64 @@ void ClientPacketFunctions::SendHousePurchace(Client* client, HouseZone* hz, int
 	safe_delete(packet);
 }
 
+void ClientPacketFunctions::SendHousingList(Client* client) {
+	std::vector<PlayerHouse*> houses = world.GetAllPlayerHouses(client->GetCharacterID());
+	// this packet must be sent first otherwise it blocks out the enter house option after paying upkeep
+	PacketStruct* packet = configReader.getStruct("WS_CharacterHousingList", client->GetVersion());
+	packet->setArrayLengthByName("num_houses", houses.size());
+	for (int i = 0; i < houses.size(); i++)
+	{
+		PlayerHouse* ph = (PlayerHouse*)houses[i];
+		HouseZone* hz = world.GetHouseZone(ph->house_id);
+		string name;
+		name = ph->player_name;
+		name.append("'s ");
+		name.append(hz->name);
+		packet->setArrayDataByName("house_id", ph->unique_id, i);
+		string* zone_name = database.GetZoneName(hz->zone_id);
+		packet->setArrayDataByName("zone", zone_name->c_str(), i);
+		packet->setArrayDataByName("house_city", hz->name.c_str(), i);
+		packet->setArrayDataByName("house_address", "", i); // need this pulled from live
+		packet->setArrayDataByName("house_description", name.c_str(), i);
+		packet->setArrayDataByName("index", i + 1, i); // they send 2, 4, 6, 8 as the index ID's on the client..
+
+		// this seems to be some kind of timestamp, if we keep updating then in conjunction with upkeep_due
+		// in SendBaseHouseWindow/WS_PlayerHouseBaseScreen being a >0 number we can access 'enter house'
+		if ( client->GetVersion() >= 63119 )
+			packet->setArrayDataByName("unknown2a", ph->upkeep_due, i);
+		else
+			packet->setArrayDataByName("unknown2", ph->upkeep_due, i);
+	}
+	client->QueuePacket(packet->serialize());
+}
+
 void ClientPacketFunctions::SendBaseHouseWindow(Client* client, HouseZone* hz, PlayerHouse* ph, int32 spawnID) {
+	// if we don't send this then the enter house option won't be available if upkeep is paid
+	if (!hz || !ph)
+	{
+		client->SimpleMessage(CHANNEL_COLOR_RED, "HouseZone or PlayerHouse missing and cannot send SendBaseHouseWindow");
+		return;
+	}
+
+	string name;
+	name = ph->player_name;
+	name.append("'s ");
+	name.append(hz->name);
+
 	PacketStruct* packet = configReader.getStruct("WS_PlayerHouseBaseScreen", client->GetVersion());
 	if (packet) {
 		packet->setDataByName("house_id", ph->unique_id);
 		packet->setDataByName("spawn_id", spawnID);
-		string name;
-		name = ph->player_name;
-		name.append("'s ");
-		name.append(hz->name);
+		packet->setDataByName("character_id", client->GetPlayer()->GetCharacterID());
 		packet->setDataByName("house_name", name.c_str());
 		packet->setDataByName("zone_name", hz->name.c_str());
 		packet->setDataByName("upkeep_cost_coins", hz->upkeep_coin);
 		packet->setDataByName("upkeep_cost_status", hz->upkeep_status);
-		int32 upkeep_due = ph->upkeep_due - Timer::GetUnixTimeStamp();
+		int32 upkeep_due = 0;
+
+		if (((sint64)ph->upkeep_due - (sint64)Timer::GetUnixTimeStamp()) > 0)
+			upkeep_due = ph->upkeep_due - Timer::GetUnixTimeStamp();
+
 		packet->setDataByName("upkeep_due", upkeep_due);
 
 		packet->setDataByName("escrow_balance_coins", ph->escrow_coins);
@@ -119,7 +165,11 @@ void ClientPacketFunctions::SendHouseVisitWindow(Client* client, vector<PlayerHo
 					packet->setArrayDataByName("house_owner", ph->player_name.c_str(), i);
 					packet->setArrayDataByName("house_location", hz->name.c_str(), i);
 					packet->setArrayDataByName("house_zone", client->GetCurrentZone()->GetZoneName(), i);
-					packet->setArrayDataByName("access_level", 1, i);
+
+					if ( string(client->GetPlayer()->GetName()).compare(ph->player_name) == 0 )
+						packet->setArrayDataByName("access_level", 4, i);
+					else
+						packet->setArrayDataByName("access_level", 1, i);
 					packet->setArrayDataByName("visit_flag", 0, i); // 0 = allowed to visit, 1 = owner hasn't paid upkeep
 					i++;
 				}
@@ -145,4 +195,187 @@ void ClientPacketFunctions::SendHouseVisitWindow(Client* client, vector<PlayerHo
 <Data ElementName="unknown4" Type="int32" Size="1" />
 <Data ElementName="unknown5" Type="int8" Size="1" />
 </Struct>
-*/
+*/
+
+
+void ClientPacketFunctions::SendLocalizedTextMessage(Client* client)
+{
+	/***
+	-- OP_ReloadLocalizedTxtMsg --
+5/26/2020 19:08:41
+69.174.200.100 -> 192.168.1.1
+0000:	01 FF 63 01 62 00 00 00 1C 00 49 72 6F 6E 74 6F ..c.b.....Ironto
+0010:	65 73 20 45 61 73 74 20 4C 61 72 67 65 20 49 6E es East Large In
+0020:	6E 20 52 6F 6F 6D 07 01 00 00 00 1C 00 49 72 6F n Room.......Iro
+0030:	6E 74 6F 65 73 20 45 61 73 74 20 4C 61 72 67 65 ntoes East Large
+0040:	20 49 6E 6E 20 52 6F 6F 6D 07 02 00 00 00 1C 00  Inn Room.......
+0050:	49 72 6F 6E 74 6F 65 73 20 45 61 73 74 20 4C 61 Irontoes East La
+0060:	72 67 65 20 49 6E 6E 20 52 6F 6F 6D 07 03 00 00 rge Inn Room....
+0070:	00 1C 00 49 72 6F 6E 74 6F 65 73 20 45 61 73 74 ...Irontoes East
+0080:	20 4C 61 72 67 65 20 49 6E 6E 20 52 6F 6F 6D 07  Large Inn Room.
+0090:	04 00 00 00 1C 00 49 72 6F 6E 74 6F 65 73 20 45 ......Irontoes E
+00A0:	61 73 74 20 4C 61 72 67 65 20 49 6E 6E 20 52 6F ast Large Inn Ro
+00B0:	6F 6D 07 05 00 00 00 1C 00 49 72 6F 6E 74 6F 65 om.......Irontoe
+00C0:	73 20 45 61 73 74 20 4C 61 72 67 65 20 49 6E 6E s East Large Inn
+00D0:	20 52 6F 6F 6D 07 06 00 00 00 1C 00 49 72 6F 6E  Room.......Iron
+00E0:	74 6F 65 73 20 45 61 73 74 20 4C 61 72 67 65 20 toes East Large 
+00F0:	49 6E 6E 20 52 6F 6F 6D 07 07 00 00 00 19 00 51 Inn Room.......Q
+0100:	65 79 6E 6F 73 20 47 75 69 6C 64 20 48 61 6C 6C eynos Guild Hall
+0110:	2C 20 54 69 65 72 20 31 07 08 00 00 00 16 00 4C , Tier 1.......L
+0120:	69 6F 6E 27 73 20 4D 61 6E 65 20 53 75 69 74 65 ion's Mane Suite
+0130:	20 52 6F 6F 6D 07 09 00 00 00 16 00 4C 69 6F 6E  Room.......Lion
+0140:	27 73 20 4D 61 6E 65 20 53 75 69 74 65 20 52 6F 's Mane Suite Ro
+0150:	6F 6D 07 0A 00 00 00 16 00 4C 69 6F 6E 27 73 20 om.......Lion's 
+0160:	4D 61 6E 65 20 53 75 69 74 65 20 52 6F 6F 6D 07 Mane Suite Room.
+0170:	0B 00 00 00 16 00 4C 69 6F 6E 27 73 20 4D 61 6E ......Lion's Man
+0180:	65 20 53 75 69 74 65 20 52 6F 6F 6D 07 0C 00 00 e Suite Room....
+0190:	00 0E 00 32 20 4C 75 63 69 65 20 53 74 72 65 65 ...2 Lucie Stree
+01A0:	74 07 0D 00 00 00 0F 00 31 37 20 54 72 61 6E 71 t.......17 Tranq
+01B0:	75 69 6C 20 57 61 79 07 0E 00 00 00 0E 00 38 20 uil Way.......8 
+01C0:	4C 75 63 69 65 20 53 74 72 65 65 74 07 0F 00 00 Lucie Street....
+01D0:	00 0F 00 31 32 20 4C 75 63 69 65 20 53 74 72 65 ...12 Lucie Stre
+01E0:	65 74 07 10 00 00 00 0F 00 31 38 20 4C 75 63 69 et.......18 Luci
+01F0:	65 20 53 74 72 65 65 74 07 11 00 00 00 0F 00 32 e Street.......2
+0200:	30 20 4C 75 63 69 65 20 53 74 72 65 65 74 07 12 0 Lucie Street..
+0210:	00 00 00 0E 00 33 20 54 72 61 6E 71 75 69 6C 20 .....3 Tranquil 
+0220:	57 61 79 07 13 00 00 00 0E 00 37 20 54 72 61 6E Way.......7 Tran
+0230:	71 75 69 6C 20 57 61 79 07 14 00 00 00 0F 00 31 quil Way.......1
+0240:	33 20 54 72 61 6E 71 75 69 6C 20 57 61 79 07 15 3 Tranquil Way..
+0250:	00 00 00 0F 00 31 35 20 54 72 61 6E 71 75 69 6C .....15 Tranquil
+0260:	20 57 61 79 07 16 00 00 00 19 00 51 65 79 6E 6F  Way.......Qeyno
+0270:	73 20 47 75 69 6C 64 20 48 61 6C 6C 2C 20 54 69 s Guild Hall, Ti
+0280:	65 72 20 32 07 17 00 00 00 0F 00 38 20 45 72 6F er 2.......8 Ero
+0290:	6C 6C 69 73 69 20 4C 61 6E 65 07 18 00 00 00 0F llisi Lane......
+02A0:	00 35 20 45 72 6F 6C 6C 69 73 69 20 4C 61 6E 65 .5 Erollisi Lane
+02B0:	07 19 00 00 00 0E 00 35 20 4B 61 72 61 6E 61 20 .......5 Karana 
+02C0:	43 6F 75 72 74 07 1A 00 00 00 0D 00 32 20 42 61 Court.......2 Ba
+02D0:	79 6C 65 20 43 6F 75 72 74 07 1B 00 00 00 0D 00 yle Court.......
+02E0:	34 20 42 61 79 6C 65 20 43 6F 75 72 74 07 1C 00 4 Bayle Court...
+02F0:	00 00 16 00 4C 69 6F 6E 27 73 20 4D 61 6E 65 20 ....Lion's Mane 
+0300:	53 75 69 74 65 20 52 6F 6F 6D 07 1D 00 00 00 16 Suite Room......
+0310:	00 4C 69 6F 6E 27 73 20 4D 61 6E 65 20 53 75 69 .Lion's Mane Sui
+0320:	74 65 20 52 6F 6F 6D 07 1E 00 00 00 16 00 4C 69 te Room.......Li
+0330:	6F 6E 27 73 20 4D 61 6E 65 20 53 75 69 74 65 20 on's Mane Suite 
+0340:	52 6F 6F 6D 07 1F 00 00 00 16 00 4C 69 6F 6E 27 Room.......Lion'
+0350:	73 20 4D 61 6E 65 20 53 75 69 74 65 20 52 6F 6F s Mane Suite Roo
+0360:	6D 07 20 00 00 00 0E 00 35 20 4C 75 63 69 65 20 m. .....5 Lucie 
+0370:	53 74 72 65 65 74 07 21 00 00 00 0F 00 32 30 20 Street.!.....20 
+0380:	4B 61 72 61 6E 61 20 43 6F 75 72 74 07 22 00 00 Karana Court."..
+0390:	00 0E 00 39 20 4C 75 63 69 65 20 53 74 72 65 65 ...9 Lucie Stree
+03A0:	74 07 23 00 00 00 0F 00 31 35 20 4C 75 63 69 65 t.#.....15 Lucie
+03B0:	20 53 74 72 65 65 74 07 24 00 00 00 0F 00 31 37  Street.$.....17
+03C0:	20 4C 75 63 69 65 20 53 74 72 65 65 74 07 25 00  Lucie Street.%.
+03D0:	00 00 0F 00 32 31 20 4C 75 63 69 65 20 53 74 72 ....21 Lucie Str
+03E0:	65 65 74 07 26 00 00 00 0E 00 36 20 4B 61 72 61 eet.&.....6 Kara
+03F0:	6E 61 20 43 6F 75 72 74 07 27 00 00 00 0F 00 31 na Court.'.....1
+0400:	32 20 4B 61 72 61 6E 61 20 43 6F 75 72 74 07 28 2 Karana Court.(
+0410:	00 00 00 0F 00 31 34 20 4B 61 72 61 6E 61 20 43 .....14 Karana C
+0420:	6F 75 72 74 07 29 00 00 00 0F 00 31 38 20 4B 61 ourt.).....18 Ka
+0430:	72 61 6E 61 20 43 6F 75 72 74 07 2A 00 00 00 1E rana Court.*....
+0440:	00 43 6F 6E 63 6F 72 64 69 75 6D 20 54 6F 77 65 .Concordium Towe
+0450:	72 20 4D 61 67 69 63 61 6C 20 4D 61 6E 6F 72 07 r Magical Manor.
+0460:	2B 00 00 00 15 00 41 72 63 61 6E 65 20 41 63 61 +.....Arcane Aca
+0470:	64 65 6D 79 20 50 6F 72 74 61 6C 07 2C 00 00 00 demy Portal.,...
+0480:	13 00 43 6F 75 72 74 20 6F 66 20 74 68 65 20 4D ..Court of the M
+0490:	61 73 74 65 72 07 2D 00 00 00 13 00 43 69 74 79 aster.-.....City
+04A0:	20 6F 66 20 4D 69 73 74 20 45 73 74 61 74 65 07  of Mist Estate.
+04B0:	2E 00 00 00 10 00 44 61 72 6B 6C 69 67 68 74 20 ......Darklight 
+04C0:	50 61 6C 61 63 65 07 2F 00 00 00 11 00 44 65 65 Palace./.....Dee
+04D0:	70 77 61 74 65 72 20 52 65 74 72 65 61 74 07 30 pwater Retreat.0
+04E0:	00 00 00 24 00 44 68 61 6C 67 61 72 20 50 72 65 ...$.Dhalgar Pre
+04F0:	63 69 70 69 63 65 20 6F 66 20 74 68 65 20 44 65 cipice of the De
+0500:	65 70 20 50 6F 72 74 61 6C 07 31 00 00 00 12 00 ep Portal.1.....
+0510:	44 69 6D 65 6E 73 69 6F 6E 61 6C 20 50 6F 63 6B Dimensional Pock
+0520:	65 74 07 32 00 00 00 0B 00 44 6F 6A 6F 20 50 6F et.2.....Dojo Po
+0530:	72 74 61 6C 07 33 00 00 00 21 00 45 6C 61 62 6F rtal.3...!.Elabo
+0540:	72 61 74 65 20 45 73 74 61 74 65 20 6F 66 20 55 rate Estate of U
+0550:	6E 72 65 73 74 20 50 6F 72 74 61 6C 07 34 00 00 nrest Portal.4..
+0560:	00 11 00 45 74 68 65 72 6E 65 72 65 20 45 6E 63 ...Ethernere Enc
+0570:	6C 61 76 65 07 35 00 00 00 10 00 45 76 65 72 66 lave.5.....Everf
+0580:	72 6F 73 74 20 50 6F 72 74 61 6C 07 36 00 00 00 rost Portal.6...
+0590:	16 00 46 65 61 72 66 75 6C 20 52 65 74 72 65 61 ..Fearful Retrea
+05A0:	74 20 50 6F 72 74 61 6C 07 37 00 00 00 0F 00 46 t Portal.7.....F
+05B0:	65 6C 77 69 74 68 65 20 50 6F 72 74 61 6C 07 38 elwithe Portal.8
+05C0:	00 00 00 10 00 46 72 65 65 62 6C 6F 6F 64 20 50 .....Freeblood P
+05D0:	6F 72 74 61 6C 07 39 00 00 00 0C 00 46 72 69 67 ortal.9.....Frig
+05E0:	68 74 20 4D 61 6E 6F 72 07 3A 00 00 00 11 00 47 ht Manor.:.....G
+05F0:	61 6C 6C 65 6F 6E 20 6F 66 20 44 72 65 61 6D 73 alleon of Dreams
+0600:	07 3B 00 00 00 14 00 48 61 6C 6C 20 6F 66 20 74 .;.....Hall of t
+0610:	68 65 20 43 68 61 6D 70 69 6F 6E 07 3C 00 00 00 he Champion.<...
+0620:	10 00 48 75 61 20 4D 65 69 6E 20 52 65 74 72 65 ..Hua Mein Retre
+0630:	61 74 07 3D 00 00 00 1C 00 49 73 6C 65 20 6F 66 at.=.....Isle of
+0640:	20 52 65 66 75 67 65 20 50 72 65 73 74 69 67 65  Refuge Prestige
+0650:	20 48 6F 6D 65 07 3E 00 00 00 0F 00 4B 65 72 61  Home.>.....Kera
+0660:	66 79 72 6D 27 73 20 4C 61 69 72 07 3F 00 00 00 fyrm's Lair.?...
+0670:	0E 00 4B 72 6F 6D 7A 65 6B 20 50 6F 72 74 61 6C ..Kromzek Portal
+0680:	07 40 00 00 00 10 00 4C 61 76 61 73 74 6F 72 6D .@.....Lavastorm
+0690:	20 50 6F 72 74 61 6C 07 41 00 00 00 0E 00 4C 69  Portal.A.....Li
+06A0:	62 72 61 72 79 20 50 6F 72 74 61 6C 07 42 00 00 brary Portal.B..
+06B0:	00 0B 00 4D 61 72 61 20 45 73 74 61 74 65 07 43 ...Mara Estate.C
+06C0:	00 00 00 21 00 4D 61 6A 27 44 75 6C 20 41 73 74 ...!.Maj'Dul Ast
+06D0:	72 6F 6E 6F 6D 65 72 27 73 20 54 6F 77 65 72 20 ronomer's Tower 
+06E0:	50 6F 72 74 61 6C 07 44 00 00 00 14 00 4D 61 6A Portal.D.....Maj
+06F0:	27 44 75 6C 20 53 75 69 74 65 20 50 6F 72 74 61 'Dul Suite Porta
+0700:	6C 07 45 00 00 00 17 00 4D 69 73 74 6D 6F 6F 72 l.E.....Mistmoor
+0710:	65 20 43 72 61 67 73 20 45 73 74 61 74 65 73 07 e Crags Estates.
+0720:	46 00 00 00 0D 00 4F 61 6B 6D 79 73 74 20 47 6C F.....Oakmyst Gl
+0730:	61 64 65 07 47 00 00 00 12 00 4F 70 65 72 61 20 ade.G.....Opera 
+0740:	48 6F 75 73 65 20 50 6F 72 74 61 6C 07 48 00 00 House Portal.H..
+0750:	00 16 00 50 65 72 73 6F 6E 61 6C 20 47 72 6F 74 ...Personal Grot
+0760:	74 6F 20 50 6F 72 74 61 6C 07 49 00 00 00 17 00 to Portal.I.....
+0770:	52 75 6D 20 52 75 6E 6E 65 72 73 20 43 6F 76 65 Rum Runners Cove
+0780:	20 50 6F 72 74 61 6C 07 4A 00 00 00 12 00 50 6C  Portal.J.....Pl
+0790:	61 6E 65 74 61 72 69 75 6D 20 50 6F 72 74 61 6C anetarium Portal
+07A0:	07 4B 00 00 00 14 00 52 65 73 65 61 72 63 68 65 .K.....Researche
+07B0:	72 27 73 20 53 61 6E 63 74 75 6D 07 4C 00 00 00 r's Sanctum.L...
+07C0:	1E 00 52 65 73 69 64 65 6E 63 65 20 6F 66 20 74 ..Residence of t
+07D0:	68 65 20 42 6C 61 64 65 73 20 50 6F 72 74 61 6C he Blades Portal
+07E0:	07 4D 00 00 00 16 00 53 61 6E 63 74 75 73 20 53 .M.....Sanctus S
+07F0:	65 72 75 20 50 72 6F 6D 65 6E 61 64 65 07 4E 00 eru Promenade.N.
+0800:	00 00 22 00 53 61 6E 74 61 20 47 6C 75 67 27 73 ..".Santa Glug's
+0810:	20 43 68 65 65 72 66 75 6C 20 48 6F 6C 69 64 61  Cheerful Holida
+0820:	79 20 48 6F 6D 65 07 4F 00 00 00 17 00 53 65 63 y Home.O.....Sec
+0830:	6C 75 64 65 64 20 53 61 6E 63 74 75 6D 20 50 6F luded Sanctum Po
+0840:	72 74 61 6C 07 50 00 00 00 18 00 53 6B 79 62 6C rtal.P.....Skybl
+0850:	61 64 65 20 53 6B 69 66 66 20 4C 61 75 6E 63 68 ade Skiff Launch
+0860:	70 61 64 07 51 00 00 00 0E 00 53 6E 6F 77 79 20 pad.Q.....Snowy 
+0870:	44 77 65 6C 6C 69 6E 67 07 52 00 00 00 1D 00 53 Dwelling.R.....S
+0880:	70 72 6F 63 6B 65 74 27 73 20 49 6E 74 65 72 6C procket's Interl
+0890:	6F 63 6B 69 6E 67 20 50 6C 61 6E 65 07 53 00 00 ocking Plane.S..
+08A0:	00 17 00 53 74 6F 72 6D 20 54 6F 77 65 72 20 49 ...Storm Tower I
+08B0:	73 6C 65 20 50 6F 72 74 61 6C 07 54 00 00 00 21 sle Portal.T...!
+08C0:	00 52 65 6C 69 63 20 54 69 6E 6B 65 72 20 50 72 .Relic Tinker Pr
+08D0:	65 73 74 69 67 65 20 48 6F 6D 65 20 50 6F 72 74 estige Home Port
+08E0:	61 6C 07 55 00 00 00 10 00 54 65 6E 65 62 72 6F al.U.....Tenebro
+08F0:	75 73 20 50 6F 72 74 61 6C 07 56 00 00 00 10 00 us Portal.V.....
+0900:	54 68 65 20 42 61 75 62 62 6C 65 73 68 69 72 65 The Baubbleshire
+0910:	07 57 00 00 00 0F 00 54 69 6E 6B 65 72 65 72 27 .W.....Tinkerer'
+0920:	73 20 49 73 6C 65 07 58 00 00 00 12 00 54 6F 77 s Isle.X.....Tow
+0930:	65 72 20 6F 66 20 4B 6E 6F 77 6C 65 64 67 65 07 er of Knowledge.
+0940:	59 00 00 00 15 00 55 6E 63 61 6E 6E 79 20 45 73 Y.....Uncanny Es
+0950:	74 61 74 65 20 50 6F 72 74 61 6C 07 5A 00 00 00 tate Portal.Z...
+0960:	1E 00 56 61 63 61 6E 74 20 45 73 74 61 74 65 20 ..Vacant Estate 
+0970:	6F 66 20 55 6E 72 65 73 74 20 50 6F 72 74 61 6C of Unrest Portal
+0980:	07 5B 00 00 00 18 00 56 61 6C 65 20 6F 66 20 48 .[.....Vale of H
+0990:	61 6C 66 70 69 6E 74 20 44 65 6C 69 67 68 74 07 alfpint Delight.
+09A0:	5C 00 00 00 26 00 4C 69 6F 6E 27 73 20 4D 61 6E \...&.Lion's Man
+09B0:	65 20 56 65 73 74 69 67 65 20 52 6F 6F 6D 20 2D e Vestige Room -
+09C0:	20 4E 65 74 74 6C 65 76 69 6C 6C 65 07 5D 00 00  Nettleville.]..
+09D0:	00 2C 00 4C 69 6F 6E 27 73 20 4D 61 6E 65 20 56 .,.Lion's Mane V
+09E0:	65 73 74 69 67 65 20 52 6F 6F 6D 20 2D 20 53 74 estige Room - St
+09F0:	61 72 63 72 65 73 74 20 43 6F 6D 6D 75 6E 65 07 arcrest Commune.
+0A00:	5E 00 00 00 29 00 4C 69 6F 6E 27 73 20 4D 61 6E ^...).Lion's Man
+0A10:	65 20 56 65 73 74 69 67 65 20 52 6F 6F 6D 20 2D e Vestige Room -
+0A20:	20 47 72 61 79 73 74 6F 6E 65 20 59 61 72 64 07  Graystone Yard.
+0A30:	5F 00 00 00 2C 00 4C 69 6F 6E 27 73 20 4D 61 6E _...,.Lion's Man
+0A40:	65 20 56 65 73 74 69 67 65 20 52 6F 6F 6D 20 2D e Vestige Room -
+0A50:	20 43 61 73 74 6C 65 76 69 65 77 20 48 61 6D 6C  Castleview Haml
+0A60:	65 74 07 60 00 00 00 2A 00 4C 69 6F 6E 27 73 20 et.`...*.Lion's 
+0A70:	4D 61 6E 65 20 56 65 73 74 69 67 65 20 52 6F 6F Mane Vestige Roo
+0A80:	6D 20 2D 20 54 68 65 20 57 69 6C 6C 6F 77 20 57 m - The Willow W
+0A90:	6F 6F 64 07 61 00 00 00 2B 00 4C 69 6F 6E 27 73 ood.a...+.Lion's
+0AA0:	20 4D 61 6E 65 20 56 65 73 74 69 67 65 20 52 6F  Mane Vestige Ro
+0AB0:	6F 6D 20 2D 20 54 68 65 20 42 61 75 62 62 6C 65 om - The Baubble
+0AC0:	73 68 69 72 65 07 62 00 00 00 FF FF FF FF       shire.b.......
+*/
+}

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

@@ -388,6 +388,10 @@ void Widget::HandleUse(Client* client, string command, int8 overrideWidgetType){
 		if (hz)
 			ph = world.GetPlayerHouseByHouseID(client->GetPlayer()->GetCharacterID(), hz->id);
 		if (ph) {
+			// if we aren't in our own house we should get the full list of houses we can visit
+			if ( client->GetCurrentZone()->GetInstanceType() != Instance_Type::PERSONAL_HOUSE_INSTANCE )
+				ClientPacketFunctions::SendHouseVisitWindow(client, world.GetAllPlayerHousesByHouseID(m_houseID));
+
 			ClientPacketFunctions::SendBaseHouseWindow(client, hz, ph, id);
 		}
 		else {
@@ -402,6 +406,7 @@ void Widget::HandleUse(Client* client, string command, int8 overrideWidgetType){
 			HouseZone* hz = world.GetHouseZone(ph->house_id);
 			if (hz) {
 				int32 id = client->GetPlayer()->GetIDWithPlayerSpawn(this);
+				ClientPacketFunctions::SendHouseVisitWindow(client, world.GetAllPlayerHousesByHouseID(m_houseID));
 				ClientPacketFunctions::SendBaseHouseWindow(client, hz, ph, id);
 			}
 		}

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

@@ -500,6 +500,7 @@ public:
 	/* Housing */
 	void				LoadHouseZones();
 	int64				AddPlayerHouse(int32 char_id, int32 house_id, int32 instance_id, int32 upkeep_due);
+	void				SetHouseUpkeepDue(int32 char_id, int32 house_id, int32 instance_id, int32 upkeep_due);
 	void				RemovePlayerHouse(int32 char_id, int32 house_id);
 	void				LoadPlayerHouses();
 

+ 64 - 11
EQ2/source/WorldServer/client.cpp

@@ -733,6 +733,21 @@ void Client::SendCharInfo() {
 
 	if (!player->Alive())
 		DisplayDeadWindow();
+
+	ClientPacketFunctions::SendLocalizedTextMessage(this);
+
+	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);
+		}
+	}
+
+	ClientPacketFunctions::SendHousingList(this);
 }
 
 void Client::SendZoneSpawns() {
@@ -1781,10 +1796,19 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 			packet->LoadPacketData(app->pBuffer, app->size);
 			HouseZone* hz = world.GetHouseZone(packet->getType_int64_ByName("house_id"));
 			if (hz) {
+				std::vector<PlayerHouse*> houses = world.GetAllPlayerHouses(GetCharacterID());
+				if (houses.size() > 24)
+				{
+					SimpleMessage(CHANNEL_COLOR_YELLOW, "You already own 25 houses and may not own another.");
+					break;
+				}
 				ZoneServer* instance_zone = zone_list.GetByInstanceID(0, hz->zone_id);
 				int32 upkeep_due = Timer::GetUnixTimeStamp() + 604800; // 604800 = 7 days
 				int64 unique_id = database.AddPlayerHouse(GetPlayer()->GetCharacterID(), hz->id, instance_zone->GetInstanceID(), upkeep_due);
 				world.AddPlayerHouse(GetPlayer()->GetCharacterID(), hz->id, unique_id, instance_zone->GetInstanceID(), upkeep_due, 0, 0, GetPlayer()->GetName());
+				ClientPacketFunctions::SendHousingList(this);
+				PlayerHouse* ph = world.GetPlayerHouseByUniqueID(unique_id);
+				ClientPacketFunctions::SendBaseHouseWindow(this, hz, ph, this->GetPlayer()->GetID());
 			}
 		}
 
@@ -1816,9 +1840,49 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 		LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_PayHouseUpkeepMsg", opcode, opcode);
 		DumpPacket(app);
 		PacketStruct* packet = configReader.getStruct("WS_PayUpkeep", GetVersion());
+
 		if (packet) {
 			packet->LoadPacketData(app->pBuffer, app->size);
 
+			int64 houseID = packet->getType_int64_ByName("house_id");
+			PlayerHouse* ph = world.GetPlayerHouseByUniqueID(houseID);
+			if (ph)
+			{
+				HouseZone* hz = world.GetHouseZone(ph->house_id);
+				if (!hz)
+				{
+					Message(CHANNEL_COLOR_YELLOW, "HouseZone ID %u does NOT exist!", ph->house_id);
+					break;
+				}
+
+				int32 upkeep_due = Timer::GetUnixTimeStamp() + 604800;
+				if (((sint64)ph->upkeep_due - (sint64)Timer::GetUnixTimeStamp()) > 0)
+				{
+					upkeep_due = ph->upkeep_due + 604800; // 604800 = 7 days
+
+					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.");
+						break;
+					}
+				}
+
+				// TODO: Need support for upkeep_status, but alas status_points are not implemented!
+				if (!hz->upkeep_coin || hz->upkeep_coin && player->RemoveCoins(hz->upkeep_coin)) // TODO: Need option to take from bank if player does not have enough coin on them
+				{
+					ph->upkeep_due = upkeep_due;
+					database.SetHouseUpkeepDue(GetCharacterID(), ph->house_id, ph->instance_id, ph->upkeep_due);
+					ClientPacketFunctions::SendHousingList(this);
+					ClientPacketFunctions::SendBaseHouseWindow(this, hz, ph, this->GetPlayer()->GetID());
+					PlaySound("coin_cha_ching");
+				}
+				else
+				{
+					SimpleMessage(CHANNEL_COLOR_YELLOW, "You do not have enough money to pay for upkeep.");
+				}
+			}
+			else
+				Message(CHANNEL_COLOR_YELLOW, "PlayerHouse ID %u does NOT exist!", houseID);
 		}
 
 		safe_delete(packet);
@@ -8313,17 +8377,6 @@ bool Client::HandleNewLogin(int32 account_id, int32 access_code)
 				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);
 			}