diff --git a/src/common/Network/PacketDef/Ipcs.h b/src/common/Network/PacketDef/Ipcs.h index 56f8e6a0..18943f97 100644 --- a/src/common/Network/PacketDef/Ipcs.h +++ b/src/common/Network/PacketDef/Ipcs.h @@ -69,11 +69,11 @@ namespace Packets { SocialRequestError = 0x00AD, - Playtime = 0x00DF, // updated 4.2 CFRegistered = 0x00B8, // updated 4.1 SocialRequestResponse = 0x00BB, // updated 4.1 CancelAllianceForming = 0x00C6, // updated 4.2 + Playtime = 0x00F5, // updated 4.3 Chat = 0x00F7, // updated 4.3 SocialList = 0x00FD, // updated 4.3 diff --git a/src/servers/sapphire_zone/Inventory/Inventory.cpp b/src/servers/sapphire_zone/Inventory/Inventory.cpp index 2d475021..a4d5e015 100644 --- a/src/servers/sapphire_zone/Inventory/Inventory.cpp +++ b/src/servers/sapphire_zone/Inventory/Inventory.cpp @@ -128,7 +128,7 @@ Core::ItemPtr Core::Inventory::getItemAt( uint16_t containerId, uint8_t slotId ) return m_inventoryMap[containerId]->getItem( slotId ); } -Core::ItemPtr Core::Inventory::createItem( uint32_t catalogId, uint8_t quantity ) +Core::ItemPtr Core::Inventory::createItem( uint32_t catalogId, uint16_t quantity ) { auto pExdData = g_fw.get< Data::ExdDataGenerated >(); auto pDb = g_fw.get< Db::DbWorkerPool< Db::CharaDbConnection > >(); @@ -144,7 +144,7 @@ Core::ItemPtr Core::Inventory::createItem( uint32_t catalogId, uint8_t quantity uint8_t flags = 0; - std::string itemName( itemInfo->name ); +// std::string itemName( itemInfo->name ); ItemPtr pItem( new Item( catalogId ) ); @@ -311,7 +311,7 @@ void Core::Inventory::updateBagDb( InventoryType type ) bool Core::Inventory::isArmory( uint16_t containerId ) { - return + return containerId == ArmoryBody || containerId == ArmoryEar || containerId == ArmoryFeet || @@ -404,6 +404,12 @@ void Core::Inventory::updateItemDb( Core::ItemPtr pItem ) const " WHERE itemId = " + std::to_string( pItem->getUId() ) ); } +void Core::Inventory::deleteItemDb( Core::ItemPtr item ) const +{ + auto pDb = g_fw.get< Db::DbWorkerPool< Db::CharaDbConnection > >(); + pDb->execute( "UPDATE charaglobalitem SET IS_DELETE = 1 WHERE itemId = " + std::to_string( item->getUId() ) ); +} + bool Core::Inventory::removeCurrency( CurrencyType type, uint32_t amount ) { @@ -468,12 +474,12 @@ bool Core::Inventory::isOneHandedWeapon( ItemUICategory weaponCategory ) bool Core::Inventory::isObtainable( uint32_t catalogId, uint8_t quantity ) { - + return true; } -int16_t Core::Inventory::addItem( uint16_t inventoryId, int8_t slotId, uint32_t catalogId, uint8_t quantity ) +int16_t Core::Inventory::addItem( uint16_t inventoryId, int8_t slotId, uint32_t catalogId, uint16_t quantity, bool isHq, bool silent ) { auto pDb = g_fw.get< Db::DbWorkerPool< Db::CharaDbConnection > >(); auto pExdData = g_fw.get< Data::ExdDataGenerated >(); @@ -504,7 +510,9 @@ int16_t Core::Inventory::addItem( uint16_t inventoryId, int8_t slotId, uint32_t } auto item = createItem( catalogId, quantity ); - + + item->setHq( isHq ); + if( rSlotId != -1 ) { @@ -523,7 +531,8 @@ int16_t Core::Inventory::addItem( uint16_t inventoryId, int8_t slotId, uint32_t invUpPacket.data().condition = 30000; m_pOwner->queuePacket( invUpPacket ); - m_pOwner->queuePacket( ActorControlPacket143( m_pOwner->getId(), ItemObtainIcon, catalogId, item->getStackSize() ) ); + if( !silent ) + m_pOwner->queuePacket( ActorControlPacket143( m_pOwner->getId(), ItemObtainIcon, catalogId, item->getStackSize() ) ); } @@ -598,6 +607,71 @@ bool Core::Inventory::updateContainer( uint16_t containerId, uint8_t slotId, Ite return true; } +void Core::Inventory::splitItem( uint16_t fromInventoryId, uint8_t fromSlotId, uint16_t toInventoryId, uint8_t toSlot, uint16_t itemCount ) +{ + auto fromItem = m_inventoryMap[fromInventoryId]->getItem( fromSlotId ); + if( !fromItem ) + return; + + // check we have enough items in the origin slot + // nb: don't let the client 'split' a whole stack into another slot + if( fromItem->getStackSize() < itemCount ) + // todo: correct the invalid item split? does retail do this or does it just ignore it? + return; + + // make sure toInventoryId & toSlot are actually free so we don't orphan an item + if( m_inventoryMap[toInventoryId]->getItem( toSlot ) ) + // todo: correct invalid move? again, not sure what retail does here + return; + + auto newSlot = addItem( toInventoryId, toSlot, fromItem->getId(), itemCount, fromItem->isHq(), true ); + if( newSlot == -1 ) + return; + + auto newItem = m_inventoryMap[toInventoryId]->getItem( static_cast< uint8_t >( newSlot ) ); + + fromItem->setStackSize( fromItem->getStackSize() - itemCount ); + + updateContainer( fromInventoryId, fromSlotId, fromItem ); + updateContainer( toInventoryId, toSlot, newItem ); + + updateItemDb( fromItem ); +} + +void Core::Inventory::mergeItem( uint16_t fromInventoryId, uint8_t fromSlotId, uint16_t toInventoryId, uint8_t toSlot ) +{ + auto fromItem = m_inventoryMap[fromInventoryId]->getItem( fromSlotId ); + auto toItem = m_inventoryMap[toInventoryId]->getItem( toSlot ); + + if( !fromItem || !toItem ) + return; + + if( fromItem->getId() != toItem->getId() ) + return; + + uint32_t stackSize = fromItem->getStackSize() + toItem->getStackSize(); + uint32_t stackOverflow = stackSize - std::min< uint32_t >( m_maxSlotSize, stackSize ); + + // we can destroy the original stack if there's no overflow + if( stackOverflow == 0 ) + { + m_inventoryMap[fromInventoryId]->removeItem( fromSlotId ); + deleteItemDb( fromItem ); + } + else + { + fromItem->setStackSize( stackOverflow ); + updateItemDb( fromItem ); + } + + + toItem->setStackSize( stackSize ); + updateItemDb( toItem ); + + updateContainer( fromInventoryId, fromSlotId, fromItem ); + updateContainer( toInventoryId, toSlot, toItem ); +} + void Core::Inventory::swapItem( uint16_t fromInventoryId, uint8_t fromSlotId, uint16_t toInventoryId, uint8_t toSlot ) { auto fromItem = m_inventoryMap[fromInventoryId]->getItem( fromSlotId ); @@ -609,7 +683,7 @@ void Core::Inventory::swapItem( uint16_t fromInventoryId, uint8_t fromSlotId, ui // An item is being moved from bag0-3 to equippment, meaning // the swapped out item will be placed in the matching armory. - if( isEquipment( toInventoryId ) + if( isEquipment( toInventoryId ) && !isEquipment( fromInventoryId ) && !isArmory( fromInventoryId ) ) { @@ -631,6 +705,8 @@ void Core::Inventory::discardItem( uint16_t fromInventoryId, uint8_t fromSlotId uint32_t transactionId = 1; auto fromItem = m_inventoryMap[fromInventoryId]->getItem( fromSlotId ); + + deleteItemDb( fromItem ); m_inventoryMap[fromInventoryId]->removeItem( fromSlotId ); updateContainer( fromInventoryId, fromSlotId, nullptr ); @@ -655,7 +731,7 @@ Core::ItemPtr Core::Inventory::loadItem( uint64_t uId ) { auto pExdData = g_fw.get< Data::ExdDataGenerated >(); auto pDb = g_fw.get< Db::DbWorkerPool< Db::CharaDbConnection > >(); - // load actual item + // load actual item auto itemRes = pDb->query( "SELECT catalogId, stack, flags FROM charaglobalitem WHERE itemId = " + std::to_string( uId ) + ";" ); if( !itemRes->next() ) return nullptr; @@ -664,10 +740,10 @@ Core::ItemPtr Core::Inventory::loadItem( uint64_t uId ) { auto itemInfo = pExdData->get< Core::Data::Item >( itemRes->getUInt( 1 ) ); bool isHq = itemRes->getUInt( 3 ) == 1 ? true : false; - ItemPtr pItem( new Item( uId, + ItemPtr pItem( new Item( uId, itemRes->getUInt( 1 ), itemInfo->modelMain, - itemInfo->modelSub, + itemInfo->modelSub, static_cast< ItemUICategory >( itemInfo->itemUICategory ), isHq ) ); pItem->setStackSize( itemRes->getUInt( 2 ) ); diff --git a/src/servers/sapphire_zone/Inventory/Inventory.h b/src/servers/sapphire_zone/Inventory/Inventory.h index 997e840c..082fbc2d 100644 --- a/src/servers/sapphire_zone/Inventory/Inventory.h +++ b/src/servers/sapphire_zone/Inventory/Inventory.h @@ -140,12 +140,14 @@ public: InvSlotPairVec getSlotsOfItemsInInventory( uint32_t catalogId ); InvSlotPair getFreeBagSlot(); - int16_t addItem( uint16_t inventoryId, int8_t slotId, uint32_t catalogId, uint8_t quantity = 1 ); + int16_t addItem( uint16_t inventoryId, int8_t slotId, uint32_t catalogId, uint16_t quantity = 1, bool isHq = false, bool silent = false ); void moveItem( uint16_t fromInventoryId, uint8_t fromSlotId, uint16_t toInventoryId, uint8_t toSlot ); void swapItem( uint16_t fromInventoryId, uint8_t fromSlotId, uint16_t toInventoryId, uint8_t toSlot ); void discardItem( uint16_t fromInventoryId, uint8_t fromSlotId ); + void splitItem( uint16_t fromInventoryId, uint8_t fromSlotId, uint16_t toInventoryId, uint8_t toSlot, uint16_t splitCount ); + void mergeItem( uint16_t fromInventoryId, uint8_t fromSlotId, uint16_t toInventoryId, uint8_t toSlot ); - ItemPtr createItem( uint32_t catalogId, uint8_t quantity = 1 ); + ItemPtr createItem( uint32_t catalogId, uint16_t quantity = 1 ); ItemPtr loadItem( uint64_t uId ); @@ -168,6 +170,7 @@ public: void updateBagDb( InventoryType type ); void updateMannequinDb( InventoryType type ); void updateItemDb( ItemPtr pItem ) const; + void deleteItemDb( ItemPtr pItem ) const; bool isArmory( uint16_t containerId ); bool isEquipment( uint16_t containerId ); @@ -197,6 +200,7 @@ public: private: Entity::Player* m_pOwner; InventoryMap m_inventoryMap; + const uint32_t m_maxSlotSize = 999; }; } diff --git a/src/servers/sapphire_zone/Network/Handlers/InventoryHandler.cpp b/src/servers/sapphire_zone/Network/Handlers/InventoryHandler.cpp index 63c09215..71ec3f2e 100644 --- a/src/servers/sapphire_zone/Network/Handlers/InventoryHandler.cpp +++ b/src/servers/sapphire_zone/Network/Handlers/InventoryHandler.cpp @@ -47,6 +47,8 @@ void Core::Network::GameConnection::inventoryModifyHandler( const Packets::GameP uint8_t toSlot = inPacket.getValAt< uint8_t >( 0x44 ); uint16_t fromContainer = inPacket.getValAt< uint16_t >( 0x2C ); uint16_t toContainer = inPacket.getValAt< uint16_t >( 0x40 ); + // todo: check packet handler in game and see if this is sent as a u16 or u32 + uint16_t splitCount = inPacket.getValAt< uint16_t >( 0x48 ); ZoneChannelPacket< FFXIVIpcInventoryActionAck > ackPacket( player.getId() ); ackPacket.data().sequence = seq; @@ -83,13 +85,13 @@ void Core::Network::GameConnection::inventoryModifyHandler( const Packets::GameP case InventoryOperation::Merge: // merge stack action { - + player.getInventory()->mergeItem( fromContainer, fromSlot, toContainer, toSlot ); } break; case InventoryOperation::Split: // split stack action { - + player.getInventory()->splitItem( fromContainer, fromSlot, toContainer, toSlot, splitCount ); } break;