1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-04-25 14:07:46 +00:00

Gearset handler, mount item action, some GLA starting quests.

This is #732 with 25eaa62fa0 merged, cleaned up and slightly modified to avoid client desync.
This commit is contained in:
Skyliegirl33 2023-02-07 05:15:03 +09:00 committed by collett
parent 14860047b6
commit ea27ff3a55
19 changed files with 630 additions and 28 deletions

View file

@ -734,6 +734,8 @@ namespace Sapphire::Common
{
ItemActionVFX = 852,
ItemActionVFX2 = 944,
ItemActionMount = 1322,
ItemActionOrchestrion = 5845,
};
enum ActionEffectDisplayType : uint8_t

View file

@ -31,6 +31,8 @@ namespace Sapphire::Network::ActorControl
DefeatMsg = 0x06,
GainExpMsg = 0x07,
ClassJobUpdate = 0x9,
LevelUpEffect = 0x0A,
ExpChainMsg = 0x0C,
@ -263,19 +265,21 @@ namespace Sapphire::Network::ActorControl
HuntingLogSectionFinish = 0x21F,
HuntingLogRankFinish = 0x220,
SetMaxGearSets = 0x230,
SetCharaGearParamUI = 0x260,
ToggleWireframeRendering = 0x261,
ExamineError = 0x2BF,
SetMaxGearSets = 0x320,
GearSetEquipMsg = 0x321,
SetBait = 0x325, // param1: bait ID
SetFestival = 0x386, // param1: festival.exd index
SetMountBitmask = 0x387, // param1: mount ID, param2: unlock/lock (1/0)
ToggleOrchestrionUnlock = 0x396,
EventBattleDialog = 0x39D,

View file

@ -471,6 +471,14 @@ struct FFXIVIpcCFCommenceHandler :
uint8_t padding[7];
};
struct FFXIVInventoryEquipRecommendedItemsHandler :
FFXIVIpcBasePacket< InventoryEquipRecommendedItems >
{
uint32_t contextId;
uint16_t storageId[14];
int16_t containerIndex[14];
};
}
#endif //_CORE_NETWORK_PACKETS_ZONE_CLIENT_IPC_H

View file

@ -1344,7 +1344,10 @@ namespace Sapphire::Network::Packets::Server
uint32_t catalogId;
uint32_t someActorId;
int32_t targetStorageId;
uint32_t padding3[3];
uint16_t targetSlotId;
uint16_t padding3;
uint32_t targetStackSize;
uint32_t targetCatalogId;
};

View file

@ -0,0 +1,65 @@
// This is an automatically generated C++ script template
// Content needs to be added by hand to make it function
// In order for this script to be loaded, move it to the correct folder in <root>/scripts/
#include <Actor/Player.h>
#include "Manager/EventMgr.h"
#include <ScriptObject.h>
#include <Service.h>
// Quest Script: ClsGla001_00177
// Quest Name: So You Want to Be a Gladiator
// Quest ID: 65713
// Start NPC: 1002277
// End NPC: 1002277
using namespace Sapphire;
class ClsGla001 : public Sapphire::ScriptAPI::EventScript
{
private:
// Basic quest information
// Quest vars / flags used
// Steps in this quest ( 0 is before accepting,
// 1 is first, 255 means ready for turning it in
enum Sequence : uint8_t
{
};
// Entities found in the script data of the quest
public:
ClsGla001() : Sapphire::ScriptAPI::EventScript( 65713 ){};
~ClsGla001() = default;
//////////////////////////////////////////////////////////////////////
// Event Handlers
void onTalk( uint32_t eventId, Entity::Player& player, uint64_t actorId ) override
{
Scene00000( player );
}
private:
//////////////////////////////////////////////////////////////////////
// Available Scenes in this quest, not necessarly all are used
void Scene00000( Entity::Player& player )
{
auto callback = [ & ]( Entity::Player& player, const Event::SceneResult& result )
{
if( result.param1 > 0 && result.param2 == 1 )
{
if( player.giveQuestRewards( getId(), result.param3 ) )
{
player.finishQuest( getId() );
}
}
};
player.playScene( getId(), 0, HIDE_UI, callback );
}
};
EXPOSE_SCRIPT( ClsGla001 );

View file

@ -0,0 +1,118 @@
// FFXIVTheMovie.ParserV3.3
// fake IsAnnounce table
#include <Actor/Player.h>
#include <ScriptObject.h>
#include <Service.h>
#include "Manager/TerritoryMgr.h"
#include "Manager/EventMgr.h"
using namespace Sapphire;
class ClsGla011 : public Sapphire::ScriptAPI::EventScript
{
public:
ClsGla011() : Sapphire::ScriptAPI::EventScript( 65821 ){};
~ClsGla011() = default;
//SEQ_0, 1 entries
//SEQ_255, 1 entries
//ACTOR0 = 1002277
//ACTOR1 = 1001739
static constexpr auto CLASSJOB = 1;
//GEARSETUNLOCK = 1905
//LOGMESSAGEMONSTERNOTEPAGEUNLOCK = 1010
//UNLOCKIMAGECLASSGLA = 36
private:
void onProgress( Entity::Player& player, uint64_t param1, uint32_t param2, uint32_t type, uint32_t param3 )
{
switch( player.getQuestSeq( getId() ) )
{
case 0:
{
Scene00000( player ); // Scene00000: Normal(Talk, QuestOffer, QuestAccept, TargetCanMove), id=LULUTSU
break;
}
case 255:
{
Scene00001( player ); // Scene00001: Normal(Talk, YesNo, Message, FadeIn, QuestReward, QuestComplete, TargetCanMove, CanCancel), id=MYLLA
break;
}
default:
{
player.sendUrgent( "Sequence {} not defined.", player.getQuestSeq( getId() ) );
break;
}
}
}
public:
void onTalk( uint32_t eventId, Entity::Player& player, uint64_t actorId ) override
{
auto& eventMgr = Common::Service< World::Manager::EventMgr >::ref();
auto actor = eventMgr.mapEventActorToRealActor( static_cast< uint32_t >( actorId ) );
onProgress( player, actorId, actor, 0, 0 );
}
void onEmote( uint64_t actorId, uint32_t eventId, uint32_t emoteId, Entity::Player& player ) override
{
auto& eventMgr = Common::Service< World::Manager::EventMgr >::ref();
auto actor = eventMgr.mapEventActorToRealActor( static_cast< uint32_t >( actorId ) );
onProgress( player, actorId, actor, 1, emoteId );
}
void onBNpcKill( uint32_t npcId, Entity::Player& player ) override
{
onProgress( player, npcId, 0, 2, 0 );
}
void onWithinRange( Entity::Player& player, uint32_t eventId, uint32_t param1, float x, float y, float z ) override
{
onProgress( player, param1, param1, 3, 0 );
}
void onEnterTerritory( Sapphire::Entity::Player& player, uint32_t eventId, uint16_t param1, uint16_t param2 ) override
{
onProgress( player, param1, param2, 4, 0 );
}
private:
void checkProgressSeq0( Entity::Player& player )
{
player.updateQuest( getId(), 255 );
}
void Scene00000( Entity::Player& player )
{
player.sendDebug( "ClsGla011:65821 calling Scene00000: Normal(Talk, QuestOffer, QuestAccept, TargetCanMove), id=LULUTSU" );
auto callback = [ & ]( Entity::Player& player, const Event::SceneResult& result )
{
if( result.param1 > 0 && result.param2 == 1 )
{
checkProgressSeq0( player );
}
};
player.playScene( getId(), 0, HIDE_UI, callback );
}
void Scene00001( Entity::Player& player )
{
player.sendDebug( "ClsGla011:65821 calling Scene00001: Normal(Talk, YesNo, Message, FadeIn, QuestReward, QuestComplete, TargetCanMove, CanCancel), id=MYLLA" );
auto callback = [ & ]( Entity::Player& player, const Event::SceneResult& result )
{
if( result.param1 > 0 && result.param2 == 1 )
{
if( player.giveQuestRewards( getId(), result.param3 ) )
{
player.finishQuest( getId() );
player.setEquippedMannequin( player.getEquippedMannequin() + 1 );
player.setLevelForClass( 1, static_cast< Common::ClassJob > ( CLASSJOB ) );
}
}
};
player.playScene( getId(), 1, FADE_OUT | CONDITION_CUTSCENE | HIDE_UI, callback );
}
};
EXPOSE_SCRIPT( ClsGla011 );

View file

@ -0,0 +1,191 @@
// FFXIVTheMovie.ParserV3.3
#include <Actor/Player.h>
#include <ScriptObject.h>
#include <Service.h>
#include "Manager/TerritoryMgr.h"
#include "Manager/EventMgr.h"
using namespace Sapphire;
class ClsGla020 : public Sapphire::ScriptAPI::EventScript
{
public:
ClsGla020() : Sapphire::ScriptAPI::EventScript( 65789 ){};
~ClsGla020() = default;
//SEQ_0, 1 entries
//SEQ_1, 1 entries
//SEQ_2, 3 entries
//SEQ_255, 1 entries
//ACTOR0 = 1002277
//ACTOR1 = 1001739
//ENEMY0 = 351
//ENEMY1 = 385
//ENEMY2 = 205
//LOGMESSAGEMONSTERNOTEPAGEUNLOCK = 1010
//UNLOCKIMAGEMONSTERNOTE = 32
private:
void onProgress( Entity::Player& player, uint64_t param1, uint32_t param2, uint32_t type, uint32_t param3 )
{
switch( player.getQuestSeq( getId() ) )
{
case 0:
{
Scene00000( player ); // Scene00000: Normal(Talk, YesNo, QuestOffer, QuestAccept, TargetCanMove), id=LULUTSU
break;
}
case 1:
{
Scene00001( player ); // Scene00001: Normal(Talk, YesNo, FadeIn, TargetCanMove, CanCancel), id=MYLLA
break;
}
case 2:
{
if( param1 == 351 || param2 == 351 ) // ENEMY0 = unknown
{
if( player.getQuestUI8AL( getId() ) != 3 )
{
player.setQuestUI8AL( getId(), player.getQuestUI8AL( getId() ) + 1 );
checkProgressSeq2( player );
}
break;
}
if( param1 == 385 || param2 == 385 ) // ENEMY1 = unknown
{
if( player.getQuestUI8BH( getId() ) != 3 )
{
player.setQuestUI8BH( getId(), player.getQuestUI8BH( getId() ) + 1 );
checkProgressSeq2( player );
}
break;
}
if( param1 == 205 || param2 == 205 ) // ENEMY2 = unknown
{
if( player.getQuestUI8BL( getId() ) != 3 )
{
Scene00002( player ); // Scene00002: Normal(None), id=unknown
}
break;
}
break;
}
case 255:
{
Scene00005( player ); // Scene00005: Normal(Talk, Message, QuestReward, QuestComplete, TargetCanMove, SystemTalk), id=MYLLA
break;
}
default:
{
player.sendUrgent( "Sequence {} not defined.", player.getQuestSeq( getId() ) );
break;
}
}
}
public:
void onTalk( uint32_t eventId, Entity::Player& player, uint64_t actorId ) override
{
auto& eventMgr = Common::Service< World::Manager::EventMgr >::ref();
auto actor = eventMgr.mapEventActorToRealActor( static_cast< uint32_t >( actorId ) );
onProgress( player, actorId, actor, 0, 0 );
}
void onEmote( uint64_t actorId, uint32_t eventId, uint32_t emoteId, Entity::Player& player ) override
{
auto& eventMgr = Common::Service< World::Manager::EventMgr >::ref();
auto actor = eventMgr.mapEventActorToRealActor( static_cast< uint32_t >( actorId ) );
onProgress( player, actorId, actor, 1, emoteId );
}
void onBNpcKill( uint32_t npcId, Entity::Player& player ) override
{
onProgress( player, npcId, 0, 2, 0 );
}
void onWithinRange( Entity::Player& player, uint32_t eventId, uint32_t param1, float x, float y, float z ) override
{
onProgress( player, param1, param1, 3, 0 );
}
void onEnterTerritory( Sapphire::Entity::Player& player, uint32_t eventId, uint16_t param1, uint16_t param2 ) override
{
onProgress( player, param1, param2, 4, 0 );
}
private:
void checkProgressSeq0( Entity::Player& player )
{
player.updateQuest( getId(), 1 );
}
void checkProgressSeq1( Entity::Player& player )
{
player.updateQuest( getId(), 2 );
}
void checkProgressSeq2( Entity::Player& player )
{
if( player.getQuestUI8AL( getId() ) == 3 )
if( player.getQuestUI8BH( getId() ) == 3 )
if( player.getQuestUI8BL( getId() ) == 3 )
{
player.setQuestUI8AL( getId(), 0 );
player.setQuestUI8BH( getId(), 0 );
player.setQuestUI8BL( getId(), 0 );
player.updateQuest( getId(), 255 );
}
}
void Scene00000( Entity::Player& player )
{
player.sendDebug( "ClsGla020:65789 calling Scene00000: Normal(Talk, YesNo, QuestOffer, QuestAccept, TargetCanMove), id=LULUTSU" );
auto callback = [ & ]( Entity::Player& player, const Event::SceneResult& result )
{
if( result.param1 > 0 && result.param2 == 1 )
{
checkProgressSeq0( player );
}
};
player.playScene( getId(), 0, HIDE_UI, callback );
}
void Scene00001( Entity::Player& player )
{
player.sendDebug( "ClsGla020:65789 calling Scene00001: Normal(Talk, YesNo, FadeIn, TargetCanMove, CanCancel), id=MYLLA" );
auto callback = [ & ]( Entity::Player& player, const Event::SceneResult& result )
{
if( result.param1 == 512 || ( result.param1 > 0 && result.param2 == 1 ) )
{
checkProgressSeq1( player );
}
};
player.playScene( getId(), 1, FADE_OUT | CONDITION_CUTSCENE | HIDE_UI, callback );
}
void Scene00002( Entity::Player& player )
{
player.sendDebug( "ClsGla020:65789 calling Scene00002: Normal(None), id=unknown" );
player.setQuestUI8BL( getId(), player.getQuestUI8BL( getId() ) + 1 );
checkProgressSeq2( player );
}
void Scene00005( Entity::Player& player )
{
player.sendDebug( "ClsGla020:65789 calling Scene00005: Normal(Talk, Message, QuestReward, QuestComplete, TargetCanMove, SystemTalk), id=MYLLA" );
auto callback = [ & ]( Entity::Player& player, const Event::SceneResult& result )
{
if( result.param1 > 0 && result.param2 == 1 )
{
if( player.giveQuestRewards( getId(), result.param3 ) )
{
player.finishQuest( getId() );
}
}
};
player.playScene( getId(), 5, HIDE_UI, callback );
}
};
EXPOSE_SCRIPT( ClsGla020 );

View file

@ -47,6 +47,20 @@ void ItemAction::execute()
break;
}
case Common::ItemActionType::ItemActionMount:
{
handleMountItem();
break;
}
case Common::ItemActionType::ItemActionOrchestrion:
{
handleOrchestrionItem();
break;
}
}
}
@ -55,6 +69,21 @@ void ItemAction::interrupt()
}
void ItemAction::handleMountItem()
{
auto player = getSourceChara()->getAsPlayer();
player->unlockMount( m_itemAction->data[ 0 ] );
player->dropInventoryItem ( static_cast< Common::InventoryType >( m_itemSourceContainer ), m_itemSourceSlot, false );
}
void ItemAction::handleOrchestrionItem()
{
auto player = getSourceChara()->getAsPlayer();
//player->learnSong( m_itemAction->data[ 0 ], m_itemAction->data[ 1 ] );
//player->dropInventoryItem ( static_cast< Common::InventoryType >( m_itemSourceContainer ), m_itemSourceSlot, false );
player->sendUrgent( "orchestrion is not aligned correctly, handleOrchestrionItem() disabled until PlayerSetup fully fixed." );
}
void ItemAction::handleVFXItem()
{
Common::EffectEntry effect{};

View file

@ -27,6 +27,10 @@ namespace Sapphire::World::Action
private:
void handleVFXItem();
void handleMountItem();
void handleOrchestrionItem();
private:
Sapphire::Data::ItemActionPtr m_itemAction;

View file

@ -859,6 +859,13 @@ void Sapphire::Entity::Player::setExp( uint32_t amount )
m_expArray[ classJobIndex ] = amount;
}
void Sapphire::Entity::Player::setExp( uint8_t classId, uint32_t amount )
{
auto exdData = Common::Service< Data::ExdDataGenerated >::ref();
uint8_t classJobIndex = exdData.get< Sapphire::Data::ClassJob >( static_cast< uint8_t >( classId ) )->expArrayIndex;
m_expArray[ classJobIndex ] = amount;
}
bool Sapphire::Entity::Player::isInCombat() const
{
return m_bInCombat;
@ -910,9 +917,11 @@ void Sapphire::Entity::Player::setLevelForClass( uint8_t level, Common::ClassJob
uint8_t classJobIndex = exdData.get< Sapphire::Data::ClassJob >( static_cast< uint8_t >( classjob ) )->expArrayIndex;
if( m_classArray[ classJobIndex ] == 0 )
insertDbClass( classJobIndex );
insertDbClass( classJobIndex, level );
m_classArray[ classJobIndex ] = level;
queuePacket( makeActorControlSelf( getId(), Network::ActorControl::ClassJobUpdate, static_cast< uint8_t >( classjob ), level ) );
}
void Sapphire::Entity::Player::sendModel()
@ -1288,6 +1297,19 @@ const uint8_t* Sapphire::Entity::Player::getMountGuideBitmask() const
return m_mountGuide;
}
void Sapphire::Entity::Player::unlockMount( uint32_t mountId )
{
auto& exdData = Common::Service< Data::ExdDataGenerated >::ref();
auto mount = exdData.get< Data::Mount >( mountId );
if ( mount->order == -1 || mount->modelChara == 0 )
return;
m_mountGuide[ mount->order / 8 ] |= ( 1 << ( mount->order % 8 ) );
queuePacket( makeActorControlSelf( getId(), Network::ActorControl::SetMountBitmask, mount->order, 1 ) );
}
const bool Sapphire::Entity::Player::hasMount( uint32_t mountId ) const
{
auto& exdData = Common::Service< Data::ExdDataGenerated >::ref();
@ -1564,6 +1586,13 @@ void Sapphire::Entity::Player::setTitle( uint16_t titleId )
sendToInRangeSet( makeActorControl( getId(), SetTitle, titleId ), true );
}
void Sapphire::Entity::Player::setEquippedMannequin( uint8_t amount )
{
m_equippedMannequin = amount;
queuePacket( makeActorControlSelf( getId(), SetMaxGearSets, m_equippedMannequin ) );
}
void Sapphire::Entity::Player::setEquipDisplayFlags( uint8_t state )
{
m_equipDisplayFlags = state;
@ -1572,6 +1601,11 @@ void Sapphire::Entity::Player::setEquipDisplayFlags( uint8_t state )
sendToInRangeSet( paramPacket, true );
}
uint8_t Sapphire::Entity::Player::getEquippedMannequin() const
{
return m_equippedMannequin;
}
uint8_t Sapphire::Entity::Player::getEquipDisplayFlags() const
{
return m_equipDisplayFlags;
@ -1780,6 +1814,7 @@ void Sapphire::Entity::Player::sendZonePackets()
if( isLogin() )
{
queuePacket( makeActorControlSelf( getId(), SetCharaGearParamUI, m_equipDisplayFlags, 1 ) );
queuePacket( makeActorControlSelf( getId(), SetMaxGearSets, m_equippedMannequin ) );
}
// set flags, will be reset automatically by zoning ( only on client side though )

View file

@ -348,6 +348,9 @@ namespace Sapphire::Entity
/*! send player ilvl */
void sendItemLevel();
/*! calculate player ilvl */
void calculateItemLevel();
/*! get the current main hand model */
uint64_t getModelMainWeapon() const;
@ -404,6 +407,9 @@ namespace Sapphire::Entity
/*! sets the exp of the currently active class / job */
void setExp( uint32_t amount );
/*! sets the exp of the specified class / job */
void setExp( uint8_t classId, uint32_t amount );
/*! adds exp to the currently active class / job */
void gainExp( uint32_t amount );
@ -573,6 +579,12 @@ namespace Sapphire::Entity
/*! change gear param state */
void setEquipDisplayFlags( uint8_t state );
/*! set number of gear sets */
void setEquippedMannequin( uint8_t amount );
/*! get number of gear sets */
uint8_t getEquippedMannequin() const;
/*! get gear param state */
uint8_t getEquipDisplayFlags() const;
@ -657,6 +669,8 @@ namespace Sapphire::Entity
/*! return a const pointer to the mount guide bitmask array */
const uint8_t* getMountGuideBitmask() const;
void unlockMount( uint32_t mountId );
const bool hasMount( uint32_t mountId ) const;
bool checkAction() override;
@ -744,6 +758,9 @@ namespace Sapphire::Entity
/*! send the entire inventory sequence */
void sendInventory();
/* send only the gear inventory */
void sendGearInventory();
/*! send active quest list */
void sendQuestInfo();
@ -896,7 +913,7 @@ namespace Sapphire::Entity
void updateDbClass() const;
void insertDbClass( const uint8_t classJobIndex ) const;
void insertDbClass( const uint8_t classJobIndex, const uint8_t classJobLevel = 1 ) const;
void setMarkedForRemoval();
@ -932,12 +949,14 @@ namespace Sapphire::Entity
InvSlotPair getFreeBagSlot();
InvSlotPair getFreeContainerSlot( uint32_t containerId );
ItemPtr addItem( uint32_t catalogId, uint32_t quantity = 1, bool isHq = false, bool silent = false, bool canMerge = true, bool sendLootMessage = false );
ItemPtr addItem( ItemPtr itemToAdd, bool silent = false, bool canMerge = true, bool sendLootMessage = false );
void moveItem( uint16_t fromInventoryId, uint8_t fromSlotId, uint16_t toInventoryId, uint8_t toSlot );
void moveItem( uint16_t fromInventoryId, uint8_t fromSlotId, uint16_t toInventoryId, uint8_t toSlot, bool sendUpdate = true );
void swapItem( uint16_t fromInventoryId, uint8_t fromSlotId, uint16_t toInventoryId, uint8_t toSlot );
void swapItem( uint16_t fromInventoryId, uint8_t fromSlotId, uint16_t toInventoryId, uint8_t toSlot, bool sendUpdate = true );
void discardItem( uint16_t fromInventoryId, uint8_t fromSlotId );
@ -1054,6 +1073,8 @@ namespace Sapphire::Entity
uint8_t m_voice;
uint8_t m_equippedMannequin;
uint64_t m_modelMainWeapon;
uint64_t m_modelSubWeapon;
uint64_t m_modelSystemWeapon;

View file

@ -110,6 +110,11 @@ void Sapphire::Entity::Player::sendItemLevel()
queuePacket( makeActorControl( getId(), SetItemLevel, getItemLevel(), 0 ) );
}
void Sapphire::Entity::Player::calculateItemLevel()
{
m_itemLevel = calculateEquippedGearItemLevel();
}
void Sapphire::Entity::Player::equipWeapon( ItemPtr pItem, bool updateClass )
{
auto& exdData = Common::Service< Sapphire::Data::ExdDataGenerated >::ref();
@ -436,6 +441,14 @@ void Sapphire::Entity::Player::sendInventory()
}
}
void Sapphire::Entity::Player::sendGearInventory()
{
auto& invMgr = Common::Service< World::Manager::InventoryMgr >::ref();
invMgr.sendInventoryContainer( *this, m_storageMap[ GearSet0 ] );
}
Sapphire::Entity::Player::InvSlotPairVec Sapphire::Entity::Player::getSlotsOfItemsInInventory( uint32_t catalogId )
{
InvSlotPairVec outVec;
@ -451,6 +464,17 @@ Sapphire::Entity::Player::InvSlotPairVec Sapphire::Entity::Player::getSlotsOfIte
return outVec;
}
Sapphire::Entity::Player::InvSlotPair Sapphire::Entity::Player::getFreeContainerSlot( uint32_t containerId )
{
auto freeSlot = static_cast < int8_t >( m_storageMap[ containerId ]->getFreeSlot() );
if( freeSlot != -1 )
return std::make_pair( containerId, freeSlot );
// no room in inventory
return std::make_pair( 0, -1 );
}
Sapphire::Entity::Player::InvSlotPair Sapphire::Entity::Player::getFreeBagSlot()
{
for( auto i : { Bag0, Bag1, Bag2, Bag3 } )
@ -717,7 +741,7 @@ Sapphire::ItemPtr Sapphire::Entity::Player::addItem( uint32_t catalogId, uint32_
}
void
Sapphire::Entity::Player::moveItem( uint16_t fromInventoryId, uint8_t fromSlotId, uint16_t toInventoryId, uint8_t toSlot )
Sapphire::Entity::Player::moveItem( uint16_t fromInventoryId, uint8_t fromSlotId, uint16_t toInventoryId, uint8_t toSlot, bool sendUpdate )
{
auto tmpItem = m_storageMap[ fromInventoryId ]->getItem( fromSlotId );
@ -736,13 +760,13 @@ Sapphire::Entity::Player::moveItem( uint16_t fromInventoryId, uint8_t fromSlotId
writeInventory( static_cast< InventoryType >( fromInventoryId ) );
if( static_cast< InventoryType >( toInventoryId ) == GearSet0 )
equipItem( static_cast< GearSetSlot >( toSlot ), tmpItem, true );
equipItem( static_cast< GearSetSlot >( toSlot ), tmpItem, sendUpdate );
if( static_cast< InventoryType >( fromInventoryId ) == GearSet0 )
unequipItem( static_cast< GearSetSlot >( fromSlotId ), tmpItem, true );
unequipItem( static_cast< GearSetSlot >( fromSlotId ), tmpItem, sendUpdate );
if( static_cast< InventoryType >( toInventoryId ) == GearSet0 ||
static_cast< InventoryType >( fromInventoryId ) == GearSet0 )
if( ( static_cast< InventoryType >( toInventoryId ) == GearSet0 ||
static_cast< InventoryType >( fromInventoryId ) == GearSet0 ) && sendUpdate )
sendStatusEffectUpdate(); // send if any equip is changed
}
@ -855,7 +879,7 @@ void Sapphire::Entity::Player::mergeItem( uint16_t fromInventoryId, uint8_t from
}
void Sapphire::Entity::Player::swapItem( uint16_t fromInventoryId, uint8_t fromSlotId,
uint16_t toInventoryId, uint8_t toSlot )
uint16_t toInventoryId, uint8_t toSlot, bool sendUpdate )
{
auto fromItem = m_storageMap[ fromInventoryId ]->getItem( fromSlotId );
auto toItem = m_storageMap[ toInventoryId ]->getItem( toSlot );
@ -872,8 +896,7 @@ void Sapphire::Entity::Player::swapItem( uint16_t fromInventoryId, uint8_t fromS
{
updateContainer( fromInventoryId, fromSlotId, nullptr );
auto& exdData = Common::Service< Data::ExdDataGenerated >::ref();
auto itemInfo = exdData.get< Sapphire::Data::Item >( toItem->getId() );
fromInventoryId = World::Manager::ItemMgr::getCharaEquipSlotCategoryToArmoryId( static_cast< Common::EquipSlotCategory >( itemInfo->equipSlotCategory ) );
fromInventoryId = World::Manager::ItemMgr::getCharaEquipSlotCategoryToArmoryId( static_cast< Common::EquipSlotCategory >( toItem->getEquipSlotCategory() ) );
fromSlotId = static_cast < uint8_t >( m_storageMap[ fromInventoryId ]->getFreeSlot() );
}
@ -883,8 +906,8 @@ void Sapphire::Entity::Player::swapItem( uint16_t fromInventoryId, uint8_t fromS
updateContainer( toInventoryId, toSlot, fromItem, fromInventoryId != toInventoryId );
updateContainer( fromInventoryId, fromSlotId, toItem );
if( static_cast< InventoryType >( toInventoryId ) == GearSet0 ||
static_cast< InventoryType >( fromInventoryId ) == GearSet0 )
if( ( static_cast< InventoryType >( toInventoryId ) == GearSet0 ||
static_cast< InventoryType >( fromInventoryId ) == GearSet0 ) && sendUpdate )
sendStatusEffectUpdate(); // send if any equip is changed
}

View file

@ -159,6 +159,8 @@ bool Sapphire::Entity::Player::load( uint32_t charId, World::SessionPtr pSession
m_gmRank = res->getUInt8( "GMRank" );
m_equippedMannequin = res->getUInt8( "EquippedMannequin" );
m_equipDisplayFlags = res->getUInt8( "EquipDisplayFlags" );
m_pose = res->getUInt8( "Pose" );
@ -452,7 +454,7 @@ void Sapphire::Entity::Player::updateSql()
memcpy( orchestrionVec.data(), m_orchestrion, sizeof( m_orchestrion ) );
stmt->setBinary( 42, mountsVec );
stmt->setInt( 44, 0 ); // EquippedMannequin
stmt->setInt( 44, m_equippedMannequin ); // EquippedMannequin
stmt->setInt( 45, 0 ); // DisplayFlags
std::vector< uint8_t > questCompleteVec( sizeof( m_questCompleteFlags ) );
@ -538,14 +540,14 @@ void Sapphire::Entity::Player::updateDbMonsterNote()
db.execute( stmt );
}
void Sapphire::Entity::Player::insertDbClass( const uint8_t classJobIndex ) const
void Sapphire::Entity::Player::insertDbClass( const uint8_t classJobIndex, const uint8_t classJobLevel ) const
{
auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref();
auto stmtClass = db.getPreparedStatement( Db::CHARA_CLASS_INS );
stmtClass->setInt( 1, getId() );
stmtClass->setInt( 2, classJobIndex );
stmtClass->setInt( 3, 0 );
stmtClass->setInt( 4, 1 );
stmtClass->setInt( 4, classJobLevel );
db.directExecute( stmtClass );
}

View file

@ -32,6 +32,7 @@ Sapphire::Item::Item( uint64_t uId, uint32_t catalogId, bool isHq ) :
m_block = itemInfo->block;
m_defense = itemInfo->defensePhys;
m_defenseMag = itemInfo->defenseMag;
m_equipSlotCategory = itemInfo->equipSlotCategory;
for( int i = 0; i < 6; ++i )
{
@ -181,6 +182,11 @@ uint32_t Sapphire::Item::getAdditionalData() const
return m_additionalData;
}
uint8_t Sapphire::Item::getEquipSlotCategory() const
{
return m_equipSlotCategory;
}
uint16_t Sapphire::Item::getSpiritbond() const
{
return m_spiritBond;

View file

@ -76,6 +76,8 @@ namespace Sapphire
uint32_t getAdditionalData() const;
uint8_t getEquipSlotCategory() const;
void setSpiritbond( uint16_t spiritbond );
uint16_t getSpiritbond() const;
@ -117,6 +119,7 @@ namespace Sapphire
uint32_t m_defenseMag;
uint32_t m_additionalData;
uint8_t m_equipSlotCategory;
BaseParamStruct m_baseParam[6];

View file

@ -138,6 +138,8 @@ Sapphire::Network::GameConnection::GameConnection( Sapphire::Network::HivePtr pH
setZoneHandler( ClientZoneIpcType::WorldInteractionHandler, "WorldInteractionHandler", &GameConnection::worldInteractionhandler );
setZoneHandler( ClientZoneIpcType::Dive, "Dive", &GameConnection::diveHandler );
setZoneHandler( ClientZoneIpcType::InventoryEquipRecommendedItems, "InventoryEquipRecommendedItemsHandler", &GameConnection::inventoryEquipRecommendedItemsHandler );
setChatHandler( ClientChatIpcType::TellReq, "TellReq", &GameConnection::tellHandler );
}

View file

@ -196,6 +196,8 @@ namespace Sapphire::Network
DECLARE_HANDLER( diveHandler );
DECLARE_HANDLER( eventYieldHandler );
DECLARE_HANDLER( inventoryEquipRecommendedItemsHandler );
};
}

View file

@ -38,6 +38,7 @@
#include "Network/PacketWrappers/EventStartPacket.h"
#include "Network/PacketWrappers/EventFinishPacket.h"
#include "Network/PacketWrappers/PlayerStateFlagsPacket.h"
#include "Network/PacketWrappers/UpdateInventorySlotPacket.h"
#include "Manager/DebugCommandMgr.h"
#include "Manager/EventMgr.h"
@ -45,8 +46,10 @@
#include "Manager/TerritoryMgr.h"
#include "Manager/HousingMgr.h"
#include "Manager/RNGMgr.h"
#include "Manager/ItemMgr.h"
#include "Action/Action.h"
#include "Inventory/Item.h"
#include "Session.h"
#include "ServerMgr.h"
@ -928,4 +931,85 @@ void Sapphire::Network::GameConnection::diveHandler( const Packets::FFXIVARR_PAC
setPos->data().waitForLoad = 25;
setPos->data().unknown1 = 0;
player.queuePacket( setPos ); // need delay, same as above.
}
// Also used for gearsets
void Sapphire::Network::GameConnection::inventoryEquipRecommendedItemsHandler( const Packets::FFXIVARR_PACKET_RAW& inPacket, Entity::Player& player )
{
const auto packet = ZoneChannelPacket< Client::FFXIVInventoryEquipRecommendedItemsHandler >( inPacket );
// Loop over all slots
for (int slot = 0; slot < 14; slot++ )
{
const auto fromSlot = packet.data().containerIndex[ slot ];
const auto fromContainer = packet.data().storageId[ slot ];
if ( fromContainer == Common::GearSet0 )
continue;
const auto fromItem = fromSlot == -1 ? nullptr : player.getItemAt( fromContainer, fromSlot );
const auto equippedItem = player.getItemAt( Common::GearSet0, slot );
if ( fromItem && equippedItem )
{
player.swapItem( fromContainer, fromSlot, Common::GearSet0, slot, slot == 0 || slot == 13 );
player.queuePacket( std::make_shared< UpdateInventorySlotPacket >( player.getId(), fromSlot, fromContainer, *equippedItem, 0 ) );
player.queuePacket( std::make_shared< UpdateInventorySlotPacket >( player.getId(), slot, Common::GearSet0, *fromItem, 0 ) );
}
else if ( fromItem && !equippedItem )
{
player.moveItem( fromContainer, fromSlot, Common::GearSet0, slot, slot == 0 || slot == 13 );
player.queuePacket( std::make_shared< UpdateInventorySlotPacket >( player.getId(), fromSlot, fromContainer, 0 ) );
player.queuePacket( std::make_shared< UpdateInventorySlotPacket >( player.getId(), slot, Common::GearSet0, *fromItem, 0 ) );
}
else if ( !fromItem && equippedItem )
{
auto containerId = World::Manager::ItemMgr::getCharaEquipSlotCategoryToArmoryId( static_cast< Common::EquipSlotCategory >( equippedItem->getEquipSlotCategory() ) );
auto freeContainerSlot = player.getFreeContainerSlot( containerId );
if( freeContainerSlot.second > 0 )
player.moveItem( Common::GearSet0, slot, freeContainerSlot.first, freeContainerSlot.second, slot == 0 || slot == 13 );
else
{
for( int i = Common::Bag0; i <= Common::Bag3; i++ )
{
freeContainerSlot = player.getFreeContainerSlot( i );
if( freeContainerSlot.second > 0 )
break;
}
if( freeContainerSlot.second > 0 )
player.moveItem( Common::GearSet0, slot, freeContainerSlot.first, freeContainerSlot.second, slot == 0 || slot == 13 );
else
{
player.sendUrgent( "No free inventory space to swap out item at container:{}, slot{}", Common::GearSet0, slot );
continue;
}
}
player.queuePacket( std::make_shared< UpdateInventorySlotPacket >( player.getId(), freeContainerSlot.second, freeContainerSlot.first, *equippedItem, 0 ) );
player.queuePacket( std::make_shared< UpdateInventorySlotPacket >( player.getId(), slot, Common::GearSet0, 0 ) );
}
}
// Send the gear inventory
player.sendGearInventory();
// Send the player stats
player.sendStats();
// Send the player model
player.sendModel();
// Send the item level
player.calculateItemLevel();
player.sendItemLevel();
// Send status effect update
player.sendStatusEffectUpdate();
if( packet.data().contextId < 0xFE ) // >= 0xFE are not geaesets
{
auto actorControl2 = makeZonePacket< FFXIVIpcActorControlSelf >( player.getId(), player.getId() );
actorControl2->data().category = static_cast< uint16_t >( Network::ActorControl::GearSetEquipMsg );
actorControl2->data().param1 = packet.data().contextId;
player.queuePacket( actorControl2 );
}
}

View file

@ -15,22 +15,22 @@ namespace Sapphire::Network::Packets::Server
class UpdateInventorySlotPacket : public ZoneChannelPacket< FFXIVIpcUpdateInventorySlot >
{
public:
UpdateInventorySlotPacket( uint32_t playerId, uint8_t slot, uint16_t storageId, const Item& item ) :
UpdateInventorySlotPacket( uint32_t playerId, uint8_t slot, uint16_t storageId, const Item& item, uint32_t sequence = 0 ) :
ZoneChannelPacket< FFXIVIpcUpdateInventorySlot >( playerId, playerId )
{
initialize( slot, storageId, item );
initialize( slot, storageId, item, sequence );
};
UpdateInventorySlotPacket( uint32_t playerId, uint8_t slot, uint16_t storageId ) :
UpdateInventorySlotPacket( uint32_t playerId, uint8_t slot, uint16_t storageId, uint32_t sequence = 0 ) :
ZoneChannelPacket< FFXIVIpcUpdateInventorySlot >( playerId, playerId )
{
initialize( slot, storageId );
initialize( slot, storageId, sequence );
};
private:
void initialize( uint8_t slot, uint16_t storageId, const Item& item )
void initialize( uint8_t slot, uint16_t storageId, const Item& item, uint32_t sequence )
{
m_data.sequence = 0;
m_data.sequence = sequence;
m_data.containerId = storageId;
m_data.slot = slot;
m_data.quantity = item.getStackSize();
@ -54,9 +54,9 @@ namespace Sapphire::Network::Packets::Server
//uint8_t buffer5;
};
void initialize( uint8_t slot, uint16_t storageId )
void initialize( uint8_t slot, uint16_t storageId, uint32_t sequence )
{
m_data.sequence = 0;
m_data.sequence = sequence;
m_data.containerId = storageId;
m_data.slot = slot;
m_data.quantity = 0;