1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-05-06 10:47:45 +00:00

Selling items and buying back from shop, show corresponding messages in chat.

This commit is contained in:
collett 2020-05-11 06:25:25 +09:00
parent 2823923a88
commit b70435413e
11 changed files with 232 additions and 40 deletions

View file

@ -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
};
////////////////////////////////////////////////////////////////////////////////

View file

@ -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*/

View file

@ -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 ) );

View file

@ -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 ) );

View file

@ -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;
};
}

View file

@ -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;

View file

@ -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

View file

@ -4,8 +4,15 @@
#include <Actor/Player.h>
#include <Common.h>
#include <Service.h>
#include <Network/GamePacket.h>
#include <Network/PacketDef/Zone/ServerZoneDef.h>
#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;
}

View file

@ -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 );
};
}

View file

@ -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 )

View file

@ -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;
};
};
}