2018-03-06 22:22:19 +01:00
|
|
|
#include <Common.h>
|
|
|
|
#include <Logging/Logger.h>
|
2018-06-23 21:38:04 +02:00
|
|
|
#include <Network/CommonActorControl.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
|
|
|
|
2019-10-09 18:14:53 +02:00
|
|
|
#include "Network/PacketWrappers/ActorControlPacket.h"
|
|
|
|
#include "Network/PacketWrappers/ActorControlSelfPacket.h"
|
2018-07-26 14:09:09 +02:00
|
|
|
#include "Network/PacketWrappers/UpdateInventorySlotPacket.h"
|
2017-08-08 13:53:47 +02:00
|
|
|
|
2017-12-08 15:38:25 +01:00
|
|
|
#include "Inventory/Item.h"
|
2018-07-24 23:58:08 +02:00
|
|
|
#include "Inventory/ItemContainer.h"
|
2018-12-23 13:26:33 +01:00
|
|
|
|
2017-08-08 13:53:47 +02:00
|
|
|
|
2018-03-02 07:22:25 -03:00
|
|
|
#include "Player.h"
|
|
|
|
#include "Framework.h"
|
|
|
|
|
2018-07-24 23:58:08 +02:00
|
|
|
#include <Network/PacketDef/Zone/ServerZoneDef.h>
|
|
|
|
|
|
|
|
#include <Exd/ExdDataGenerated.h>
|
|
|
|
#include <Logging/Logger.h>
|
|
|
|
#include <Database/DatabaseDef.h>
|
|
|
|
|
|
|
|
#include "Actor/Player.h"
|
|
|
|
|
|
|
|
#include "Network/PacketWrappers/ServerNoticePacket.h"
|
2019-10-09 18:14:53 +02:00
|
|
|
#include "Network/PacketWrappers/ActorControlSelfPacket.h"
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-12-18 22:59:01 +11:00
|
|
|
#include "Manager/InventoryMgr.h"
|
2018-12-23 13:26:33 +01:00
|
|
|
#include "Manager/ItemMgr.h"
|
2018-07-24 23:58:08 +02:00
|
|
|
|
|
|
|
#include "Framework.h"
|
|
|
|
#include <Network/CommonActorControl.h>
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
using namespace Sapphire::Common;
|
|
|
|
using namespace Sapphire::Network::Packets;
|
|
|
|
using namespace Sapphire::Network::Packets::Server;
|
|
|
|
using namespace Sapphire::Network::ActorControl;
|
2017-08-08 13:53:47 +02:00
|
|
|
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
void Sapphire::Entity::Player::initInventory()
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
auto setupContainer = [ this ]( InventoryType type, uint8_t maxSize, const std::string& tableName,
|
|
|
|
bool isMultiStorage, bool isPersistentStorage = true )
|
2018-12-29 00:53:52 +01:00
|
|
|
{ m_storageMap[ type ] = make_ItemContainer( type, maxSize, tableName, isMultiStorage, m_pFw, isPersistentStorage ); };
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// main bags
|
|
|
|
setupContainer( Bag0, 34, "charaiteminventory", true );
|
|
|
|
setupContainer( Bag1, 34, "charaiteminventory", true );
|
|
|
|
setupContainer( Bag2, 34, "charaiteminventory", true );
|
|
|
|
setupContainer( Bag3, 34, "charaiteminventory", true );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// gear set
|
|
|
|
setupContainer( GearSet0, 13, "charaitemgearset", true );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// gil contianer
|
|
|
|
setupContainer( Currency, 11, "charaiteminventory", true );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// crystals??
|
|
|
|
setupContainer( Crystal, 11, "charaiteminventory", true );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// armory weapons - 0
|
|
|
|
setupContainer( ArmoryMain, 34, "charaiteminventory", true );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// armory offhand - 1
|
|
|
|
setupContainer( ArmoryOff, 34, "charaiteminventory", true );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
//armory head - 2
|
|
|
|
setupContainer( ArmoryHead, 34, "charaiteminventory", true );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
//armory body - 3
|
|
|
|
setupContainer( ArmoryBody, 34, "charaiteminventory", true );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
//armory hand - 4
|
|
|
|
setupContainer( ArmoryHand, 34, "charaiteminventory", true );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
//armory waist - 5
|
|
|
|
setupContainer( ArmoryWaist, 34, "charaiteminventory", true );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
//armory legs - 6
|
|
|
|
setupContainer( ArmoryLegs, 34, "charaiteminventory", true );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
//armory feet - 7
|
|
|
|
setupContainer( ArmoryFeet, 34, "charaiteminventory", true );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
//neck
|
|
|
|
setupContainer( ArmoryNeck, 34, "charaiteminventory", true );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
//earring
|
|
|
|
setupContainer( ArmoryEar, 34, "charaiteminventory", true );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
//wrist
|
|
|
|
setupContainer( ArmoryWrist, 34, "charaiteminventory", true );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
//armory rings - 11
|
|
|
|
setupContainer( ArmoryRing, 34, "charaiteminventory", true );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
//soul crystals - 13
|
|
|
|
setupContainer( ArmorySoulCrystal, 34, "charaiteminventory", true );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// item hand in container
|
|
|
|
// non-persistent container, will not save its contents
|
|
|
|
setupContainer( HandIn, 10, "", true, false );
|
2018-08-12 22:53:21 +10:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
loadInventory();
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
void Sapphire::Entity::Player::sendItemLevel()
|
2017-11-16 00:03:36 -02:00
|
|
|
{
|
2019-10-09 18:42:25 +02:00
|
|
|
queuePacket( makeActorControl( getId(), SetItemLevel, getItemLevel(), 0 ) );
|
2017-11-16 00:03:36 -02:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
void Sapphire::Entity::Player::equipWeapon( ItemPtr pItem, bool updateClass )
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2018-12-25 01:57:50 +01:00
|
|
|
auto exdData = m_pFw->get< Sapphire::Data::ExdDataGenerated >();
|
2018-08-29 21:40:59 +02:00
|
|
|
if( !exdData )
|
|
|
|
return;
|
2017-08-08 13:53:47 +02:00
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
auto itemInfo = exdData->get< Sapphire::Data::Item >( pItem->getId() );
|
2018-08-29 21:40:59 +02:00
|
|
|
auto itemClassJob = itemInfo->classJobUse;
|
2018-11-29 16:55:48 +01:00
|
|
|
auto classJobInfo = exdData->get< Sapphire::Data::ClassJob >( static_cast< uint32_t >( getClass() ) );
|
2018-10-18 07:48:34 +03:00
|
|
|
auto currentParentClass = static_cast< ClassJob >( classJobInfo->classJobParent );
|
2018-08-29 21:40:59 +02:00
|
|
|
auto newClassJob = static_cast< ClassJob >( itemClassJob );
|
2018-08-26 16:19:05 +10:00
|
|
|
|
2018-10-17 23:25:41 +03:00
|
|
|
if( ( isClassJobUnlocked( newClassJob ) ) && ( currentParentClass != newClassJob ) )
|
2018-10-18 20:59:04 +03:00
|
|
|
{
|
|
|
|
if ( updateClass )
|
|
|
|
setClassJob( newClassJob );
|
|
|
|
else
|
|
|
|
return;
|
|
|
|
}
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
void Sapphire::Entity::Player::equipSoulCrystal( ItemPtr pItem, bool updateJob )
|
2018-10-20 14:20:39 +03:00
|
|
|
{
|
2018-12-25 01:57:50 +01:00
|
|
|
auto exdData = m_pFw->get< Sapphire::Data::ExdDataGenerated >();
|
2018-10-20 14:20:39 +03:00
|
|
|
if ( !exdData )
|
|
|
|
return;
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
auto itemInfo = exdData->get< Sapphire::Data::Item >( pItem->getId() );
|
2018-10-20 14:20:39 +03:00
|
|
|
auto itemClassJob = itemInfo->classJobUse;
|
|
|
|
auto newClassJob = static_cast< ClassJob >( itemClassJob );
|
|
|
|
|
|
|
|
if ( isClassJobUnlocked( newClassJob ) && updateJob )
|
|
|
|
setClassJob( newClassJob );
|
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
void Sapphire::Entity::Player::updateModels( GearSetSlot equipSlotId, const Sapphire::ItemPtr& pItem, bool updateClass )
|
2018-09-17 22:52:57 +02:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
uint64_t model = pItem->getModelId1();
|
|
|
|
uint64_t model2 = pItem->getModelId2();
|
2019-04-13 22:41:29 +10:00
|
|
|
uint64_t stain = pItem->getStain();
|
2017-08-08 13:53:47 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
switch( equipSlotId )
|
|
|
|
{
|
2018-09-17 22:52:57 +02:00
|
|
|
case MainHand:
|
2019-04-13 22:41:29 +10:00
|
|
|
m_modelMainWeapon = model | ( stain << 48 );
|
|
|
|
|
2017-08-08 13:53:47 +02:00
|
|
|
m_modelSubWeapon = model2;
|
2019-04-13 22:41:29 +10:00
|
|
|
|
|
|
|
if( m_modelSubWeapon > 0 )
|
|
|
|
{
|
|
|
|
m_modelSubWeapon = m_modelSubWeapon | ( stain << 48 );
|
|
|
|
}
|
|
|
|
|
2018-10-18 20:59:04 +03:00
|
|
|
equipWeapon( pItem, updateClass );
|
2017-08-08 13:53:47 +02:00
|
|
|
break;
|
|
|
|
|
2018-09-17 22:52:57 +02:00
|
|
|
case OffHand:
|
2019-04-13 22:41:29 +10:00
|
|
|
m_modelSubWeapon = model | ( stain << 48 );
|
2017-08-08 13:53:47 +02:00
|
|
|
break;
|
|
|
|
|
2018-09-17 22:52:57 +02:00
|
|
|
case SoulCrystal:
|
2018-10-20 14:20:39 +03:00
|
|
|
equipSoulCrystal( pItem, updateClass );
|
2017-08-08 13:53:47 +02:00
|
|
|
break;
|
|
|
|
|
2018-09-17 22:52:57 +02:00
|
|
|
case Waist:
|
|
|
|
break;
|
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
default: // any other slot
|
2018-09-17 22:52:57 +02:00
|
|
|
auto modelSlot = equipSlotToModelSlot( equipSlotId );
|
|
|
|
if( modelSlot == GearModelSlot::ModelInvalid )
|
|
|
|
break;
|
2019-04-13 22:41:29 +10:00
|
|
|
|
|
|
|
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-08-29 21:40:59 +02:00
|
|
|
}
|
2018-09-17 22:52:57 +02:00
|
|
|
}
|
2017-08-08 13:53:47 +02:00
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
Sapphire::Common::GearModelSlot Sapphire::Entity::Player::equipSlotToModelSlot( Common::GearSetSlot slot )
|
2018-09-17 22:52:57 +02:00
|
|
|
{
|
|
|
|
switch( slot )
|
2018-08-29 21:40:59 +02:00
|
|
|
{
|
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;
|
2018-08-29 21:40:59 +02:00
|
|
|
}
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
2019-03-20 22:16:02 +01:00
|
|
|
// equip an item
|
|
|
|
void Sapphire::Entity::Player::equipItem( Common::GearSetSlot equipSlotId, ItemPtr pItem, bool sendUpdate )
|
|
|
|
{
|
|
|
|
|
|
|
|
//g_framework.getLogger().debug( "Equipping into slot " + std::to_string( equipSlotId ) );
|
|
|
|
if( sendUpdate )
|
|
|
|
{
|
|
|
|
updateModels( equipSlotId, pItem, true );
|
|
|
|
sendModel();
|
|
|
|
m_itemLevel = calculateEquippedGearItemLevel();
|
|
|
|
sendItemLevel();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
updateModels( equipSlotId, pItem, false );
|
2019-03-21 18:06:48 +01:00
|
|
|
|
|
|
|
auto baseParams = pItem->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 ) ] += pItem->getDefense();
|
|
|
|
m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::MagicDefense ) ] += pItem->getDefenseMag();
|
|
|
|
|
2019-03-21 18:53:32 +01:00
|
|
|
calculateStats();
|
|
|
|
if( sendUpdate )
|
|
|
|
{
|
|
|
|
sendStats();
|
|
|
|
sendStatusUpdate();
|
|
|
|
}
|
2019-03-20 22:16:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-03-21 18:06:48 +01:00
|
|
|
void Sapphire::Entity::Player::unequipItem( Common::GearSetSlot equipSlotId, ItemPtr pItem, 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;
|
2017-11-16 00:03:36 -02:00
|
|
|
|
2019-03-21 18:06:48 +01:00
|
|
|
if( sendUpdate )
|
|
|
|
{
|
|
|
|
sendModel();
|
|
|
|
|
|
|
|
m_itemLevel = calculateEquippedGearItemLevel();
|
|
|
|
sendItemLevel();
|
|
|
|
}
|
2018-10-20 14:20:39 +03:00
|
|
|
|
|
|
|
if ( equipSlotId == SoulCrystal )
|
|
|
|
unequipSoulCrystal( pItem );
|
2019-03-21 18:06:48 +01:00
|
|
|
|
|
|
|
auto baseParams = pItem->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 ) ] -= pItem->getDefense();
|
|
|
|
m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::MagicDefense ) ] -= pItem->getDefenseMag();
|
|
|
|
|
2019-03-21 18:53:32 +01:00
|
|
|
calculateStats();
|
|
|
|
|
2019-03-21 18:06:48 +01:00
|
|
|
if( sendUpdate )
|
|
|
|
{
|
|
|
|
sendStats();
|
2019-03-21 18:53:32 +01:00
|
|
|
sendStatusUpdate();
|
2019-03-21 18:06:48 +01:00
|
|
|
}
|
2018-10-20 14:20:39 +03:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
void Sapphire::Entity::Player::unequipSoulCrystal( ItemPtr pItem )
|
2018-10-20 14:20:39 +03:00
|
|
|
{
|
2018-12-25 01:57:50 +01:00
|
|
|
auto exdData = m_pFw->get< Sapphire::Data::ExdDataGenerated >();
|
2018-10-20 14:20:39 +03:00
|
|
|
if ( !exdData )
|
|
|
|
return;
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
auto currentClassJob = exdData->get< Sapphire::Data::ClassJob >( static_cast< uint32_t >( getClass() ) );
|
2018-10-20 14:20:39 +03:00
|
|
|
auto parentClass = static_cast< ClassJob >( currentClassJob->classJobParent );
|
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
|
2018-11-29 16:55:48 +01:00
|
|
|
void Sapphire::Entity::Player::addCurrency( CurrencyType type, uint32_t amount )
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2018-08-29 21:40:59 +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 );
|
|
|
|
|
2018-10-25 12:44:51 +11:00
|
|
|
auto invUpdate = std::make_shared< UpdateInventorySlotPacket >( getId(),
|
2018-08-29 21:40:59 +02:00
|
|
|
static_cast< uint8_t >( type ) - 1,
|
|
|
|
Common::InventoryType::Currency,
|
|
|
|
*currItem );
|
|
|
|
queuePacket( invUpdate );
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
void Sapphire::Entity::Player::removeCurrency( Common::CurrencyType type, uint32_t amount )
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
auto currItem = m_storageMap[ Currency ]->getItem( static_cast< uint8_t >( type ) - 1 );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( !currItem )
|
|
|
|
return;
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
uint32_t currentAmount = currItem->getStackSize();
|
|
|
|
if( amount > currentAmount )
|
|
|
|
currItem->setStackSize( 0 );
|
|
|
|
else
|
|
|
|
currItem->setStackSize( currentAmount - amount );
|
|
|
|
writeItem( currItem );
|
2017-08-08 13:53:47 +02:00
|
|
|
|
2018-10-25 12:44:51 +11:00
|
|
|
auto invUpdate = std::make_shared< UpdateInventorySlotPacket >( getId(),
|
2018-08-29 21:40:59 +02:00
|
|
|
static_cast< uint8_t >( type ) - 1,
|
|
|
|
Common::InventoryType::Currency,
|
|
|
|
*currItem );
|
|
|
|
queuePacket( invUpdate );
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
void Sapphire::Entity::Player::addCrystal( Common::CrystalType type, uint32_t amount )
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
auto currItem = m_storageMap[ Crystal ]->getItem( static_cast< uint8_t >( type ) - 1 );
|
2017-08-08 13:53:47 +02:00
|
|
|
|
2018-08-29 21:40:59 +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 );
|
|
|
|
}
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
uint32_t currentAmount = currItem->getStackSize();
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
currItem->setStackSize( currentAmount + amount );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
writeItem( currItem );
|
2017-08-08 13:53:47 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
writeInventory( Crystal );
|
2018-07-26 23:12:02 +10:00
|
|
|
|
2017-08-08 13:53:47 +02:00
|
|
|
|
2018-10-25 12:44:51 +11:00
|
|
|
auto invUpdate = std::make_shared< UpdateInventorySlotPacket >( getId(),
|
2018-08-29 21:40:59 +02:00
|
|
|
static_cast< uint8_t >( type ) - 1,
|
|
|
|
Common::InventoryType::Crystal,
|
|
|
|
*currItem );
|
|
|
|
queuePacket( invUpdate );
|
2019-10-09 18:42:25 +02:00
|
|
|
queuePacket( makeActorControlSelf( getId(), ItemObtainIcon, static_cast< uint8_t >( type ) + 1, amount ) );
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
void Sapphire::Entity::Player::removeCrystal( Common::CrystalType type, uint32_t amount )
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
auto currItem = m_storageMap[ Crystal ]->getItem( static_cast< uint8_t >( type ) - 1 );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( !currItem )
|
|
|
|
return;
|
2017-08-08 13:53:47 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
uint32_t currentAmount = currItem->getStackSize();
|
|
|
|
if( amount > currentAmount )
|
|
|
|
currItem->setStackSize( 0 );
|
|
|
|
else
|
|
|
|
currItem->setStackSize( currentAmount - amount );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
writeItem( currItem );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-10-25 12:44:51 +11:00
|
|
|
auto invUpdate = std::make_shared< UpdateInventorySlotPacket >( getId(),
|
2018-08-29 21:40:59 +02:00
|
|
|
static_cast< uint8_t >( type ) - 1,
|
|
|
|
Common::InventoryType::Crystal,
|
|
|
|
*currItem );
|
|
|
|
queuePacket( invUpdate );
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
void Sapphire::Entity::Player::sendInventory()
|
2018-07-24 23:58:08 +02:00
|
|
|
{
|
2018-12-25 01:57:50 +01:00
|
|
|
auto pInvMgr = m_pFw->get< World::Manager::InventoryMgr >();
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-12-20 20:41:16 +11:00
|
|
|
for( auto it = m_storageMap.begin(); it != m_storageMap.end(); ++it )
|
2018-12-18 22:59:01 +11:00
|
|
|
{
|
2018-12-20 20:41:16 +11:00
|
|
|
pInvMgr->sendInventoryContainer( *this, it->second );
|
2018-08-29 21:40:59 +02:00
|
|
|
}
|
2018-07-24 23:58:08 +02:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
Sapphire::Entity::Player::InvSlotPairVec Sapphire::Entity::Player::getSlotsOfItemsInInventory( uint32_t catalogId )
|
2018-07-24 23:58:08 +02:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
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;
|
2018-07-24 23:58:08 +02:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
Sapphire::Entity::Player::InvSlotPair Sapphire::Entity::Player::getFreeBagSlot()
|
2018-07-24 23:58:08 +02:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
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 );
|
2018-07-24 23:58:08 +02:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
Sapphire::ItemPtr Sapphire::Entity::Player::getItemAt( uint16_t containerId, uint8_t slotId )
|
2018-07-24 23:58:08 +02:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return m_storageMap[ containerId ]->getItem( slotId );
|
2018-07-24 23:58:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
uint32_t Sapphire::Entity::Player::getCurrency( CurrencyType type )
|
2018-07-24 23:58:08 +02:00
|
|
|
{
|
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
auto currItem = m_storageMap[ Currency ]->getItem( static_cast< uint8_t >( type ) - 1 );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( !currItem )
|
|
|
|
return 0;
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
return currItem->getStackSize();
|
2018-07-24 23:58:08 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
uint32_t Sapphire::Entity::Player::getCrystal( CrystalType type )
|
2018-07-24 23:58:08 +02:00
|
|
|
{
|
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
auto currItem = m_storageMap[ Crystal ]->getItem( static_cast< uint8_t >( type ) - 1 );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( !currItem )
|
|
|
|
return 0;
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
return currItem->getStackSize();
|
2018-07-24 23:58:08 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
void Sapphire::Entity::Player::writeInventory( InventoryType type )
|
2018-07-24 23:58:08 +02:00
|
|
|
{
|
2018-12-25 01:57:50 +01:00
|
|
|
auto pDb = m_pFw->get< Db::DbWorkerPool< Db::ZoneDbConnection > >();
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
auto storage = m_storageMap[ type ];
|
2018-07-26 23:40:46 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( !storage->isPersistentStorage() )
|
|
|
|
return;
|
2018-08-12 22:53:21 +10:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
std::string query = "UPDATE " + storage->getTableName() + " SET ";
|
2018-07-26 23:12:02 +10:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
for( int32_t i = 0; i <= storage->getMaxSize(); i++ )
|
|
|
|
{
|
|
|
|
auto currItem = storage->getItem( i );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( i > 0 )
|
|
|
|
query += ", ";
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
query += "container_" + std::to_string( i ) + " = " + std::to_string( currItem ? currItem->getUId() : 0 );
|
|
|
|
}
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
query += " WHERE CharacterId = " + std::to_string( getId() );
|
2018-07-26 23:40:46 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( storage->isMultiStorage() )
|
|
|
|
query += " AND storageId = " + std::to_string( static_cast< uint16_t >( type ) );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
pDb->execute( query );
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
void Sapphire::Entity::Player::writeItem( Sapphire::ItemPtr pItem ) const
|
2018-07-24 23:58:08 +02:00
|
|
|
{
|
2018-12-25 01:57:50 +01:00
|
|
|
auto pDb = m_pFw->get< Db::DbWorkerPool< Db::ZoneDbConnection > >();
|
2018-08-31 23:25:53 +10:00
|
|
|
auto stmt = pDb->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() );
|
|
|
|
|
|
|
|
pDb->directExecute( stmt );
|
2018-07-24 23:58:08 +02:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
void Sapphire::Entity::Player::deleteItemDb( Sapphire::ItemPtr item ) const
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2018-12-25 01:57:50 +01:00
|
|
|
auto pDb = m_pFw->get< Db::DbWorkerPool< Db::ZoneDbConnection > >();
|
2018-08-31 23:25:53 +10:00
|
|
|
auto stmt = pDb->getPreparedStatement( Db::CHARA_ITEMGLOBAL_DELETE );
|
|
|
|
|
|
|
|
stmt->setInt64( 1, item->getUId() );
|
|
|
|
|
|
|
|
pDb->directExecute( stmt );
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
bool Sapphire::Entity::Player::isObtainable( uint32_t catalogId, uint8_t quantity )
|
2018-07-24 23:58:08 +02:00
|
|
|
{
|
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
return true;
|
2018-07-24 23:58:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-02-24 18:23:43 +09:00
|
|
|
Sapphire::ItemPtr Sapphire::Entity::Player::addItem( uint32_t catalogId, uint32_t quantity, bool isHq, bool silent, bool canMerge )
|
2018-07-24 23:58:08 +02:00
|
|
|
{
|
2018-12-25 01:57:50 +01:00
|
|
|
auto pDb = m_pFw->get< Db::DbWorkerPool< Db::ZoneDbConnection > >();
|
|
|
|
auto pExdData = m_pFw->get< Data::ExdDataGenerated >();
|
2018-11-29 16:55:48 +01:00
|
|
|
auto itemInfo = pExdData->get< Sapphire::Data::Item >( catalogId );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// if item data doesn't exist or it's a blank field
|
|
|
|
if( !itemInfo || itemInfo->levelItem == 0 )
|
|
|
|
{
|
|
|
|
return nullptr;
|
|
|
|
}
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
quantity = std::min< uint32_t >( quantity, itemInfo->stackSize );
|
2018-08-25 00:15:26 +10:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// used for item obtain notification
|
|
|
|
uint32_t originalQuantity = quantity;
|
2018-08-25 00:15:26 +10:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
std::pair< uint16_t, uint8_t > freeBagSlot;
|
|
|
|
bool foundFreeSlot = false;
|
2018-08-25 00:15:26 +10:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
std::vector< uint16_t > bags = { Bag0, Bag1, Bag2, Bag3 };
|
2018-08-25 22:38:07 +10:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// add the related armoury bag to the applicable bags and try and fill a free slot there before falling back to regular inventory
|
|
|
|
if( itemInfo->isEquippable && getEquipDisplayFlags() & StoreNewItemsInArmouryChest )
|
|
|
|
{
|
2018-12-23 13:26:33 +01:00
|
|
|
auto bag = World::Manager::ItemMgr::getCharaEquipSlotCategoryToArmoryId( itemInfo->equipSlotCategory );
|
2018-08-25 22:38:07 +10:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
bags.insert( bags.begin(), bag );
|
|
|
|
}
|
2018-08-25 22:38:07 +10:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
for( auto bag : bags )
|
|
|
|
{
|
|
|
|
auto storage = m_storageMap[ bag ];
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
for( uint8_t slot = 0; slot < storage->getMaxSize(); slot++ )
|
|
|
|
{
|
2020-02-24 18:23:43 +09:00
|
|
|
if( !canMerge && foundFreeSlot )
|
|
|
|
break;
|
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
auto item = storage->getItem( slot );
|
|
|
|
|
|
|
|
// add any items that are stackable
|
2020-02-24 18:23:43 +09:00
|
|
|
if( canMerge && item && !itemInfo->isEquippable && item->getId() == catalogId )
|
2018-08-25 00:15:26 +10:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
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;
|
|
|
|
}
|
2020-01-03 01:07:42 +09:00
|
|
|
else
|
|
|
|
quantity = 0;
|
2018-08-29 21:40:59 +02:00
|
|
|
|
|
|
|
item->setStackSize( newStackSize );
|
|
|
|
writeItem( item );
|
|
|
|
|
2018-10-25 12:44:51 +11:00
|
|
|
auto slotUpdate = std::make_shared< UpdateInventorySlotPacket >( getId(), slot, bag, *item );
|
2018-08-29 21:40:59 +02:00
|
|
|
queuePacket( slotUpdate );
|
|
|
|
|
|
|
|
// return existing stack if we have no overflow - items fit into a preexisting stack
|
|
|
|
if( quantity == 0 )
|
|
|
|
{
|
2019-10-09 18:42:25 +02:00
|
|
|
queuePacket( makeActorControlSelf( getId(), ItemObtainIcon, catalogId, originalQuantity ) );
|
2018-08-29 21:40:59 +02:00
|
|
|
|
2020-01-05 06:20:50 +09:00
|
|
|
auto soundEffectPacket = makeZonePacket< FFXIVIpcInventoryActionAck >( getId() );
|
|
|
|
soundEffectPacket->data().sequence = 0xFFFFFFFF;
|
|
|
|
soundEffectPacket->data().type = 6;
|
|
|
|
queuePacket( soundEffectPacket );
|
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
return item;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else if( !item && !foundFreeSlot )
|
|
|
|
{
|
|
|
|
freeBagSlot = { bag, slot };
|
|
|
|
foundFreeSlot = true;
|
2018-08-12 22:30:18 +10:00
|
|
|
}
|
2018-08-29 21:40:59 +02:00
|
|
|
}
|
|
|
|
}
|
2018-08-12 22:30:18 +10:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// couldn't find a free slot and we still have some quantity of items left, shits fucked
|
|
|
|
if( !foundFreeSlot )
|
|
|
|
return nullptr;
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
auto item = createItem( catalogId, quantity );
|
|
|
|
item->setHq( isHq );
|
2018-08-25 00:15:26 +10:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
auto storage = m_storageMap[ freeBagSlot.first ];
|
|
|
|
storage->setItem( freeBagSlot.second, item );
|
2018-08-25 00:15:26 +10:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
writeInventory( static_cast< InventoryType >( freeBagSlot.first ) );
|
2018-08-25 00:15:26 +10:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( !silent )
|
|
|
|
{
|
2018-10-25 12:44:51 +11:00
|
|
|
auto invUpdate = std::make_shared< UpdateInventorySlotPacket >( getId(), freeBagSlot.second, freeBagSlot.first,
|
2018-08-29 21:40:59 +02:00
|
|
|
*item );
|
|
|
|
queuePacket( invUpdate );
|
2018-08-25 00:15:26 +10:00
|
|
|
|
2019-10-09 18:42:25 +02:00
|
|
|
queuePacket( makeActorControlSelf( getId(), ItemObtainIcon, catalogId, originalQuantity ) );
|
2020-01-05 06:20:50 +09:00
|
|
|
|
|
|
|
auto soundEffectPacket = makeZonePacket< FFXIVIpcInventoryActionAck >( getId() );
|
|
|
|
soundEffectPacket->data().sequence = 0xFFFFFFFF;
|
|
|
|
soundEffectPacket->data().type = 6;
|
|
|
|
queuePacket( soundEffectPacket );
|
2018-08-29 21:40:59 +02:00
|
|
|
}
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
return item;
|
2018-07-24 23:58:08 +02:00
|
|
|
}
|
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
void
|
2018-11-29 16:55:48 +01:00
|
|
|
Sapphire::Entity::Player::moveItem( uint16_t fromInventoryId, uint8_t fromSlotId, uint16_t toInventoryId, uint8_t toSlot )
|
2018-07-24 23:58:08 +02:00
|
|
|
{
|
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
auto tmpItem = m_storageMap[ fromInventoryId ]->getItem( fromSlotId );
|
|
|
|
auto& itemMap = m_storageMap[ fromInventoryId ]->getItemMap();
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( tmpItem == nullptr )
|
|
|
|
return;
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
itemMap[ fromSlotId ].reset();
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
m_storageMap[ toInventoryId ]->setItem( toSlot, tmpItem );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
writeInventory( static_cast< InventoryType >( toInventoryId ) );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( fromInventoryId != toInventoryId )
|
|
|
|
writeInventory( static_cast< InventoryType >( fromInventoryId ) );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( static_cast< InventoryType >( toInventoryId ) == GearSet0 )
|
|
|
|
equipItem( static_cast< GearSetSlot >( toSlot ), tmpItem, true );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( static_cast< InventoryType >( fromInventoryId ) == GearSet0 )
|
2019-03-21 18:06:48 +01:00
|
|
|
unequipItem( static_cast< GearSetSlot >( fromSlotId ), tmpItem, true );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
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
|
2018-07-24 23:58:08 +02:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
bool Sapphire::Entity::Player::updateContainer( uint16_t storageId, uint8_t slotId, ItemPtr pItem )
|
2018-07-24 23:58:08 +02:00
|
|
|
{
|
2018-12-23 13:26:33 +01:00
|
|
|
auto containerType = World::Manager::ItemMgr::getContainerType( storageId );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2019-03-21 18:06:48 +01:00
|
|
|
auto pOldItem = getItemAt( storageId, slotId );
|
2018-08-29 21:40:59 +02:00
|
|
|
m_storageMap[ storageId ]->setItem( slotId, pItem );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
switch( containerType )
|
|
|
|
{
|
|
|
|
case Armory:
|
|
|
|
case Bag:
|
|
|
|
case CurrencyCrystal:
|
|
|
|
{
|
|
|
|
writeInventory( static_cast< InventoryType >( storageId ) );
|
|
|
|
break;
|
|
|
|
}
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
case GearSet:
|
|
|
|
{
|
|
|
|
if( pItem )
|
2019-03-21 18:06:48 +01:00
|
|
|
{
|
|
|
|
if( pOldItem )
|
|
|
|
unequipItem( static_cast< GearSetSlot >( slotId ), pOldItem, false );
|
2018-08-29 21:40:59 +02:00
|
|
|
equipItem( static_cast< GearSetSlot >( slotId ), pItem, true );
|
2019-03-21 18:06:48 +01:00
|
|
|
}
|
2018-08-29 21:40:59 +02:00
|
|
|
else
|
2019-03-21 18:06:48 +01:00
|
|
|
unequipItem( static_cast< GearSetSlot >( slotId ), pItem, true );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
writeInventory( static_cast< InventoryType >( storageId ) );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
return true;
|
2018-07-24 23:58:08 +02:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
void Sapphire::Entity::Player::splitItem( uint16_t fromInventoryId, uint8_t fromSlotId,
|
2019-03-21 18:06:48 +01:00
|
|
|
uint16_t toInventoryId, uint8_t toSlot, uint16_t itemCount )
|
2018-07-24 23:58:08 +02:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
if( itemCount == 0 )
|
|
|
|
return;
|
2018-08-12 22:30:18 +10:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
auto fromItem = m_storageMap[ fromInventoryId ]->getItem( fromSlotId );
|
|
|
|
if( !fromItem )
|
|
|
|
return;
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// 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;
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// 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;
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2020-02-24 18:23:43 +09:00
|
|
|
auto newItem = addItem( fromItem->getId(), itemCount, fromItem->isHq(), true, false );
|
2018-08-29 21:40:59 +02:00
|
|
|
if( !newItem )
|
|
|
|
return;
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
fromItem->setStackSize( fromItem->getStackSize() - itemCount );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
updateContainer( fromInventoryId, fromSlotId, fromItem );
|
|
|
|
updateContainer( toInventoryId, toSlot, newItem );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
writeItem( fromItem );
|
2018-07-24 23:58:08 +02:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
void Sapphire::Entity::Player::mergeItem( uint16_t fromInventoryId, uint8_t fromSlotId,
|
2019-03-21 18:06:48 +01:00
|
|
|
uint16_t toInventoryId, uint8_t toSlot )
|
2018-07-24 23:58:08 +02:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
auto fromItem = m_storageMap[ fromInventoryId ]->getItem( fromSlotId );
|
|
|
|
auto toItem = m_storageMap[ toInventoryId ]->getItem( toSlot );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( !fromItem || !toItem )
|
|
|
|
return;
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( fromItem->getId() != toItem->getId() )
|
|
|
|
return;
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
uint32_t stackSize = fromItem->getStackSize() + toItem->getStackSize();
|
|
|
|
uint32_t stackOverflow = stackSize - std::min< uint32_t >( fromItem->getMaxStackSize(), stackSize );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// 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 );
|
|
|
|
}
|
2018-07-24 23:58:08 +02:00
|
|
|
|
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
toItem->setStackSize( stackSize );
|
|
|
|
writeItem( toItem );
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
updateContainer( fromInventoryId, fromSlotId, fromItem );
|
|
|
|
updateContainer( toInventoryId, toSlot, toItem );
|
2018-07-24 23:58:08 +02:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
void Sapphire::Entity::Player::swapItem( uint16_t fromInventoryId, uint8_t fromSlotId,
|
2019-03-21 18:06:48 +01:00
|
|
|
uint16_t toInventoryId, uint8_t toSlot )
|
2018-07-24 23:58:08 +02:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
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 ) )
|
2018-08-29 21:40:59 +02:00
|
|
|
{
|
|
|
|
updateContainer( fromInventoryId, fromSlotId, nullptr );
|
2018-12-23 13:26:33 +01:00
|
|
|
fromInventoryId = World::Manager::ItemMgr::getCharaEquipSlotCategoryToArmoryId( toSlot );
|
2018-08-29 21:40:59 +02:00
|
|
|
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 );
|
2018-08-29 21:40:59 +02:00
|
|
|
|
|
|
|
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
|
2018-07-24 23:58:08 +02:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
void Sapphire::Entity::Player::discardItem( uint16_t fromInventoryId, uint8_t fromSlotId )
|
2018-07-24 23:58:08 +02:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
// 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();
|
2018-08-29 21:40:59 +02:00
|
|
|
|
|
|
|
auto fromItem = m_storageMap[ fromInventoryId ]->getItem( fromSlotId );
|
|
|
|
|
|
|
|
deleteItemDb( fromItem );
|
|
|
|
|
|
|
|
m_storageMap[ fromInventoryId ]->removeItem( fromSlotId );
|
|
|
|
updateContainer( fromInventoryId, fromSlotId, nullptr );
|
|
|
|
|
2019-07-29 22:22:45 +10:00
|
|
|
auto invTransPacket = makeZonePacket< FFXIVIpcInventoryTransaction >( getId() );
|
2019-02-09 22:59:14 +11:00
|
|
|
invTransPacket->data().sequence = sequence;
|
2018-08-29 21:40:59 +02:00
|
|
|
invTransPacket->data().ownerId = getId();
|
|
|
|
invTransPacket->data().storageId = fromInventoryId;
|
|
|
|
invTransPacket->data().catalogId = fromItem->getId();
|
|
|
|
invTransPacket->data().stackSize = fromItem->getStackSize();
|
|
|
|
invTransPacket->data().slotId = fromSlotId;
|
2020-02-24 18:23:43 +09:00
|
|
|
invTransPacket->data().type = Common::InventoryOperation::Discard;
|
2018-08-29 21:40:59 +02:00
|
|
|
queuePacket( invTransPacket );
|
|
|
|
|
2019-07-29 22:22:45 +10:00
|
|
|
auto invTransFinPacket = makeZonePacket< FFXIVIpcInventoryTransactionFinish >( getId() );
|
2019-02-09 22:59:14 +11:00
|
|
|
invTransFinPacket->data().sequenceId = sequence;
|
|
|
|
invTransFinPacket->data().sequenceId1 = sequence;
|
2018-08-29 21:40:59 +02:00
|
|
|
queuePacket( invTransFinPacket );
|
2018-07-24 23:58:08 +02:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
uint16_t Sapphire::Entity::Player::calculateEquippedGearItemLevel()
|
2018-07-24 23:58:08 +02:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
uint32_t iLvlResult = 0;
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
auto gearSetMap = m_storageMap[ GearSet0 ]->getItemMap();
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
auto it = gearSetMap.begin();
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
while( it != gearSetMap.end() )
|
|
|
|
{
|
|
|
|
auto currItem = it->second;
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2019-04-25 18:26:35 +10:00
|
|
|
if( currItem && currItem->getCategory() != Common::ItemUICategory::SoulCrystal )
|
2018-08-29 21:40:59 +02:00
|
|
|
{
|
|
|
|
iLvlResult += currItem->getItemLevel();
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// 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() ) )
|
2018-08-29 21:40:59 +02:00
|
|
|
{
|
|
|
|
iLvlResult += currItem->getItemLevel();
|
2018-07-24 23:58:08 +02:00
|
|
|
}
|
2018-08-29 21:40:59 +02:00
|
|
|
}
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
it++;
|
|
|
|
}
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-11-01 23:56:43 +01:00
|
|
|
return static_cast< uint16_t >( std::min( static_cast< int32_t >( iLvlResult / 13 ), 9999 ) );
|
2018-07-24 23:58:08 +02:00
|
|
|
}
|
|
|
|
|
2019-04-24 23:25:07 +10:00
|
|
|
Sapphire::ItemPtr Sapphire::Entity::Player::getEquippedWeapon()
|
|
|
|
{
|
|
|
|
return m_storageMap[ GearSet0 ]->getItem( GearSetSlot::MainHand );
|
|
|
|
}
|
2018-07-24 23:58:08 +02:00
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
uint8_t Sapphire::Entity::Player::getFreeSlotsInBags()
|
2018-07-24 23:58:08 +02:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
uint8_t slots = 0;
|
|
|
|
for( uint8_t container : { Bag0, Bag1, Bag2, Bag3 } )
|
|
|
|
{
|
|
|
|
const auto& storage = m_storageMap[ container ];
|
|
|
|
slots += ( storage->getMaxSize() - storage->getEntryCount() );
|
|
|
|
}
|
|
|
|
return slots;
|
2018-07-24 23:58:08 +02:00
|
|
|
}
|
2018-08-31 22:57:33 +10:00
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
bool Sapphire::Entity::Player::collectHandInItems( std::vector< uint32_t > itemIds )
|
2018-08-31 22:57:33 +10:00
|
|
|
{
|
|
|
|
// 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 ];
|
2018-08-31 22:57:33 +10:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2018-12-20 20:41:16 +11:00
|
|
|
|
|
|
|
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 type, uint16_t slotId )
|
|
|
|
{
|
|
|
|
auto& container = m_storageMap[ type ];
|
|
|
|
|
|
|
|
auto item = container->getItem( slotId );
|
|
|
|
if( !item )
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
// unlink item
|
2018-12-29 21:51:43 +11:00
|
|
|
container->removeItem( slotId, false );
|
2018-12-26 18:11:18 +11:00
|
|
|
updateContainer( type, slotId, nullptr );
|
|
|
|
|
|
|
|
auto seq = getNextInventorySequence();
|
|
|
|
|
|
|
|
// send inv update
|
2019-07-29 22:22:45 +10:00
|
|
|
auto invTransPacket = makeZonePacket< FFXIVIpcInventoryTransaction >( getId() );
|
2019-02-09 22:59:14 +11:00
|
|
|
invTransPacket->data().sequence = seq;
|
2018-12-26 18:11:18 +11:00
|
|
|
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 );
|
|
|
|
|
2019-07-29 22:22:45 +10:00
|
|
|
auto invTransFinPacket = makeZonePacket< FFXIVIpcInventoryTransactionFinish >( getId() );
|
2019-02-09 22:59:14 +11:00
|
|
|
invTransFinPacket->data().sequenceId = seq;
|
|
|
|
invTransFinPacket->data().sequenceId1 = seq;
|
2018-12-26 18:11:18 +11:00
|
|
|
queuePacket( invTransFinPacket );
|
|
|
|
|
|
|
|
return item;
|
2018-12-28 02:17:29 +11:00
|
|
|
}
|
|
|
|
|
2018-12-28 11:49:12 +11:00
|
|
|
bool Sapphire::Entity::Player::getFreeInventoryContainerSlot( Inventory::InventoryContainerPair& containerPair ) const
|
2018-12-28 02:17:29 +11:00
|
|
|
{
|
|
|
|
for( auto bagId : { Bag0, Bag1, Bag2, Bag3 } )
|
|
|
|
{
|
|
|
|
auto needle = m_storageMap.find( bagId );
|
|
|
|
if( needle == m_storageMap.end() )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
auto& container = needle->second;
|
|
|
|
|
|
|
|
for( uint16_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 )
|
|
|
|
{
|
2018-12-28 10:17:05 +11:00
|
|
|
updateContainer( type, slot, item );
|
2018-12-28 02:17:29 +11:00
|
|
|
|
|
|
|
auto slotUpdate = std::make_shared< UpdateInventorySlotPacket >( getId(), slot, type, *item );
|
|
|
|
queuePacket( slotUpdate );
|
2018-12-28 10:17:05 +11:00
|
|
|
|
2018-12-30 17:44:03 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Sapphire::Entity::Player::findFirstItemWithId( uint32_t catalogId,
|
|
|
|
Inventory::InventoryContainerPair& location )
|
|
|
|
{
|
|
|
|
for( auto bagId : { Bag0, Bag1, Bag2, Bag3 } )
|
|
|
|
{
|
|
|
|
auto& container = m_storageMap[ bagId ];
|
|
|
|
|
|
|
|
for( const auto& item : container->getItemMap() )
|
|
|
|
{
|
|
|
|
if( item.second->getId() != catalogId )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
location = std::make_pair( bagId, item.first );
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2018-12-26 18:11:18 +11:00
|
|
|
}
|