1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-04-29 15:47:46 +00:00
sapphire/src/world/Manager/AchievementMgr.cpp
2023-01-19 13:03:54 -03:00

192 lines
6.1 KiB
C++

#include <Exd/ExdData.h>
#include <Util/Util.h>
#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;
}