#pragma once #include #include #include #include #include #include #include "Actor/Player.h" namespace Sapphire::World::Manager { class AchievementMgr { public: AchievementMgr() = default; ~AchievementMgr() = default; bool cacheAchievements(); /// /// progress an achievement by its given properties /// each type has a specific logic for progress, attention should be given to the type being passed /// optional parameter for overriding the amount to increment a given progress by /// /// example usage for Achievement::Type::General: /// /// To Crush Your Enemies I /// Defeat 100 enemies. /// /// type (col[7]): 1 -> Common::Achievement::Type::General /// subtype (col[8]): 11 -> Common::Achievement::GeneralSubtype::EnemyDefeatCount /// /// progressAchievementByType< Common::Achievement::Type::General >( player, GeneralSubtype::EnemyDefeatCount ); /// /// example usage for Achievement::Type::ClassJob: /// /// progressAchievementByType< Common::Achievement::Type::Classjob >( player, static_cast< uint32_t >( player.getClass() ) ); /// /// /// /// /// /// template < auto AchievementType > void progressAchievementByType( Entity::Player& player, int32_t argument, uint32_t progressCount = 1 ) { progressAchievement< decltype( AchievementType ), AchievementType >( player, argument, progressCount ); } /// /// check if player has an achievement by its given id /// /// /// /// true/false bool hasAchievementUnlocked( Entity::Player& player, uint32_t achievementId ); /// /// get a pair of current progress and maximum count for a given achievement id /// /// /// /// pair of current and maximum progress std::pair< uint32_t, uint32_t > getAchievementDataById( Entity::Player& player, uint32_t achievementId ); private: // map achievement IDs to achv data using AchievementDetailCache = std::unordered_map< uint32_t, std::shared_ptr< Excel::ExcelStruct< Excel::Achievement > > >; // map achievement keys (either type or union key:subtype) to achievement IDs using AchievementKeyCache = std::unordered_map< uint32_t, std::vector< uint32_t > >; AchievementDetailCache m_achievementDetailCacheMap; AchievementKeyCache m_achievementKeyCacheMap; // cache fetch functions std::shared_ptr< Excel::ExcelStruct< Excel::Achievement > > getAchievementDetail( uint32_t achvId ) const; std::vector< uint32_t > getAchievementIdByType( Common::Achievement::Type type ) const; std::vector< uint32_t > getAchievementIdByType( uint32_t type ) const; /// /// get a key for merged achievements (type:subtype) that have progress data /// /// /// /// data key for given type:subtype Common::AchievementDataKey getKeyFromType( Common::Achievement::Type achvType, int32_t argument ); /// /// parse and unlock achievements linked to a given achievement id /// /// /// void handleLinkedAchievementsForId( Entity::Player& player, uint32_t achievementId ); /// /// internal use: unlock achievement in the player achievement unlock flagmask, from a given id /// progressAchievement should be used instead, due to certain achievements using progress data /// /// /// void unlockAchievement( Entity::Player& player, uint32_t achievementId ); template< typename AchievementTypeT, AchievementTypeT achievementType > inline void progressAchievement( Entity::Player& player, int32_t argument, uint32_t progressCount ); }; template<> inline void AchievementMgr::progressAchievement< Common::Achievement::Type, Common::Achievement::Type::General >( Entity::Player& player, int32_t subtype, uint32_t progressCount ) { auto achvData = player.getAchievementData(); auto dataKey = getKeyFromType( Common::Achievement::Type::General, subtype ); if( !achvData.progressData.count( dataKey.u32 ) ) achvData.progressData[ dataKey.u32 ] = 0; achvData.progressData[ dataKey.u32 ] += progressCount; player.setAchievementData( achvData ); const auto achvIdList = getAchievementIdByType( dataKey.u32 ); for( auto achvId : achvIdList ) { if( hasAchievementUnlocked( player, achvId ) ) continue; auto pAchv = getAchievementDetail( achvId ); if( !pAchv ) continue; auto achvExdData = pAchv->data(); if( achvExdData.ConditionArg[ 1 ] <= static_cast< int32_t >( achvData.progressData[ dataKey.u32 ] ) ) unlockAchievement( player, achvId ); } } template<> inline void AchievementMgr::progressAchievement< Common::Achievement::Type, Common::Achievement::Type::Classjob >( Entity::Player& player, int32_t classJob, uint32_t unused ) { auto achvData = player.getAchievementData(); auto dataKey = getKeyFromType( Common::Achievement::Type::Classjob, classJob ); if( !achvData.progressData.count( dataKey.u32 ) ) achvData.progressData[ dataKey.u32 ] = 0; auto level = player.getLevelForClass( static_cast< Common::ClassJob >( classJob ) ); achvData.progressData[ dataKey.u32 ] = level; const auto achvIdList = getAchievementIdByType( dataKey.u32 ); for( auto achvId : achvIdList ) { if( hasAchievementUnlocked( player, achvId ) ) continue; auto pAchv = getAchievementDetail( achvId ); if( !pAchv ) continue; auto& achvExdData = pAchv->data(); if( achvExdData.ConditionArg[ 1 ] <= static_cast< int32_t >( achvData.progressData[ dataKey.u32 ] ) ) unlockAchievement( player, achvId ); } } template<> inline void AchievementMgr::progressAchievement< Common::Achievement::Type, Common::Achievement::Type::Quest >( Entity::Player& player, int32_t questId, uint32_t unused ) { auto& achvDataList = player.getAchievementData().progressData; // 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; } } } } }