diff --git a/src/common/Common.h b/src/common/Common.h index e6cf7371..c9928c06 100644 --- a/src/common/Common.h +++ b/src/common/Common.h @@ -734,6 +734,8 @@ namespace Sapphire::Common { ItemActionVFX = 852, ItemActionVFX2 = 944, + ItemActionMount = 1322, + ItemActionOrchestrion = 5845, }; enum ActionEffectDisplayType : uint8_t diff --git a/src/common/Network/CommonActorControl.h b/src/common/Network/CommonActorControl.h index b1e397c4..14bda64b 100644 --- a/src/common/Network/CommonActorControl.h +++ b/src/common/Network/CommonActorControl.h @@ -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, diff --git a/src/common/Network/PacketDef/Zone/ClientZoneDef.h b/src/common/Network/PacketDef/Zone/ClientZoneDef.h index 3755e800..f15ca0bf 100644 --- a/src/common/Network/PacketDef/Zone/ClientZoneDef.h +++ b/src/common/Network/PacketDef/Zone/ClientZoneDef.h @@ -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 diff --git a/src/common/Network/PacketDef/Zone/ServerZoneDef.h b/src/common/Network/PacketDef/Zone/ServerZoneDef.h index 62a6d5ca..7e33d643 100644 --- a/src/common/Network/PacketDef/Zone/ServerZoneDef.h +++ b/src/common/Network/PacketDef/Zone/ServerZoneDef.h @@ -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; }; diff --git a/src/scripts/quest/classquest/GLA/ClsGla001.cpp b/src/scripts/quest/classquest/GLA/ClsGla001.cpp new file mode 100644 index 00000000..e3d23fa6 --- /dev/null +++ b/src/scripts/quest/classquest/GLA/ClsGla001.cpp @@ -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 /scripts/ + +#include +#include "Manager/EventMgr.h" +#include +#include + +// 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 ); \ No newline at end of file diff --git a/src/scripts/quest/classquest/GLA/ClsGla011.cpp b/src/scripts/quest/classquest/GLA/ClsGla011.cpp new file mode 100644 index 00000000..d0cee628 --- /dev/null +++ b/src/scripts/quest/classquest/GLA/ClsGla011.cpp @@ -0,0 +1,118 @@ +// FFXIVTheMovie.ParserV3.3 +// fake IsAnnounce table +#include +#include +#include +#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 ); diff --git a/src/scripts/quest/classquest/GLA/ClsGla020.cpp b/src/scripts/quest/classquest/GLA/ClsGla020.cpp new file mode 100644 index 00000000..f8039b74 --- /dev/null +++ b/src/scripts/quest/classquest/GLA/ClsGla020.cpp @@ -0,0 +1,191 @@ +// FFXIVTheMovie.ParserV3.3 +#include +#include +#include +#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 ); diff --git a/src/world/Action/ItemAction.cpp b/src/world/Action/ItemAction.cpp index 96971e8b..29f87757 100644 --- a/src/world/Action/ItemAction.cpp +++ b/src/world/Action/ItemAction.cpp @@ -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{}; diff --git a/src/world/Action/ItemAction.h b/src/world/Action/ItemAction.h index 4d60d8ff..fdce1464 100644 --- a/src/world/Action/ItemAction.h +++ b/src/world/Action/ItemAction.h @@ -27,6 +27,10 @@ namespace Sapphire::World::Action private: void handleVFXItem(); + void handleMountItem(); + + void handleOrchestrionItem(); + private: Sapphire::Data::ItemActionPtr m_itemAction; diff --git a/src/world/Actor/Player.cpp b/src/world/Actor/Player.cpp index 48ce94ef..4d8d2667 100644 --- a/src/world/Actor/Player.cpp +++ b/src/world/Actor/Player.cpp @@ -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 ) diff --git a/src/world/Actor/Player.h b/src/world/Actor/Player.h index d5cfcce6..62fe1f6b 100644 --- a/src/world/Actor/Player.h +++ b/src/world/Actor/Player.h @@ -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; diff --git a/src/world/Actor/PlayerInventory.cpp b/src/world/Actor/PlayerInventory.cpp index b9fe5488..cd752de3 100644 --- a/src/world/Actor/PlayerInventory.cpp +++ b/src/world/Actor/PlayerInventory.cpp @@ -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 } diff --git a/src/world/Actor/PlayerSql.cpp b/src/world/Actor/PlayerSql.cpp index 1ec21bac..bd3fe3d3 100644 --- a/src/world/Actor/PlayerSql.cpp +++ b/src/world/Actor/PlayerSql.cpp @@ -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 ); } diff --git a/src/world/Inventory/Item.cpp b/src/world/Inventory/Item.cpp index ac41926c..de311fc5 100644 --- a/src/world/Inventory/Item.cpp +++ b/src/world/Inventory/Item.cpp @@ -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; diff --git a/src/world/Inventory/Item.h b/src/world/Inventory/Item.h index dff845cc..71b8ee11 100644 --- a/src/world/Inventory/Item.h +++ b/src/world/Inventory/Item.h @@ -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]; diff --git a/src/world/Network/GameConnection.cpp b/src/world/Network/GameConnection.cpp index 34be4dc2..25d65cf0 100644 --- a/src/world/Network/GameConnection.cpp +++ b/src/world/Network/GameConnection.cpp @@ -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 ); } diff --git a/src/world/Network/GameConnection.h b/src/world/Network/GameConnection.h index 92e1d661..576a7bb9 100644 --- a/src/world/Network/GameConnection.h +++ b/src/world/Network/GameConnection.h @@ -196,6 +196,8 @@ namespace Sapphire::Network DECLARE_HANDLER( diveHandler ); DECLARE_HANDLER( eventYieldHandler ); + + DECLARE_HANDLER( inventoryEquipRecommendedItemsHandler ); }; } diff --git a/src/world/Network/Handlers/PacketHandlers.cpp b/src/world/Network/Handlers/PacketHandlers.cpp index c51d9335..97ab09ab 100644 --- a/src/world/Network/Handlers/PacketHandlers.cpp +++ b/src/world/Network/Handlers/PacketHandlers.cpp @@ -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 ); + } } \ No newline at end of file diff --git a/src/world/Network/PacketWrappers/UpdateInventorySlotPacket.h b/src/world/Network/PacketWrappers/UpdateInventorySlotPacket.h index 5e46f9b9..cbbb6394 100644 --- a/src/world/Network/PacketWrappers/UpdateInventorySlotPacket.h +++ b/src/world/Network/PacketWrappers/UpdateInventorySlotPacket.h @@ -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;