diff --git a/bin/sql/schema/schema.sql b/bin/sql/schema/schema.sql index 185d0225..cdaa247e 100644 --- a/bin/sql/schema/schema.sql +++ b/bin/sql/schema/schema.sql @@ -404,9 +404,6 @@ CREATE TABLE `house` ( `Comment` binary(193) DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0', `HouseName` binary(23) DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0', `Endorsements` bigint(20) DEFAULT NULL, - `HousePartModels` binary(32) DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0', - `HousePartColours` binary(8) DEFAULT '\0\0\0\0\0\0\0\0', - `HouseInteriorModels` binary(40) DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0', `UPDATE_DATE` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(`HouseId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -530,13 +527,15 @@ CREATE TABLE `landset` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE `houseiteminventory` ( - `LandIdent` BIGINT(20) UNSIGNED NOT NULL, - `ContainerId` INT(10) UNSIGNED NOT NULL, - `ItemId` INT(20) NOT NULL, - `SlotId` INT(10) UNSIGNED NOT NULL, - INDEX `landIdent` (`landIdent`) + `LandIdent` BIGINT(20) UNSIGNED NOT NULL, + `ContainerId` INT(10) UNSIGNED NOT NULL, + `SlotId` INT(10) UNSIGNED NOT NULL, + `ItemId` INT(20) NOT NULL, + PRIMARY KEY (`LandIdent`, `ContainerId`, `SlotId`), + INDEX `landIdent` (`LandIdent`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; + CREATE TABLE `spawngroup` ( `id` int(10) NOT NULL AUTO_INCREMENT, `territoryTypeId` int(5) NOT NULL, @@ -564,3 +563,14 @@ CREATE TABLE `zonepositions` ( `radius` int(11) NOT NULL DEFAULT '2', PRIMARY KEY(`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; + +CREATE TABLE `landplaceditems` ( + `ItemId` INT(20) UNSIGNED NOT NULL, + `PosX` INT(10) NOT NULL, + `PosY` INT(10) NOT NULL, + `PosZ` INT(10) NOT NULL, + `Rotation` INT(10) NOT NULL, + PRIMARY KEY (`ItemId`) +) +COLLATE='latin1_swedish_ci' ENGINE=InnoDB; + diff --git a/src/api/PlayerMinimal.cpp b/src/api/PlayerMinimal.cpp index 1c12470d..c8932f51 100644 --- a/src/api/PlayerMinimal.cpp +++ b/src/api/PlayerMinimal.cpp @@ -409,6 +409,7 @@ void PlayerMinimal::insertDbGlobalItem( uint32_t itemId, uint64_t uniqueId ) con stmtItemGlobal->setInt( 1, m_id ); stmtItemGlobal->setInt64( 2, uniqueId ); stmtItemGlobal->setInt( 3, itemId ); + stmtItemGlobal->setInt( 4, 1 ); // stack of 1 g_charaDb.directExecute( stmtItemGlobal ); } diff --git a/src/common/Common.h b/src/common/Common.h index 96e46fc8..fdb52426 100644 --- a/src/common/Common.h +++ b/src/common/Common.h @@ -26,6 +26,13 @@ namespace Sapphire::Common float z; }; + struct FFXIVARR_POSITION3_U16 + { + uint16_t x; + uint16_t y; + uint16_t z; + }; + struct ActiveLand { uint8_t ward; @@ -225,13 +232,15 @@ namespace Sapphire::Common // housing interior containers HousingInteriorAppearance = 25002, - // 50 in each container max, 300 slots max + // 50 in each container max, 400 slots max HousingInteriorPlacedItems1 = 25003, HousingInteriorPlacedItems2 = 25004, HousingInteriorPlacedItems3 = 25005, HousingInteriorPlacedItems4 = 25006, HousingInteriorPlacedItems5 = 25007, HousingInteriorPlacedItems6 = 25008, + HousingInteriorPlacedItems7 = 25009, + HousingInteriorPlacedItems8 = 25010, // 50 max per container, 400 slots max // slot limit increased 'temporarily' for relocation for all estates @@ -247,9 +256,9 @@ namespace Sapphire::Common // housing exterior containers - HousingOutdoorPlacedItems = 25001, - HousingOutdoorAppearance = 25000, - HousingOutdoorStoreroom = 27000, + HousingExteriorAppearance = 25000, + HousingExteriorPlacedItems = 25001, + HousingExteriorStoreroom = 27000, }; @@ -769,8 +778,9 @@ namespace Sapphire::Common MountSkill = 0xD, }; - enum HousePartSlot + enum HouseExteriorSlot { + HousePermit, ExteriorRoof, ExteriorWall, ExteriorWindow, @@ -842,13 +852,11 @@ namespace Sapphire::Common uint32_t unkown1; //12 }; - struct YardObject + struct HousingObject { uint32_t itemId; uint16_t itemRotation; - uint16_t pos_x; - uint16_t pos_y; - uint16_t pos_z; + Common::FFXIVARR_POSITION3_U16 pos; }; enum HouseSize : uint8_t diff --git a/src/common/Database/ZoneDbConnection.cpp b/src/common/Database/ZoneDbConnection.cpp index a19f32d9..d708ecd9 100644 --- a/src/common/Database/ZoneDbConnection.cpp +++ b/src/common/Database/ZoneDbConnection.cpp @@ -172,8 +172,8 @@ void Sapphire::Db::ZoneDbConnection::doPrepareStatements() /// ITEM GLOBAL prepareStatement( CHARA_ITEMGLOBAL_INS, - "INSERT INTO charaglobalitem ( CharacterId, ItemId, catalogId, UPDATE_DATE ) VALUES ( ?, ?, ?, NOW() );", - CONNECTION_BOTH ); + "INSERT INTO charaglobalitem ( CharacterId, ItemId, catalogId, stack, UPDATE_DATE ) VALUES ( ?, ?, ?, ?, NOW() );", + CONNECTION_SYNC ); /// BNPC TEMPLATES prepareStatement( ZONE_SEL_BNPCTEMPLATES, @@ -193,7 +193,7 @@ void Sapphire::Db::ZoneDbConnection::doPrepareStatements() /// HOUSING prepareStatement( HOUSING_HOUSE_INS, - "INSERT INTO house ( LandSetId, HouseId ) VALUES ( ?, ? );", + "INSERT INTO house ( LandSetId, HouseId, HouseName ) VALUES ( ?, ?, ? );", CONNECTION_BOTH ); prepareStatement( HOUSING_HOUSE_UP, @@ -201,10 +201,13 @@ void Sapphire::Db::ZoneDbConnection::doPrepareStatements() CONNECTION_BOTH ); prepareStatement( LAND_INV_SEL_ALL, - "SELECT houseiteminventory.*, charaglobalitem.catalogId, charaglobalitem.stain, charaglobalitem.CharacterId " + "SELECT houseiteminventory.*, charaglobalitem.catalogId, charaglobalitem.stain, charaglobalitem.CharacterId, " + "landplaceditems.PosX, landplaceditems.PosY, landplaceditems.PosZ, landplaceditems.Rotation " "FROM houseiteminventory " "LEFT JOIN charaglobalitem " - "ON houseiteminventory.ItemId = charaglobalitem.itemId;", + "ON houseiteminventory.ItemId = charaglobalitem.itemId " + "LEFT JOIN landplaceditems " + "ON houseiteminventory.ItemId = landplaceditems.ItemId;", CONNECTION_BOTH ); prepareStatement( LAND_INV_SEL_HOUSE, @@ -222,6 +225,28 @@ void Sapphire::Db::ZoneDbConnection::doPrepareStatements() "ON land.HouseId = house.HouseId;", CONNECTION_SYNC ); + prepareStatement( LAND_INV_UP, + "INSERT INTO houseiteminventory ( LandIdent, ContainerId, SlotId, ItemId ) " + "VALUES ( ?, ?, ?, ? ) " + "ON DUPLICATE KEY UPDATE ItemId = ?;", + CONNECTION_BOTH ); + + prepareStatement( LAND_INV_DEL, + "DELETE FROM houseiteminventory " + "WHERE LandIdent = ? AND ContainerId = ? AND SlotId = ?;", + CONNECTION_BOTH ); + + prepareStatement( LAND_INV_UP_ITEMPOS, + "INSERT INTO landplaceditems ( ItemId, PosX, PosY, PosZ, Rotation ) " + "VALUES ( ?, ?, ?, ?, ? ) " + "ON DUPLICATE KEY UPDATE PosX = ?, PosY = ?, PosZ = ?, Rotation = ?;", + CONNECTION_BOTH ); + + prepareStatement( LAND_INV_DEL_ITEMPOS, + "DELETE FROM landplaceditems " + "WHERE ItemId = ?;", + CONNECTION_BOTH ); + /*prepareStatement( LAND_INS, "INSERT INTO land ( LandSetId ) VALUES ( ? );", CONNECTION_BOTH ); diff --git a/src/common/Database/ZoneDbConnection.h b/src/common/Database/ZoneDbConnection.h index 179993d9..fa2458e3 100644 --- a/src/common/Database/ZoneDbConnection.h +++ b/src/common/Database/ZoneDbConnection.h @@ -91,6 +91,8 @@ namespace Sapphire::Db LAND_INV_SEL_HOUSE, LAND_INV_DEL, LAND_INV_UP, + LAND_INV_UP_ITEMPOS, + LAND_INV_DEL_ITEMPOS, MAX_STATEMENTS diff --git a/src/common/Network/CommonActorControl.h b/src/common/Network/CommonActorControl.h index 0f0eda35..9c03d685 100644 --- a/src/common/Network/CommonActorControl.h +++ b/src/common/Network/CommonActorControl.h @@ -213,8 +213,34 @@ enum ActorControlType : uint16_t // Housing ShowHousingItemUI = 0x3F7, ShowBuildPresetUI = 0x3E9, + /*! + * param1 = plot id + */ + ShowEstateExternalAppearanceUI = 0x3EA, + ShowEstateInternalAppearanceUI = 0x3EB, BuildPresetResponse = 0x3ED, + /*! + * param1 = u16 landid + * u16 slotid + */ + RemoveExteriorHousingItem = 0x3EF, + + /*! + * param1 = object array index + */ + RemoveInteriorHousingItem = 0x3F1, + + /*! + * param1 = identity shit + * u16 1 - container id + * u16 2 - plot id + * param2 = item shit + * u16 1 - slot + */ + HousingItemMoveConfirm = 0x3F9, + OpenEstateSettingsUI = 0x3FF, + /*! * param1 = outdoor furnishings * u8 0 - relocation available, 1 = available @@ -306,6 +332,8 @@ enum ActorControlType : uint16_t SetEstateLightingLevel = 0x40B, // param1 = light level 0 - 5 maps to UI val 5-0 RequestHousingBuildPreset = 0x44C, + RequestEstateExteriorRemodel = 0x044D, // param11 = land id + RequestEstateInteriorRemodel = 0x44E, RequestEstateHallRemoval = 0x44F, RequestBuildPreset = 0x450, // no idea what this is, it gets sent with BuildPresetHandler and has the plot id in param1 RequestLandSignFree = 0x451, @@ -313,6 +341,7 @@ enum ActorControlType : uint16_t RequestWardLandInfo = 0x453, RequestLandRelinquish = 0x454, RequestLandInventory = 0x0458, + RequestHousingItemRemove = 0x0459, RequestEstateRename = 0x45A, RequestEstateEditGreeting = 0x45B, RequestEstateGreeting = 0x45C, // sends FFXIVIpcHousingEstateGreeting in return @@ -322,6 +351,7 @@ enum ActorControlType : uint16_t RequestHousingItemUI = 0x463, RequestSharedEstateSettings = 0x46F, UpdateEstateLightingLevel = 0x471, + HousingItemSelectedInUI = 0x47E, CompanionAction = 0x6A4, CompanionSetBarding = 0x6A5, diff --git a/src/common/Network/GamePacketNew.h b/src/common/Network/GamePacketNew.h index 7cc943c6..0bb8a078 100644 --- a/src/common/Network/GamePacketNew.h +++ b/src/common/Network/GamePacketNew.h @@ -169,7 +169,7 @@ namespace Sapphire::Network::Packets // Set the values of static fields. // The size must be the sum of the segment header and the content - m_segHdr.size = sizeof( FFXIVARR_PACKET_SEGMENT_HEADER ) + getContentSize(); + m_segHdr.size = static_cast< uint32_t >( sizeof( FFXIVARR_PACKET_SEGMENT_HEADER ) + getContentSize() ); m_segHdr.type = getSegmentType(); } diff --git a/src/common/Network/PacketContainer.cpp b/src/common/Network/PacketContainer.cpp index 02a42c93..a61c38c0 100644 --- a/src/common/Network/PacketContainer.cpp +++ b/src/common/Network/PacketContainer.cpp @@ -24,7 +24,7 @@ void Sapphire::Network::Packets::PacketContainer::addPacket( Sapphire::Network:: { m_entryList.push_back( entry ); - m_ipcHdr.size += entry->getSize(); + m_ipcHdr.size += static_cast< uint32_t >( entry->getSize() ); m_ipcHdr.count++; } diff --git a/src/common/Network/PacketDef/Ipcs.h b/src/common/Network/PacketDef/Ipcs.h index 51a4c4bf..ede6ed36 100644 --- a/src/common/Network/PacketDef/Ipcs.h +++ b/src/common/Network/PacketDef/Ipcs.h @@ -201,9 +201,10 @@ namespace Sapphire::Network::Packets HousingShowEstateGuestAccess = 0x022A, // updated 4.4 HousingObjectInitialize = 0x022C, // updated 4.4 + HousingInternalObjectSpawn = 0x22D, // updated 4.4 HousingWardInfo = 0x022F, // updated 4.4 - YardObjectMove = 0x0230, // updated 4.4 + HousingObjectMove = 0x0230, // updated 4.4 SharedEstateSettingsResponse = 0x023C, // updated 4.4 @@ -294,6 +295,8 @@ namespace Sapphire::Network::Packets InventoryModifyHandler = 0x0142, // updated 4.4 + ReqPlaceHousingItem = 0x145, // updated 4.4 + BuildPresetHandler = 0x014A, // updated 4.4 TalkEventHandler = 0x014B, // updated 4.4 EmoteEventHandler = 0x014C, // updated 4.4 @@ -311,6 +314,7 @@ namespace Sapphire::Network::Packets LandRenameHandler = 0x0171, // updated 4.4 HousingUpdateHouseGreeting = 0x0172, // updated 4.4 + HousingUpdateObjectPosition = 0x0173, // updated 4.4 SetSharedEstateSettings = 0x0177, // updated 4.4 diff --git a/src/common/Network/PacketDef/Zone/ClientZoneDef.h b/src/common/Network/PacketDef/Zone/ClientZoneDef.h index b2b33033..12b452dd 100644 --- a/src/common/Network/PacketDef/Zone/ClientZoneDef.h +++ b/src/common/Network/PacketDef/Zone/ClientZoneDef.h @@ -41,7 +41,8 @@ struct FFXIVIpcClientTrigger : /* 0004 */ uint32_t param11; /* 0008 */ uint32_t param12; /* 000C */ uint32_t param2; - /* 0010 */ char unk_10[8]; + /* 0010 */ uint32_t param4; // todo: really? + /* 0014 */ uint32_t param5; /* 0018 */ uint64_t param3; }; @@ -240,6 +241,35 @@ struct FFXIVIpcMarketBoardRequestItemListings : /* 0004 */ uint32_t padding; }; +struct FFXIVIpcReqPlaceHousingItem : + FFXIVIpcBasePacket< ReqPlaceHousingItem > +{ + /* 0000 */ uint16_t landId; // 0 when plot 0 or inside an estate + /* 0002 */ uint16_t unknown1; + /* 0004 */ uint32_t unknown2; + /* 0008 */ uint16_t sourceInvContainerId; + /* 000A */ uint16_t sourceInvSlotId; + + /* 000C */ Common::FFXIVARR_POSITION3 position; + /* 0018 */ float rotation; + + /* 001C */ uint32_t unknown3; // always 1? + /* 0020 */ uint32_t unknown4[2]; // always 0 it looks like +}; + +struct FFXIVIpcHousingUpdateObjectPosition : + FFXIVIpcBasePacket< HousingUpdateObjectPosition > +{ + /* 0000 */ Common::LandIdent ident; + /* 0008 */ uint16_t slot; + /* 000A */ uint16_t unk; + + /* 000C */ Common::FFXIVARR_POSITION3 pos; + /* 0018 */ float rotation; + + /* 001C */ uint32_t padding; +}; + } } } diff --git a/src/common/Network/PacketDef/Zone/ServerZoneDef.h b/src/common/Network/PacketDef/Zone/ServerZoneDef.h index 8c7768a9..40cd48a3 100644 --- a/src/common/Network/PacketDef/Zone/ServerZoneDef.h +++ b/src/common/Network/PacketDef/Zone/ServerZoneDef.h @@ -1679,26 +1679,20 @@ struct FFXIVIpcLandSetInitialize : FFXIVIpcBasePacket< LandSetInitialize > LandStruct land[ 30 ]; }; -struct FFXIVIpcYardObjectSpawn : FFXIVIpcBasePacket +struct FFXIVIpcYardObjectSpawn : FFXIVIpcBasePacket< YardObjectSpawn > { - uint8_t landSetId; + uint8_t landId; uint8_t objectArray; uint16_t unknown1; - uint32_t itemId; - uint16_t itemRotation; - uint16_t pos_x; - uint16_t pos_y; - uint16_t pos_z; + Common::HousingObject object; }; -struct FFXIVIpcYardObjectMove : FFXIVIpcBasePacket +struct FFXIVIpcHousingObjectMove : FFXIVIpcBasePacket< HousingObjectMove > { uint16_t itemRotation; uint8_t objectArray; - uint8_t landSetId; - uint16_t pos_x; - uint16_t pos_y; - uint16_t pos_z; + uint8_t landId; + Common::FFXIVARR_POSITION3_U16 pos; uint16_t unknown1; uint16_t unknown2; uint16_t unknown3; @@ -1711,10 +1705,23 @@ struct FFXIVIpcHousingObjectInitialize : FFXIVIpcBasePacket< HousingObjectInitia uint8_t packetNum; uint8_t packetTotal; uint8_t u2; //Outdoor 0 / Indoor 100(?) - Common::YardObject object[100]; + Common::HousingObject object[100]; uint32_t unknown4; //unused }; +struct FFXIVIpcHousingInternalObjectSpawn : FFXIVIpcBasePacket< HousingInternalObjectSpawn > +{ + uint16_t containerId; + uint8_t containerOffset; + uint8_t pad1; + + uint16_t itemId; + uint8_t unk2; + uint8_t pad2; + uint16_t rotation; + Common::FFXIVARR_POSITION3_U16 pos; +}; + struct FFXIVIpcHousingIndoorInitialize : FFXIVIpcBasePacket< HousingIndoorInitialize > { uint16_t u1; diff --git a/src/world/Actor/Player.h b/src/world/Actor/Player.h index b0c8b1cf..2289d197 100644 --- a/src/world/Actor/Player.h +++ b/src/world/Actor/Player.h @@ -365,6 +365,10 @@ namespace Sapphire::Entity Common::GearModelSlot equipSlotToModelSlot( Common::GearSetSlot slot ); + bool getFreeInventoryContainerSlot( Inventory::InventoryContainerPair& containerPair ) const; + + void insertInventoryItem( Common::InventoryType type, uint16_t slot, const Sapphire::ItemPtr item ); + /*! * Collect real item handins from container * @param itemIds a vector of each catalog id to collect @@ -919,6 +923,8 @@ namespace Sapphire::Entity void setActiveLand( uint8_t land, uint8_t ward ); Common::ActiveLand getActiveLand() const; + Sapphire::ItemPtr dropInventoryItem( Common::InventoryType type, uint16_t slotId ); + ////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/world/Actor/PlayerInventory.cpp b/src/world/Actor/PlayerInventory.cpp index 67545381..21547da4 100644 --- a/src/world/Actor/PlayerInventory.cpp +++ b/src/world/Actor/PlayerInventory.cpp @@ -754,7 +754,7 @@ void Sapphire::Entity::Player::swapItem( uint16_t fromInventoryId, uint8_t fromS void Sapphire::Entity::Player::discardItem( uint16_t fromInventoryId, uint8_t fromSlotId ) { // i am not entirely sure how this should be generated or if it even is important for us... - uint32_t transactionId = 1; + uint32_t transactionId = getNextInventorySequence(); auto fromItem = m_storageMap[ fromInventoryId ]->getItem( fromSlotId ); @@ -869,3 +869,70 @@ uint32_t Sapphire::Entity::Player::getNextInventorySequence() { return m_inventorySequence++; } + +Sapphire::ItemPtr Sapphire::Entity::Player::dropInventoryItem( Sapphire::Common::InventoryType type, uint16_t slotId ) +{ + auto& container = m_storageMap[ type ]; + + auto item = container->getItem( slotId ); + if( !item ) + return nullptr; + + // unlink item + container->removeItem( slotId ); + updateContainer( type, slotId, nullptr ); + + auto seq = getNextInventorySequence(); + + // send inv update + auto invTransPacket = makeZonePacket< FFXIVIpcInventoryTransaction >( getId() ); + invTransPacket->data().transactionId = seq; + invTransPacket->data().ownerId = getId(); + invTransPacket->data().storageId = type; + invTransPacket->data().catalogId = item->getId(); + invTransPacket->data().stackSize = item->getStackSize(); + invTransPacket->data().slotId = slotId; + invTransPacket->data().type = 7; + queuePacket( invTransPacket ); + + auto invTransFinPacket = makeZonePacket< FFXIVIpcInventoryTransactionFinish >( getId() ); + invTransFinPacket->data().transactionId = seq; + invTransFinPacket->data().transactionId1 = seq; + queuePacket( invTransFinPacket ); + + return item; +} + +bool Sapphire::Entity::Player::getFreeInventoryContainerSlot( Inventory::InventoryContainerPair& containerPair ) const +{ + for( auto bagId : { Bag0, Bag1, Bag2, Bag3 } ) + { + auto needle = m_storageMap.find( bagId ); + if( needle == m_storageMap.end() ) + continue; + + auto& container = needle->second; + + for( uint16_t idx = 0; idx < container->getMaxSize(); idx++ ) + { + auto item = container->getItem( idx ); + if( !item ) + { + containerPair = std::make_pair( bagId, idx ); + return true; + } + } + } + + return false; +} + +void Sapphire::Entity::Player::insertInventoryItem( Sapphire::Common::InventoryType type, uint16_t slot, + const Sapphire::ItemPtr item ) +{ + updateContainer( type, slot, item ); + + auto slotUpdate = std::make_shared< UpdateInventorySlotPacket >( getId(), slot, type, *item ); + queuePacket( slotUpdate ); + +} \ No newline at end of file diff --git a/src/world/Actor/PlayerSql.cpp b/src/world/Actor/PlayerSql.cpp index 136ddd40..4f834d95 100644 --- a/src/world/Actor/PlayerSql.cpp +++ b/src/world/Actor/PlayerSql.cpp @@ -84,6 +84,17 @@ bool Sapphire::Entity::Player::load( uint32_t charId, SessionPtr pSession ) pCurrZone = pTeriMgr->getZoneByTerritoryTypeId( zoneId ); } } + else if( pTeriMgr->isInternalEstateTerritory( zoneId ) ) + { + // todo: this needs to go to the area just outside of the plot door + pCurrZone = pTeriMgr->getZoneByLandSetId( m_prevTerritoryId ); + + zoneId = m_prevTerritoryTypeId; + m_pos.x = m_prevPos.x; + m_pos.y = m_prevPos.y; + m_pos.z = m_prevPos.z; + setRot( m_prevRot ); + } else if( pTeriMgr->isHousingTerritory( zoneId ) ) { pCurrZone = pTeriMgr->getZoneByLandSetId( m_territoryId ); @@ -580,10 +591,7 @@ Sapphire::ItemPtr Sapphire::Entity::Player::createItem( uint32_t catalogId, uint uint8_t flags = 0; - ItemPtr pItem = make_Item( Items::Util::getNextUId(), - catalogId, - itemInfo->modelMain, - itemInfo->modelSub ); + ItemPtr pItem = make_Item( Items::Util::getNextUId(), catalogId ); pItem->setStackSize( quantity ); diff --git a/src/world/ForwardsZone.h b/src/world/ForwardsZone.h index 585eb46c..6d689826 100644 --- a/src/world/ForwardsZone.h +++ b/src/world/ForwardsZone.h @@ -35,6 +35,13 @@ namespace World::Territory::Housing TYPE_FORWARD( HousingInteriorTerritory ); } +namespace Inventory +{ +using InventoryContainerPair = std::pair< Common::InventoryType, uint8_t >; +using InventoryTypeList = std::vector< Common::InventoryType >; +TYPE_FORWARD( HousingItem ); +} + namespace World::Manager { TYPE_FORWARD( HousingMgr ); diff --git a/src/world/Inventory/HousingItem.cpp b/src/world/Inventory/HousingItem.cpp new file mode 100644 index 00000000..15352dab --- /dev/null +++ b/src/world/Inventory/HousingItem.cpp @@ -0,0 +1,29 @@ +#include "HousingItem.h" + +Sapphire::Inventory::HousingItem::HousingItem( uint64_t uId, uint32_t catalogId ) : + Sapphire::Item( uId, catalogId, false ) +{ + m_stackSize = 1; + m_spiritBond = 1; + m_reservedFlag = 1092616192; // wat? +} + +uint16_t Sapphire::Inventory::HousingItem::getRot() const +{ + return m_rotation; +} + +void Sapphire::Inventory::HousingItem::setRot( uint16_t rot ) +{ + m_rotation = rot; +} + +Sapphire::Common::FFXIVARR_POSITION3_U16 Sapphire::Inventory::HousingItem::getPos() const +{ + return m_position; +} + +void Sapphire::Inventory::HousingItem::setPos( Sapphire::Common::FFXIVARR_POSITION3_U16 pos ) +{ + m_position = pos; +} \ No newline at end of file diff --git a/src/world/Inventory/HousingItem.h b/src/world/Inventory/HousingItem.h new file mode 100644 index 00000000..d4fbe368 --- /dev/null +++ b/src/world/Inventory/HousingItem.h @@ -0,0 +1,26 @@ +#ifndef SAPPHIRE_HOUSINGITEM_H +#define SAPPHIRE_HOUSINGITEM_H + +#include "Item.h" + +namespace Sapphire::Inventory +{ + class HousingItem : public Item + { + public: + HousingItem( uint64_t uId, uint32_t catalogId ); + virtual ~HousingItem() = default; + + void setRot( uint16_t rot ); + uint16_t getRot() const; + + void setPos( Common::FFXIVARR_POSITION3_U16 pos ); + Common::FFXIVARR_POSITION3_U16 getPos() const; + + private: + Common::FFXIVARR_POSITION3_U16 m_position; + uint16_t m_rotation; + }; +} + +#endif //SAPPHIRE_HOUSINGITEM_H diff --git a/src/world/Inventory/Item.cpp b/src/world/Inventory/Item.cpp index 5421f355..d8af0f17 100644 --- a/src/world/Inventory/Item.cpp +++ b/src/world/Inventory/Item.cpp @@ -7,14 +7,14 @@ extern Sapphire::Framework g_fw; -Sapphire::Item::Item( uint64_t uId, uint32_t catalogId, uint64_t model1, uint64_t model2, bool isHq ) : +Sapphire::Item::Item( uint64_t uId, uint32_t catalogId, bool isHq ) : m_id( catalogId ), m_uId( uId ), - m_model1( model1 ), - m_model2( model2 ), m_isHq( isHq ), m_stain( 0 ), - m_durability( 30000 ) + m_durability( 30000 ), + m_spiritBond( 0 ), + m_reservedFlag( 0 ) { auto pExdData = g_fw.get< Data::ExdDataGenerated >(); auto itemInfo = pExdData->get< Sapphire::Data::Item >( catalogId ); @@ -22,11 +22,14 @@ Sapphire::Item::Item( uint64_t uId, uint32_t catalogId, uint64_t model1, uint64_ m_delayMs = itemInfo->delayms; m_physicalDmg = itemInfo->damagePhys; m_magicalDmg = itemInfo->damageMag; + m_model1 = itemInfo->modelMain; + m_model2 = itemInfo->modelSub; m_weaponDmg = ( m_physicalDmg != 0 ) ? m_physicalDmg : m_magicalDmg; m_autoAttackDmg = static_cast< float >( m_weaponDmg * m_delayMs ) / 3000; m_category = static_cast< Common::ItemUICategory >( itemInfo->itemUICategory ); m_itemLevel = itemInfo->levelItem; m_maxStackSize = itemInfo->stackSize; + m_additionalData = itemInfo->additionalData; } float Sapphire::Item::getAutoAttackDmg() const @@ -154,3 +157,28 @@ void Sapphire::Item::setStain( uint16_t stain ) { m_stain = stain; } + +uint32_t Sapphire::Item::getAdditionalData() const +{ + return m_additionalData; +} + +uint16_t Sapphire::Item::getSpiritbond() const +{ + return m_spiritBond; +} + +void Sapphire::Item::setSpiritbond( uint16_t spiritbond ) +{ + m_spiritBond = spiritbond; +} + +uint32_t Sapphire::Item::getReservedFlag() const +{ + return m_reservedFlag; +} + +void Sapphire::Item::setReservedFlag( uint32_t flag ) +{ + m_reservedFlag = flag; +} \ No newline at end of file diff --git a/src/world/Inventory/Item.h b/src/world/Inventory/Item.h index e5babd3d..34fa02f7 100644 --- a/src/world/Inventory/Item.h +++ b/src/world/Inventory/Item.h @@ -10,9 +10,9 @@ namespace Sapphire { public: - Item( uint64_t uId, uint32_t catalogId, uint64_t model1, uint64_t model2, bool isHq = false ); + Item( uint64_t uId, uint32_t catalogId, bool isHq = false ); - ~Item() = default; + virtual ~Item() = default; uint32_t getId() const; @@ -62,6 +62,14 @@ namespace Sapphire uint16_t getStain() const; void setStain( uint16_t stain ); + uint32_t getAdditionalData() const; + + void setSpiritbond( uint16_t spiritbond ); + uint16_t getSpiritbond() const; + + void setReservedFlag( uint32_t flag ); + uint32_t getReservedFlag() const; + protected: uint32_t m_id; @@ -87,6 +95,10 @@ namespace Sapphire uint16_t m_itemLevel; uint16_t m_durability; uint16_t m_stain; + uint16_t m_spiritBond; + uint32_t m_reservedFlag; + + uint32_t m_additionalData; }; diff --git a/src/world/Inventory/ItemContainer.cpp b/src/world/Inventory/ItemContainer.cpp index 3328a4f9..53b8a02e 100644 --- a/src/world/Inventory/ItemContainer.cpp +++ b/src/world/Inventory/ItemContainer.cpp @@ -12,12 +12,12 @@ extern Sapphire::Framework g_fw; Sapphire::ItemContainer::ItemContainer( uint16_t storageId, uint16_t maxSize, const std::string& tableName, - bool isMultiStorage, bool isPersistentStorage ) : + bool isMultiStorage, bool removeItemOnContainerRemoval ) : m_id( storageId ), m_size( maxSize ), m_tableName( tableName ), m_bMultiStorage( isMultiStorage ), - m_isPersistentStorage( isPersistentStorage ) + m_removeItemOnContainerRemove( removeItemOnContainerRemoval ) { } @@ -45,7 +45,7 @@ void Sapphire::ItemContainer::removeItem( uint16_t slotId ) if( it != m_itemMap.end() ) { - if( m_isPersistentStorage ) + if( m_removeItemOnContainerRemove ) pDb->execute( "DELETE FROM charaglobalitem WHERE itemId = " + std::to_string( it->second->getUId() ) ); m_itemMap.erase( it ); @@ -118,7 +118,7 @@ bool Sapphire::ItemContainer::isMultiStorage() const bool Sapphire::ItemContainer::isPersistentStorage() const { - return m_isPersistentStorage; + return m_removeItemOnContainerRemove; } diff --git a/src/world/Inventory/ItemContainer.h b/src/world/Inventory/ItemContainer.h index 295fa51a..321077c0 100644 --- a/src/world/Inventory/ItemContainer.h +++ b/src/world/Inventory/ItemContainer.h @@ -15,7 +15,7 @@ namespace Sapphire public: ItemContainer( uint16_t storageId, uint16_t maxSize, const std::string& tableName, bool isMultiStorage, - bool isPersistentStorage = true ); + bool removeItemOnContainerRemoval = true ); ~ItemContainer(); @@ -48,7 +48,7 @@ namespace Sapphire uint16_t m_size; std::string m_tableName; bool m_bMultiStorage; - bool m_isPersistentStorage; + bool m_removeItemOnContainerRemove; ItemMap m_itemMap; Entity::PlayerPtr m_pOwner; }; diff --git a/src/world/Inventory/ItemUtil.cpp b/src/world/Inventory/ItemUtil.cpp index bb036828..6d896b9c 100644 --- a/src/world/Inventory/ItemUtil.cpp +++ b/src/world/Inventory/ItemUtil.cpp @@ -132,11 +132,7 @@ Sapphire::ItemPtr Sapphire::Items::Util::loadItem( uint64_t uId ) auto itemInfo = pExdData->get< Sapphire::Data::Item >( itemRes->getUInt( 1 ) ); bool isHq = itemRes->getUInt( 3 ) == 1; - ItemPtr pItem = make_Item( uId, - itemRes->getUInt( 1 ), - itemInfo->modelMain, - itemInfo->modelSub, - isHq ); + ItemPtr pItem = make_Item( uId, itemRes->getUInt( 1 ), isHq ); pItem->setStackSize( itemRes->getUInt( 2 ) ); diff --git a/src/world/Manager/HousingMgr.cpp b/src/world/Manager/HousingMgr.cpp index 50138589..f9a91042 100644 --- a/src/world/Manager/HousingMgr.cpp +++ b/src/world/Manager/HousingMgr.cpp @@ -25,8 +25,9 @@ #include "ServerMgr.h" #include "Territory/House.h" #include "InventoryMgr.h" -#include "Inventory/Item.h" +#include "Inventory/HousingItem.h" #include "Inventory/ItemContainer.h" +#include "Util/UtilMath.h" using namespace Sapphire::Common; using namespace Sapphire::Network; @@ -35,7 +36,42 @@ using namespace Sapphire::Network::Packets::Server; extern Sapphire::Framework g_fw; -Sapphire::World::Manager::HousingMgr::HousingMgr() = default; +Sapphire::World::Manager::HousingMgr::HousingMgr() +{ + m_containerMap[ 0 ] = std::make_pair( InventoryType::HousingInteriorPlacedItems1, InventoryType::HousingInteriorStoreroom1 ); + m_containerMap[ 1 ] = std::make_pair( InventoryType::HousingInteriorPlacedItems2, InventoryType::HousingInteriorStoreroom2 ); + m_containerMap[ 2 ] = std::make_pair( InventoryType::HousingInteriorPlacedItems3, InventoryType::HousingInteriorStoreroom3 ); + m_containerMap[ 3 ] = std::make_pair( InventoryType::HousingInteriorPlacedItems4, InventoryType::HousingInteriorStoreroom4 ); + m_containerMap[ 4 ] = std::make_pair( InventoryType::HousingInteriorPlacedItems5, InventoryType::HousingInteriorStoreroom5 ); + m_containerMap[ 5 ] = std::make_pair( InventoryType::HousingInteriorPlacedItems6, InventoryType::HousingInteriorStoreroom6 ); + m_containerMap[ 6 ] = std::make_pair( InventoryType::HousingInteriorPlacedItems7, InventoryType::HousingInteriorStoreroom7 ); + m_containerMap[ 7 ] = std::make_pair( InventoryType::HousingInteriorPlacedItems8, InventoryType::HousingInteriorStoreroom8 ); + + m_internalPlacedItemContainers = + { + InventoryType::HousingInteriorPlacedItems1, + InventoryType::HousingInteriorPlacedItems2, + InventoryType::HousingInteriorPlacedItems3, + InventoryType::HousingInteriorPlacedItems4, + InventoryType::HousingInteriorPlacedItems5, + InventoryType::HousingInteriorPlacedItems6, + InventoryType::HousingInteriorPlacedItems7, + InventoryType::HousingInteriorPlacedItems8, + }; + + m_internalStoreroomContainers = + { + InventoryType::HousingInteriorStoreroom1, + InventoryType::HousingInteriorStoreroom2, + InventoryType::HousingInteriorStoreroom3, + InventoryType::HousingInteriorStoreroom4, + InventoryType::HousingInteriorStoreroom5, + InventoryType::HousingInteriorStoreroom6, + InventoryType::HousingInteriorStoreroom7, + InventoryType::HousingInteriorStoreroom8, + }; +} + Sapphire::World::Manager::HousingMgr::~HousingMgr() = default; bool Sapphire::World::Manager::HousingMgr::init() @@ -48,7 +84,7 @@ bool Sapphire::World::Manager::HousingMgr::init() // 18 wards per territory, 4 territories m_landCache.reserve( 18 * 4 ); - loadLandCache(); + initLandCache(); log->debug( "HousingMgr: Checking land counts" ); @@ -91,33 +127,38 @@ bool Sapphire::World::Manager::HousingMgr::loadEstateInventories() while( res->next() ) { //uint64_t uId, uint32_t catalogId, uint64_t model1, uint64_t model2, bool isHq - uint64_t ident = res->getUInt64( "LandIdent" ); - uint16_t containerId = res->getUInt16( "ContainerId" ); - uint64_t itemId = res->getUInt64( "ItemId" ); - uint16_t slot = res->getUInt16( "SlotId" ); - uint32_t catalogId = res->getUInt( "catalogId" ); - uint8_t stain = res->getUInt8( "stain" ); - uint64_t characterId = res->getUInt64( "CharacterId" ); + auto ident = res->getUInt64( "LandIdent" ); + auto containerId = res->getUInt16( "ContainerId" ); + auto itemId = res->getUInt64( "ItemId" ); + auto slot = res->getUInt16( "SlotId" ); + auto catalogId = res->getUInt( "catalogId" ); + auto stain = res->getUInt8( "stain" ); + auto characterId = res->getUInt64( "CharacterId" ); - auto item = make_Item( itemId, catalogId, 0, 0, 0 ); + auto item = Inventory::make_HousingItem( itemId, catalogId ); item->setStain( stain ); + item->setStackSize( 1 ); // todo: need to set the owner character id on the item + // set world pos on item if its in an placed item container + if( isPlacedItemsInventory( static_cast< Common::InventoryType >( containerId ) ) ) + { + item->setPos( { + res->getUInt16( "PosX" ), + res->getUInt16( "PosY" ), + res->getUInt16( "PosZ" ) + } ); + + item->setRot( res->getUInt16( "Rotation" ) ); + } + ContainerIdToContainerMap& estateInv = m_estateInventories[ ident ]; - // check if containerId exists + // check if containerId exists, it always should - if it doesn't, something went wrong auto container = estateInv.find( containerId ); - if( container == estateInv.end() ) - { - // create container - // todo: how to handle this max slot stuff? override it on land init? - auto ic = make_ItemContainer( containerId, 400, "houseiteminventory", true ); - ic->setItem( slot, item ); + assert( container != estateInv.end() ); - estateInv[ containerId ] = ic; - } - else - container->second->setItem( slot, item ); + container->second->setItem( slot, item ); itemCount++; } @@ -127,8 +168,9 @@ bool Sapphire::World::Manager::HousingMgr::loadEstateInventories() return true; } -void Sapphire::World::Manager::HousingMgr::loadLandCache() +void Sapphire::World::Manager::HousingMgr::initLandCache() { + auto log = g_fw.get< Sapphire::Logger >(); auto pDb = g_fw.get< Db::DbWorkerPool< Db::ZoneDbConnection > >(); auto stmt = pDb->getPreparedStatement( Db::LAND_SEL_ALL ); @@ -140,7 +182,7 @@ void Sapphire::World::Manager::HousingMgr::loadLandCache() // land stuff entry.m_landSetId = res->getUInt64( "LandSetId" ); - entry.m_landId = res->getUInt64( "LandId" ); + entry.m_landId = res->getUInt( "LandId" ); entry.m_type = static_cast< Common::LandType >( res->getUInt( "Type" ) ); entry.m_size = res->getUInt8( "Size" ); @@ -159,9 +201,85 @@ void Sapphire::World::Manager::HousingMgr::loadLandCache() entry.m_endorsements = res->getUInt64( "Endorsements" ); m_landCache[ entry.m_landSetId ].push_back( entry ); + + uint16_t maxExternalItems = 0; + uint16_t maxInternalItems = 0; + + // init inventory containers + switch( entry.m_size ) + { + case HouseSize::Cottage: + entry.m_maxPlacedExternalItems = 20; + entry.m_maxPlacedInternalItems = 200; + break; + case HouseSize::House: + entry.m_maxPlacedExternalItems = 30; + entry.m_maxPlacedInternalItems = 300; + break; + case HouseSize::Mansion: + entry.m_maxPlacedExternalItems = 40; + entry.m_maxPlacedInternalItems = 400; + break; + default: + // this should never ever happen, if it does the db is fucked + log->error( "HousingMgr: Plot " + std::to_string( entry.m_landId ) + " in landset " + std::to_string( entry.m_landSetId ) + + " has an invalid land size, defaulting to cottage." ); + entry.m_maxPlacedExternalItems = 20; + entry.m_maxPlacedInternalItems = 200; + break; + } + + // setup containers + // todo: this is pretty garbage + Common::LandIdent ident; + ident.territoryTypeId = entry.m_landSetId >> 16; + ident.wardNum = entry.m_landSetId & 0xFFFF; + ident.landId = entry.m_landId; + ident.worldId = 67; + + auto& containers = getEstateInventory( ident ); + + auto makeContainer = [ &containers ]( Common::InventoryType type, uint16_t size ) + { + containers[ type ] = make_ItemContainer( type, size, "houseiteminventory", false, false ); + }; + + uint16_t count = 0; + for( int i = 0; i < 8; ++i ) + { + if( count > entry.m_maxPlacedInternalItems ) + break; + + auto& pair = m_containerMap[ i ]; + + makeContainer( pair.first, 50 ); + makeContainer( pair.second, 50 ); + + count += 50; + } + + // exterior + makeContainer( InventoryType::HousingExteriorPlacedItems, entry.m_maxPlacedExternalItems ); + makeContainer( InventoryType::HousingExteriorStoreroom, entry.m_maxPlacedExternalItems ); + + // fixed containers + makeContainer( InventoryType::HousingInteriorAppearance, 10 ); + makeContainer( InventoryType::HousingExteriorAppearance, 9 ); + } } +uint64_t Sapphire::World::Manager::HousingMgr::getNextHouseId() +{ + auto pDb = g_fw.get< Db::DbWorkerPool< Db::ZoneDbConnection > >(); + auto pQR = pDb->query( "SELECT MAX( HouseId ) FROM house" ); + + if( !pQR->next() ) + return 0; + + return pQR->getUInt64( 1 ) + 1; +} + uint32_t Sapphire::World::Manager::HousingMgr::toLandSetId( uint16_t territoryTypeId, uint8_t wardId ) const { return ( static_cast< uint32_t >( territoryTypeId ) << 16 ) | wardId; @@ -430,6 +548,104 @@ void Sapphire::World::Manager::HousingMgr::sendEstateGreeting( Entity::Player& p player.queuePacket( greetingPacket ); } +bool Sapphire::World::Manager::HousingMgr::initHouseModels( Entity::Player& player, LandPtr land, uint32_t presetCatalogId ) +{ + auto house = land->getHouse(); + assert( house ); + + auto itemMax = land->getInventoryItemMax(); + + // type, maxSize, tableName, isMultiStorage + auto intContainer = make_ItemContainer( InventoryType::HousingInteriorAppearance, itemMax.second, "houseiteminventory", true ); + auto extContainer = make_ItemContainer( InventoryType::HousingExteriorAppearance, itemMax.first, "houseiteminventory", true ); + + // add containers to inv collection + auto& houseInventory = getEstateInventory( house->getLandIdent() ); + houseInventory[ InventoryType::HousingInteriorAppearance ] = intContainer; + houseInventory[ InventoryType::HousingExteriorAppearance ] = extContainer; + + auto exdData = g_fw.get< Sapphire::Data::ExdDataGenerated >(); + auto preset = exdData->get< Sapphire::Data::HousingPreset >( getItemAdditionalData( presetCatalogId ) ); + if( !preset ) + return false; + + // high iq shit + auto invMap = std::map< uint16_t, std::map< uint32_t, int32_t > > + { + // external + { + InventoryType::HousingExteriorAppearance, + { + { HouseExteriorSlot::ExteriorRoof, preset->exteriorRoof }, + { HouseExteriorSlot::ExteriorWall, preset->exteriorWall }, + { HouseExteriorSlot::ExteriorWindow, preset->exteriorWindow }, + { HouseExteriorSlot::ExteriorDoor, preset->exteriorDoor } + } + }, + + // internal + { + InventoryType::HousingInteriorAppearance, + { + // lobby/middle floor + { HousingInteriorSlot::InteriorWall, preset->interiorWall }, + { HousingInteriorSlot::InteriorFloor, preset->interiorFlooring }, + { HousingInteriorSlot::InteriorLight, preset->interiorLighting }, + + // attic + { HousingInteriorSlot::InteriorWall_Attic, preset->otherFloorWall }, + { HousingInteriorSlot::InteriorFloor_Attic, preset->otherFloorFlooring }, + { HousingInteriorSlot::InteriorLight_Attic, preset->otherFloorLighting }, + + // basement + { HousingInteriorSlot::InteriorWall_Basement, preset->basementWall }, + { HousingInteriorSlot::InteriorFloor_Basement, preset->basementFlooring }, + { HousingInteriorSlot::InteriorLight_Basement, preset->basementLighting }, + } + } + }; + + auto invMgr = g_fw.get< InventoryMgr >(); + + // create and link items + for( auto& destContainer : invMap ) + { + auto container = houseInventory[ destContainer.first ]; + + for( auto& item : destContainer.second ) + { + // small houses attic is just 0, ignore them + if( item.second == 0 ) + continue; + + auto pItem = invMgr->createItem( player, item.second ); + + container->setItem( item.first, pItem ); + } + + invMgr->saveHousingContainer( land->getLandIdent(), container ); + } + + // lift off + updateHouseModels( house ); + + return true; +} + +void Sapphire::World::Manager::HousingMgr::createHouse( Sapphire::HousePtr house ) const +{ + auto pDb = g_fw.get< Db::DbWorkerPool< Db::ZoneDbConnection > >(); + + auto stmt = pDb->getPreparedStatement( Db::HOUSING_HOUSE_INS ); + // LandSetId, HouseId, HouseName + + stmt->setUInt( 1, house->getLandSetId() ); + stmt->setUInt( 2, house->getId() ); + stmt->setString( 3, house->getHouseName() ); + + pDb->execute( stmt ); +} + void Sapphire::World::Manager::HousingMgr::buildPresetEstate( Entity::Player& player, uint8_t plotNum, uint32_t presetItem ) { auto hZone = std::dynamic_pointer_cast< HousingZone >( player.getCurrentZone() ); @@ -447,9 +663,19 @@ void Sapphire::World::Manager::HousingMgr::buildPresetEstate( Entity::Player& pl // todo: check if permit is in inventory and remove one - if( !pLand->setPreset( presetItem ) ) + // create house + auto ident = pLand->getLandIdent(); + auto house = make_House( getNextHouseId(), pLand->getLandSetId(), ident, + "Estate #" + std::to_string( ident.landId + 1 ), "" ); + + pLand->setHouse( house ); + + // create inventory items + if( !initHouseModels( player, pLand, presetItem ) ) return; + createHouse( house ); + pLand->setState( HouseState::privateHouse ); pLand->setLandType( LandType::Private ); hZone->sendLandUpdate( plotNum ); @@ -465,10 +691,10 @@ void Sapphire::World::Manager::HousingMgr::buildPresetEstate( Entity::Player& pl player.eventStart( player.getId(), 0x000B0095, Event::EventHandler::EventType::Housing, 1, 1 ); player.playScene( 0x000B0095, 0, SET_BASE | HIDE_HOTBAR , 0, 1, plotNum, nullptr ); - player.setLandFlags( LandFlagsSlot::Private, EstateBuilt, pLand->getLandIdent() ); + player.setLandFlags( LandFlagsSlot::Private, EstateBuilt, ident ); player.sendLandFlagsSlot( LandFlagsSlot::Private ); - hZone->registerHouseEntranceEObj( plotNum ); + hZone->registerEstateEntranceEObj( plotNum ); } void Sapphire::World::Manager::HousingMgr::requestEstateRename( Entity::Player& player, const Common::LandIdent ident ) @@ -598,7 +824,7 @@ void Sapphire::World::Manager::HousingMgr::sendEstateInventory( Entity::Player& if( !internalZone ) return; - auto ident = internalZone->getIdent(); + auto ident = internalZone->getLandIdent(); auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum ); auto exteriorZone = getHousingZoneByLandSetId( landSetId ); @@ -624,12 +850,13 @@ void Sapphire::World::Manager::HousingMgr::sendEstateInventory( Entity::Player& if( targetLand->getOwnerId() != player.getId() ) return; - auto container = getEstateInventory( targetLand->getLandIdent() )[ inventoryType ]; - if( !container ) + auto& containers = getEstateInventory( targetLand->getLandIdent() ); + auto needle = containers.find( inventoryType ); + if( needle == containers.end() ) return; auto invMgr = g_fw.get< Manager::InventoryMgr >(); - invMgr->sendInventoryContainer( player, container ); + invMgr->sendInventoryContainer( player, needle->second ); } const Sapphire::World::Manager::HousingMgr::LandSetLandCacheMap& @@ -647,11 +874,7 @@ Sapphire::World::Manager::HousingMgr::LandIdentToInventoryContainerMap& Sapphire::World::Manager::HousingMgr::ContainerIdToContainerMap& Sapphire::World::Manager::HousingMgr::getEstateInventory( uint64_t ident ) { - auto map = m_estateInventories.find( ident ); - - assert( map != m_estateInventories.end() ); - - return map->second; + return m_estateInventories[ ident ]; } Sapphire::World::Manager::HousingMgr::ContainerIdToContainerMap& @@ -666,30 +889,650 @@ void Sapphire::World::Manager::HousingMgr::updateHouseModels( Sapphire::HousePtr { assert( house ); - auto getItemData = []( uint32_t itemId ) - { - auto pExdData = g_fw.get< Data::ExdDataGenerated >(); - auto info = pExdData->get< Sapphire::Data::Item >( itemId ); - return info->additionalData; - }; + auto& containers = getEstateInventory( house->getLandIdent() ); - auto containers = getEstateInventory( house->getLandIdent() ); - - auto extContainer = containers.find( static_cast< uint16_t >( InventoryType::HousingOutdoorAppearance ) ); + auto extContainer = containers.find( static_cast< uint16_t >( InventoryType::HousingExteriorAppearance ) ); if( extContainer != containers.end() ) { for( auto& item : extContainer->second->getItemMap() ) { - house->setHousePart( static_cast< Common::HousePartSlot >( item.first ), getItemData( item.second->getId() ) ); + // in the Slot array, the first slot is actually the permit + // but the models array starts from the 2nd entry of the enum + // so we skip the first one, and then any subsequent entries is slotid - 1 + + auto slotId = item.first - 1; + if( slotId < 0 ) + continue; + + house->setExteriorModel( static_cast< Common::HouseExteriorSlot >( slotId ), + getItemAdditionalData( item.second->getId() ), item.second->getStain() ); } } + else + { + g_fw.get< Logger >()->error( "Plot " + std::to_string( house->getLandIdent().landId ) + " has an invalid inventory configuration for outdoor appearance." ); + } auto intContainer = containers.find( static_cast< uint16_t >( InventoryType::HousingInteriorAppearance ) ); if( intContainer != containers.end() ) { for( auto& item : intContainer->second->getItemMap() ) { - house->setHouseInteriorPart( static_cast< Common::HousingInteriorSlot >( item.first ), getItemData( item.second->getId() ) ); + house->setInteriorModel( static_cast< Common::HousingInteriorSlot >( item.first ), + getItemAdditionalData( item.second->getId() ) ); } } + else + { + g_fw.get< Logger >()->error( "Plot " + std::to_string( house->getLandIdent().landId ) + " has an invalid inventory configuration for indoor appearance." ); + } } + +uint32_t Sapphire::World::Manager::HousingMgr::getItemAdditionalData( uint32_t catalogId ) +{ + auto pExdData = g_fw.get< Data::ExdDataGenerated >(); + auto info = pExdData->get< Sapphire::Data::Item >( catalogId ); + return info->additionalData; +} + +bool Sapphire::World::Manager::HousingMgr::isPlacedItemsInventory( Sapphire::Common::InventoryType type ) +{ + return type == InventoryType::HousingExteriorPlacedItems || + type == InventoryType::HousingInteriorPlacedItems1 || + type == InventoryType::HousingInteriorPlacedItems2 || + type == InventoryType::HousingInteriorPlacedItems3 || + type == InventoryType::HousingInteriorPlacedItems4 || + type == InventoryType::HousingInteriorPlacedItems5 || + type == InventoryType::HousingInteriorPlacedItems6; +} + +void Sapphire::World::Manager::HousingMgr::reqPlaceHousingItem( Sapphire::Entity::Player& player, uint16_t landId, + uint16_t containerId, uint16_t slotId, + Sapphire::Common::FFXIVARR_POSITION3 pos, + float rotation ) +{ + // retail process is: + // - unlink item from current container + // - add it to destination container + // - resend container + // - send spawn packet + // - send actrl 3f3, all params are 0 + + LandPtr land; + bool isOutside = false; + + // inside housing territory + if( auto zone = std::dynamic_pointer_cast< HousingZone >( player.getCurrentZone() ) ) + { + land = zone->getLand( landId ); + + isOutside = true; + } + // otherwise, inside a house. landId is 0 when inside a plot + else if( auto zone = std::dynamic_pointer_cast< Territory::Housing::HousingInteriorTerritory >( player.getCurrentZone() ) ) + { + // todo: this whole process is retarded and needs to be fixed + // perhaps maintain a list of estates by ident inside housingmgr? + auto ident = zone->getLandIdent(); + auto landSet = toLandSetId( ident.territoryTypeId, ident.wardNum ); + + land = getHousingZoneByLandSetId( landSet )->getLand( ident.landId ); + } + // wtf? + else + return; + + if( !land ) + return; + + // todo: add proper permissions checks + if( land->getOwnerId() != player.getId() ) + return; + + // todo: check item position and make sure it's not outside the plot + // retail uses a radius based check + + // unlink item + Inventory::HousingItemPtr item; + + if( containerId == InventoryType::Bag0 || + containerId == InventoryType::Bag1 || + containerId == InventoryType::Bag2 || + containerId == InventoryType::Bag3 ) + { + auto tmpItem = player.dropInventoryItem( static_cast< Common::InventoryType >( containerId ), slotId ); + + item = Inventory::make_HousingItem( tmpItem->getUId(), tmpItem->getId() ); + + // set params + item->setPos( { + Util::floatToUInt16( pos.x ), + Util::floatToUInt16( pos.y ), + Util::floatToUInt16( pos.z ) + } ); + + item->setRot( Util::floatToUInt16Rot( rotation ) ); + } + else + { + player.sendUrgent( "The inventory you are using to place an item is not supported." ); + return; + } + + auto ident = land->getLandIdent(); + + bool status = false; + + if( isOutside ) + status = placeExternalItem( player, item, ident ); + else + status = placeInteriorItem( player, item ); + + if( status ) + player.queuePacket( Server::makeActorControl143( player.getId(), 0x3f3 ) ); + else + player.sendUrgent( "An internal error occurred when placing the item." ); +} + +bool Sapphire::World::Manager::HousingMgr::placeExternalItem( Entity::Player& player, + Inventory::HousingItemPtr item, + Common::LandIdent ident ) +{ + auto invMgr = g_fw.get< InventoryMgr >(); + + auto& container = getEstateInventory( ident )[ InventoryType::HousingExteriorPlacedItems ]; + + auto freeSlot = container->getFreeSlot(); + + // todo: what happens when this fails? at the moment the player will just lose the item + if( freeSlot == -1 ) + return false; + + // add item to inv + container->setItem( freeSlot, item ); + + // we need to save the item again as removing it from the container on the player will remove it from charaglobalitem + // todo: this needs to be handled a bit better as it might be possible to overwrite another item that is created in the meantime + invMgr->saveItem( player, item ); + + invMgr->sendInventoryContainer( player, container ); + invMgr->saveHousingContainer( ident, container ); + invMgr->updateHousingItemPosition( item ); + + // add to zone and spawn + auto zone = std::dynamic_pointer_cast< HousingZone >( player.getCurrentZone() ); + assert( zone ); + + zone->spawnYardObject( ident.landId, freeSlot, *item ); + + return true; +} + +bool Sapphire::World::Manager::HousingMgr::placeInteriorItem( Entity::Player& player, + Inventory::HousingItemPtr item ) +{ + auto invMgr = g_fw.get< InventoryMgr >(); + + auto zone = std::dynamic_pointer_cast< Territory::Housing::HousingInteriorTerritory >( player.getCurrentZone() ); + assert( zone ); + + auto ident = zone->getLandIdent(); + + auto& containers = getEstateInventory( ident ); + + // find first free container + uint8_t containerIdx = 0; + for( auto containerId : m_internalPlacedItemContainers ) + { + auto needle = containers.find( containerId ); + if( needle == containers.end() ) + continue; + + auto container = needle->second; + auto freeSlot = container->getFreeSlot(); + if( freeSlot == -1 ) + { + containerIdx++; + continue; + } + + // have a free slot + container->setItem( freeSlot, item ); + + // todo: see comment above in placeExternalItem where the same func is called + invMgr->saveItem( player, item ); + + // resend container + invMgr->sendInventoryContainer( player, container ); + invMgr->saveHousingContainer( ident, container ); + invMgr->updateHousingItemPosition( item ); + + auto zone = std::dynamic_pointer_cast< Territory::Housing::HousingInteriorTerritory >( player.getCurrentZone() ); + assert( zone ); + + zone->spawnHousingObject( containerIdx, freeSlot, containerId, item ); + + return true; + } + + return false; +} + +Sapphire::Common::HousingObject Sapphire::World::Manager::HousingMgr::getYardObjectForItem( Inventory::HousingItemPtr item ) const +{ + Sapphire::Common::HousingObject obj {}; + + obj.pos = item->getPos(); + obj.itemRotation = item->getRot(); + obj.itemId = item->getAdditionalData(); + + return obj; +} + +void Sapphire::World::Manager::HousingMgr::sendInternalEstateInventoryBatch( Sapphire::Entity::Player& player, + bool storeroom ) +{ + auto zone = std::dynamic_pointer_cast< Territory::Housing::HousingInteriorTerritory >( player.getCurrentZone() ); + if( !zone ) + return; + + // todo: perms check + + Inventory::InventoryTypeList containerIds; + + if( storeroom ) + containerIds = m_internalStoreroomContainers; + else + containerIds = m_internalPlacedItemContainers; + + auto invMgr = g_fw.get< Manager::InventoryMgr >(); + auto& containers = getEstateInventory( zone->getLandIdent() ); + + for( auto containerId : containerIds ) + { + auto container = containers.find( containerId ); + if( container == containers.end() ) + break; + + invMgr->sendInventoryContainer( player, container->second ); + } +} + +void Sapphire::World::Manager::HousingMgr::reqMoveHousingItem( Entity::Player& player, + Common::LandIdent ident, uint16_t slot, + Common::FFXIVARR_POSITION3 pos, float rot ) +{ + auto landSet = toLandSetId( ident.territoryTypeId, ident.wardNum ); + auto land = getHousingZoneByLandSetId( landSet )->getLand( ident.landId ); + + if( !land ) + return; + + // todo: proper perms checks + if( land->getOwnerId() != player.getId() ) + return; + + // todo: what happens when either of these fail? how does the server let the client know that the moment failed + // as is, if it does fail, the client will be locked and unable to move any item until reentering the territory + if( auto terri = std::dynamic_pointer_cast< Territory::Housing::HousingInteriorTerritory >( player.getCurrentZone() ) ) + { + moveInternalItem( player, ident, *terri, slot, pos, rot ); + } + else if( auto terri = std::dynamic_pointer_cast< HousingZone >( player.getCurrentZone() ) ) + { + moveExternalItem( player, ident, slot, *terri, pos, rot ); + } +} + +bool Sapphire::World::Manager::HousingMgr::moveInternalItem( Entity::Player& player, Common::LandIdent ident, + Territory::Housing::HousingInteriorTerritory& terri, uint16_t slot, + Common::FFXIVARR_POSITION3 pos, float rot ) +{ + auto containerIdx = static_cast< uint16_t >( slot / 50 ); + auto slotIdx = slot % 50; + + uint16_t containerId = 0; + try + { + containerId = m_internalPlacedItemContainers.at( containerIdx ); + } + catch( const std::out_of_range& ex ) + { + return false; + } + + auto& containers = getEstateInventory( ident ); + + auto needle = containers.find( containerId ); + if( needle == containers.end() ) + return false; + + auto container = needle->second; + + auto item = std::dynamic_pointer_cast< Inventory::HousingItem >( container->getItem( slotIdx ) ); + if( !item ) + return false; + + item->setPos( { + Util::floatToUInt16( pos.x ), + Util::floatToUInt16( pos.y ), + Util::floatToUInt16( pos.z ) + } ); + + item->setRot( Util::floatToUInt16Rot( rot ) ); + + // save + auto invMgr = g_fw.get< InventoryMgr >(); + invMgr->updateHousingItemPosition( item ); + + terri.updateHousingObjectPosition( player, slot, item->getPos(), item->getRot() ); + + // send confirmation to player + uint32_t param1 = ( ident.landId << 16 ) | containerId; + + player.queuePacket( Server::makeActorControl143( player.getId(), ActorControl::HousingItemMoveConfirm, param1, slotIdx ) ); + + // todo: update it for other players?? + + return true; +} + +bool Sapphire::World::Manager::HousingMgr::moveExternalItem( Entity::Player& player, + Common::LandIdent ident, uint16_t slot, + Sapphire::HousingZone& terri, Common::FFXIVARR_POSITION3 pos, + float rot ) +{ + auto land = terri.getLand( ident.landId ); + + // todo: add proper perms check + if( land->getOwnerId() != player.getId() ) + return false; + + auto& containers = getEstateInventory( ident ); + auto needle = containers.find( InventoryType::HousingExteriorPlacedItems ); + if( needle == containers.end() ) + return false; + + auto container = needle->second; + + auto item = std::dynamic_pointer_cast< Inventory::HousingItem >( container->getItem( slot ) ); + if( !item ) + return false; + + item->setPos( { + Util::floatToUInt16( pos.x ), + Util::floatToUInt16( pos.y ), + Util::floatToUInt16( pos.z ) + } ); + + item->setRot( Util::floatToUInt16Rot( rot ) ); + + auto invMgr = g_fw.get< InventoryMgr >(); + invMgr->updateHousingItemPosition( item ); + + terri.updateYardObjectPos( player, slot, ident.landId, *item ); + + uint32_t param1 = ( ident.landId << 16 ) | InventoryType::HousingExteriorPlacedItems; + player.queuePacket( Server::makeActorControl143( player.getId(), ActorControl::HousingItemMoveConfirm, param1, slot ) ); + + + return true; +} + +void Sapphire::World::Manager::HousingMgr::reqRemoveHousingItem( Sapphire::Entity::Player& player, uint16_t plot, + uint16_t containerId, uint16_t slot, + bool sendToStoreroom ) +{ + if( auto terri = std::dynamic_pointer_cast< Territory::Housing::HousingInteriorTerritory >( player.getCurrentZone() ) ) + { + auto ident = terri->getLandIdent(); + auto landSet = toLandSetId( ident.territoryTypeId, ident.wardNum ); + auto land = getHousingZoneByLandSetId( landSet )->getLand( ident.landId ); + + if( !land ) + return; + + // todo: proper perms checks + if( land->getOwnerId() != player.getId() ) + return; + + removeInternalItem( player, *terri, containerId, slot, sendToStoreroom ); + } + else if( auto terri = std::dynamic_pointer_cast< HousingZone >( player.getCurrentZone() ) ) + { + auto land = terri->getLand( plot ); + if( !land ) + return; + + if( land->getOwnerId() != player.getId() ) + return; + + auto containerType = static_cast< Common::InventoryType >( containerId ); + + removeExternalItem( player, *terri, *land, containerType, slot, sendToStoreroom ); + } +} + +bool Sapphire::World::Manager::HousingMgr::removeInternalItem( Entity::Player& player, + Territory::Housing::HousingInteriorTerritory& terri, + uint16_t containerId, uint16_t slotId, + bool sendToStoreroom ) +{ + auto& containers = getEstateInventory( terri.getLandIdent() ); + + // validate the container id first + // we also need the idx of the container so we can get the slot offset + bool foundContainer = false; + uint8_t containerIdx = 0; + for( auto cId : m_internalPlacedItemContainers ) + { + if( containerId == cId ) + { + foundContainer = true; + + break; + } + + containerIdx++; + } + + if( !foundContainer ) + return false; + + auto needle = containers.find( containerId ); + if( needle == containers.end() ) + return false; + + auto container = needle->second; + + auto item = std::dynamic_pointer_cast< Inventory::HousingItem >( container->getItem( slotId ) ); + if( !item ) + return false; + + if( !sendToStoreroom ) + { + // make sure the player has a free inv slot first + Inventory::InventoryContainerPair containerPair; + if( !player.getFreeInventoryContainerSlot( containerPair ) ) + return false; + + auto invMgr = g_fw.get< InventoryMgr >(); + + // remove it from housing inventory + container->removeItem( slotId ); + invMgr->sendInventoryContainer( player, container ); + invMgr->removeHousingItemPosition( *item ); + invMgr->removeItemFromHousingContainer( terri.getLandIdent(), containerId, slotId ); + + // add to player inv + player.insertInventoryItem( containerPair.first, containerPair.second, item ); + + // todo: set item as bound/unsellable/untradable + } + else + { + ItemContainerPtr freeContainer; + Inventory::InventoryContainerPair freeSlotPair; + freeContainer = getFreeEstateInventorySlot( terri.getLandIdent(), freeSlotPair, m_internalStoreroomContainers ); + + if( !freeContainer ) + return false; + + auto invMgr = g_fw.get< InventoryMgr >(); + + container->removeItem( slotId ); + invMgr->sendInventoryContainer( player, container ); + invMgr->removeHousingItemPosition( *item ); + invMgr->removeItemFromHousingContainer( terri.getLandIdent(), containerId, slotId ); + + freeContainer->setItem( slotId, item ); + invMgr->sendInventoryContainer( player, freeContainer ); + invMgr->saveHousingContainer( terri.getLandIdent(), freeContainer ); + } + + // despawn + auto arraySlot = ( containerIdx * 50 ) + slotId; + terri.removeHousingObject( arraySlot ); + + return true; +} + +bool Sapphire::World::Manager::HousingMgr::removeExternalItem( Entity::Player& player, HousingZone& terri, Land& land, + Common::InventoryType containerType, uint16_t slotId, + bool sendToStoreroom ) +{ + auto& containers = getEstateInventory( land.getLandIdent() ); + + auto needle = containers.find( containerType ); + if( needle == containers.end() ) + return false; + + auto& sourceContainer = needle->second; + + auto item = std::dynamic_pointer_cast< Inventory::HousingItem >( sourceContainer->getItem( slotId ) ); + if( !item ) + return false; + + auto invMgr = g_fw.get< InventoryMgr >(); + + if( sendToStoreroom ) + { + auto& storeroomContainer = containers[ InventoryType::HousingExteriorStoreroom ]; + auto freeSlot = storeroomContainer->getFreeSlot(); + + if( freeSlot == -1 ) + return false; + + sourceContainer->removeItem( slotId ); + invMgr->sendInventoryContainer( player, sourceContainer ); + invMgr->removeHousingItemPosition( *item ); + invMgr->removeItemFromHousingContainer( land.getLandIdent(), sourceContainer->getId(), slotId ); + + storeroomContainer->setItem( freeSlot, item ); + invMgr->sendInventoryContainer( player, storeroomContainer ); + invMgr->saveHousingContainer( land.getLandIdent(), storeroomContainer ); + } + else + { + Inventory::InventoryContainerPair containerPair; + if( !player.getFreeInventoryContainerSlot( containerPair ) ) + return false; + + // remove from housing inv + sourceContainer->removeItem( slotId ); + invMgr->sendInventoryContainer( player, sourceContainer ); + invMgr->removeHousingItemPosition( *item ); + invMgr->removeItemFromHousingContainer( land.getLandIdent(), sourceContainer->getId(), slotId ); + + // add to player inv + player.insertInventoryItem( containerPair.first, containerPair.second, item ); + } + + terri.despawnYardObject( land.getLandIdent().landId, slotId ); + + return true; +} + +Sapphire::ItemContainerPtr Sapphire::World::Manager::HousingMgr::getFreeEstateInventorySlot( Common::LandIdent ident, + Inventory::InventoryContainerPair& pair, + Inventory::InventoryTypeList bagList ) +{ + auto& estateContainers = getEstateInventory( ident ); + + for( auto bag : bagList ) + { + auto needle = estateContainers.find( bag ); + if( needle == estateContainers.end() ) + continue; + + auto container = needle->second; + + auto freeSlot = container->getFreeSlot(); + + if( freeSlot == -1 ) + continue; + + pair = std::make_pair( bag, freeSlot ); + return container; + } + + return nullptr; +} + +void Sapphire::World::Manager::HousingMgr::reqEstateExteriorRemodel( Sapphire::Entity::Player& player, uint16_t plot ) +{ + auto terri = std::dynamic_pointer_cast< HousingZone >( player.getCurrentZone() ); + if( !terri ) + return; + + auto land = terri->getLand( plot ); + if( !land ) + return; + + // todo: proper perms checks + if( land->getOwnerId() != player.getId() ) + return; + + auto& inv = getEstateInventory( land->getLandIdent() ); + auto needle = inv.find( InventoryType::HousingExteriorAppearance ); + if( needle == inv.end() ) + return; + + auto invMgr = g_fw.get< InventoryMgr >(); + + invMgr->sendInventoryContainer( player, needle->second ); + + auto pkt = Server::makeActorControl143( player.getId(), Network::ActorControl::ShowEstateExternalAppearanceUI, plot ); + player.queuePacket( pkt ); + +} + +void Sapphire::World::Manager::HousingMgr::reqEstateInteriorRemodel( Sapphire::Entity::Player& player ) +{ + auto terri = std::dynamic_pointer_cast< Territory::Housing::HousingInteriorTerritory >( player.getCurrentZone() ); + if( !terri ) + return; + + auto ident = terri->getLandIdent(); + auto landSet = toLandSetId( ident.territoryTypeId, ident.wardNum ); + auto land = getHousingZoneByLandSetId( landSet )->getLand( ident.landId ); + + if( !land ) + return; + + // todo: proper perms checks + if( land->getOwnerId() != player.getId() ) + return; + + auto& inv = getEstateInventory( land->getLandIdent() ); + auto needle = inv.find( InventoryType::HousingInteriorAppearance ); + if( needle == inv.end() ) + return; + + auto invMgr = g_fw.get< InventoryMgr >(); + + invMgr->sendInventoryContainer( player, needle->second ); + + auto pkt = Server::makeActorControl143( player.getId(), Network::ActorControl::ShowEstateInternalAppearanceUI ); + player.queuePacket( pkt ); +} \ No newline at end of file diff --git a/src/world/Manager/HousingMgr.h b/src/world/Manager/HousingMgr.h index d0c59a6c..273ec484 100644 --- a/src/world/Manager/HousingMgr.h +++ b/src/world/Manager/HousingMgr.h @@ -5,6 +5,7 @@ #include "Territory/HousingZone.h" #include #include +#include namespace Sapphire::Data { @@ -18,11 +19,15 @@ namespace Sapphire::World::Manager public: + /*! + * @brief Structure that is generated and filled on world launch. Used to store housing data during init + * so we don't need to query them individually. + */ struct LandCacheEntry { // land table uint64_t m_landSetId; - uint64_t m_landId; + uint16_t m_landId; Common::LandType m_type; uint8_t m_size; @@ -42,6 +47,9 @@ namespace Sapphire::World::Manager uint64_t m_buildTime; uint64_t m_endorsements; + + uint16_t m_maxPlacedExternalItems; + uint16_t m_maxPlacedInternalItems; }; /*! @@ -106,6 +114,13 @@ namespace Sapphire::World::Manager */ void sendEstateInventory( Entity::Player& player, uint16_t inventoryType, uint8_t plotNum ); + /*! + * @brief Sends all the available internal inventories in one go. Used to initially populate the menu. + * @param player The player to send the containers to + * @param storeroom True if we should send the storeroom, false we send the placed items + */ + void sendInternalEstateInventoryBatch( Entity::Player& player, bool storeroom = false ); + /*! * @brief Get the land & house data that was cached on world startup. * @return @@ -131,13 +146,167 @@ namespace Sapphire::World::Manager * @return A map containing container ids to ItemContainerPtr */ ContainerIdToContainerMap& getEstateInventory( Common::LandIdent ident ); + + /** + * @brief Sets up inventories and spawns the base items for the house appearance + * @param land The house to update + */ + bool initHouseModels( Entity::Player& player, LandPtr land, uint32_t presetCatalogId ); + + void reqPlaceHousingItem( Entity::Player& player, uint16_t landId, uint16_t containerId, uint16_t slotId, + Common::FFXIVARR_POSITION3 pos, float rotation ); + + /*! + * @brief Returns the equivalent YardObject for a HousingItem + * @param item The item to convert into a YardObject + * @return The resultant YardObject + */ + Common::HousingObject getYardObjectForItem( Inventory::HousingItemPtr item ) const; + + + void reqMoveHousingItem( Entity::Player& player, Common::LandIdent ident, uint16_t slot, + Common::FFXIVARR_POSITION3 pos, float rot ); + + + void reqRemoveHousingItem( Sapphire::Entity::Player& player, uint16_t plot, + uint16_t containerId, uint16_t slot, + bool sendToStoreroom ); + + void reqEstateExteriorRemodel( Entity::Player& player, uint16_t plot ); + + void reqEstateInteriorRemodel( Entity::Player& player ); + private: - void loadLandCache(); + + ItemContainerPtr getFreeEstateInventorySlot( Common::LandIdent ident, + Inventory::InventoryContainerPair& pair, + Inventory::InventoryTypeList bagList ); + + /*! + * + * @param player + * @param terri + * @param containerId + * @param slotId + * @param sendToStoreroom + * @return + */ + bool removeInternalItem( Entity::Player& player, Territory::Housing::HousingInteriorTerritory& terri, + uint16_t containerId, uint16_t slotId, bool sendToStoreroom ); + + /*! + * + * @param player + * @param terri + * @param slotId + * @param sendToStoreroom + * @return + */ + bool removeExternalItem( Entity::Player& player, HousingZone& terri, Land& land, + Common::InventoryType containerType, uint16_t slotId, + bool sendToStoreroom ); + + /*! + * @brief Processes the movement of an item placed in a HousingZone + * + * This function assumes that the player has permission to move the item. + * + * @param player The player who placed the item + * @param ident The ident of the land that the item belongs to + * @param containerIdx The index of the container + * @param slot The slot of the item + * @param pos The new position + * @param rot The new rotation + * @return true if moved successfully + */ + bool moveExternalItem( Entity::Player& player, Common::LandIdent ident, uint16_t slot, + Sapphire::HousingZone& terri, Common::FFXIVARR_POSITION3 pos, float rot ); + + /*! + * @brief Processes the movement of an item placed inside a HousingInteriorTerritory + * + * This function assumes that the player has permission to move the item. + * + * @param player The player who placed the item + * @param ident The ident of the land that the item belongs to + * @param slot The index of the container + * @param slotIdx The slot of the item + * @param pos The new position + * @param rot The new rotation + * @return true if moved successfully + */ + bool moveInternalItem( Entity::Player& player, Common::LandIdent ident, + Territory::Housing::HousingInteriorTerritory& terri, uint16_t slot, + Common::FFXIVARR_POSITION3 pos, float rot ); + + /*! + * @brief Processes the spawning and linking of a newly placed housing item for external items + * @param player The player who is placing the item + * @param item The item that we're placing + * @param ident The land that is going to own the item + * @return true if the item was placed successfully, false if there's no free container slots to place it + */ + bool placeExternalItem( Entity::Player& player, Inventory::HousingItemPtr item, Common::LandIdent ident ); + + /*! + * @brief Processing the spawning and linking of a newly placed item for interior items + * @param player The player who is placing the item + * @param item The item that we're placing + * @return true if the item was placed successfully, false if there's no free spots to place it + */ + bool placeInteriorItem( Entity::Player& player, Inventory::HousingItemPtr item ); + + /*! + * @brief Creates a house and saves the minimum amount required to persist a house through restarts. + * + * Any other changes will be covered by the usual saving logic and can be safely ignored here. + * + * @param house The house to create in the house table + */ + void createHouse( HousePtr house ) const; + + /*! + * @brief Gets the next available house id + * @return The next available house id + */ + uint64_t getNextHouseId(); + + /*! + * @brief Loads all the land entries from the database and caches them to speed up housing territory init + */ + void initLandCache(); + + /*! + * @brief Loads all the inventories for every estate on the world and sets up their containers + * @return True if it was successful + */ bool loadEstateInventories(); + /*! + * @brief Gets the additionalData field from item.exd for an item + * @param catalogId The item id to lookup in item.exd + * @return The additionalData field from item.exd + */ + uint32_t getItemAdditionalData( uint32_t catalogId ); + + /*! + * @brief Checks whether an estate inventory contains items that are placed and have a world position + * + * Eg, any item inside the 'placed' items container _should_ have a world position and can be spawned. + * + * @param type The inventory type that contains items + * @return true if contains items that would have a world position + */ + bool isPlacedItemsInventory( Common::InventoryType type ); + LandSetLandCacheMap m_landCache; LandIdentToInventoryContainerMap m_estateInventories; + Inventory::InventoryTypeList m_internalPlacedItemContainers; + Inventory::InventoryTypeList m_internalStoreroomContainers; + + std::array< std::pair< Common::InventoryType, Common::InventoryType >, 8 > m_containerMap; + }; } diff --git a/src/world/Manager/InventoryMgr.cpp b/src/world/Manager/InventoryMgr.cpp index 6529d617..5c960f35 100644 --- a/src/world/Manager/InventoryMgr.cpp +++ b/src/world/Manager/InventoryMgr.cpp @@ -3,10 +3,18 @@ #include #include "Actor/Player.h" #include "Inventory/ItemContainer.h" -#include "Inventory/Item.h" +#include "Inventory/HousingItem.h" +#include "Inventory/ItemUtil.h" #include #include +#include +#include + +#include "Framework.h" + +extern Sapphire::Framework g_fw; + using namespace Sapphire::Network::Packets; void Sapphire::World::Manager::InventoryMgr::sendInventoryContainer( Sapphire::Entity::Player& player, @@ -41,8 +49,9 @@ void Sapphire::World::Manager::InventoryMgr::sendInventoryContainer( Sapphire::E itemInfoPacket->data().quantity = itM->second->getStackSize(); itemInfoPacket->data().catalogId = itM->second->getId(); itemInfoPacket->data().condition = itM->second->getDurability(); - itemInfoPacket->data().spiritBond = 0; - itemInfoPacket->data().hqFlag = itM->second->isHq() ? 1 : 0; + itemInfoPacket->data().spiritBond = itM->second->getSpiritbond(); + itemInfoPacket->data().reservedFlag = itM->second->getReservedFlag(); + itemInfoPacket->data().hqFlag = static_cast< uint8_t >( itM->second->isHq() ? 1 : 0 ); itemInfoPacket->data().stain = itM->second->getStain(); player.queuePacket( itemInfoPacket ); @@ -55,4 +64,121 @@ void Sapphire::World::Manager::InventoryMgr::sendInventoryContainer( Sapphire::E containerInfoPacket->data().containerId = container->getId(); player.queuePacket( containerInfoPacket ); +} + +Sapphire::ItemPtr Sapphire::World::Manager::InventoryMgr::createItem( Entity::Player& player, + uint32_t catalogId, uint32_t quantity ) +{ + auto pExdData = g_fw.get< Data::ExdDataGenerated >(); + auto pDb = g_fw.get< Db::DbWorkerPool< Db::ZoneDbConnection > >(); + auto itemInfo = pExdData->get< Sapphire::Data::Item >( catalogId ); + + if( !itemInfo ) + return nullptr; + + auto item = make_Item( Items::Util::getNextUId(), catalogId ); + + item->setStackSize( std::max< uint32_t >( 1, quantity ) ); + + saveItem( player, item ); + + return item; +} + +void Sapphire::World::Manager::InventoryMgr::saveHousingContainer( Common::LandIdent ident, + Sapphire::ItemContainerPtr container ) +{ + auto u64ident = *reinterpret_cast< uint64_t* >( &ident ); + + for( auto& item : container->getItemMap() ) + { + saveHousingContainerItem( u64ident, container->getId(), item.first, item.second->getUId() ); + } +} + +void Sapphire::World::Manager::InventoryMgr::removeItemFromHousingContainer( Sapphire::Common::LandIdent ident, + uint16_t containerId, + uint16_t slotId ) +{ + auto pDb = g_fw.get< Db::DbWorkerPool< Db::ZoneDbConnection > >(); + + auto stmt = pDb->getPreparedStatement( Db::LAND_INV_DEL ); + + auto u64ident = *reinterpret_cast< uint64_t* >( &ident ); + + stmt->setUInt64( 1, u64ident ); + stmt->setUInt( 2, containerId ); + stmt->setUInt( 3, slotId ); + + pDb->directExecute( stmt ); +} + +void Sapphire::World::Manager::InventoryMgr::saveHousingContainerItem( uint64_t ident, + uint16_t containerId, uint16_t slotId, + uint64_t itemId ) +{ + auto pDb = g_fw.get< Db::DbWorkerPool< Db::ZoneDbConnection > >(); + + auto stmt = pDb->getPreparedStatement( Db::LAND_INV_UP ); + // LandIdent, ContainerId, SlotId, ItemId, ItemId + + stmt->setUInt64( 1, ident ); + stmt->setUInt( 2, containerId ); + stmt->setUInt( 3, slotId ); + stmt->setUInt64( 4, itemId ); + + // see query, we have to pass itemid in twice + // the second time is for the ON DUPLICATE KEY UPDATE condition + stmt->setUInt64( 5, itemId ); + + pDb->execute( stmt ); +} + +void Sapphire::World::Manager::InventoryMgr::updateHousingItemPosition( Sapphire::Inventory::HousingItemPtr item ) +{ + auto pDb = g_fw.get< Db::DbWorkerPool< Db::ZoneDbConnection > >(); + + auto stmt = pDb->getPreparedStatement( Db::LAND_INV_UP_ITEMPOS ); + // ItemId, PosX, PosY, PosZ, Rotation, PosX, PosY, PosZ, Rotation + + auto pos = item->getPos(); + auto rot = item->getRot(); + + stmt->setUInt64( 1, item->getUId() ); + + stmt->setUInt( 2, pos.x ); + stmt->setUInt( 3, pos.y ); + stmt->setUInt( 4, pos.z ); + stmt->setInt( 5, rot ); + + stmt->setUInt( 6, pos.x ); + stmt->setUInt( 7, pos.y ); + stmt->setUInt( 8, pos.z ); + stmt->setInt( 9, rot ); + + pDb->execute( stmt ); +} + +void Sapphire::World::Manager::InventoryMgr::removeHousingItemPosition( Sapphire::Inventory::HousingItem& item ) +{ + auto pDb = g_fw.get< Db::DbWorkerPool< Db::ZoneDbConnection > >(); + + auto stmt = pDb->getPreparedStatement( Db::LAND_INV_DEL_ITEMPOS ); + + stmt->setUInt64( 1, item.getUId() ); + + pDb->directExecute( stmt ); +} + +void Sapphire::World::Manager::InventoryMgr::saveItem( Sapphire::Entity::Player& player, Sapphire::ItemPtr item ) +{ + auto pDb = g_fw.get< Db::DbWorkerPool< Db::ZoneDbConnection > >(); + auto stmt = pDb->getPreparedStatement( Db::CHARA_ITEMGLOBAL_INS ); + + stmt->setUInt( 1, player.getId() ); + stmt->setUInt64( 2, item->getUId() ); + stmt->setUInt( 3, item->getId() ); + stmt->setUInt( 4, item->getStackSize() ); + + pDb->directExecute( stmt ); } \ No newline at end of file diff --git a/src/world/Manager/InventoryMgr.h b/src/world/Manager/InventoryMgr.h index 26fae576..b98170c7 100644 --- a/src/world/Manager/InventoryMgr.h +++ b/src/world/Manager/InventoryMgr.h @@ -9,7 +9,83 @@ namespace Sapphire::World::Manager class InventoryMgr { public: + /*! + * @brief Sends an item container to a player + * + * This does no checks on the container itself. You can send another players inventory to a different + * player if you so wish - not that you should + * + * Automagically manages inventory packet sequencing. + * + * @param player The player to send the container to + * @param container The container to send to the player + */ void sendInventoryContainer( Sapphire::Entity::Player& player, Sapphire::ItemContainerPtr container ); + + /*! + * @brief Creates an item, saves it to the global item table and returns a ptr to the created item + * @param player The player that will 'own' the item + * @param catalogId The ID for the item, see item.exd + * @param quantity how much item to make + * @return An ItemPtr to the newly created item + */ + Sapphire::ItemPtr createItem( Entity::Player& player, uint32_t catalogId, uint32_t quantity = 1 ); + + /*! + * @brief Commits a housing containers contents to the db + * @param ident The identity of the owner of the container + * @param container The container to save to the db + */ + void saveHousingContainer( Common::LandIdent ident, Sapphire::ItemContainerPtr container ); + + /*! + * @brief Unlinks an item from the housing container in the db + * @param ident The identity of the estate that owns the item + * @param containerId The container the item is in + * @param slotId The slot the item is in + */ + void removeItemFromHousingContainer( Common::LandIdent ident, uint16_t containerId, uint16_t slotId ); + + /*! + * @brief Update an item in the db + * @param item The item to commit to the db + */ + void updateItem( Sapphire::ItemPtr item ); + + /*! + * @brief Updates the position/rotation of a housing object + * @param item The item to update + */ + void updateHousingItemPosition( Sapphire::Inventory::HousingItemPtr item ); + + /*! + * @brief Removes the position/rotation from a housing object + * @param item The item to remove the position from. + */ + void removeHousingItemPosition( Sapphire::Inventory::HousingItem& item ); + + /*! + * @brief Saves an item to the global item table + * @param player The player which owns the item + * @param item The item to save + */ + void saveItem( Entity::Player& player, ItemPtr item ); + + private: + /*! + * @brief Saves an individual item to the db. + * + * This will create a new row in the event the slot doesn't exist in the db, otherwise this + * will overwrite the itemid in the existing row. + * + * @param ident + * @param containerId + * @param slotId + * @param itemId + */ + void saveHousingContainerItem( uint64_t ident, + uint16_t containerId, uint16_t slotId, + uint64_t itemId ); }; } diff --git a/src/world/Manager/TerritoryMgr.cpp b/src/world/Manager/TerritoryMgr.cpp index cd1a2301..f1f5ea63 100644 --- a/src/world/Manager/TerritoryMgr.cpp +++ b/src/world/Manager/TerritoryMgr.cpp @@ -106,11 +106,20 @@ bool Sapphire::World::Manager::TerritoryMgr::isPrivateTerritory( uint32_t territ return pTeri->territoryIntendedUse == TerritoryIntendedUse::OpeningArea || pTeri->territoryIntendedUse == TerritoryIntendedUse::Inn || - pTeri->territoryIntendedUse == TerritoryIntendedUse::HousingPrivateArea || pTeri->territoryIntendedUse == TerritoryIntendedUse::JailArea || pTeri->territoryIntendedUse == TerritoryIntendedUse::MSQPrivateArea; } +bool Sapphire::World::Manager::TerritoryMgr::isInternalEstateTerritory( uint32_t territoryTypeId ) const +{ + auto pTeri = getTerritoryDetail( territoryTypeId ); + + if( !pTeri ) + return false; + + return pTeri->territoryIntendedUse == TerritoryIntendedUse::HousingPrivateArea; +} + bool Sapphire::World::Manager::TerritoryMgr::isDefaultTerritory( uint32_t territoryTypeId ) const { auto pTeri = getTerritoryDetail( territoryTypeId ); diff --git a/src/world/Manager/TerritoryMgr.h b/src/world/Manager/TerritoryMgr.h index 794c87af..68fc1243 100644 --- a/src/world/Manager/TerritoryMgr.h +++ b/src/world/Manager/TerritoryMgr.h @@ -87,6 +87,13 @@ namespace Sapphire::World::Manager /*! returns true if the territoryType in question is not a private zone */ bool isPrivateTerritory( uint32_t territoryTypeId ) const; + /*! + * @brief Checks if a territory type is an internal housing area + * @param territoryTypeId The territory to test + * @return true if it is a housing area, false if not + */ + bool isInternalEstateTerritory( uint32_t territoryTypeId ) const; + /*! returns true if the territoryType is a default non-instanced zone */ bool isDefaultTerritory( uint32_t territoryTypeId ) const; diff --git a/src/world/Network/GameConnection.cpp b/src/world/Network/GameConnection.cpp index 25c42c9b..7530d2b5 100644 --- a/src/world/Network/GameConnection.cpp +++ b/src/world/Network/GameConnection.cpp @@ -88,6 +88,9 @@ Sapphire::Network::GameConnection::GameConnection( Sapphire::Network::HivePtr pH setZoneHandler( ClientZoneIpcType::LandRenameHandler, "LandRenameHandler", &GameConnection::landRenameHandler ); setZoneHandler( ClientZoneIpcType::HousingUpdateHouseGreeting, "HousingUpdateHouseGreeting", &GameConnection::housingUpdateGreetingHandler ); + setZoneHandler( ClientZoneIpcType::ReqPlaceHousingItem, "ReqPlaceHousingItem", &GameConnection::reqPlaceHousingItem ); + setZoneHandler( ClientZoneIpcType::HousingUpdateObjectPosition, "HousingUpdateObjectPosition", + &GameConnection::reqMoveHousingItem ); setZoneHandler( ClientZoneIpcType::TalkEventHandler, "EventHandlerTalk", &GameConnection::eventHandlerTalk ); setZoneHandler( ClientZoneIpcType::EmoteEventHandler, "EventHandlerEmote", &GameConnection::eventHandlerEmote ); diff --git a/src/world/Network/GameConnection.h b/src/world/Network/GameConnection.h index 3efbdde5..b6fa0738 100644 --- a/src/world/Network/GameConnection.h +++ b/src/world/Network/GameConnection.h @@ -171,6 +171,10 @@ namespace Sapphire::Network DECLARE_HANDLER( tellHandler ); + DECLARE_HANDLER( reqPlaceHousingItem ); + + DECLARE_HANDLER( reqMoveHousingItem ); + }; } diff --git a/src/world/Network/Handlers/ClientTriggerHandler.cpp b/src/world/Network/Handlers/ClientTriggerHandler.cpp index e61e168d..44b309e4 100644 --- a/src/world/Network/Handlers/ClientTriggerHandler.cpp +++ b/src/world/Network/Handlers/ClientTriggerHandler.cpp @@ -79,12 +79,16 @@ void Sapphire::Network::GameConnection::clientTriggerHandler( const Packets::FFX const auto param12 = packet.data().param12; const auto param2 = packet.data().param2; const auto param3 = packet.data().param3; + const auto param4 = packet.data().param4; + const auto param5 = packet.data().param5; pLog->debug( "[" + std::to_string( m_pSession->getId() ) + "] Incoming action: " + Util::intToHexString( static_cast< uint32_t >( commandId & 0xFFFF ), 4 ) + "\nparam1: " + Util::intToHexString( static_cast< uint64_t >( param1 & 0xFFFFFFFFFFFFFFF ), 16 ) + "\nparam2: " + Util::intToHexString( static_cast< uint32_t >( param2 & 0xFFFFFFFF ), 8 ) + - "\nparam3: " + Util::intToHexString( static_cast< uint64_t >( param3 & 0xFFFFFFFFFFFFFFF ), 16 ) + "\nparam3: " + Util::intToHexString( static_cast< uint64_t >( param3 & 0xFFFFFFFFFFFFFFF ), 16 ) + + "\nparam4: " + Util::intToHexString( static_cast< uint32_t >( param4 & 0xFFFFFFFF ), 8 ) + + "\nparam5: " + Util::intToHexString( static_cast< uint32_t >( param5 & 0xFFFFFFFF ), 8 ) ); @@ -425,14 +429,13 @@ void Sapphire::Network::GameConnection::clientTriggerHandler( const Packets::FFX { uint8_t plot = ( param12 & 0xFF ); - auto housingMgr = g_fw.get< HousingMgr >(); if( !housingMgr ) break; - uint16_t inventoryType = Common::InventoryType::HousingOutdoorPlacedItems; + uint16_t inventoryType = Common::InventoryType::HousingExteriorPlacedItems; if( param2 == 1 ) - inventoryType = Common::InventoryType::HousingOutdoorStoreroom; + inventoryType = Common::InventoryType::HousingExteriorStoreroom; housingMgr->sendEstateInventory( player, inventoryType, plot ); @@ -440,15 +443,45 @@ void Sapphire::Network::GameConnection::clientTriggerHandler( const Packets::FFX } case ClientTriggerType::RequestEstateInventory: { - // only sent if param1 is 1, because the client sends this with 0 when you open the ui for whatever reason - if( param1 != 1 ) - return; - auto housingMgr = g_fw.get< HousingMgr >(); if( !housingMgr ) break; - // housingMgr->sendHousingInventory( player, Common::InventoryType::HousingInd, 255 ); + // param1 = 1 - storeroom + // param1 = 0 - placed items + + if( param1 == 1 ) + housingMgr->sendInternalEstateInventoryBatch( player, true ); + else + housingMgr->sendInternalEstateInventoryBatch( player ); + + break; + } + case ClientTriggerType::RequestHousingItemRemove: + { + auto housingMgr = g_fw.get< HousingMgr >(); + + auto slot = param4 & 0xFF; + auto sendToStoreroom = ( param4 >> 16 ) != 0; + + //player, plot, containerId, slot, sendToStoreroom + housingMgr->reqRemoveHousingItem( player, param12, param2, slot, sendToStoreroom ); + + break; + } + case ClientTriggerType::RequestEstateExteriorRemodel: + { + auto housingMgr = g_fw.get< HousingMgr >(); + + housingMgr->reqEstateExteriorRemodel( player, param11 ); + + break; + } + case ClientTriggerType::RequestEstateInteriorRemodel: + { + auto housingMgr = g_fw.get< HousingMgr >(); + + housingMgr->reqEstateInteriorRemodel( player ); break; } diff --git a/src/world/Network/Handlers/PacketHandlers.cpp b/src/world/Network/Handlers/PacketHandlers.cpp index 1d3f394a..3dc5b303 100644 --- a/src/world/Network/Handlers/PacketHandlers.cpp +++ b/src/world/Network/Handlers/PacketHandlers.cpp @@ -705,3 +705,26 @@ void Sapphire::Network::GameConnection::housingUpdateGreetingHandler( const Pack pHousingMgr->updateEstateGreeting( player, packet.data().ident, std::string( packet.data().greeting ) ); } + +void Sapphire::Network::GameConnection::reqPlaceHousingItem( const Packets::FFXIVARR_PACKET_RAW& inPacket, + Entity::Player& player ) +{ + auto housingMgr = g_fw.get< HousingMgr >(); + const auto packet = ZoneChannelPacket< Client::FFXIVIpcReqPlaceHousingItem >( inPacket ); + const auto& data = packet.data(); + + housingMgr->reqPlaceHousingItem( player, data.landId, data.sourceInvContainerId, data.sourceInvSlotId, + data.position, data.rotation ); +} + +void Sapphire::Network::GameConnection::reqMoveHousingItem( const Packets::FFXIVARR_PACKET_RAW& inPacket, + Entity::Player& player ) +{ + auto housingMgr = g_fw.get< HousingMgr >(); + + const auto packet = ZoneChannelPacket< Client::FFXIVIpcHousingUpdateObjectPosition >( inPacket ); + const auto& data = packet.data(); + + housingMgr->reqMoveHousingItem( player, data.ident, data.slot, data.pos, data.rotation ); + +} \ No newline at end of file diff --git a/src/world/Network/PacketWrappers/UpdateInventorySlotPacket.h b/src/world/Network/PacketWrappers/UpdateInventorySlotPacket.h index 312b7abf..4f10459a 100644 --- a/src/world/Network/PacketWrappers/UpdateInventorySlotPacket.h +++ b/src/world/Network/PacketWrappers/UpdateInventorySlotPacket.h @@ -29,11 +29,11 @@ namespace Sapphire::Network::Packets::Server m_data.slot = slot; m_data.quantity = item.getStackSize(); m_data.catalogId = item.getId(); - m_data.reservedFlag = 0; // no idea + m_data.reservedFlag = item.getReservedFlag(); // no idea m_data.signatureId = 0; m_data.hqFlag = item.isHq() ? 1 : 0; m_data.condition = 60000; // 200% - m_data.spiritBond = 0; + m_data.spiritBond = item.getSpiritbond(); m_data.color = 0; m_data.glamourCatalogId = 0; m_data.materia1 = 0; diff --git a/src/world/Territory/House.cpp b/src/world/Territory/House.cpp index 404f9f47..4e5d3855 100644 --- a/src/world/Territory/House.cpp +++ b/src/world/Territory/House.cpp @@ -20,9 +20,7 @@ Sapphire::House::House( uint32_t houseId, uint32_t landSetId, Common::LandIdent m_landIdent( ident ), m_estateName( estateName ), m_estateComment( estateComment ) -{ - -} +{} Sapphire::House::~House() = default; @@ -55,44 +53,14 @@ Sapphire::Common::LandIdent Sapphire::House::getLandIdent() const return m_landIdent; } -uint32_t Sapphire::House::getHouseId() const +uint32_t Sapphire::House::getId() const { return m_houseId; } -uint8_t Sapphire::House::getHousePartColor( Common::HousePartSlot slot ) const -{ - return m_houseModelsCache[ slot ].second; -} - -uint32_t Sapphire::House::getHouseInteriorPart( Common::HousingInteriorSlot slot ) const -{ - return m_houseInteriorModels[ slot ]; -} - -void Sapphire::House::setHousePart( Common::HousePartSlot slot, uint32_t id ) -{ - m_houseModelsCache[ slot ].first = id; -} - -void Sapphire::House::setHousePartColor( Common::HousePartSlot slot, uint32_t id ) -{ - m_houseModelsCache[ slot ].second = id; -} - -void Sapphire::House::setHouseInteriorPart( Common::HousingInteriorSlot slot, uint32_t id ) -{ - m_houseInteriorModels[ slot ] = id; -} - -uint32_t Sapphire::House::getHousePart( Common::HousePartSlot slot ) const -{ - return m_houseModelsCache[ slot ].first; -} - Sapphire::House::HouseModelsArray const& Sapphire::House::getHouseModels() const { - return m_houseModelsCache; + return m_exteriorModelCache; } const std::string& Sapphire::House::getHouseName() const @@ -117,4 +85,24 @@ void Sapphire::House::setHouseName( const std::string& name ) m_estateName = name; updateHouseDb(); +} + +void Sapphire::House::setExteriorModel( Sapphire::Common::HouseExteriorSlot slot, uint32_t modelId, uint16_t stain ) +{ + m_exteriorModelCache[ slot ] = std::make_pair( modelId, stain ); +} + +Sapphire::House::HousePart Sapphire::House::getExteriorModel( Sapphire::Common::HouseExteriorSlot slot ) +{ + return m_exteriorModelCache[ slot ]; +} + +void Sapphire::House::setInteriorModel( Sapphire::Common::HousingInteriorSlot slot, uint32_t modelId ) +{ + m_interiorModelCache[ slot ] = modelId; +} + +uint32_t Sapphire::House::getInteriorModel( Sapphire::Common::HousingInteriorSlot slot ) +{ + return m_interiorModelCache[ slot ]; } \ No newline at end of file diff --git a/src/world/Territory/House.h b/src/world/Territory/House.h index f4c71817..b307ecd6 100644 --- a/src/world/Territory/House.h +++ b/src/world/Territory/House.h @@ -16,13 +16,13 @@ namespace Sapphire const std::string& estateComment ); virtual ~House(); - using HousePart = std::pair< uint32_t, uint8_t >; + using HousePart = std::pair< uint32_t, uint16_t >; using HouseModelsArray = std::array< HousePart, 8 >; //gerneral uint32_t getLandSetId() const; Common::LandIdent getLandIdent() const; - uint32_t getHouseId() const; + uint32_t getId() const; const std::string& getHouseName() const; void setHouseName( const std::string& name ); @@ -31,12 +31,11 @@ namespace Sapphire void setHouseGreeting( const std::string& greeting ); //functions - void setHousePart( Common::HousePartSlot slot, uint32_t id ); - void setHousePartColor( Common::HousePartSlot slot, uint32_t id ); - void setHouseInteriorPart( Common::HousingInteriorSlot slot, uint32_t id ); - uint32_t getHousePart( Common::HousePartSlot slot ) const; - uint8_t getHousePartColor( Common::HousePartSlot slot ) const; - uint32_t getHouseInteriorPart( Common::HousingInteriorSlot slot ) const; + void setExteriorModel( Common::HouseExteriorSlot slot, uint32_t modelId, uint16_t stain ); + HousePart getExteriorModel( Common::HouseExteriorSlot slot ); + + void setInteriorModel( Common::HousingInteriorSlot slot, uint32_t modelId ); + uint32_t getInteriorModel( Common::HousingInteriorSlot slot ); HouseModelsArray const& getHouseModels() const; @@ -49,8 +48,8 @@ namespace Sapphire uint64_t m_buildTime; - HouseModelsArray m_houseModelsCache; - uint32_t m_houseInteriorModels[10]; + HouseModelsArray m_exteriorModelCache; + uint32_t m_interiorModelCache[10]; std::string m_estateComment; std::string m_estateName; diff --git a/src/world/Territory/Housing/HousingInteriorTerritory.cpp b/src/world/Territory/Housing/HousingInteriorTerritory.cpp index da5c399d..d54ce928 100644 --- a/src/world/Territory/Housing/HousingInteriorTerritory.cpp +++ b/src/world/Territory/Housing/HousingInteriorTerritory.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include "Actor/Player.h" #include "Actor/Actor.h" @@ -13,6 +15,8 @@ #include "Manager/HousingMgr.h" #include "Territory/Land.h" #include "Territory/House.h" +#include "Inventory/ItemContainer.h" +#include "Inventory/HousingItem.h" #include "Forwards.h" #include "HousingInteriorTerritory.h" @@ -28,27 +32,27 @@ using namespace Sapphire::World::Manager; using namespace Sapphire; using namespace Sapphire::World::Territory; -Housing::HousingInteriorTerritory::HousingInteriorTerritory( Common::LandIdent ident, uint16_t territoryTypeId, - uint32_t guId, - const std::string& internalName, - const std::string& contentName ) : +Sapphire::World::Territory::Housing::HousingInteriorTerritory::HousingInteriorTerritory( Common::LandIdent ident, + uint16_t territoryTypeId, + uint32_t guId, + const std::string& internalName, + const std::string& contentName ) : Zone( territoryTypeId, guId, internalName, contentName ), m_landIdent( ident ) { m_lastActivityTime = static_cast< uint32_t >( Util::getTimeSeconds() ); } -Housing::HousingInteriorTerritory::~HousingInteriorTerritory() -{ +Housing::HousingInteriorTerritory::~HousingInteriorTerritory() = default; +bool Sapphire::World::Territory::Housing::HousingInteriorTerritory::init() +{ + updateHousingObjects(); + + return true; } -bool Housing::HousingInteriorTerritory::init() -{ - return false; -} - -void Housing::HousingInteriorTerritory::onPlayerZoneIn( Entity::Player& player ) +void Sapphire::World::Territory::Housing::HousingInteriorTerritory::onPlayerZoneIn( Entity::Player& player ) { auto pHousingMgr = g_fw.get< HousingMgr >(); auto pLog = g_fw.get< Logger >(); @@ -56,7 +60,7 @@ void Housing::HousingInteriorTerritory::onPlayerZoneIn( Entity::Player& player ) "HousingInteriorTerritory::onPlayerZoneIn: Zone#" + std::to_string( getGuId() ) + "|" + std::to_string( getTerritoryTypeId() ) + ", Entity#" + std::to_string( player.getId() ) ); - auto indoorInitPacket = makeZonePacket< FFXIVIpcHousingIndoorInitialize >( player.getId() ); + auto indoorInitPacket = makeZonePacket< Server::FFXIVIpcHousingIndoorInitialize >( player.getId() ); indoorInitPacket->data().u1 = 0; indoorInitPacket->data().u2 = 0; indoorInitPacket->data().u3 = 0; @@ -68,43 +72,159 @@ void Housing::HousingInteriorTerritory::onPlayerZoneIn( Entity::Player& player ) for( auto i = 0; i < 10; i++ ) { - indoorInitPacket->data().indoorItems[ i ] = pHouse->getHouseInteriorPart( static_cast< Common::HousingInteriorSlot >( i ) ); + indoorInitPacket->data().indoorItems[ i ] = pHouse->getInteriorModel( + static_cast< Common::HousingInteriorSlot >( i ) ); } - - uint32_t yardPacketNum; - uint32_t yardPacketTotal = 2 + pLand->getSize(); - player.queuePacket( indoorInitPacket ); - for( yardPacketNum = 0; yardPacketNum < yardPacketTotal; yardPacketNum++ ) + auto yardPacketTotal = static_cast< uint8_t >( 2 + pLand->getSize() ); + for( uint8_t yardPacketNum = 0; yardPacketNum < yardPacketTotal; yardPacketNum++ ) { - auto objectInitPacket = makeZonePacket< FFXIVIpcHousingObjectInitialize >( player.getId() ); + auto objectInitPacket = makeZonePacket< Server::FFXIVIpcHousingObjectInitialize >( player.getId() ); memcpy( &objectInitPacket->data().landIdent, &m_landIdent, sizeof( Common::LandIdent ) ); objectInitPacket->data().u1 = 0; objectInitPacket->data().u2 = 100; objectInitPacket->data().packetNum = yardPacketNum; objectInitPacket->data().packetTotal = yardPacketTotal; - //TODO: Add Objects here + auto yardObjectSize = sizeof( Common::HousingObject ); + memcpy( &objectInitPacket->data().object, m_housingObjects.data() + ( yardPacketNum * 100 ), yardObjectSize * 100 ); player.queuePacket( objectInitPacket ); } } -void Housing::HousingInteriorTerritory::onUpdate( uint32_t currTime ) +void Sapphire::World::Territory::Housing::HousingInteriorTerritory::onUpdate( uint32_t currTime ) { if( m_playerMap.size() > 0 ) m_lastActivityTime = currTime; } -uint32_t Housing::HousingInteriorTerritory::getLastActivityTime() const +uint32_t Sapphire::World::Territory::Housing::HousingInteriorTerritory::getLastActivityTime() const { return m_lastActivityTime; } -const Common::LandIdent Housing::HousingInteriorTerritory::getIdent() const +const Common::LandIdent Sapphire::World::Territory::Housing::HousingInteriorTerritory::getLandIdent() const { return m_landIdent; +} + +void Sapphire::World::Territory::Housing::HousingInteriorTerritory::updateHousingObjects() +{ + auto housingMgr = g_fw.get< Manager::HousingMgr >(); + + auto containerIds = { + InventoryType::HousingInteriorPlacedItems1, + InventoryType::HousingInteriorPlacedItems2, + InventoryType::HousingInteriorPlacedItems3, + InventoryType::HousingInteriorPlacedItems4, + InventoryType::HousingInteriorPlacedItems5, + InventoryType::HousingInteriorPlacedItems6, + InventoryType::HousingInteriorPlacedItems7, + InventoryType::HousingInteriorPlacedItems8, + }; + + // zero out the array + // there's some really weird behaviour where *some* values will cause the linkshell invite notification to pop up + // for some reason + Common::HousingObject obj {}; + memset( &obj, 0x0, sizeof( Common::HousingObject ) ); + m_housingObjects.fill( obj ); + + auto containers = housingMgr->getEstateInventory( getLandIdent() ); + + uint8_t containerIdx = 0; + for( auto containerId : containerIds ) + { + auto container = containers.find( containerId ); + if( container == containers.end() ) + // no more containers left + break; + + for( const auto& item : container->second->getItemMap() ) + { + auto housingItem = std::dynamic_pointer_cast< Inventory::HousingItem >( item.second ); + assert( housingItem ); + + auto offset = item.first + ( containerIdx * 50 ); + + auto obj = housingMgr->getYardObjectForItem( housingItem ); + + m_housingObjects[ offset ] = obj; + } + + containerIdx++; + } +} + +void Sapphire::World::Territory::Housing::HousingInteriorTerritory::spawnHousingObject( uint8_t containerIdx, + uint16_t slot, + uint16_t containerType, + Inventory::HousingItemPtr item ) +{ + auto housingMgr = g_fw.get< Manager::HousingMgr >(); + + auto offset = ( containerIdx * 50 ) + slot; + auto obj = housingMgr->getYardObjectForItem( item ); + + m_housingObjects[ offset ] = obj; + + for( const auto& player : m_playerMap ) + { + auto objectSpawnPkt = makeZonePacket< Server::FFXIVIpcHousingInternalObjectSpawn >( player.second->getId() ); + + objectSpawnPkt->data().containerId = containerType; + objectSpawnPkt->data().containerOffset = slot; + + objectSpawnPkt->data().itemId = item->getAdditionalData() & 0xFFFF; + objectSpawnPkt->data().rotation = item->getRot(); + objectSpawnPkt->data().pos = item->getPos(); + + player.second->queuePacket( objectSpawnPkt ); + } +} + +void Sapphire::World::Territory::Housing::HousingInteriorTerritory::updateHousingObjectPosition( Entity::Player& sourcePlayer, + uint16_t slot, + Common::FFXIVARR_POSITION3_U16 pos, + uint16_t rot ) +{ + auto& obj = m_housingObjects[ slot ]; + + obj.pos = pos; + obj.itemRotation = rot; + + // todo: how does this update on other clients? + + for( const auto& player : m_playerMap ) + { + if( player.second->getId() == sourcePlayer.getId() ) + continue; + + auto moveObjPkt = makeZonePacket< Server::FFXIVIpcHousingObjectMove >( player.second->getId() ); + + moveObjPkt->data().itemRotation = obj.itemRotation; + moveObjPkt->data().pos = obj.pos; + + // todo: how does this work when an item is in a slot >50 or u8 max? my guess is landid is the container index, but not sure... + moveObjPkt->data().objectArray = static_cast< uint8_t >( slot % 50 ); + moveObjPkt->data().landId = static_cast< uint8_t >( slot / 50 ); + + player.second->queuePacket( moveObjPkt ); + } +} + +void Sapphire::World::Territory::Housing::HousingInteriorTerritory::removeHousingObject( uint16_t slot ) +{ + memset( m_housingObjects.data() + slot, 0x0, sizeof( Common::HousingObject ) ); + + for( const auto& player : m_playerMap ) + { + auto pkt = Server::makeActorControl143( player.second->getId(), Network::ActorControl::RemoveInteriorHousingItem, slot ); + + player.second->queuePacket( pkt ); + } } \ No newline at end of file diff --git a/src/world/Territory/Housing/HousingInteriorTerritory.h b/src/world/Territory/Housing/HousingInteriorTerritory.h index f8287df8..8d1450a8 100644 --- a/src/world/Territory/Housing/HousingInteriorTerritory.h +++ b/src/world/Territory/Housing/HousingInteriorTerritory.h @@ -1,5 +1,7 @@ #include "ForwardsZone.h" #include "Territory/Zone.h" +#include "Common.h" +#include namespace Sapphire::World::Territory::Housing { @@ -20,10 +22,19 @@ namespace Sapphire::World::Territory::Housing uint32_t getLastActivityTime() const; - const Common::LandIdent getIdent() const; + const Common::LandIdent getLandIdent() const; + + void updateHousingObjects(); + void spawnHousingObject( uint8_t containerIdx, uint16_t slot, uint16_t containerType, + Inventory::HousingItemPtr item ); + void updateHousingObjectPosition( + Entity::Player& sourcePlayer, uint16_t slot, Sapphire::Common::FFXIVARR_POSITION3_U16 pos, uint16_t rot ); + void removeHousingObject( uint16_t slot ); private: Common::LandIdent m_landIdent; uint32_t m_lastActivityTime; + + std::array< Sapphire::Common::HousingObject, 400 > m_housingObjects; }; } \ No newline at end of file diff --git a/src/world/Territory/HousingZone.cpp b/src/world/Territory/HousingZone.cpp index 075059e2..615fa69c 100644 --- a/src/world/Territory/HousingZone.cpp +++ b/src/world/Territory/HousingZone.cpp @@ -6,12 +6,16 @@ #include #include #include +#include +#include #include "Actor/Player.h" #include "Actor/Actor.h" #include "Actor/EventObject.h" #include "Land.h" #include "House.h" +#include "Inventory/HousingItem.h" +#include "Inventory/ItemContainer.h" #include "Forwards.h" #include "HousingZone.h" @@ -51,7 +55,7 @@ bool Sapphire::HousingZone::init() } - int housingIndex; + uint32_t housingIndex = 0; if( m_territoryTypeId == 339 ) housingIndex = 0; else if( m_territoryTypeId == 340 ) @@ -64,6 +68,52 @@ bool Sapphire::HousingZone::init() auto pExdData = g_fw.get< Data::ExdDataGenerated >(); auto info = pExdData->get< Sapphire::Data::HousingLandSet >( housingIndex ); + // build yard objects array indices + int16_t cursor = -1; + uint16_t index = 0; + for( const auto size : info->plotSize ) + { + uint16_t itemMax = 0; + switch( size ) + { + case 0: + itemMax = 20; + break; + + case 1: + itemMax = 30; + break; + + case 2: + itemMax = 40; + break; + } + + int16_t start = cursor + 1; + int16_t end = cursor + itemMax; + + m_yardObjectArrayBounds[ index++ ] = std::make_pair( start, end ); + + // reset cursor for subdivision + if( index == 30 ) + { + cursor = -1; + + continue; + } + + cursor += itemMax; + } + + // zero out the yard obj arrays so we don't leak memory like SE does :^) + Common::HousingObject obj {}; + memset( &obj, 0x0, sizeof( Common::HousingObject ) ); + + for( auto& arr : m_yardObjects ) + { + arr.fill( obj ); + } + auto housingMgr = g_fw.get< World::Manager::HousingMgr >(); auto landCache = housingMgr->getLandCacheMap(); @@ -83,7 +133,8 @@ bool Sapphire::HousingZone::init() // setup house if( entry.m_houseId ) { - auto house = make_House( entry.m_houseId, m_landSetId, land->getLandIdent(), entry.m_estateName, entry.m_estateComment ); + auto house = make_House( entry.m_houseId, m_landSetId, land->getLandIdent(), entry.m_estateName, + entry.m_estateComment ); housingMgr->updateHouseModels( house ); land->setHouse( house ); @@ -94,7 +145,9 @@ bool Sapphire::HousingZone::init() m_landPtrMap[ entry.m_landId ] = land; if( entry.m_houseId > 0 ) - registerHouseEntranceEObj( entry.m_landId ); + registerEstateEntranceEObj( entry.m_landId ); + + updateYardObjects( land->getLandIdent() ); } return true; @@ -109,6 +162,8 @@ void Sapphire::HousingZone::onPlayerZoneIn( Entity::Player& player ) "HousingZone::onPlayerZoneIn: Zone#" + std::to_string( getGuId() ) + "|" + std::to_string( getTerritoryTypeId() ) + ", Entity#" + std::to_string( player.getId() ) ); + auto isInSubdivision = isPlayerSubInstance( player ) ? true : false; + uint32_t yardPacketNum; uint32_t yardPacketTotal = 8; @@ -122,14 +177,20 @@ void Sapphire::HousingZone::onPlayerZoneIn( Entity::Player& player ) housingObjectInit->data().packetNum = yardPacketNum; housingObjectInit->data().packetTotal = yardPacketTotal; - //TODO: Add Objects here + auto yardObjectSize = sizeof( Common::HousingObject ); + + auto& objects = m_yardObjects[ isInSubdivision ? 1 : 0 ]; + + memcpy( &housingObjectInit->data().object, objects.data() + ( yardPacketNum * 100 ), yardObjectSize * 100 ); player.queuePacket( housingObjectInit ); } auto landSetMap = makeZonePacket< FFXIVIpcLandSetMap >( player.getId() ); - landSetMap->data().subdivision = !isPlayerSubInstance( player ) ? 2 : 1; - uint8_t startIndex = !isPlayerSubInstance( player ) ? 0 : 30; + landSetMap->data().subdivision = isInSubdivision ? 1 : 2; + + uint8_t startIndex = isInSubdivision ? 30 : 0; + for( uint8_t i = startIndex, count = 0; i < ( startIndex + 30 ); i++, count++ ) { landSetMap->data().landInfo[ count ].status = 1; @@ -252,16 +313,112 @@ Sapphire::LandPtr Sapphire::HousingZone::getLand( uint8_t id ) return it->second; } -Sapphire::Entity::EventObjectPtr Sapphire::HousingZone::registerHouseEntranceEObj( uint8_t plotId ) +Sapphire::Entity::EventObjectPtr Sapphire::HousingZone::registerEstateEntranceEObj( uint8_t landId ) { - auto land = getLand( plotId ); + auto land = getLand( landId ); assert( land ); auto eObj = Entity::make_EventObject( getNextEObjId(), 2002737, 0, 4, land->getMapMarkerPosition(), 0.f, "entrance" ); - eObj->setHousingLink( plotId << 8 ); + eObj->setHousingLink( landId << 8 ); eObj->setScale( 1.f ); registerEObj( eObj ); return eObj; +} + +void Sapphire::HousingZone::updateYardObjects( Sapphire::Common::LandIdent ident ) +{ + auto housingMgr = g_fw.get< World::Manager::HousingMgr >(); + auto& landStorage = housingMgr->getEstateInventory( ident ); + + auto yardContainer = landStorage[ InventoryType::HousingExteriorPlacedItems ]; + + auto arrayBounds = m_yardObjectArrayBounds[ ident.landId ]; + auto yardMapIndex = ident.landId <= 29 ? 0 : 1; + + for( const auto& item : yardContainer->getItemMap() ) + { + auto housingItem = std::dynamic_pointer_cast< Inventory::HousingItem >( item.second ); + assert( housingItem ); + + auto idx = item.first + arrayBounds.first; + m_yardObjects[ yardMapIndex ][ idx ] = housingMgr->getYardObjectForItem( housingItem ); + } +} + +void Sapphire::HousingZone::spawnYardObject( uint8_t landId, uint16_t slotId, Inventory::HousingItem& item ) +{ + auto bounds = m_yardObjectArrayBounds[ landId ]; + auto offset = bounds.first + slotId; + + Common::HousingObject obj {}; + + obj.itemId = item.getAdditionalData(); + obj.itemRotation = item.getRot(); + + obj.pos = item.getPos(); + + // link obj + auto yardMapIndex = landId <= 29 ? 0 : 1; + m_yardObjects[ yardMapIndex ][ offset ] = obj; + + // spawn obj in zone + for( const auto& player : m_playerMap ) + { + auto packet = makeZonePacket< Server::FFXIVIpcYardObjectSpawn >( player.second->getId() ); + + packet->data().landId = landId; + packet->data().objectArray = static_cast< uint8_t >( slotId ); + packet->data().object = obj; + + player.second->queuePacket( packet ); + } +} + +void Sapphire::HousingZone::updateYardObjectPos( Entity::Player& sourcePlayer, uint16_t slot, uint16_t landId, + Inventory::HousingItem& item ) +{ + auto bounds = m_yardObjectArrayBounds[ landId ]; + auto offset = bounds.first + slot; + auto yardMapIndex = landId <= 29 ? 0 : 1; + + auto& obj = m_yardObjects[ yardMapIndex ][ offset ]; + + obj.itemRotation = item.getRot(); + obj.pos = item.getPos(); + + for( const auto& player : m_playerMap ) + { + // don't send it from the origin player, it already has the position from the move request + if( player.second->getId() == sourcePlayer.getId() ) + continue; + + auto packet = makeZonePacket< Server::FFXIVIpcHousingObjectMove >( player.second->getId() ); + + packet->data().itemRotation = item.getRot(); + packet->data().pos = item.getPos(); + + packet->data().landId = landId; + packet->data().objectArray = slot; + + player.second->queuePacket( packet ); + } +} + +void Sapphire::HousingZone::despawnYardObject( uint16_t landId, uint16_t slot ) +{ + auto bounds = m_yardObjectArrayBounds[ landId ]; + auto offset = bounds.first + slot; + auto yardMapIndex = landId <= 29 ? 0 : 1; + + memset( &m_yardObjects[ yardMapIndex ][ offset ], 0x00, sizeof( Common::HousingObject ) ); + + for( const auto& player : m_playerMap ) + { + auto param = ( landId << 16 ) | slot; + auto pkt = Server::makeActorControl143( player.second->getId(), Network::ActorControl::RemoveExteriorHousingItem, param ); + + player.second->queuePacket( pkt ); + } } \ No newline at end of file diff --git a/src/world/Territory/HousingZone.h b/src/world/Territory/HousingZone.h index 3868f31a..8cfcef73 100644 --- a/src/world/Territory/HousingZone.h +++ b/src/world/Territory/HousingZone.h @@ -4,6 +4,8 @@ #include "Zone.h" #include "Forwards.h" +#include + namespace Sapphire { enum class LandPurchaseResult @@ -50,15 +52,40 @@ namespace Sapphire uint32_t getLandSetId() const; Sapphire::LandPtr getLand( uint8_t id ); - Entity::EventObjectPtr registerHouseEntranceEObj( uint8_t plotId ); + Entity::EventObjectPtr registerEstateEntranceEObj( uint8_t landId ); + + void updateYardObjects( Common::LandIdent ident ); + void spawnYardObject( uint8_t landId, uint16_t slotId, Sapphire::Inventory::HousingItem& item ); + void updateYardObjectPos( Entity::Player& sourcePlayer, uint16_t slot, uint16_t landId, + Inventory::HousingItem& item ); + void despawnYardObject( uint16_t landId, uint16_t slot ); private: using LandPtrMap = std::unordered_map< uint8_t, Sapphire::LandPtr >; + using YardObjectArray = std::array< Common::HousingObject, 800 >; + using YardObjectSubdivisionArray = std::array< YardObjectArray, 2 >; + + /*! + * @brief Maps the start and end index of the yard object array for a specific plot + * + * pair.first = start index + * pair.second = end index + */ + using YardObjectArrayBoundsPair = std::pair< uint16_t, uint16_t >; + + /*! + * @brief Maps each plot to a YardObjectArrayBoundsPair to the start/end index of the yard object array. + */ + using YardObjectArrayBoundsArray = std::array< YardObjectArrayBoundsPair, 60 >; + const uint32_t m_landSetMax = 18; LandPtrMap m_landPtrMap; uint8_t m_wardNum; uint32_t m_landSetId; uint32_t m_territoryTypeId; + + YardObjectSubdivisionArray m_yardObjects; + YardObjectArrayBoundsArray m_yardObjectArrayBounds; }; } diff --git a/src/world/Territory/Land.cpp b/src/world/Territory/Land.cpp index 4a1d075a..e9d6fa5a 100644 --- a/src/world/Territory/Land.cpp +++ b/src/world/Territory/Land.cpp @@ -70,43 +70,6 @@ void Sapphire::Land::init( Common::LandType type, uint8_t size, uint8_t state, u m_mapMarkerPosition.y = info->y; m_mapMarkerPosition.z = info->z; } - - switch( m_size ) - { - case HouseSize::Cottage: - m_maxPlacedExternalItems = 20; - m_maxPlacedInternalItems = 200; - break; - case HouseSize::House: - m_maxPlacedExternalItems = 30; - m_maxPlacedInternalItems = 300; - break; - case HouseSize::Mansion: - m_maxPlacedExternalItems = 40; - m_maxPlacedInternalItems = 400; - break; - default: - break; - } - - // init item containers -// auto setupContainer = [ this ]( InventoryType type, uint16_t maxSize ) -// { -// m_landInventoryMap[ type ] = make_ItemContainer( type, maxSize, "houseiteminventory", true, true ); -// }; -// -// setupContainer( InventoryType::HousingOutdoorAppearance, 8 ); -// setupContainer( InventoryType::HousingOutdoorPlacedItems, m_maxPlacedExternalItems ); -// setupContainer( InventoryType::HousingOutdoorStoreroom, m_maxPlacedExternalItems ); -// -// setupContainer( InventoryType::HousingInteriorAppearance, 9 ); -// -// // nb: so we're going to store these internally in one container because SE is fucked in the head -// // but when an inventory is requested, we will split them into groups of 50 -// setupContainer( InventoryType::HousingInteriorPlacedItems1, m_maxPlacedInternalItems ); -// setupContainer( InventoryType::HousingInteriorStoreroom1, m_maxPlacedInternalItems ); -// -// loadItemContainerContents(); } uint32_t Sapphire::Land::convertItemIdToHousingItemId( uint32_t itemId ) @@ -251,7 +214,7 @@ void Sapphire::Land::updateLandDb() uint32_t houseId = 0; if( getHouse() ) - houseId = getHouse()->getHouseId(); + houseId = getHouse()->getId(); // todo: change to prepared statement auto pDb = g_fw.get< Db::DbWorkerPool< Db::ZoneDbConnection > >(); @@ -281,54 +244,7 @@ void Sapphire::Land::update( uint32_t currTime ) } } -uint32_t Sapphire::Land::getNextHouseId() +Sapphire::Land::InvMaxItemsPair Sapphire::Land::getInventoryItemMax() const { - auto pDb = g_fw.get< Db::DbWorkerPool< Db::ZoneDbConnection > >(); - auto pQR = pDb->query( "SELECT MAX( HouseId ) FROM house" ); - - if( !pQR->next() ) - return 0; - - return pQR->getUInt( 1 ) + 1; -} - -bool Sapphire::Land::setPreset( uint32_t itemId ) -{ - auto housingItemId = convertItemIdToHousingItemId( itemId ); - - auto exdData = g_fw.get< Sapphire::Data::ExdDataGenerated >(); - if( !exdData ) - return false; - - auto housingPreset = exdData->get< Sapphire::Data::HousingPreset >( housingItemId ); - if( !housingPreset ) - return false; - - if( !getHouse() ) - { - // todo: i guess we'd create a house here? - auto newId = getNextHouseId(); - - m_pHouse = make_House( newId, getLandSetId(), getLandIdent(), "Estate #" + std::to_string( m_landIdent.landId + 1 ), "" ); - } - - - getHouse()->setHousePart( Common::HousePartSlot::ExteriorRoof, convertItemIdToHousingItemId( housingPreset->exteriorRoof ) ); - getHouse()->setHousePart( Common::HousePartSlot::ExteriorWall, convertItemIdToHousingItemId( housingPreset->exteriorWall ) ); - getHouse()->setHousePart( Common::HousePartSlot::ExteriorWindow, convertItemIdToHousingItemId( housingPreset->exteriorWindow ) ); - getHouse()->setHousePart( Common::HousePartSlot::ExteriorDoor, convertItemIdToHousingItemId( housingPreset->exteriorDoor ) ); - - getHouse()->setHouseInteriorPart( Common::HousingInteriorSlot::InteriorWall, convertItemIdToHousingItemId( housingPreset->interiorWall ) ); - getHouse()->setHouseInteriorPart( Common::HousingInteriorSlot::InteriorFloor, convertItemIdToHousingItemId( housingPreset->interiorFlooring ) ); - getHouse()->setHouseInteriorPart( Common::HousingInteriorSlot::InteriorLight, convertItemIdToHousingItemId( housingPreset->interiorLighting ) ); - getHouse()->setHouseInteriorPart( Common::HousingInteriorSlot::InteriorWall_Attic, convertItemIdToHousingItemId( housingPreset->otherFloorWall ) ); - getHouse()->setHouseInteriorPart( Common::HousingInteriorSlot::InteriorFloor_Attic, convertItemIdToHousingItemId( housingPreset->otherFloorFlooring ) ); - getHouse()->setHouseInteriorPart( Common::HousingInteriorSlot::InteriorLight_Attic, convertItemIdToHousingItemId( housingPreset->otherFloorLighting ) ); - getHouse()->setHouseInteriorPart( Common::HousingInteriorSlot::InteriorWall_Basement, convertItemIdToHousingItemId( housingPreset->basementWall ) ); - getHouse()->setHouseInteriorPart( Common::HousingInteriorSlot::InteriorFloor_Basement, convertItemIdToHousingItemId( housingPreset->basementFlooring ) ); - getHouse()->setHouseInteriorPart( Common::HousingInteriorSlot::InteriorLight_Basement, convertItemIdToHousingItemId( housingPreset->basementLighting ) ); - getHouse()->setHouseInteriorPart( Common::HousingInteriorSlot::InteriorLight_Mansion, convertItemIdToHousingItemId( housingPreset->mansionLighting ) ); - - - return true; + return std::make_pair( m_maxPlacedExternalItems, m_maxPlacedInternalItems ); } \ No newline at end of file diff --git a/src/world/Territory/Land.h b/src/world/Territory/Land.h index 19977e77..e80ff593 100644 --- a/src/world/Territory/Land.h +++ b/src/world/Territory/Land.h @@ -20,6 +20,7 @@ namespace Sapphire void init( Common::LandType type, uint8_t size, uint8_t state, uint32_t currentPrice, uint64_t ownerId, uint64_t houseId ); using LandInventoryMap = std::unordered_map< uint16_t, ItemContainerPtr >; + using InvMaxItemsPair = std::pair< uint16_t, uint16_t >; //Primary state void setSize( uint8_t size ); @@ -61,6 +62,8 @@ namespace Sapphire void setLandTag( uint8_t slot, uint8_t tag ); uint8_t getLandTag( uint8_t slot ); + InvMaxItemsPair getInventoryItemMax() const; + private: uint32_t convertItemIdToHousingItemId( uint32_t itemId ); uint32_t getNextHouseId();