diff --git a/src/common/Network/CommonActorControl.h b/src/common/Network/CommonActorControl.h index 965d816a..5cce81d3 100644 --- a/src/common/Network/CommonActorControl.h +++ b/src/common/Network/CommonActorControl.h @@ -333,6 +333,7 @@ enum ActorControlType : uint16_t RequestWardLandInfo = 0x453, RequestLandRelinquish = 0x454, RequestLandInventory = 0x0458, + RequestHousingItemRemove = 0x0459, RequestEstateRename = 0x45A, RequestEstateEditGreeting = 0x45B, RequestEstateGreeting = 0x45C, // sends FFXIVIpcHousingEstateGreeting in return diff --git a/src/common/Network/PacketDef/Zone/ClientZoneDef.h b/src/common/Network/PacketDef/Zone/ClientZoneDef.h index d42e21ed..87867f0a 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 housingParam; // todo: param4? + /* 0014 */ char unk_14[4]; /* 0018 */ uint64_t param3; }; diff --git a/src/world/Actor/Player.h b/src/world/Actor/Player.h index e620359f..3a59f726 100644 --- a/src/world/Actor/Player.h +++ b/src/world/Actor/Player.h @@ -365,6 +365,12 @@ namespace Sapphire::Entity Common::GearModelSlot equipSlotToModelSlot( Common::GearSetSlot slot ); + using InventoryContainerPair = std::pair< Common::InventoryType, uint8_t >; + + bool getFreeInventoryContainerSlot( Entity::Player::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 diff --git a/src/world/Actor/PlayerInventory.cpp b/src/world/Actor/PlayerInventory.cpp index d908cbcf..850c45e2 100644 --- a/src/world/Actor/PlayerInventory.cpp +++ b/src/world/Actor/PlayerInventory.cpp @@ -901,4 +901,38 @@ Sapphire::ItemPtr Sapphire::Entity::Player::dropInventoryItem( Sapphire::Common: queuePacket( invTransFinPacket ); return item; +} + +bool Sapphire::Entity::Player::getFreeInventoryContainerSlot( Entity::Player::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 ) +{ + auto& container = m_storageMap[ type ]; + container->setItem( 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/Manager/HousingMgr.cpp b/src/world/Manager/HousingMgr.cpp index 1fe8071f..7d47d347 100644 --- a/src/world/Manager/HousingMgr.cpp +++ b/src/world/Manager/HousingMgr.cpp @@ -1126,6 +1126,8 @@ void Sapphire::World::Manager::HousingMgr::sendInternalEstateInventoryBatch( Sap if( !zone ) return; + // todo: perms check + InventoryTypeList containerIds; if( storeroom ) @@ -1133,8 +1135,6 @@ void Sapphire::World::Manager::HousingMgr::sendInternalEstateInventoryBatch( Sap else containerIds = m_internalPlacedItemContainers; - // todo: perms check - auto invMgr = g_fw.get< Manager::InventoryMgr >(); auto& containers = getEstateInventory( zone->getLandIdent() ); @@ -1267,4 +1267,111 @@ bool Sapphire::World::Manager::HousingMgr::moveExternalItem( Entity::Player& pla 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; + + removeExternalItem( player, *terri, containerId, 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 + 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; + + item->setStackSize( 1 ); + + if( !sendToStoreroom ) + { + // make sure the player has a free inv slot first + Entity::Player::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 + + // despawn + auto arraySlot = ( containerIdx * 50 ) + slotId; + + terri.removeHousingObject( arraySlot ); + } + + return true; +} + +bool Sapphire::World::Manager::HousingMgr::removeExternalItem( Entity::Player& player, + HousingZone& terri, + uint16_t containerId, uint16_t slotId, + bool sendToStoreroom ) +{ + } \ No newline at end of file diff --git a/src/world/Manager/HousingMgr.h b/src/world/Manager/HousingMgr.h index 5fc3dc56..53c42362 100644 --- a/src/world/Manager/HousingMgr.h +++ b/src/world/Manager/HousingMgr.h @@ -170,12 +170,40 @@ namespace Sapphire::World::Manager Common::FFXIVARR_POSITION3 pos, float rot ); + void reqRemoveHousingItem( Sapphire::Entity::Player& player, uint16_t plot, + uint16_t containerId, uint16_t slot, + bool sendToStoreroom ); + private: + /*! + * + * @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 containerId + * @param slotId + * @param sendToStoreroom + * @return + */ + bool removeExternalItem( Entity::Player& player, HousingZone& terri, + uint16_t containerId, uint16_t slotId, bool sendToStoreroom ); + /*! * @brief Processes the movement of an item placed in a HousingZone * - * This assumes that the player has permission to move the item. + * 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 @@ -183,7 +211,7 @@ namespace Sapphire::World::Manager * @param slot The slot of the item * @param pos The new position * @param rot The new rotation - * @return + * @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 ); @@ -191,7 +219,7 @@ namespace Sapphire::World::Manager /*! * @brief Processes the movement of an item placed inside a HousingInteriorTerritory * - * This assumes that the player has permission to move the item. + * 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 @@ -199,7 +227,7 @@ namespace Sapphire::World::Manager * @param slotIdx The slot of the item * @param pos The new position * @param rot The new rotation - * @return + * @return true if moved successfully */ bool moveInternalItem( Entity::Player& player, Common::LandIdent ident, Territory::Housing::HousingInteriorTerritory& terri, uint16_t slot, diff --git a/src/world/Manager/InventoryMgr.cpp b/src/world/Manager/InventoryMgr.cpp index f5b59620..81c117e0 100644 --- a/src/world/Manager/InventoryMgr.cpp +++ b/src/world/Manager/InventoryMgr.cpp @@ -95,6 +95,23 @@ void Sapphire::World::Manager::InventoryMgr::saveHousingContainer( Common::LandI } } +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 ) @@ -141,6 +158,17 @@ void Sapphire::World::Manager::InventoryMgr::updateHousingItemPosition( Sapphire 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 > >(); diff --git a/src/world/Manager/InventoryMgr.h b/src/world/Manager/InventoryMgr.h index 466ff563..b98170c7 100644 --- a/src/world/Manager/InventoryMgr.h +++ b/src/world/Manager/InventoryMgr.h @@ -38,6 +38,14 @@ namespace Sapphire::World::Manager */ 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 @@ -50,6 +58,12 @@ namespace Sapphire::World::Manager */ 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 diff --git a/src/world/Network/Handlers/ClientTriggerHandler.cpp b/src/world/Network/Handlers/ClientTriggerHandler.cpp index 0140b78d..18841020 100644 --- a/src/world/Network/Handlers/ClientTriggerHandler.cpp +++ b/src/world/Network/Handlers/ClientTriggerHandler.cpp @@ -79,12 +79,14 @@ 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 housingParam = packet.data().housingParam; 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 ) + + "\nhousingParam: " + Util::intToHexString( static_cast< uint32_t >( housingParam & 0xFFFFFFFF ), 8 ) ); @@ -454,6 +456,18 @@ void Sapphire::Network::GameConnection::clientTriggerHandler( const Packets::FFX break; } + case ClientTriggerType::RequestHousingItemRemove: + { + auto housingMgr = g_fw.get< HousingMgr >(); + + auto slot = housingParam & 0xFF; + auto sendToStoreroom = ( housingParam >> 16 ) != 0; + + //player, plot, containerId, slot, sendToStoreroom + housingMgr->reqRemoveHousingItem( player, param12, param2, slot, sendToStoreroom ); + + break; + } default: { diff --git a/src/world/Territory/Housing/HousingInteriorTerritory.cpp b/src/world/Territory/Housing/HousingInteriorTerritory.cpp index 064ba620..b1be765a 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" @@ -195,4 +197,16 @@ void Sapphire::World::Territory::Housing::HousingInteriorTerritory::updateHousin obj.itemRotation = rot; // todo: how does this update on other clients? +} + +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 f77fc49a..3fda4add 100644 --- a/src/world/Territory/Housing/HousingInteriorTerritory.h +++ b/src/world/Territory/Housing/HousingInteriorTerritory.h @@ -28,6 +28,7 @@ namespace Sapphire::World::Territory::Housing void spawnHousingObject( uint8_t containerIdx, uint16_t slot, uint16_t containerType, Inventory::HousingItemPtr item ); void updateHousingObjectPosition( uint16_t slot, Common::FFXIVARR_POSITION3_U16 pos, uint16_t rot ); + void removeHousingObject( uint16_t slot ); private: Common::LandIdent m_landIdent;