From b70435413e15075822b3a08c40685ffab821057b Mon Sep 17 00:00:00 2001 From: collett Date: Mon, 11 May 2020 06:25:25 +0900 Subject: [PATCH] Selling items and buying back from shop, show corresponding messages in chat. --- src/common/Network/PacketDef/Ipcs.h | 4 +- .../Network/PacketDef/Zone/ServerZoneDef.h | 15 +++- src/scripts/common/GilShop.cpp | 16 ++-- src/world/Actor/Player.cpp | 17 ++++ src/world/Actor/Player.h | 18 +++- src/world/Actor/PlayerEvent.cpp | 41 +++++++-- src/world/Actor/PlayerInventory.cpp | 52 ++++++----- src/world/Manager/ShopMgr.cpp | 88 +++++++++++++++++++ src/world/Manager/ShopMgr.h | 2 + src/world/Network/Handlers/EventHandlers.cpp | 4 +- .../UpdateInventorySlotPacket.h | 15 ++++ 11 files changed, 232 insertions(+), 40 deletions(-) diff --git a/src/common/Network/PacketDef/Ipcs.h b/src/common/Network/PacketDef/Ipcs.h index 8bbd0a01..286cb736 100644 --- a/src/common/Network/PacketDef/Ipcs.h +++ b/src/common/Network/PacketDef/Ipcs.h @@ -209,7 +209,7 @@ namespace Sapphire::Network::Packets DirectorVars = 0x00E6, // updated 5.18 SomeDirectorUnk1 = 0x0084, // updated 5.18 - SomeDirectorUnk2 = 0x00C1, // updated 5.18 + SomeDirectorUnk2 = 0xF0C1, // updated 5.18 SomeDirectorUnk4 = 0x01F3, // updated 5.21 hotfix SomeDirectorUnk8 = 0x028A, // updated 5.18 SomeDirectorUnk16 = 0x028C, // updated 5.18 @@ -376,6 +376,8 @@ namespace Sapphire::Network::Packets PerformNoteHandler = 0x029B, // updated 4.3 + + ShopMessage = 0x00C1, // updated 5.25 }; //////////////////////////////////////////////////////////////////////////////// diff --git a/src/common/Network/PacketDef/Zone/ServerZoneDef.h b/src/common/Network/PacketDef/Zone/ServerZoneDef.h index ee87644c..d1c8edbd 100644 --- a/src/common/Network/PacketDef/Zone/ServerZoneDef.h +++ b/src/common/Network/PacketDef/Zone/ServerZoneDef.h @@ -1411,7 +1411,8 @@ namespace Sapphire::Network::Packets::Server uint16_t scene; uint16_t padding; uint32_t sceneFlags; - uint8_t paramCount; + uint32_t unknown; + uint8_t paramSize; uint8_t padding2[3]; uint32_t params[ArgCount]; }; @@ -2005,6 +2006,18 @@ namespace Sapphire::Network::Packets::Server uint32_t unknown2; }; + struct FFXIVIpcShopMessage : FFXIVIpcBasePacket< ShopMessage > + { + uint32_t shopId; + uint32_t msgType; + uint32_t unknown2; + uint32_t itemId; + uint32_t amount; + uint32_t price; + uint32_t unknown6; + uint32_t unknown7; + }; + } #endif /*_CORE_NETWORK_PACKETS_SERVER_IPC_H*/ diff --git a/src/scripts/common/GilShop.cpp b/src/scripts/common/GilShop.cpp index 93b93d9f..d18047c2 100644 --- a/src/scripts/common/GilShop.cpp +++ b/src/scripts/common/GilShop.cpp @@ -25,23 +25,27 @@ public: private: void shopInteractionCallback( Entity::Player& player, const Event::SceneResult& result ) { - // item purchase - if( result.param1 == 768 ) + // buy, sell, buy back + if( result.param1 == 768 || result.param1 == 512 ) { // buy if( result.param2 == 1 ) { auto& shopMgr = Common::Service< Sapphire::World::Manager::ShopMgr >::ref(); - - shopMgr.purchaseGilShopItem( player, result.eventId, result.param3, result.param4 ); } - // sell // can't sell if the vendor is yourself (eg, housing permit shop) else if( result.param2 == 2 && result.actorId != player.getId() ) { - + auto& shopMgr = Common::Service< Sapphire::World::Manager::ShopMgr >::ref(); + shopMgr.shopSellItem( player, result.eventId, result.param3, result.param4 ); + } + //buy back + else if( result.param2 == 3 && result.actorId != player.getId() ) + { + auto& shopMgr = Common::Service< Sapphire::World::Manager::ShopMgr >::ref(); + shopMgr.shopBuyBack( player, result.eventId, result.param3 ); } player.playGilShop( result.eventId, SCENE_FLAGS, result.param2, std::bind( &GilShop::shopInteractionCallback, this, std::placeholders::_1, std::placeholders::_2 ) ); diff --git a/src/world/Actor/Player.cpp b/src/world/Actor/Player.cpp index 691d90a3..15c30e93 100644 --- a/src/world/Actor/Player.cpp +++ b/src/world/Actor/Player.cpp @@ -1330,6 +1330,7 @@ void Sapphire::Entity::Player::performZoning( uint16_t zoneId, const Common::FFX m_bMarkedForZoning = true; setRot( rotation ); setZone( zoneId ); + clearBuyBackMap(); } bool Sapphire::Entity::Player::isMarkedForZoning() const @@ -2245,6 +2246,22 @@ bool Sapphire::Entity::Player::checkAction() return true; } +std::vector< Sapphire::Entity::ShopBuyBackEntry >& Sapphire::Entity::Player::getBuyBackListForShop( uint32_t shopId ) +{ + return m_shopBuyBackMap[ shopId ]; +} + +void Sapphire::Entity::Player::addBuyBackItemForShop( uint32_t shopId, const Sapphire::Entity::ShopBuyBackEntry& entry ) +{ + auto& list = m_shopBuyBackMap[ shopId ]; + list.insert( list.begin(), entry ); +} + +void Sapphire::Entity::Player::clearBuyBackMap() +{ + m_shopBuyBackMap.clear(); +} + void Sapphire::Entity::Player::gaugeClear() { std::memset( &m_gauge, 0, sizeof( m_gauge ) ); diff --git a/src/world/Actor/Player.h b/src/world/Actor/Player.h index 72325649..35f602a0 100644 --- a/src/world/Actor/Player.h +++ b/src/world/Actor/Player.h @@ -33,6 +33,13 @@ namespace Sapphire::Entity } }; + struct ShopBuyBackEntry + { + ItemPtr item; + uint32_t amount; + uint32_t value; + }; + /** Class representing the Player * Inheriting from Actor * @@ -921,7 +928,8 @@ namespace Sapphire::Entity InvSlotPair getFreeBagSlot(); - Sapphire::ItemPtr addItem( uint32_t catalogId, uint32_t quantity = 1, bool isHq = false, bool slient = false, bool canMerge = true ); + ItemPtr addItem( uint32_t catalogId, uint32_t quantity = 1, bool isHq = false, bool slient = false, bool canMerge = true ); + ItemPtr addItem( ItemPtr itemToAdd, bool slient = false, bool canMerge = true ); void moveItem( uint16_t fromInventoryId, uint8_t fromSlotId, uint16_t toInventoryId, uint8_t toSlot ); @@ -972,7 +980,7 @@ namespace Sapphire::Entity void setActiveLand( uint8_t land, uint8_t ward ); Common::ActiveLand getActiveLand() const; - Sapphire::ItemPtr dropInventoryItem( Common::InventoryType type, uint16_t slotId ); + Sapphire::ItemPtr dropInventoryItem( Common::InventoryType type, uint16_t slotId, bool slient = false ); // Job UI ////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -996,6 +1004,10 @@ namespace Sapphire::Entity uint8_t m_lastMoveflag; bool m_falling; + std::vector< ShopBuyBackEntry >& getBuyBackListForShop( uint32_t shopId ); + void addBuyBackItemForShop( uint32_t shopId, const ShopBuyBackEntry& entry ); + void clearBuyBackMap(); + private: uint32_t m_lastWrite; uint32_t m_lastPing; @@ -1137,7 +1149,7 @@ namespace Sapphire::Entity Common::Util::SpawnIndexAllocator< uint8_t > m_actorSpawnIndexAllocator; std::array< Common::HuntingLogEntry, 12 > m_huntingLogEntries; - + std::unordered_map< uint32_t, std::vector< ShopBuyBackEntry > > m_shopBuyBackMap; }; } diff --git a/src/world/Actor/PlayerEvent.cpp b/src/world/Actor/PlayerEvent.cpp index a0189b8b..7c7c888b 100644 --- a/src/world/Actor/PlayerEvent.cpp +++ b/src/world/Actor/PlayerEvent.cpp @@ -12,6 +12,7 @@ #include "Network/PacketWrappers/EventFinishPacket.h" #include "Network/PacketWrappers/DirectorPlayScenePacket.h" +#include "Inventory/Item.h" #include "Territory/Territory.h" #include "ServerMgr.h" @@ -147,19 +148,49 @@ void Sapphire::Entity::Player::playGilShop( uint32_t eventId, uint32_t flags, ui openGilShopPacket->data().actorId = getId(); switch( param1 ) { + case 0: + { + openGilShopPacket->data().paramSize = 0xA1; + break; + } case 1: { - openGilShopPacket->data().params[ 0 ] = 0x02; - openGilShopPacket->data().params[ 1 ] = 1; - openGilShopPacket->data().params[ 2 ] = 0x64; + openGilShopPacket->data().paramSize = 0x02; + openGilShopPacket->data().params[ 0 ] = 1; + openGilShopPacket->data().params[ 1 ] = 0x64; break; } case 2: { - openGilShopPacket->data().params[ 0 ] = 0xA2; - openGilShopPacket->data().params[ 1 ] = 2; + openGilShopPacket->data().paramSize = 0xA2; + openGilShopPacket->data().params[ 0 ] = 2; break; } + case 3: + { + openGilShopPacket->data().paramSize = 0xA2; + openGilShopPacket->data().params[ 0 ] = 3; + openGilShopPacket->data().params[ 1 ] = 0x64; + break; + } + } + + auto& buyBackList = getBuyBackListForShop( eventId ); + int index = param1 == 0 ? 1 : 2; + for( auto& entry : buyBackList ) + { + if( index >= openGilShopPacket->data().paramSize ) + break; + openGilShopPacket->data().params[ index++ ] = entry.item->getId(); + openGilShopPacket->data().params[ index++ ] = entry.amount; + openGilShopPacket->data().params[ index++ ] = entry.value; + index += 2; + openGilShopPacket->data().params[ index++ ] = eventId; + index += 2; + openGilShopPacket->data().params[ index++ ] = ( ( entry.item->getDurability() << 16 ) + static_cast< uint16_t >( entry.item->isHq() ? 1 : 0 ) ); + openGilShopPacket->data().params[ index++ ] = ( ( entry.item->getStain() << 16 ) + entry.item->getSpiritbond() ); + openGilShopPacket->data().params[ index++ ] = 0; // glamour + index += 5; } openGilShopPacket->data().scene = 10; diff --git a/src/world/Actor/PlayerInventory.cpp b/src/world/Actor/PlayerInventory.cpp index d10c7c66..5f597444 100644 --- a/src/world/Actor/PlayerInventory.cpp +++ b/src/world/Actor/PlayerInventory.cpp @@ -521,15 +521,13 @@ void Sapphire::Entity::Player::deleteItemDb( Sapphire::ItemPtr item ) const bool Sapphire::Entity::Player::isObtainable( uint32_t catalogId, uint8_t quantity ) { - return true; } - -Sapphire::ItemPtr Sapphire::Entity::Player::addItem( uint32_t catalogId, uint32_t quantity, bool isHq, bool silent, bool canMerge ) +Sapphire::ItemPtr Sapphire::Entity::Player::addItem( ItemPtr itemToAdd, bool silent, bool canMerge ) { auto& exdData = Common::Service< Data::ExdDataGenerated >::ref(); - auto itemInfo = exdData.get< Sapphire::Data::Item >( catalogId ); + auto itemInfo = exdData.get< Sapphire::Data::Item >( itemToAdd->getId() ); // if item data doesn't exist or it's a blank field if( !itemInfo || itemInfo->levelItem == 0 ) @@ -537,10 +535,10 @@ Sapphire::ItemPtr Sapphire::Entity::Player::addItem( uint32_t catalogId, uint32_ return nullptr; } - quantity = std::min< uint32_t >( quantity, itemInfo->stackSize ); + itemToAdd->setStackSize( std::min< uint32_t >( itemToAdd->getStackSize(), itemInfo->stackSize ) ); // used for item obtain notification - uint32_t originalQuantity = quantity; + uint32_t originalQuantity = itemToAdd->getStackSize(); std::pair< uint16_t, uint8_t > freeBagSlot; bool foundFreeSlot = false; @@ -567,7 +565,7 @@ Sapphire::ItemPtr Sapphire::Entity::Player::addItem( uint32_t catalogId, uint32_ auto item = storage->getItem( slot ); // add any items that are stackable - if( canMerge && item && !itemInfo->isEquippable && item->getId() == catalogId ) + if( canMerge && item && !itemInfo->isEquippable && item->getId() == itemToAdd->getId() ) { uint32_t count = item->getStackSize(); uint32_t maxStack = item->getMaxStackSize(); @@ -577,18 +575,18 @@ Sapphire::ItemPtr Sapphire::Entity::Player::addItem( uint32_t catalogId, uint32_ continue; // check slot is same quality - if( item->isHq() != isHq ) + if( item->isHq() != itemToAdd->isHq() ) continue; // update stack - uint32_t newStackSize = count + quantity; + uint32_t newStackSize = count + itemToAdd->getStackSize(); if( newStackSize > maxStack ) { - quantity = newStackSize - maxStack; + itemToAdd->setStackSize( newStackSize - maxStack ); newStackSize = maxStack; } else - quantity = 0; + itemToAdd->setStackSize( 0 ); item->setStackSize( newStackSize ); writeItem( item ); @@ -597,9 +595,9 @@ Sapphire::ItemPtr Sapphire::Entity::Player::addItem( uint32_t catalogId, uint32_ queuePacket( slotUpdate ); // return existing stack if we have no overflow - items fit into a preexisting stack - if( quantity == 0 ) + if( itemToAdd->getStackSize() == 0 ) { - queuePacket( makeActorControlSelf( getId(), ItemObtainIcon, catalogId, originalQuantity ) ); + queuePacket( makeActorControlSelf( getId(), ItemObtainIcon, itemToAdd->getId(), originalQuantity ) ); auto soundEffectPacket = makeZonePacket< FFXIVIpcInventoryActionAck >( getId() ); soundEffectPacket->data().sequence = 0xFFFFFFFF; @@ -622,21 +620,17 @@ Sapphire::ItemPtr Sapphire::Entity::Player::addItem( uint32_t catalogId, uint32_ if( !foundFreeSlot ) return nullptr; - auto item = createItem( catalogId, quantity ); - item->setHq( isHq ); - auto storage = m_storageMap[ freeBagSlot.first ]; - storage->setItem( freeBagSlot.second, item ); + storage->setItem( freeBagSlot.second, itemToAdd ); writeInventory( static_cast< InventoryType >( freeBagSlot.first ) ); if( !silent ) { - auto invUpdate = std::make_shared< UpdateInventorySlotPacket >( getId(), freeBagSlot.second, freeBagSlot.first, - *item ); + auto invUpdate = std::make_shared< UpdateInventorySlotPacket >( getId(), freeBagSlot.second, freeBagSlot.first, *itemToAdd ); queuePacket( invUpdate ); - queuePacket( makeActorControlSelf( getId(), ItemObtainIcon, catalogId, originalQuantity ) ); + queuePacket( makeActorControlSelf( getId(), ItemObtainIcon, itemToAdd->getId(), originalQuantity ) ); auto soundEffectPacket = makeZonePacket< FFXIVIpcInventoryActionAck >( getId() ); soundEffectPacket->data().sequence = 0xFFFFFFFF; @@ -644,7 +638,15 @@ Sapphire::ItemPtr Sapphire::Entity::Player::addItem( uint32_t catalogId, uint32_ queuePacket( soundEffectPacket ); } - return item; + return itemToAdd; +} + + +Sapphire::ItemPtr Sapphire::Entity::Player::addItem( uint32_t catalogId, uint32_t quantity, bool isHq, bool silent, bool canMerge ) +{ + auto item = createItem( catalogId, quantity ); + item->setHq( isHq ); + return addItem( item, silent, canMerge ); } void @@ -927,7 +929,7 @@ uint32_t Sapphire::Entity::Player::getNextInventorySequence() return m_inventorySequence++; } -Sapphire::ItemPtr Sapphire::Entity::Player::dropInventoryItem( Sapphire::Common::InventoryType type, uint16_t slotId ) +Sapphire::ItemPtr Sapphire::Entity::Player::dropInventoryItem( Sapphire::Common::InventoryType type, uint16_t slotId, bool slient ) { auto& container = m_storageMap[ type ]; @@ -939,6 +941,12 @@ Sapphire::ItemPtr Sapphire::Entity::Player::dropInventoryItem( Sapphire::Common: container->removeItem( slotId, false ); updateContainer( type, slotId, nullptr ); + if( !slient ) + { + auto invUpdate = std::make_shared< UpdateInventorySlotPacket >( getId(), slotId, static_cast< uint16_t >( type ) ); + queuePacket( invUpdate ); + } + auto seq = getNextInventorySequence(); // send inv update diff --git a/src/world/Manager/ShopMgr.cpp b/src/world/Manager/ShopMgr.cpp index 80df3a04..3e3ad10a 100644 --- a/src/world/Manager/ShopMgr.cpp +++ b/src/world/Manager/ShopMgr.cpp @@ -4,8 +4,15 @@ #include #include #include +#include +#include + +#include "Inventory/Item.h" +#include "Inventory/ItemContainer.h" using namespace Sapphire; +using namespace Sapphire::Network::Packets; +using namespace Sapphire::Network::Packets::Server; bool Sapphire::World::Manager::ShopMgr::purchaseGilShopItem( Entity::Player& player, uint32_t shopId, uint16_t itemId, uint32_t quantity ) { @@ -29,5 +36,86 @@ bool Sapphire::World::Manager::ShopMgr::purchaseGilShopItem( Entity::Player& pla player.removeCurrency( Common::CurrencyType::Gil, price ); + auto packet = makeZonePacket< FFXIVIpcShopMessage >( player.getId() ); + packet->data().shopId = shopId; + packet->data().msgType = 1687; + packet->data().unknown2 = 3; + packet->data().itemId = gilShopItem->item; + packet->data().amount = quantity; + packet->data().price = price; + packet->data().unknown6 = 0; + packet->data().unknown7 = 0; + player.queuePacket( packet ); + return true; } + +bool Sapphire::World::Manager::ShopMgr::shopSellItem( Sapphire::Entity::Player& player, uint32_t shopId, uint16_t containerId, uint16_t slotId ) +{ + auto item = player.getItemAt( containerId, slotId ); + if( item ) + { + auto& exdData = Common::Service< Data::ExdDataGenerated >::ref(); + auto itemData = exdData.get< Data::Item >( item->getId() ); + if( itemData && !itemData->isIndisposable ) + { + auto value = itemData->priceLow * item->getStackSize(); + player.dropInventoryItem( static_cast< Common::InventoryType >( containerId ), slotId ); + player.addCurrency( Common::CurrencyType::Gil, value ); + Entity::ShopBuyBackEntry entry = + { + item, + item->getStackSize(), + itemData->priceLow + }; + player.addBuyBackItemForShop( shopId, entry ); + auto packet = makeZonePacket< FFXIVIpcShopMessage >( player.getId() ); + packet->data().shopId = shopId; + packet->data().msgType = 1688; + packet->data().unknown2 = 3; + packet->data().itemId = item->getId(); + packet->data().amount = item->getStackSize(); + packet->data().price = value; + packet->data().unknown6 = 0; + packet->data().unknown7 = 0; + player.queuePacket( packet ); + + return true; + } + } + return false; +} + +bool Sapphire::World::Manager::ShopMgr::shopBuyBack( Sapphire::Entity::Player& player, uint32_t shopId, uint16_t index ) +{ + auto& buyBackList = player.getBuyBackListForShop( shopId ); + if( buyBackList.size() > index ) + { + auto& entry = buyBackList[ index ]; + if( player.getCurrency( Common::CurrencyType::Gil ) < entry.value ) + return false; + + auto originalStack = entry.item->getStackSize(); + + if( !player.addItem( entry.item ) ) + return false; + + player.removeCurrency( Common::CurrencyType::Gil, entry.value ); + + buyBackList.erase( buyBackList.begin() + index ); + + auto packet = makeZonePacket< FFXIVIpcShopMessage >( player.getId() ); + packet->data().shopId = shopId; + packet->data().msgType = 1689; + packet->data().unknown2 = 3; + packet->data().itemId = entry.item->getId(); + packet->data().amount = originalStack; + packet->data().price = entry.value * originalStack; + packet->data().unknown6 = 0; + packet->data().unknown7 = 0; + player.queuePacket( packet ); + + return true; + } + return false; +} diff --git a/src/world/Manager/ShopMgr.h b/src/world/Manager/ShopMgr.h index 5e40ae1d..fcda6e32 100644 --- a/src/world/Manager/ShopMgr.h +++ b/src/world/Manager/ShopMgr.h @@ -7,5 +7,7 @@ namespace Sapphire::World::Manager public: ShopMgr() = default; bool purchaseGilShopItem( Sapphire::Entity::Player& player, uint32_t shopId, uint16_t itemId, uint32_t quantity ); + bool shopSellItem( Sapphire::Entity::Player& player, uint32_t shopId, uint16_t containerId, uint16_t slotId ); + bool shopBuyBack( Sapphire::Entity::Player& player, uint32_t shopId, uint16_t index ); }; } \ No newline at end of file diff --git a/src/world/Network/Handlers/EventHandlers.cpp b/src/world/Network/Handlers/EventHandlers.cpp index 33fe1641..ae2ccb87 100644 --- a/src/world/Network/Handlers/EventHandlers.cpp +++ b/src/world/Network/Handlers/EventHandlers.cpp @@ -204,8 +204,8 @@ void Sapphire::Network::GameConnection::eventHandlerReturn( const Packets::FFXIV std::string eventName = eventMgr.getEventName( eventId ); - player.sendDebug( "eventId: {0} ({0:08X}) scene: {1}, p1: {2}, p2: {3}, p3: {4}", - eventId, scene, param1, param2, param3 ); + player.sendDebug( "eventId: {0} ({0:08X}) scene: {1}, p1: {2}, p2: {3}, p3: {4}, p4: {5}", + eventId, scene, param1, param2, param3, param4 ); auto pEvent = player.getEvent( eventId ); if( pEvent ) diff --git a/src/world/Network/PacketWrappers/UpdateInventorySlotPacket.h b/src/world/Network/PacketWrappers/UpdateInventorySlotPacket.h index c16042d2..5e46f9b9 100644 --- a/src/world/Network/PacketWrappers/UpdateInventorySlotPacket.h +++ b/src/world/Network/PacketWrappers/UpdateInventorySlotPacket.h @@ -21,6 +21,12 @@ namespace Sapphire::Network::Packets::Server initialize( slot, storageId, item ); }; + UpdateInventorySlotPacket( uint32_t playerId, uint8_t slot, uint16_t storageId ) : + ZoneChannelPacket< FFXIVIpcUpdateInventorySlot >( playerId, playerId ) + { + initialize( slot, storageId ); + }; + private: void initialize( uint8_t slot, uint16_t storageId, const Item& item ) { @@ -47,6 +53,15 @@ namespace Sapphire::Network::Packets::Server //uint8_t buffer4; //uint8_t buffer5; }; + + void initialize( uint8_t slot, uint16_t storageId ) + { + m_data.sequence = 0; + m_data.containerId = storageId; + m_data.slot = slot; + m_data.quantity = 0; + m_data.catalogId = 0; + }; }; }