1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-05-14 14:37:45 +00:00
sapphire/src/world/Manager/AchievementMgr.h
2023-02-02 02:30:31 -03:00

252 lines
8.9 KiB
C++

#pragma once
#include <unordered_map>
#include <vector>
#include <algorithm>
#include <type_traits>
#include <Service.h>
#include <Common.h>
#include "Actor/Player.h"
namespace Sapphire::World::Manager
{
class AchievementMgr
{
public:
AchievementMgr() = default;
~AchievementMgr() = default;
bool cacheAchievements();
/// <summary>
/// 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() ) );
///
/// </summary>
/// <typeparam name="AchievementType"></typeparam>
/// <param name="player"></param>
/// <param name="argument"></param>
/// <param name="progressCount"></param>
template < auto AchievementType >
void progressAchievementByType( Entity::Player& player, int32_t argument, uint32_t progressCount = 1 )
{
progressAchievement< decltype( AchievementType ), AchievementType >( player, argument, progressCount );
}
/// <summary>
/// check if player has an achievement by its given id
/// </summary>
/// <param name="player"></param>
/// <param name="achievementId"></param>
/// <returns>true/false</returns>
bool hasAchievementUnlocked( Entity::Player& player, uint32_t achievementId );
/// <summary>
/// get a pair of current progress and maximum count for a given achievement id
/// </summary>
/// <param name="player"></param>
/// <param name="achievementId"></param>
/// <returns>pair of current and maximum progress</returns>
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;
/// <summary>
/// get a key for merged achievements (type:subtype) that have progress data
/// </summary>
/// <param name="achvType"></param>
/// <param name="argument"></param>
/// <returns>data key for given type:subtype</returns>
Common::AchievementDataKey getKeyFromType( Common::Achievement::Type achvType, int32_t argument );
/// <summary>
/// parse and unlock achievements linked to a given achievement id
/// </summary>
/// <param name="player"></param>
/// <param name="achievementId"></param>
void handleLinkedAchievementsForId( Entity::Player& player, uint32_t achievementId );
/// <summary>
/// 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
/// </summary>
/// <param name="player"></param>
/// <param name="achievementId"></param>
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;
}
}
}
}
}