diff --git a/deps/datReader/Exd/Structs.h b/deps/datReader/Exd/Structs.h index dc464068..a5cace83 100644 --- a/deps/datReader/Exd/Structs.h +++ b/deps/datReader/Exd/Structs.h @@ -838,9 +838,10 @@ namespace Excel uint16_t Priority; uint8_t Category; uint8_t Point; + int8_t UnknownConditional; // previously 2.3~3.05 padding0[0] uint8_t ConditionType; uint8_t Detail; - int8_t padding0[2]; + int8_t padding0; }; /* 200982 */ diff --git a/src/common/Common.h b/src/common/Common.h index 95848527..26353fb8 100644 --- a/src/common/Common.h +++ b/src/common/Common.h @@ -1014,10 +1014,10 @@ namespace Sapphire::Common Classjob, Unknown_4,// Materia related? id 304 Unknown_5,// Hunt related? id 1259 - QuestUnk_6, + Quest, // Quests that need all required args met Unknown_7, Unknown_8,// Map discovery related - QuestUnk_9, + QuestAny,// Quests that need any required args met ChocoboRank, PvPRank, WolvesDenMatches, diff --git a/src/world/Actor/Player.cpp b/src/world/Actor/Player.cpp index 749e66b0..370680b4 100644 --- a/src/world/Actor/Player.cpp +++ b/src/world/Actor/Player.cpp @@ -1323,6 +1323,11 @@ Player::AchievementDataList& Player::getAchievementDataList() return m_achievementData; } +Player::AchievementHistory& Player::getAchievementHistory() +{ + return m_achievementHistory; +} + void Player::setMaxGearSets( uint8_t amount ) { if( amount == 1 ) diff --git a/src/world/Actor/Player.h b/src/world/Actor/Player.h index ed0228a8..dcc0db7c 100644 --- a/src/world/Actor/Player.h +++ b/src/world/Actor/Player.h @@ -27,6 +27,7 @@ namespace Sapphire::Entity public: using AchievementDataList = std::map< uint32_t, uint32_t >; using AchievementList = std::array< uint8_t, 2048 / 8 >; // up to 2048 achievements + using AchievementHistory = std::array< uint16_t, 5 >; using TitleList = std::array< uint8_t, 48 >; using HowToList = std::array< uint8_t, 34 >; using MinionList = std::array< uint8_t, 40 >; @@ -373,6 +374,9 @@ namespace Sapphire::Entity /*! get player's achievement data list */ AchievementDataList& getAchievementDataList(); + /*! get player's achievement data history */ + AchievementHistory& getAchievementHistory(); + /*! set number of gear sets */ void setMaxGearSets( uint8_t amount ); @@ -870,6 +874,7 @@ namespace Sapphire::Entity AchievementList m_achievementList{}; AchievementDataList m_achievementData{}; + AchievementHistory m_achievementHistory{}; uint16_t m_activeTitle{}; TitleList m_titleList{}; HowToList m_howTo{}; diff --git a/src/world/Actor/PlayerQuest.cpp b/src/world/Actor/PlayerQuest.cpp index 15ce8c11..dd53f18a 100644 --- a/src/world/Actor/PlayerQuest.cpp +++ b/src/world/Actor/PlayerQuest.cpp @@ -19,10 +19,10 @@ void Sapphire::Entity::Player::finishQuest( uint16_t questId, uint32_t optionalC auto& questMgr = Common::Service< World::Manager::QuestMgr >::ref(); + updateQuestsCompleted( questId ); + //@todo should probably be changed to a bool function in case reward can not be obtained as quests will not complete in that case questMgr.onCompleteQuest( *this, questId, optionalChoice ); - - updateQuestsCompleted( questId ); } void Sapphire::Entity::Player::unfinishQuest( uint16_t questId ) diff --git a/src/world/Manager/AchievementMgr.cpp b/src/world/Manager/AchievementMgr.cpp index a82940bd..633ba67c 100644 --- a/src/world/Manager/AchievementMgr.cpp +++ b/src/world/Manager/AchievementMgr.cpp @@ -57,6 +57,13 @@ void AchievementMgr::unlockAchievement( Entity::Player& player, uint32_t achieve player.getAchievementList()[ index ] |= value; + // handle player achievement history + // todo: verify retail behavior due to client copying the last achievement unlocked + /* auto& achvHistory = player.getAchievementHistory(); + + std::rotate( achvHistory.rbegin(), achvHistory.rbegin() + 1, achvHistory.rend() ); + achvHistory[ 0 ] = achievementId;*/ + // fire packets Common::Service< World::Manager::PlayerMgr >::ref().onUnlockAchievement( player, achievementId ); @@ -74,7 +81,7 @@ bool AchievementMgr::hasAchievementUnlocked( Entity::Player& player, uint32_t ac { uint16_t index; uint8_t value; - Common::Util::valueToFlagByteIndexValue( static_cast< uint16_t >( achievementId ), value, index ); + Common::Util::valueToFlagByteIndexValue( achievementId, value, index ); return ( player.getAchievementList()[ index ] & value ) != 0; } @@ -138,9 +145,6 @@ void AchievementMgr::handleLinkedAchievementsForId( Entity::Player& player, uint bool hasAllAchievements = true; for( const auto linkedAchvId : linkedAchv ) { - if( linkedAchvId == 0 ) - continue; - if( !hasAchievementUnlocked( player, linkedAchvId ) ) { hasAllAchievements = false; diff --git a/src/world/Manager/AchievementMgr.h b/src/world/Manager/AchievementMgr.h index 9b65ec22..c5db191e 100644 --- a/src/world/Manager/AchievementMgr.h +++ b/src/world/Manager/AchievementMgr.h @@ -119,4 +119,83 @@ namespace Sapphire::World::Manager } } + + template<> + inline void AchievementMgr::progressAchievement< Common::Achievement::Type, Common::Achievement::Type::Quest >( Entity::Player& player, int32_t questId, uint32_t unused ) + { + auto& achvDataList = player.getAchievementDataList(); + + // get achievements that need all achv in args completed + const auto questAchvAllList = getAchievementIdByType( Common::Achievement::Type::Quest ); + // get achievements that need any of the achvs in args completed + const auto questAchvAnyList = getAchievementIdByType( Common::Achievement::Type::QuestAny ); + + // handle achv type for all quests in args completed + for( auto achvId : questAchvAllList ) + { + if( hasAchievementUnlocked( player, achvId ) ) + continue; + + auto pAchv = getAchievementDetail( achvId ); + + auto achvExdData = pAchv->data(); + + std::set< uint16_t > linkedQuests{ std::make_move_iterator( std::begin( achvExdData.ConditionArg ) ), + std::make_move_iterator( std::end( achvExdData.ConditionArg ) ) }; + + // clear empty achievement links + linkedQuests.erase( 0 ); + + // check if passed quest ID is tied to this achievement + if( !linkedQuests.count( questId ) ) + continue; + + // verify if player has all the required quests completed + bool hasAllAchievements = true; + for( const auto questId : linkedQuests ) + { + if( !player.isQuestCompleted( questId ) ) + { + hasAllAchievements = false; + break; + } + } + + // unlock achievement if all required quests are completed + if( hasAllAchievements ) + unlockAchievement( player, achvId ); + } + + // handle achv type for all quests in args completed + for( auto achvId : questAchvAnyList ) + { + if( hasAchievementUnlocked( player, achvId ) ) + continue; + + auto pAchv = getAchievementDetail( achvId ); + + auto achvExdData = pAchv->data(); + + std::set< int32_t > linkedQuests{ std::make_move_iterator( std::begin( achvExdData.ConditionArg ) ), + std::make_move_iterator( std::end( achvExdData.ConditionArg ) ) }; + + // clear empty quest ids + linkedQuests.erase( 0 ); + + // check if passed quest ID is tied to this achievement + if( !linkedQuests.count( questId ) ) + continue; + + // verify if player has any of the required quests completed + bool hasAllAchievements = true; + for( const auto questId : linkedQuests ) + { + if( player.isQuestCompleted( questId ) ) + { + unlockAchievement( player, achvId ); + break; + } + } + } + } } diff --git a/src/world/Manager/PlayerMgr.cpp b/src/world/Manager/PlayerMgr.cpp index 16439b7b..a6d894e9 100644 --- a/src/world/Manager/PlayerMgr.cpp +++ b/src/world/Manager/PlayerMgr.cpp @@ -79,7 +79,8 @@ void PlayerMgr::onSendAchievementList( Entity::Player& player ) auto& server = Common::Service< World::WorldServer >::ref(); auto achvPacket = makeZonePacket< FFXIVIpcAchievement >( player.getId() ); - std::memcpy( &achvPacket->data().complete[ 0 ], &player.getAchievementList()[ 0 ], sizeof( &achvPacket->data().complete ) ); + std::memcpy( &achvPacket->data().complete[ 0 ], &player.getAchievementList()[ 0 ], sizeof( achvPacket->data().complete ) ); + std::memcpy( &achvPacket->data().history[ 0 ], &player.getAchievementHistory()[ 0 ], sizeof( achvPacket->data().history ) ); server.queueForPlayer( player.getCharacterId(), achvPacket ); } diff --git a/src/world/Manager/QuestMgr.cpp b/src/world/Manager/QuestMgr.cpp index c03f7ac1..f8d62e69 100644 --- a/src/world/Manager/QuestMgr.cpp +++ b/src/world/Manager/QuestMgr.cpp @@ -11,6 +11,7 @@ #include "Network/PacketWrappers/Notice2Packet.h" #include "QuestMgr.h" +#include "AchievementMgr.h" #include "Actor/Player.h" @@ -41,6 +42,9 @@ void QuestMgr::onCompleteQuest( Entity::Player& player, uint16_t questId, uint32 server.queueForPlayer( player.getCharacterId(), questFinishPacket ); giveQuestRewards( player, questId, optionalChoice ); + + auto& achvMgr = Common::Service< World::Manager::AchievementMgr >::ref(); + achvMgr.progressAchievementByType< Common::Achievement::Type::Quest >( player, questId ); } void QuestMgr::onRemoveQuest( Entity::Player &player, uint8_t questIndex )