1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-04-26 14:37:44 +00:00
sapphire/src/world/Actor/PlayerInventory.cpp

1111 lines
34 KiB
C++
Raw Normal View History

2018-03-06 22:22:19 +01:00
#include <Common.h>
#include <Logging/Logger.h>
2018-10-26 09:40:47 +02:00
#include <algorithm>
2017-08-08 13:53:47 +02:00
2019-07-21 22:33:33 +10:00
#include "Territory/Territory.h"
2017-08-08 13:53:47 +02:00
#include "Inventory/Item.h"
#include "Inventory/ItemContainer.h"
2018-12-23 13:26:33 +01:00
#include <Exd/ExdData.h>
#include <Logging/Logger.h>
#include <Database/DatabaseDef.h>
#include "Actor/Player.h"
#include <Network/CommonActorControl.h>
#include "Network/PacketWrappers/ActorControlPacket.h"
#include "Network/PacketWrappers/ActorControlSelfPacket.h"
#include "Network/PacketWrappers/UpdateInventorySlotPacket.h"
#include "Network/PacketWrappers/ServerNoticePacket.h"
#include <Network/PacketDef/Zone/ServerZoneDef.h>
#include "Manager/InventoryMgr.h"
2018-12-23 13:26:33 +01:00
#include "Manager/ItemMgr.h"
#include "Manager/PlayerMgr.h"
2020-03-01 01:00:57 +11:00
#include <Service.h>
using namespace Sapphire::Common;
using namespace Sapphire::Network::Packets;
using namespace Sapphire::Network::Packets::WorldPackets::Server;
using namespace Sapphire::Network::ActorControl;
2017-08-08 13:53:47 +02:00
void Sapphire::Entity::Player::initInventory()
2017-08-08 13:53:47 +02:00
{
2021-12-03 10:53:13 +01:00
const uint8_t inventorySize = 25;
auto setupContainer = [ this ]( InventoryType type, uint8_t maxSize, const std::string& tableName,
bool isMultiStorage, bool isPersistentStorage = true )
2020-03-01 01:00:57 +11:00
{ m_storageMap[ type ] = make_ItemContainer( type, maxSize, tableName, isMultiStorage, isPersistentStorage ); };
// main bags
2021-12-03 10:53:13 +01:00
setupContainer( Bag0, inventorySize, "charaiteminventory", true );
setupContainer( Bag1, inventorySize, "charaiteminventory", true );
setupContainer( Bag2, inventorySize, "charaiteminventory", true );
setupContainer( Bag3, inventorySize, "charaiteminventory", true );
// gear set
setupContainer( GearSet0, 13, "charaitemgearset", true );
// gil contianer
setupContainer( Currency, 11, "charaiteminventory", true );
// crystals??
setupContainer( Crystal, 11, "charaiteminventory", true );
// armory weapons - 0
2021-12-03 10:53:13 +01:00
setupContainer( ArmoryMain, inventorySize, "charaiteminventory", true );
// armory offhand - 1
2021-12-03 10:53:13 +01:00
setupContainer( ArmoryOff, inventorySize, "charaiteminventory", true );
//armory head - 2
2021-12-03 10:53:13 +01:00
setupContainer( ArmoryHead, inventorySize, "charaiteminventory", true );
//armory body - 3
2021-12-03 10:53:13 +01:00
setupContainer( ArmoryBody, inventorySize, "charaiteminventory", true );
//armory hand - 4
2021-12-03 10:53:13 +01:00
setupContainer( ArmoryHand, inventorySize, "charaiteminventory", true );
//armory waist - 5
2021-12-03 10:53:13 +01:00
setupContainer( ArmoryWaist, inventorySize, "charaiteminventory", true );
//armory legs - 6
2021-12-03 10:53:13 +01:00
setupContainer( ArmoryLegs, inventorySize, "charaiteminventory", true );
//armory feet - 7
2021-12-03 10:53:13 +01:00
setupContainer( ArmoryFeet, inventorySize, "charaiteminventory", true );
//neck
2021-12-03 10:53:13 +01:00
setupContainer( ArmoryNeck, inventorySize, "charaiteminventory", true );
//earring
2021-12-03 10:53:13 +01:00
setupContainer( ArmoryEar, inventorySize, "charaiteminventory", true );
//wrist
2021-12-03 10:53:13 +01:00
setupContainer( ArmoryWrist, inventorySize, "charaiteminventory", true );
//armory rings - 11
2021-12-03 10:53:13 +01:00
setupContainer( ArmoryRing, inventorySize, "charaiteminventory", true );
//soul crystals - 13
2021-12-03 10:53:13 +01:00
setupContainer( ArmorySoulCrystal, inventorySize, "charaiteminventory", true );
// item hand in container
// non-persistent container, will not save its contents
setupContainer( HandIn, 10, "", true, false );
loadInventory();
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::sendItemLevel()
{
calculateEquippedGearItemLevel();
Service< World::Manager::PlayerMgr >::ref().onPlayerItemLevelUpdate( *this );
}
void Sapphire::Entity::Player::equipWeapon( const Item& item )
2017-08-08 13:53:47 +02:00
{
auto& exdData = Common::Service< Sapphire::Data::ExdData >::ref();
2017-08-08 13:53:47 +02:00
2022-01-27 21:24:54 +01:00
auto itemInfo = exdData.getRow< Excel::Item >( item.getId() );
auto itemClassJob = itemInfo->data().Class;
2022-01-27 21:24:54 +01:00
auto classJobInfo = exdData.getRow< Excel::ClassJob >( static_cast< uint32_t >( getClass() ) );
auto currentParentClass = static_cast< ClassJob >( classJobInfo->data().MainClass );
auto newClassJob = static_cast< ClassJob >( itemClassJob );
if( ( isClassJobUnlocked( newClassJob ) ) && ( currentParentClass != newClassJob ) )
{
setClassJob( newClassJob );
}
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::equipSoulCrystal( const Item& item )
{
auto& exdData = Common::Service< Sapphire::Data::ExdData >::ref();
2022-01-27 21:24:54 +01:00
auto itemInfo = exdData.getRow< Excel::Item >( item.getId() );
auto itemClassJob = itemInfo->data().Class;
auto newClassJob = static_cast< ClassJob >( itemClassJob );
if( isClassJobUnlocked( newClassJob ) )
setClassJob( newClassJob );
}
void Sapphire::Entity::Player::updateModels( GearSetSlot equipSlotId, const Sapphire::Item& item )
2018-09-17 22:52:57 +02:00
{
uint64_t model = item.getModelId1();
uint64_t model2 = item.getModelId2();
uint64_t stain = item.getStain();
2017-08-08 13:53:47 +02:00
switch( equipSlotId )
{
2018-09-17 22:52:57 +02:00
case MainHand:
m_modelMainWeapon = model | ( stain << 48 );
2017-08-08 13:53:47 +02:00
m_modelSubWeapon = model2;
if( m_modelSubWeapon > 0 )
m_modelSubWeapon = m_modelSubWeapon | ( stain << 48 );
2017-08-08 13:53:47 +02:00
break;
2018-09-17 22:52:57 +02:00
case OffHand:
m_modelSubWeapon = model | ( stain << 48 );
2017-08-08 13:53:47 +02:00
break;
// these have no model
2018-09-17 22:52:57 +02:00
case SoulCrystal:
case Waist:
break;
default: // any other slot
2018-09-17 22:52:57 +02:00
auto modelSlot = equipSlotToModelSlot( equipSlotId );
if( modelSlot == GearModelSlot::ModelInvalid )
break;
model = model | stain << 24;
2018-09-17 22:52:57 +02:00
m_modelEquip[ static_cast< uint8_t >( modelSlot ) ] = static_cast< uint32_t >( model );
2017-08-08 13:53:47 +02:00
break;
}
2018-09-17 22:52:57 +02:00
}
2017-08-08 13:53:47 +02:00
Sapphire::Common::GearModelSlot Sapphire::Entity::Player::equipSlotToModelSlot( Common::GearSetSlot slot )
2018-09-17 22:52:57 +02:00
{
switch( slot )
{
2018-09-17 22:52:57 +02:00
case MainHand:
case OffHand:
case Waist:
case SoulCrystal:
default:
return GearModelSlot::ModelInvalid;
case Head:
return GearModelSlot::ModelHead;
case Body:
return GearModelSlot::ModelBody;
case Hands:
return GearModelSlot::ModelHands;
case Legs:
return GearModelSlot::ModelLegs;
case Feet:
return GearModelSlot::ModelFeet;
case Neck:
return GearModelSlot::ModelNeck;
case Ear:
return GearModelSlot::ModelEar;
case Wrist:
return GearModelSlot::ModelWrist;
case Ring1:
return GearModelSlot::ModelRing1;
case Ring2:
return GearModelSlot::ModelRing2;
}
2017-08-08 13:53:47 +02:00
}
// equip an item
void Sapphire::Entity::Player::equipItem( Common::GearSetSlot equipSlotId, Item& item, bool sendUpdate )
{
switch( equipSlotId )
{
case MainHand:
equipWeapon( item );
break;
case SoulCrystal:
equipSoulCrystal( item );
break;
default:
break;
}
updateModels( equipSlotId, item );
auto baseParams = item.getBaseParams();
for( auto i = 0; i < 6; ++i )
{
if( baseParams[ i ].baseParam != static_cast< uint8_t >( Common::BaseParam::None ) )
m_bonusStats[ baseParams[ i ].baseParam ] += baseParams[ i ].value;
}
m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::Defense ) ] += item.getDefense();
m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::MagicDefense ) ] += item.getDefenseMag();
calculateStats();
if( sendUpdate )
{
sendModel();
sendItemLevel();
sendStats();
sendStatusUpdate();
}
}
void Sapphire::Entity::Player::unequipItem( Common::GearSetSlot equipSlotId, Item& item, bool sendUpdate )
2017-08-08 13:53:47 +02:00
{
2018-09-17 22:52:57 +02:00
auto modelSlot = equipSlotToModelSlot( equipSlotId );
if( modelSlot != GearModelSlot::ModelInvalid )
m_modelEquip[ static_cast< uint8_t >( modelSlot ) ] = 0;
if ( equipSlotId == SoulCrystal )
unequipSoulCrystal();
auto baseParams = item.getBaseParams();
for( auto i = 0; i < 6; ++i )
{
if( baseParams[ i ].baseParam != static_cast< uint8_t >( Common::BaseParam::None ) )
m_bonusStats[ baseParams[ i ].baseParam ] -= baseParams[ i ].value;
}
m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::Defense ) ] -= item.getDefense();
m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::MagicDefense ) ] -= item.getDefenseMag();
calculateStats();
if( sendUpdate )
{
sendModel();
sendItemLevel();
sendStats();
sendStatusUpdate();
}
}
void Sapphire::Entity::Player::unequipSoulCrystal()
{
auto& exdData = Common::Service< Sapphire::Data::ExdData >::ref();
2022-01-27 21:24:54 +01:00
auto currentClassJob = exdData.getRow< Excel::ClassJob >( static_cast< uint32_t >( getClass() ) );
auto parentClass = static_cast< ClassJob >( currentClassJob->data().MainClass );
2018-10-20 14:36:56 +03:00
setClassJob( parentClass );
2017-08-08 13:53:47 +02:00
}
// TODO: these next functions are so similar that they could likely be simplified
void Sapphire::Entity::Player::addCurrency( CurrencyType type, uint32_t amount )
2017-08-08 13:53:47 +02:00
{
auto slot = static_cast< uint8_t >( static_cast< uint8_t >( type ) - 1 );
auto currItem = m_storageMap[ Currency ]->getItem( slot );
if( !currItem )
{
// TODO: map currency type to itemid
currItem = createItem( 1 );
m_storageMap[ Currency ]->setItem( slot, currItem );
}
uint32_t currentAmount = currItem->getStackSize();
currItem->setStackSize( currentAmount + amount );
writeItem( currItem );
updateContainer( Currency, slot, currItem );
auto seq = getNextInventorySequence();
auto invUpdate = makeZonePacket< FFXIVIpcItemOperation >( getId() );
invUpdate->data().contextId = seq;
invUpdate->data().srcStorageId = Common::InventoryType::Currency;
invUpdate->data().srcStack = currItem->getStackSize();
invUpdate->data().srcContainerIndex = static_cast< int16_t >( type ) - 1;
invUpdate->data().srcEntity = getId();
invUpdate->data().srcCatalogId = currItem->getId();
invUpdate->data().operationType = Common::ITEM_OPERATION_TYPE::ITEM_OPERATION_TYPE_UPDATEITEM;
queuePacket( invUpdate );
auto invTransFinPacket = makeZonePacket< FFXIVIpcItemOperationBatch >( getId() );
invTransFinPacket->data().contextId = seq;
invTransFinPacket->data().operationId = seq;
invTransFinPacket->data().operationType = Common::ITEM_OPERATION_TYPE::ITEM_OPERATION_TYPE_UPDATEITEM;
queuePacket( invTransFinPacket );
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::removeCurrency( Common::CurrencyType type, uint32_t amount )
2017-08-08 13:53:47 +02:00
{
auto currItem = m_storageMap[ Currency ]->getItem( static_cast< uint8_t >( type ) - 1 );
if( !currItem )
return;
uint32_t currentAmount = currItem->getStackSize();
if( amount > currentAmount )
currItem->setStackSize( 0 );
else
currItem->setStackSize( currentAmount - amount );
writeItem( currItem );
auto seq = getNextInventorySequence();
2017-08-08 13:53:47 +02:00
auto invUpdate = makeZonePacket< FFXIVIpcItemOperation >( getId() );
invUpdate->data().contextId = seq;
invUpdate->data().srcStorageId = Common::InventoryType::Currency;
invUpdate->data().srcStack = currItem->getStackSize();
invUpdate->data().srcContainerIndex = static_cast< int16_t >( type ) - 1;
invUpdate->data().srcCatalogId = currItem->getId();
invUpdate->data().operationType = Common::ITEM_OPERATION_TYPE::ITEM_OPERATION_TYPE_UPDATEITEM;
queuePacket( invUpdate );
auto invTransFinPacket = makeZonePacket< FFXIVIpcItemOperationBatch >( getId() );
invTransFinPacket->data().contextId = seq;
invTransFinPacket->data().operationId = seq;
invTransFinPacket->data().operationType = Common::ITEM_OPERATION_TYPE::ITEM_OPERATION_TYPE_UPDATEITEM;
queuePacket( invTransFinPacket );
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::addCrystal( Common::CrystalType type, uint32_t amount )
2017-08-08 13:53:47 +02:00
{
auto currItem = m_storageMap[ Crystal ]->getItem( static_cast< uint8_t >( type ) - 1 );
2017-08-08 13:53:47 +02:00
if( !currItem )
{
// TODO: map currency type to itemid
currItem = createItem( static_cast< uint8_t >( type ) + 1 );
m_storageMap[ Crystal ]->setItem( static_cast< uint8_t >( type ) - 1, currItem );
}
uint32_t currentAmount = currItem->getStackSize();
currItem->setStackSize( currentAmount + amount );
writeItem( currItem );
2017-08-08 13:53:47 +02:00
writeInventory( Crystal );
2018-07-26 23:12:02 +10:00
auto seq = getNextInventorySequence();
2017-08-08 13:53:47 +02:00
auto invUpdate = makeZonePacket< FFXIVIpcItemOperation >( getId() );
invUpdate->data().contextId = seq;
invUpdate->data().srcStorageId = Common::InventoryType::Currency;
invUpdate->data().srcStack = currItem->getStackSize();
invUpdate->data().srcContainerIndex = static_cast< int16_t >( type ) - 1;
invUpdate->data().srcEntity = getId();
invUpdate->data().srcCatalogId = currItem->getId();
invUpdate->data().operationType = Common::ITEM_OPERATION_TYPE::ITEM_OPERATION_TYPE_UPDATEITEM;
queuePacket( invUpdate );
auto invTransFinPacket = makeZonePacket< FFXIVIpcItemOperationBatch >( getId() );
invTransFinPacket->data().contextId = seq;
invTransFinPacket->data().operationId = seq;
invTransFinPacket->data().operationType = Common::ITEM_OPERATION_TYPE::ITEM_OPERATION_TYPE_UPDATEITEM;
queuePacket( invTransFinPacket );
queuePacket( makeActorControlSelf( getId(), ItemObtainIcon, static_cast< uint8_t >( type ) + 1, amount ) );
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::removeCrystal( Common::CrystalType type, uint32_t amount )
2017-08-08 13:53:47 +02:00
{
auto currItem = m_storageMap[ Crystal ]->getItem( static_cast< uint8_t >( type ) - 1 );
if( !currItem )
return;
2017-08-08 13:53:47 +02:00
uint32_t currentAmount = currItem->getStackSize();
if( amount > currentAmount )
currItem->setStackSize( 0 );
else
currItem->setStackSize( currentAmount - amount );
writeItem( currItem );
auto seq = getNextInventorySequence();
auto invUpdate = makeZonePacket< FFXIVIpcItemOperation >( getId() );
invUpdate->data().contextId = seq;
invUpdate->data().srcStorageId = Common::InventoryType::Currency;
invUpdate->data().srcStack = currItem->getStackSize();
invUpdate->data().srcContainerIndex = static_cast< int16_t >( type ) - 1;
invUpdate->data().srcEntity = getId();
invUpdate->data().srcCatalogId = currItem->getId();
invUpdate->data().operationType = Common::ITEM_OPERATION_TYPE::ITEM_OPERATION_TYPE_UPDATEITEM;
queuePacket( invUpdate );
auto invTransFinPacket = makeZonePacket< FFXIVIpcItemOperationBatch >( getId() );
invTransFinPacket->data().contextId = seq;
invTransFinPacket->data().operationId = seq;
invTransFinPacket->data().operationType = Common::ITEM_OPERATION_TYPE::ITEM_OPERATION_TYPE_UPDATEITEM;
queuePacket( invTransFinPacket );
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::sendInventory()
{
2020-03-01 01:00:57 +11:00
auto& invMgr = Common::Service< World::Manager::InventoryMgr >::ref();
for( auto it = m_storageMap.begin(); it != m_storageMap.end(); ++it )
{
2020-03-01 01:00:57 +11:00
invMgr.sendInventoryContainer( *this, it->second );
}
}
Sapphire::Entity::Player::InvSlotPairVec Sapphire::Entity::Player::getSlotsOfItemsInInventory( uint32_t catalogId )
{
InvSlotPairVec outVec;
for( auto i : { Bag0, Bag1, Bag2, Bag3 } )
{
auto inv = m_storageMap[ i ];
for( auto item : inv->getItemMap() )
{
if( item.second && item.second->getId() == catalogId )
outVec.push_back( std::make_pair( i, static_cast< int8_t >( item.first ) ) );
}
}
return outVec;
}
Sapphire::Entity::Player::InvSlotPair Sapphire::Entity::Player::getFreeBagSlot()
{
for( auto i : { Bag0, Bag1, Bag2, Bag3 } )
{
auto freeSlot = static_cast< int8_t >( m_storageMap[ i ]->getFreeSlot() );
if( freeSlot != -1 )
return std::make_pair( i, freeSlot );
}
// no room in inventory
return std::make_pair( 0, -1 );
}
2022-02-18 02:24:18 +01:00
Sapphire::Entity::Player::InvSlotPair Sapphire::Entity::Player::getFreeContainerSlot( uint32_t containerId )
{
auto freeSlot = static_cast< int8_t >( m_storageMap[ containerId ]->getFreeSlot() );
2022-02-18 02:24:18 +01:00
if( freeSlot != -1 )
return std::make_pair( containerId, freeSlot );
// no room in inventory
return std::make_pair( 0, -1 );
}
Sapphire::ItemPtr Sapphire::Entity::Player::getItemAt( uint16_t containerId, uint16_t slotId )
{
return m_storageMap[ containerId ]->getItem( slotId );
}
uint32_t Sapphire::Entity::Player::getCurrency( CurrencyType type )
{
auto currItem = m_storageMap[ Currency ]->getItem( static_cast< uint8_t >( type ) - 1 );
if( !currItem )
return 0;
return currItem->getStackSize();
}
uint32_t Sapphire::Entity::Player::getCrystal( CrystalType type )
{
auto currItem = m_storageMap[ Crystal ]->getItem( static_cast< uint8_t >( type ) - 1 );
if( !currItem )
return 0;
return currItem->getStackSize();
}
void Sapphire::Entity::Player::writeInventory( InventoryType type )
{
2020-03-01 01:00:57 +11:00
auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref();
auto storage = m_storageMap[ type ];
2018-07-26 23:40:46 +02:00
if( !storage->isPersistentStorage() )
return;
std::string query = "UPDATE " + storage->getTableName() + " SET ";
2018-07-26 23:12:02 +10:00
for( int32_t i = 0; i <= storage->getMaxSize(); i++ )
{
auto currItem = storage->getItem( i );
if( i > 0 )
query += ", ";
query += "container_" + std::to_string( i ) + " = " + std::to_string( currItem ? currItem->getUId() : 0 );
}
2021-12-02 00:08:33 +01:00
query += " WHERE CharacterId = " + std::to_string( getCharacterId() );
2018-07-26 23:40:46 +02:00
if( storage->isMultiStorage() )
query += " AND storageId = " + std::to_string( static_cast< uint16_t >( type ) );
2020-03-01 01:00:57 +11:00
db.execute( query );
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::writeItem( Sapphire::ItemPtr pItem ) const
{
2020-03-01 01:00:57 +11:00
auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref();
auto stmt = db.getPreparedStatement( Db::CHARA_ITEMGLOBAL_UP );
// todo: add more fields
stmt->setInt( 1, pItem->getStackSize() );
stmt->setInt( 2, pItem->getDurability() );
stmt->setInt( 3, pItem->getStain() );
stmt->setInt64( 4, pItem->getUId() );
2020-03-01 01:00:57 +11:00
db.directExecute( stmt );
}
void Sapphire::Entity::Player::deleteItemDb( Sapphire::ItemPtr item ) const
2017-08-08 13:53:47 +02:00
{
2020-03-01 01:00:57 +11:00
auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref();
auto stmt = db.getPreparedStatement( Db::CHARA_ITEMGLOBAL_DELETE );
stmt->setInt64( 1, item->getUId() );
2020-03-01 01:00:57 +11:00
db.directExecute( stmt );
2017-08-08 13:53:47 +02:00
}
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 )
{
auto& exdData = Common::Service< Data::ExdData >::ref();
2022-01-27 21:24:54 +01:00
auto itemInfo = exdData.getRow< Excel::Item >( catalogId );
// if item data doesn't exist or it's a blank field
if( !itemInfo || itemInfo->data().EquipLevel == 0 )
{
return nullptr;
}
quantity = std::min< uint32_t >( quantity, itemInfo->data().StackMax );
// used for item obtain notification
uint32_t originalQuantity = quantity;
std::pair< uint16_t, uint8_t > freeBagSlot;
bool foundFreeSlot = false;
std::vector< uint16_t > bags = { Bag0, Bag1, Bag2, Bag3 };
// add the related armoury bag to the applicable bags and try and fill a free slot there before falling back to regular inventory
// EXD TODO: wtf...
2022-02-18 02:24:18 +01:00
if( itemInfo->data().Slot > 0 && getEquipDisplayFlags() & StoreNewItemsInArmouryChest )
{
auto bag = World::Manager::ItemMgr::getCharaEquipSlotCategoryToArmoryId( itemInfo->data().Slot );
bags.insert( bags.begin(), bag );
}
for( auto bag : bags )
{
auto storage = m_storageMap[ bag ];
for( uint16_t slot = 0; slot < storage->getMaxSize(); slot++ )
{
2020-02-24 18:23:43 +09:00
if( !canMerge && foundFreeSlot )
break;
auto item = storage->getItem( slot );
// add any items that are stackable
2022-02-18 02:37:04 +01:00
if( canMerge && item && item->getMaxStackSize() > 0 && item->getId() == catalogId )
{
uint32_t count = item->getStackSize();
uint32_t maxStack = item->getMaxStackSize();
// if slot is full, skip it
if( count >= maxStack )
continue;
// check slot is same quality
if( item->isHq() != isHq )
continue;
// update stack
uint32_t newStackSize = count + quantity;
if( newStackSize > maxStack )
{
quantity = newStackSize - maxStack;
newStackSize = maxStack;
}
else
quantity = 0;
item->setStackSize( newStackSize );
writeItem( item );
auto seq = getNextInventorySequence();
auto slotUpdate = std::make_shared< UpdateInventorySlotPacket >( getId(), slot, bag, *item, seq );
queuePacket( slotUpdate );
auto invTransFinPacket = makeZonePacket< FFXIVIpcItemOperationBatch >( getId() );
invTransFinPacket->data().contextId = seq;
invTransFinPacket->data().operationId = seq;
invTransFinPacket->data().operationType = Common::ITEM_OPERATION_TYPE::ITEM_OPERATION_TYPE_UPDATEITEM;
queuePacket( invTransFinPacket );
// return existing stack if we have no overflow - items fit into a preexisting stack
if( quantity == 0 )
{
queuePacket( makeActorControlSelf( getId(), ItemObtainIcon, catalogId, originalQuantity ) );
return item;
}
}
else if( !item && !foundFreeSlot )
{
freeBagSlot = { bag, slot };
foundFreeSlot = true;
2018-08-12 22:30:18 +10:00
}
}
}
2018-08-12 22:30:18 +10:00
// couldn't find a free slot and we still have some quantity of items left, shits fucked
if( !foundFreeSlot )
return nullptr;
auto item = createItem( catalogId, quantity );
item->setHq( isHq );
2020-06-28 09:27:00 +09:00
auto storage = m_storageMap[ freeBagSlot.first ];
storage->setItem( freeBagSlot.second, item );
writeInventory( static_cast< InventoryType >( freeBagSlot.first ) );
if( !silent )
{
auto seq = getNextInventorySequence();
// send inv update
auto invTransPacket = makeZonePacket< FFXIVIpcItemOperation >( getId() );
invTransPacket->data().contextId = seq;
invTransPacket->data().dstEntity = getId();
invTransPacket->data().dstStorageId = freeBagSlot.first;
invTransPacket->data().dstCatalogId = item->getId();
invTransPacket->data().dstStack = item->getStackSize();
invTransPacket->data().dstContainerIndex = freeBagSlot.second;
invTransPacket->data().operationType = Common::ITEM_OPERATION_TYPE::ITEM_OPERATION_TYPE_CREATEITEM;
queuePacket( invTransPacket );
auto invTransFinPacket = makeZonePacket< FFXIVIpcItemOperationBatch >( getId() );
invTransFinPacket->data().contextId = seq;
invTransFinPacket->data().operationId = seq;
invTransFinPacket->data().operationType = Common::ITEM_OPERATION_TYPE::ITEM_OPERATION_TYPE_CREATEITEM;
queuePacket( invTransFinPacket );
queuePacket( makeActorControlSelf( getId(), ItemObtainIcon, catalogId, originalQuantity ) );
}
return item;
}
void
Sapphire::Entity::Player::moveItem( uint16_t fromInventoryId, uint16_t fromSlotId, uint16_t toInventoryId, uint16_t toSlot )
{
auto tmpItem = m_storageMap[ fromInventoryId ]->getItem( fromSlotId );
auto& itemMap = m_storageMap[ fromInventoryId ]->getItemMap();
if( tmpItem == nullptr )
return;
itemMap[ fromSlotId ].reset();
m_storageMap[ toInventoryId ]->setItem( toSlot, tmpItem );
writeInventory( static_cast< InventoryType >( toInventoryId ) );
if( fromInventoryId != toInventoryId )
writeInventory( static_cast< InventoryType >( fromInventoryId ) );
if( static_cast< InventoryType >( toInventoryId ) == GearSet0 )
equipItem( static_cast< GearSetSlot >( toSlot ), *tmpItem, true );
if( static_cast< InventoryType >( fromInventoryId ) == GearSet0 )
unequipItem( static_cast< GearSetSlot >( fromSlotId ), *tmpItem, true );
2020-01-05 17:41:38 +09:00
if( static_cast< InventoryType >( toInventoryId ) == GearSet0 ||
static_cast< InventoryType >( fromInventoryId ) == GearSet0 )
sendStatusEffectUpdate(); // send if any equip is changed
}
bool Sapphire::Entity::Player::updateContainer( uint16_t storageId, uint16_t slotId, ItemPtr pItem )
{
2018-12-23 13:26:33 +01:00
auto containerType = World::Manager::ItemMgr::getContainerType( storageId );
auto pOldItem = getItemAt( storageId, slotId );
m_storageMap[ storageId ]->setItem( slotId, pItem );
switch( containerType )
{
case Armory:
case Bag:
case CurrencyCrystal:
{
writeInventory( static_cast< InventoryType >( storageId ) );
break;
}
case GearSet:
{
if( pItem )
{
if( pOldItem )
unequipItem( static_cast< GearSetSlot >( slotId ), *pOldItem, false );
equipItem( static_cast< GearSetSlot >( slotId ), *pItem, true );
}
else
unequipItem( static_cast< GearSetSlot >( slotId ), *pItem, true );
writeInventory( static_cast< InventoryType >( storageId ) );
break;
}
default:
break;
}
return true;
}
void Sapphire::Entity::Player::splitItem( uint16_t fromInventoryId, uint16_t fromSlotId,
uint16_t toInventoryId, uint16_t toSlot, uint16_t itemCount )
{
if( itemCount == 0 )
return;
2018-08-12 22:30:18 +10:00
auto fromItem = m_storageMap[ 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_storageMap[ toInventoryId ]->getItem( toSlot ) )
// todo: correct invalid move? again, not sure what retail does here
return;
2020-02-24 18:23:43 +09:00
auto newItem = addItem( fromItem->getId(), itemCount, fromItem->isHq(), true, false );
if( !newItem )
return;
fromItem->setStackSize( fromItem->getStackSize() - itemCount );
updateContainer( fromInventoryId, fromSlotId, fromItem );
updateContainer( toInventoryId, toSlot, newItem );
writeItem( fromItem );
}
void Sapphire::Entity::Player::mergeItem( uint16_t fromInventoryId, uint16_t fromSlotId,
uint16_t toInventoryId, uint16_t toSlot )
{
auto fromItem = m_storageMap[ fromInventoryId ]->getItem( fromSlotId );
auto toItem = m_storageMap[ 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 >( fromItem->getMaxStackSize(), stackSize );
// we can destroy the original stack if there's no overflow
if( stackOverflow == 0 )
{
m_storageMap[ fromInventoryId ]->removeItem( fromSlotId );
deleteItemDb( fromItem );
}
else
{
fromItem->setStackSize( stackOverflow );
writeItem( fromItem );
}
toItem->setStackSize( stackSize );
writeItem( toItem );
updateContainer( fromInventoryId, fromSlotId, fromItem );
updateContainer( toInventoryId, toSlot, toItem );
}
void Sapphire::Entity::Player::swapItem( uint16_t fromInventoryId, uint16_t fromSlotId,
uint16_t toInventoryId, uint16_t toSlot )
{
auto fromItem = m_storageMap[ fromInventoryId ]->getItem( fromSlotId );
auto toItem = m_storageMap[ toInventoryId ]->getItem( toSlot );
auto& itemMap = m_storageMap[ fromInventoryId ]->getItemMap();
if( fromItem == nullptr || toItem == nullptr )
return;
// An item is being moved from bag0-3 to equippment, meaning
// the swapped out item will be placed in the matching armory.
2018-12-23 13:26:33 +01:00
if( World::Manager::ItemMgr::isEquipment( toInventoryId )
&& !World::Manager::ItemMgr::isEquipment( fromInventoryId )
&& !World::Manager::ItemMgr::isArmory( fromInventoryId ) )
{
updateContainer( fromInventoryId, fromSlotId, nullptr );
2022-02-18 02:24:18 +01:00
auto& exdData = Common::Service< Data::ExdData >::ref();
auto itemInfo = exdData.getRow< Excel::Item >( toItem->getId() );
fromInventoryId = World::Manager::ItemMgr::getCharaEquipSlotCategoryToArmoryId( static_cast< Common::EquipSlotCategory >( itemInfo->data().Slot ) );
fromSlotId = static_cast < uint8_t >( m_storageMap[ fromInventoryId ]->getFreeSlot() );
}
2018-12-23 13:26:33 +01:00
auto containerTypeFrom = World::Manager::ItemMgr::getContainerType( fromInventoryId );
auto containerTypeTo = World::Manager::ItemMgr::getContainerType( toInventoryId );
updateContainer( toInventoryId, toSlot, fromItem );
updateContainer( fromInventoryId, fromSlotId, toItem );
2020-01-05 17:41:38 +09:00
if( static_cast< InventoryType >( toInventoryId ) == GearSet0 ||
static_cast< InventoryType >( fromInventoryId ) == GearSet0 )
sendStatusEffectUpdate(); // send if any equip is changed
}
void Sapphire::Entity::Player::discardItem( uint16_t fromInventoryId, uint16_t fromSlotId )
{
// i am not entirely sure how this should be generated or if it even is important for us...
2019-02-09 22:59:14 +11:00
uint32_t sequence = getNextInventorySequence();
auto fromItem = m_storageMap[ fromInventoryId ]->getItem( fromSlotId );
deleteItemDb( fromItem );
m_storageMap[ fromInventoryId ]->removeItem( fromSlotId );
updateContainer( fromInventoryId, fromSlotId, nullptr );
auto invTransPacket = makeZonePacket< FFXIVIpcItemOperation >( getId() );
invTransPacket->data().contextId = sequence;
invTransPacket->data().srcEntity = getId();
invTransPacket->data().srcStorageId = fromInventoryId;
invTransPacket->data().srcCatalogId = fromItem->getId();
invTransPacket->data().srcStack = fromItem->getStackSize();
invTransPacket->data().srcContainerIndex = fromSlotId;
invTransPacket->data().operationType = Common::ITEM_OPERATION_TYPE::ITEM_OPERATION_TYPE_DELETEITEM;
queuePacket( invTransPacket );
auto invTransFinPacket = makeZonePacket< FFXIVIpcItemOperationBatch >( getId() );
invTransFinPacket->data().contextId = sequence;
invTransFinPacket->data().operationId = sequence;
invTransFinPacket->data().operationType = Common::ITEM_OPERATION_TYPE::ITEM_OPERATION_TYPE_DELETEITEM;
queuePacket( invTransFinPacket );
}
uint16_t Sapphire::Entity::Player::calculateEquippedGearItemLevel()
{
uint32_t iLvlResult = 0;
auto gearSetMap = m_storageMap[ GearSet0 ]->getItemMap();
auto it = gearSetMap.begin();
while( it != gearSetMap.end() )
{
auto currItem = it->second;
2019-04-25 18:26:35 +10:00
if( currItem && currItem->getCategory() != Common::ItemUICategory::SoulCrystal )
{
iLvlResult += currItem->getItemLevel();
// If item is weapon and isn't one-handed
2018-12-23 13:26:33 +01:00
if( currItem->isWeapon() && !World::Manager::ItemMgr::isOneHandedWeapon( currItem->getCategory() ) )
{
iLvlResult += currItem->getItemLevel();
}
}
it++;
}
uint16_t ilvl = static_cast< uint16_t >( std::min( static_cast< int32_t >( iLvlResult / 13 ), 9999 ) );
m_itemLevel = ilvl;
return ilvl;
}
Sapphire::ItemPtr Sapphire::Entity::Player::getEquippedWeapon()
{
return m_storageMap[ GearSet0 ]->getItem( GearSetSlot::MainHand );
}
uint16_t Sapphire::Entity::Player::getFreeSlotsInBags()
{
uint16_t slots = 0;
for( uint8_t container : { Bag0, Bag1, Bag2, Bag3 } )
{
const auto& storage = m_storageMap[ container ];
slots += ( storage->getMaxSize() - storage->getEntryCount() );
}
return slots;
}
bool Sapphire::Entity::Player::collectHandInItems( std::vector< uint32_t > itemIds )
{
// todo: figure out how the game gets the required stack count
2018-09-01 14:36:04 +10:00
const auto& container = m_storageMap[ HandIn ];
std::vector< uint8_t > foundItems;
auto itemMap = container->getItemMap();
for( auto& item : itemMap )
{
for( auto needle : itemIds )
{
if( item.second->getId() == needle )
{
foundItems.push_back( item.first );
break;
}
}
}
// couldn't find all the items required
if( foundItems.size() != itemIds.size() )
return false;
// remove items
for( auto item : foundItems )
{
container->removeItem( item );
}
return true;
}
uint32_t Sapphire::Entity::Player::getNextInventorySequence()
{
return m_inventorySequence++;
}
2018-12-26 18:11:18 +11:00
Sapphire::ItemPtr Sapphire::Entity::Player::dropInventoryItem( Sapphire::Common::InventoryType storageId, uint8_t slotId )
2018-12-26 18:11:18 +11:00
{
auto& container = m_storageMap[ storageId ];
2018-12-26 18:11:18 +11:00
auto item = container->getItem( slotId );
if( !item )
return nullptr;
// unlink item
container->removeItem( slotId, false );
updateContainer( storageId, slotId, nullptr );
2018-12-26 18:11:18 +11:00
auto seq = getNextInventorySequence();
// send inv update
auto invTransPacket = makeZonePacket< FFXIVIpcItemOperation >( getId() );
invTransPacket->data().contextId = seq;
invTransPacket->data().dstEntity = getId();
invTransPacket->data().dstStorageId = storageId;
invTransPacket->data().dstCatalogId = item->getId();
invTransPacket->data().dstStack = item->getStackSize();
invTransPacket->data().dstContainerIndex = slotId;
invTransPacket->data().operationType = Common::ITEM_OPERATION_TYPE::ITEM_OPERATION_TYPE_DELETEITEM;
2018-12-26 18:11:18 +11:00
queuePacket( invTransPacket );
auto invTransFinPacket = makeZonePacket< FFXIVIpcItemOperationBatch >( getId() );
invTransFinPacket->data().contextId = seq;
invTransFinPacket->data().operationId = seq;
invTransFinPacket->data().operationType = Common::ITEM_OPERATION_TYPE::ITEM_OPERATION_TYPE_DELETEITEM;
2018-12-26 18:11:18 +11:00
queuePacket( invTransFinPacket );
return item;
}
void Sapphire::Entity::Player::addSoldItem( uint32_t itemId, uint8_t stackSize )
{
if( m_soldItems.size() > 10 )
m_soldItems.pop_back();
m_soldItems.push_front( std::make_pair( itemId, stackSize ) );
}
std::deque< std::pair< uint32_t, uint8_t > > *Sapphire::Entity::Player::getSoldItems()
{
return &m_soldItems;
}
void Sapphire::Entity::Player::clearSoldItems()
{
m_soldItems.clear();
}
bool Sapphire::Entity::Player::getFreeInventoryContainerSlot( Inventory::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( uint8_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 )
{
updateContainer( type, slot, item );
auto seq = getNextInventorySequence();
auto slotUpdate = std::make_shared< UpdateInventorySlotPacket >( getId(), slot, type, *item, seq );
queuePacket( slotUpdate );
auto invTransFinPacket = makeZonePacket< FFXIVIpcItemOperationBatch >( getId() );
invTransFinPacket->data().contextId = seq;
invTransFinPacket->data().operationId = seq;
invTransFinPacket->data().operationType = Common::ITEM_OPERATION_TYPE::ITEM_OPERATION_TYPE_UPDATEITEM;
queuePacket( invTransFinPacket );
2018-12-30 17:44:03 +11:00
}
bool Sapphire::Entity::Player::findFirstItemWithId( uint32_t catalogId,
Inventory::InventoryContainerPair& location, std::initializer_list< InventoryType > bags )
2018-12-30 17:44:03 +11:00
{
for( auto bagId : bags )
2018-12-30 17:44:03 +11:00
{
auto& container = m_storageMap[ bagId ];
for( const auto& item : container->getItemMap() )
{
2022-02-12 19:20:13 +00:00
if( ( item.second && item.second->getId() != catalogId ) || !item.second )
2018-12-30 17:44:03 +11:00
continue;
location = std::make_pair( bagId, item.first );
return true;
}
}
return false;
2022-02-12 19:20:13 +00:00
}