From d3301b0b9f225f10e2516bdd78fbacdcb1ed577a Mon Sep 17 00:00:00 2001 From: NotAdam Date: Wed, 26 Dec 2018 18:11:18 +1100 Subject: [PATCH] add external housing object placing --- src/common/Database/ZoneDbConnection.cpp | 11 ++ src/common/Database/ZoneDbConnection.h | 2 + src/common/Network/PacketDef/Ipcs.h | 3 + .../Network/PacketDef/Zone/ClientZoneDef.h | 16 +++ .../Network/PacketDef/Zone/ServerZoneDef.h | 6 +- src/world/Actor/Player.h | 2 + src/world/Actor/PlayerInventory.cpp | 35 ++++- src/world/Inventory/ItemContainer.cpp | 8 +- src/world/Inventory/ItemContainer.h | 4 +- src/world/Manager/HousingMgr.cpp | 120 +++++++++++++++++- src/world/Manager/HousingMgr.h | 5 + src/world/Manager/InventoryMgr.cpp | 50 ++++++-- src/world/Manager/InventoryMgr.h | 13 ++ src/world/Network/GameConnection.cpp | 1 + src/world/Network/GameConnection.h | 2 + src/world/Network/Handlers/PacketHandlers.cpp | 11 ++ src/world/Territory/HousingZone.cpp | 33 +++++ src/world/Territory/HousingZone.h | 1 + 18 files changed, 300 insertions(+), 23 deletions(-) diff --git a/src/common/Database/ZoneDbConnection.cpp b/src/common/Database/ZoneDbConnection.cpp index 807fe35a..d708ecd9 100644 --- a/src/common/Database/ZoneDbConnection.cpp +++ b/src/common/Database/ZoneDbConnection.cpp @@ -236,6 +236,17 @@ void Sapphire::Db::ZoneDbConnection::doPrepareStatements() "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/PacketDef/Ipcs.h b/src/common/Network/PacketDef/Ipcs.h index 51a4c4bf..0273dc17 100644 --- a/src/common/Network/PacketDef/Ipcs.h +++ b/src/common/Network/PacketDef/Ipcs.h @@ -294,6 +294,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 +313,7 @@ namespace Sapphire::Network::Packets LandRenameHandler = 0x0171, // updated 4.4 HousingUpdateHouseGreeting = 0x0172, // updated 4.4 + HousingUpdateObjectRotation = 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..2cf8c863 100644 --- a/src/common/Network/PacketDef/Zone/ClientZoneDef.h +++ b/src/common/Network/PacketDef/Zone/ClientZoneDef.h @@ -240,6 +240,22 @@ 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 +}; + } } } diff --git a/src/common/Network/PacketDef/Zone/ServerZoneDef.h b/src/common/Network/PacketDef/Zone/ServerZoneDef.h index 8c7768a9..d359ae6c 100644 --- a/src/common/Network/PacketDef/Zone/ServerZoneDef.h +++ b/src/common/Network/PacketDef/Zone/ServerZoneDef.h @@ -1684,11 +1684,7 @@ struct FFXIVIpcYardObjectSpawn : FFXIVIpcBasePacket uint8_t landSetId; 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::YardObject object; }; struct FFXIVIpcYardObjectMove : FFXIVIpcBasePacket diff --git a/src/world/Actor/Player.h b/src/world/Actor/Player.h index b0c8b1cf..e620359f 100644 --- a/src/world/Actor/Player.h +++ b/src/world/Actor/Player.h @@ -919,6 +919,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..d908cbcf 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,36 @@ 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; +} \ No newline at end of file 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/Manager/HousingMgr.cpp b/src/world/Manager/HousingMgr.cpp index 1d49544e..91ccc0f7 100644 --- a/src/world/Manager/HousingMgr.cpp +++ b/src/world/Manager/HousingMgr.cpp @@ -27,6 +27,7 @@ #include "InventoryMgr.h" #include "Inventory/HousingItem.h" #include "Inventory/ItemContainer.h" +#include "Util/UtilMath.h" using namespace Sapphire::Common; using namespace Sapphire::Network; @@ -216,7 +217,7 @@ void Sapphire::World::Manager::HousingMgr::initLandCache() auto makeContainer = [ &containers ]( Common::InventoryType type, uint16_t size ) { - containers[ type ] = make_ItemContainer( type, size, "houseiteminventory", true ); + containers[ type ] = make_ItemContainer( type, size, "houseiteminventory", false ); }; uint16_t count = 0; @@ -901,11 +902,126 @@ uint32_t Sapphire::World::Manager::HousingMgr::getItemAdditionalData( uint32_t c bool Sapphire::World::Manager::HousingMgr::isPlacedItemsInventory( Sapphire::Common::InventoryType type ) { - return type == InventoryType::HousingExteriorPlacedItems || + 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; + } + // inside house + 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->getIdent(); + auto landSet = toLandSetId( ident.territoryTypeId, ident.wardNum ); + + land = getHousingZoneByLandSetId( landSet )->getLand( landId ); + } + // wtf? + else + return; + + if( !land ) + return; + + // todo: add proper permissions checks + if( land->getOwnerId() != player.getId() ) + return; + + player.sendDebug( "got item place request: "); + player.sendDebug( " - item: c: " + std::to_string( containerId ) + ", s: " + std::to_string( slotId ) ); + + // 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( pos ); + 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(); + + if( isOutside ) + { + if( !placeExternalItem( player, item, ident ) ) + player.sendUrgent( "An internal error occurred when placing the item." ); + } + else + { + player.sendUrgent( "you can't place internal items (yet)" ); + return; + } +} + +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() ); + + zone->spawnYardObject( ident.landId, freeSlot, item ); + + return true; } \ No newline at end of file diff --git a/src/world/Manager/HousingMgr.h b/src/world/Manager/HousingMgr.h index 311b58b4..a4291814 100644 --- a/src/world/Manager/HousingMgr.h +++ b/src/world/Manager/HousingMgr.h @@ -146,8 +146,13 @@ namespace Sapphire::World::Manager */ 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 ); + private: + bool placeExternalItem( Entity::Player& player, Inventory::HousingItemPtr item, Common::LandIdent ident ); + /*! * @brief Creates a house and saves the minimum amount required to persist a house through restarts. * diff --git a/src/world/Manager/InventoryMgr.cpp b/src/world/Manager/InventoryMgr.cpp index b9339b0c..4aacb24d 100644 --- a/src/world/Manager/InventoryMgr.cpp +++ b/src/world/Manager/InventoryMgr.cpp @@ -3,7 +3,7 @@ #include #include "Actor/Player.h" #include "Inventory/ItemContainer.h" -#include "Inventory/Item.h" +#include "Inventory/HousingItem.h" #include "Inventory/ItemUtil.h" #include #include @@ -79,14 +79,7 @@ Sapphire::ItemPtr Sapphire::World::Manager::InventoryMgr::createItem( Entity::Pl item->setStackSize( std::max< uint32_t >( 1, quantity ) ); - 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 ); + saveItem( player, item ); return item; } @@ -121,4 +114,43 @@ void Sapphire::World::Manager::InventoryMgr::saveHousingContainerItem( uint64_t 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->setDouble( 2, pos.x ); + stmt->setDouble( 3, pos.y ); + stmt->setDouble( 4, pos.z ); + + stmt->setInt( 5, rot ); + + stmt->setDouble( 6, pos.x ); + stmt->setDouble( 7, pos.y ); + stmt->setDouble( 8, pos.z ); + stmt->setInt( 9, rot ); + + pDb->execute( 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 c22c5342..466ff563 100644 --- a/src/world/Manager/InventoryMgr.h +++ b/src/world/Manager/InventoryMgr.h @@ -44,6 +44,19 @@ namespace Sapphire::World::Manager */ 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 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. diff --git a/src/world/Network/GameConnection.cpp b/src/world/Network/GameConnection.cpp index 25c42c9b..cf16b7db 100644 --- a/src/world/Network/GameConnection.cpp +++ b/src/world/Network/GameConnection.cpp @@ -88,6 +88,7 @@ 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::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..60710546 100644 --- a/src/world/Network/GameConnection.h +++ b/src/world/Network/GameConnection.h @@ -171,6 +171,8 @@ namespace Sapphire::Network DECLARE_HANDLER( tellHandler ); + DECLARE_HANDLER( reqPlaceHousingItem ); + }; } diff --git a/src/world/Network/Handlers/PacketHandlers.cpp b/src/world/Network/Handlers/PacketHandlers.cpp index 1d3f394a..e3207a63 100644 --- a/src/world/Network/Handlers/PacketHandlers.cpp +++ b/src/world/Network/Handlers/PacketHandlers.cpp @@ -705,3 +705,14 @@ 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 ); +} \ No newline at end of file diff --git a/src/world/Territory/HousingZone.cpp b/src/world/Territory/HousingZone.cpp index 99bcddc9..a1bbfb6d 100644 --- a/src/world/Territory/HousingZone.cpp +++ b/src/world/Territory/HousingZone.cpp @@ -353,4 +353,37 @@ void Sapphire::HousingZone::updateYardObjects( Sapphire::Common::LandIdent ident { } +} + +void Sapphire::HousingZone::spawnYardObject( uint8_t landId, uint16_t slotId, Inventory::HousingItemPtr item ) +{ + auto bounds = m_yardObjectArrayBounds[ landId ]; + auto offset = bounds.first + slotId; + + Common::YardObject obj {}; + + obj.itemId = item->getAdditionalData(); + obj.itemRotation = item->getRot(); + + auto pos = item->getPos(); + + obj.pos_x = Util::floatToUInt16( pos.x ); + obj.pos_y = Util::floatToUInt16( pos.y ); + obj.pos_z = Util::floatToUInt16( pos.z ); + + // link obj + uint8_t 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().landSetId = landId; + packet->data().objectArray = slotId; + packet->data().object = obj; + + player.second->queuePacket( packet ); + } } \ No newline at end of file diff --git a/src/world/Territory/HousingZone.h b/src/world/Territory/HousingZone.h index f3e88fc9..8b14f711 100644 --- a/src/world/Territory/HousingZone.h +++ b/src/world/Territory/HousingZone.h @@ -55,6 +55,7 @@ namespace Sapphire Entity::EventObjectPtr registerEstateEntranceEObj( uint8_t landId ); void updateYardObjects( Common::LandIdent ident ); + void spawnYardObject( uint8_t landId, uint16_t slotId, Sapphire::Inventory::HousingItemPtr item ); private: using LandPtrMap = std::unordered_map< uint8_t, Sapphire::LandPtr >;