#include #include #include "AchievementMgr.h" #include "PlayerMgr.h" using namespace Sapphire; using namespace Sapphire::Network; using namespace Sapphire::Network::Packets; using namespace Sapphire::World::Manager; bool AchievementMgr::cacheAchievements() { auto& exdData = Common::Service< Data::ExdData >::ref(); auto idList = exdData.getIdList< Excel::Achievement >(); for( auto id : idList ) { auto achvExdData = exdData.getRow< Excel::Achievement >( id ); uint32_t key = achvExdData->data().ConditionType; auto achvType = static_cast< Common::Achievement::Type >( key ); if( achvType == Common::Achievement::Type::None ) continue; // verify if achievement type has subtype if( achvType == Common::Achievement::Type::General || achvType == Common::Achievement::Type::Classjob || achvType == Common::Achievement::Type::InstanceContent ) { int32_t subtype = achvExdData->data().ConditionArg[ 0 ]; if( subtype != 0 ) key = ( getKeyFromType( achvType, subtype ) ).u32; else continue; // ignore key types with no subtype } // map achievement IDs to achv data m_achievementKeyCacheMap[ key ].emplace_back( id ); // map achievement keys (either type or union key:subtype) to achievement IDs m_achievementDetailCacheMap[ id ] = std::move( achvExdData ); } return true; } void AchievementMgr::unlockAchievement( Entity::Player& player, uint32_t achievementId ) { auto& exdData = Common::Service< Data::ExdData >::ref(); auto achvData = exdData.getRow< Excel::Achievement >( achievementId ); // set flag on mask format expected by client uint16_t index; uint8_t value; Common::Util::valueToFlagByteIndexValue( achievementId, value, index ); player.getAchievementList()[ index ] |= value; // fire packets Common::Service< World::Manager::PlayerMgr >::ref().onUnlockAchievement( player, achievementId ); // check and add title to player auto achvTitleId = achvData->data().Title; if( achvTitleId != 0 ) { player.addTitle( achvTitleId ); } handleLinkedAchievementsForId( player, achievementId ); } bool AchievementMgr::hasAchievementUnlocked( Entity::Player& player, uint32_t achievementId ) { uint16_t index; uint8_t value; Common::Util::valueToFlagByteIndexValue( static_cast< uint16_t >( achievementId ), value, index ); return ( player.getAchievementList()[ index ] & value ) != 0; } std::pair< uint32_t, uint32_t > AchievementMgr::getAchievementDataById( Entity::Player& player, uint32_t achievementId ) { auto& exdData = Common::Service< Data::ExdData >::ref(); auto& achvDataList = player.getAchievementDataList(); auto achvExdData = exdData.getRow< Excel::Achievement >( achievementId )->data(); auto achvType = static_cast< Common::Achievement::Type >( achvExdData.ConditionType ); // get paired type:subtype key for stored data auto dataKey = getKeyFromType( achvType, achvExdData.ConditionArg[ 0 ] ); // get achievement progress data, if it exists (otherwise pass 0) uint32_t currProg = 0; if( achvDataList.count( dataKey.u32 ) ) currProg = achvDataList[ dataKey.u32 ]; // get maximum progress for given achievement, as required by client uint32_t maxProg = static_cast< uint32_t >( achvExdData.ConditionArg[ 1 ] ); // cap maximum progress display to maximum progress return { std::min( currProg, maxProg ), maxProg }; } void AchievementMgr::handleLinkedAchievementsForId( Entity::Player& player, uint32_t achievementId ) { auto& exdData = Common::Service< Data::ExdData >::ref(); const auto& linkedAchievementIdList = getAchievementIdByType( Common::Achievement::Type::LinkedAchievement ); for( auto& achvId : linkedAchievementIdList ) { // skip if achievement already unlocked if( hasAchievementUnlocked( player, achvId ) ) continue; auto pAchv = getAchievementDetail( achvId ); if( !pAchv ) continue; auto achvExdData = pAchv->data(); // if achievement has other achievements linked to it if( achvExdData.ConditionType == static_cast< uint8_t >( Common::Achievement::Type::LinkedAchievement ) ) { // get all linked achievements needed to unlock std::set< int32_t > linkedAchv{ std::make_move_iterator( std::begin( achvExdData.ConditionArg ) ), std::make_move_iterator( std::end( achvExdData.ConditionArg ) ) }; // clear empty achievement links linkedAchv.erase( 0 ); // check if passed achievement ID is tied to this linked achievement if( !linkedAchv.count( achievementId ) ) continue; // verify if player has all the required achievements unlocked bool hasAllAchievements = true; for( const auto linkedAchvId : linkedAchv ) { if( linkedAchvId == 0 ) continue; if( !hasAchievementUnlocked( player, linkedAchvId ) ) { hasAllAchievements = false; break; } } // unlock achievement if linked achievement conditions are met if( hasAllAchievements ) unlockAchievement( player, achvId ); } } } Common::AchievementDataKey AchievementMgr::getKeyFromType( Common::Achievement::Type achvType, int32_t argument ) { Common::AchievementDataKey dataKey{ 0 }; dataKey.key.type = static_cast< uint8_t >( achvType ); dataKey.key.subtype = static_cast< uint16_t >( argument ); return dataKey; } const std::vector< uint32_t >& AchievementMgr::getAchievementIdByType( Common::Achievement::Type type ) const { return getAchievementIdByType( static_cast< uint32_t >( type ) ); } const std::vector< uint32_t >& AchievementMgr::getAchievementIdByType( uint32_t type ) const { auto it = m_achievementKeyCacheMap.find( type ); if( it != std::end( m_achievementKeyCacheMap ) ) return it->second; else return {}; } std::shared_ptr< Excel::ExcelStruct< Excel::Achievement > > AchievementMgr::getAchievementDetail( uint32_t achvId ) const { auto it = m_achievementDetailCacheMap.find( achvId ); if( it != std::end( m_achievementDetailCacheMap ) ) return it->second; else return nullptr; }