diff --git a/.gitignore b/.gitignore index e35b79e86..f0b722656 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,8 @@ src/tools/bin/generated/* build/ build-*/ cmake-build-*/ +bin/ +out/ # Prerequisites *.d diff --git a/src/common/Common.h b/src/common/Common.h index 713d0ce50..c9561a005 100644 --- a/src/common/Common.h +++ b/src/common/Common.h @@ -1000,6 +1000,216 @@ namespace Sapphire::Common InvincibilityIgnoreDamage, }; + namespace Achievement + { + enum class Type : uint8_t + { + None, + General, + LinkedAchievement, + Classjob, + Unknown_4,// Materia related? id 304 + Unknown_5,// Hunt related? id 1259 + QuestUnk_6, + Unknown_7, + Unknown_8,// Map discovery related + QuestUnk_9, + ChocoboRank, + PvPRank, + WolvesDenMatches, + WolvesDenWins, + InstanceContent, + BeastTribeReputation, + Unknown_16, + FrontlineMatches, + FrontlineWinsGC, + FrontlineWinsAll, + AetherCurrent, + }; + + namespace GeneralSubtype + { + enum AchievementGeneralSubtype : int32_t + { + EnemyDefeatCount = 11, + GilEnemySource = 12, + GilLevequestSource = 13, + FactionLevequestCompleted = 14, + GuildhestCompleted = 15, + MateriaAffixCount = 16, + SpiritboundMateriaCount = 17, + RegionalBattleLevequestCompleted = 18, + LocalTradeLevequestCompleted = 19, + + // Legacy subtypes - skipping + + #pragma region Synth 1 -50 Recipes + SynthWoodworkingLv01to10Recipes = 23, + SynthWoodworkingLv11to20Recipes = 24, + SynthWoodworkingLv21to30Recipes = 25, + SynthWoodworkingLv31to40Recipes = 26, + SynthWoodworkingLv41to50Recipes = 27, + SynthSmithingLv01to10Recipes = 28, + SynthSmithingLv11to20Recipes = 29, + SynthSmithingLv21to30Recipes = 30, + SynthSmithingLv31to40Recipes = 31, + SynthSmithingLv41to50Recipes = 32, + SynthArmorcraftLv01to10Recipes = 33, + SynthArmorcraftLv11to20Recipes = 34, + SynthArmorcraftLv21to30Recipes = 35, + SynthArmorcraftLv31to40Recipes = 36, + SynthArmorcraftLv41to50Recipes = 37, + SynthGoldsmithingLv01to10Recipes = 38, + SynthGoldsmithingLv11to20Recipes = 39, + SynthGoldsmithingLv21to30Recipes = 40, + SynthGoldsmithingLv31to40Recipes = 41, + SynthGoldsmithingLv41to50Recipes = 42, + SynthLeatherworkingLv01to10Recipes = 43, + SynthLeatherworkingLv11to20Recipes = 44, + SynthLeatherworkingLv21to30Recipes = 45, + SynthLeatherworkingLv31to40Recipes = 46, + SynthLeatherworkingLv41to50Recipes = 47, + SynthClothcraftLv01to10Recipes = 48, + SynthClothcraftLv11to20Recipes = 49, + SynthClothcraftLv21to30Recipes = 50, + SynthClothcraftLv31to40Recipes = 51, + SynthClothcraftLv41to50Recipes = 52, + SynthAlchemyLv01to10Recipes = 53, + SynthAlchemyLv11to20Recipes = 54, + SynthAlchemyLv21to30Recipes = 55, + SynthAlchemyLv31to40Recipes = 56, + SynthAlchemyLv41to50Recipes = 57, + SynthCookingLv01to10Recipes = 58, + SynthCookingLv11to20Recipes = 59, + SynthCookingLv21to30Recipes = 60, + SynthCookingLv31to40Recipes = 61, + SynthCookingLv41to50Recipes = 62, + #pragma endregion + + // TODO: Map gathering subtypes 63 to 128 + + GCStormSeals = 138, + GCSerpentSeals = 139, + GCFlameSeals = 140, + + MaelstromLevequestCompleted = 141, + TwinAdderLevequestCompleted = 142, + ImmortalFlamesLevequestCompleted = 143, + + MaelstromSupplyCompleted = 144, + TwinAdderSupplyCompleted = 145, + ImmortalFlamesSupplyCompleted = 146, + + MaelstromProvisioningCompleted = 147, + TwinAdderProvisioningCompleted = 148, + ImmortalFlamesProvisioningCompleted = 149, + + // Legacy subtypes - skipping + + FieldLevequestCompleted = 179, + UniqueBattleLevequestCompleted = 180, + + // TODO: Map leve subtypes 181 to 201 + + UniqueFishCaughtCount = 202, + + // TODO: Map craft subtypes 203 to 213 + + UniqueQuestsCompleted = 214, + InstanceContentCompleted = 215, // Instanced dungeons, raids or trials + UniqueInstanceContentCompleted = 216,// Unique instanced dungeons, raids or trials + + BindingCoilCompleted = 217, + UniqueGuildhestCompleted = 218, + FateCompleted = 219, + + // TODO: Map subtypes 220 to 235 + + ChocoboRidePimpedOut = 236, + + MaelstromUniqueLeveCompleted = 238, + TwinAdderUniqueLeveCompleted = 239, + ImmortalFlamesUniqueLeveCompleted = 240, + + EnlistGCMaelstrom = 241, + EnlistGCTwinAdder = 242, + EnlistGCImmortalFlames = 243, + + CommendationCount = 244, + + // TODO: Map subtypes 245 to 250 + + ResurrectStrangers = 251, + SecondCoilCompleted = 266, + UniqueBigFishCaught = 267, + RetainerVentureCompleted = 268, + + HighLvlDutyPaladinCompleted = 277, + HighLvlDutyWarriorCompleted = 278, + + RelicAnimus = 279, + RelicNovus = 280, + + HigherGradeMateriaFromTransmutation = 281, + ARRSightseeingLogCount = 284, + + InterceptorDroneDefeated = 285, + InterceptorNodeDefeated = 286, + + TrainAnotherChocoboCount = 287, + + HuntRankBDefeated = 290, + HuntRankADefeated = 291, + HuntRankSDefeated = 292, + + LootCofferLeatherMap = 293, + + DesynthesizeItemCount = 294, + + RelicNexus = 347, + FinalCoilCompleted = 348, + RelicZodiac = 349, + RelicZeta = 350, + + TripleTriadUniqueCardCount = 352, + TripleTriadNPCDefeatedCount = 353, + TripleTriadRouletteWin = 354, + TripleTriadTournamentWin = 355, + + MgpGoldSaucerAttractionSource = 356, + ChocoboRaceCount = 357, + ChocoboRaceWin = 358, + ChocoboCovering = 359, + ChocoboPedigreeLevel = 360, + ChocoboRating = 361, + + FrontlineEnemyDispatched = 362, + + GATECompleted = 363, + + // TODO: Map subtypes 364 to 368 + + HighLvlDutyDarkKnightCompleted = 369, + + GordiasCompleted = 370, + GordiasSavageCompleted = 371, + + // TODO: Map subtypes 372 to X + }; + } + } + + union AchievementDataKey + { + uint32_t u32; + + struct AchievementPackedKey { + uint8_t type; + uint16_t subtype; + uint8_t padding0; + } key; + }; + enum HierarchyType : uint8_t { NONE_2 = 0x0, diff --git a/src/common/Database/DbConnection.cpp b/src/common/Database/DbConnection.cpp index 7013d6265..fdf599cf5 100644 --- a/src/common/Database/DbConnection.cpp +++ b/src/common/Database/DbConnection.cpp @@ -2,6 +2,7 @@ #include "DbWorker.h" #include #include "Logging/Logger.h" +#include #include "PreparedStatement.h" diff --git a/src/common/Network/CommonActorControl.h b/src/common/Network/CommonActorControl.h index f80d895c3..411ce2188 100644 --- a/src/common/Network/CommonActorControl.h +++ b/src/common/Network/CommonActorControl.h @@ -235,11 +235,12 @@ namespace Sapphire::Network::ActorControl ArmoryErrorMsg = 0x201, - AchievementPopup = 0x203, + AchievementSetRate = 0x202, + AchievementComplete = 0x203, SetCutsceneFlag = 0x204, LogMsg = 0x205, // LogMessage? - AchievementMsg = 0x206, + AchievementObtainMsg = 0x206, SetItemLevel = 0x209, diff --git a/src/common/Network/PacketDef/Zone/ServerZoneDef.h b/src/common/Network/PacketDef/Zone/ServerZoneDef.h index 30951f4e1..d87134aaf 100644 --- a/src/common/Network/PacketDef/Zone/ServerZoneDef.h +++ b/src/common/Network/PacketDef/Zone/ServerZoneDef.h @@ -328,6 +328,12 @@ namespace Sapphire::Network::Packets::WorldPackets::Server uint32_t Result; }; + struct FFXIVIpcAchievement : FFXIVIpcBasePacket< Achievement > + { + uint8_t complete[256]; // bitmask of achievements, up to 8 * 256 (2048) achvs + uint16_t history[5]; // last 5 achievement IDs + }; + struct ZoneProtoDownLetterBoxAppendItemBase { uint32_t CatalogID; diff --git a/src/world/Actor/Player.cpp b/src/world/Actor/Player.cpp index 6eb5f3f1d..45e3ad7e7 100644 --- a/src/world/Actor/Player.cpp +++ b/src/world/Actor/Player.cpp @@ -447,7 +447,7 @@ void Player::teleport( uint16_t aetheryteId, uint8_t type ) // if it is a teleport in the same zone, we want to do warp instead of moveTerri bool sameTerritory = getTerritoryTypeId() == data.TerritoryType; - WarpType warpType; + WarpType warpType = WarpType::WARP_TYPE_NORMAL; // TODO: this should be simplified and a type created in server_common/common.h. if( type == 1 || type == 2 ) // teleport { @@ -824,7 +824,7 @@ void Player::setLevelForClass( uint8_t level, Common::ClassJob classjob ) m_classArray[ classJobIndex ] = level; - queuePacket( makeActorControlSelf( getId(), Network::ActorControl::ClassJobUpdate, static_cast< uint8_t >( classjob ), level ) ); + Service< World::Manager::PlayerMgr >::ref().onSetLevelForClass( *this, classjob ); } void Player::sendModel() @@ -1315,6 +1315,16 @@ void Player::setTitle( uint16_t titleId ) sendToInRangeSet( makeActorControl( getId(), SetTitle, titleId ), true ); } +Player::AchievementList& Player::getAchievementList() +{ + return m_achievementList; +} + +Player::AchievementDataList& Player::getAchievementDataList() +{ + return m_achievementData; +} + void Player::setMaxGearSets( uint8_t amount ) { if( amount == 1 ) diff --git a/src/world/Actor/Player.h b/src/world/Actor/Player.h index 27173b82d..a0b4529b9 100644 --- a/src/world/Actor/Player.h +++ b/src/world/Actor/Player.h @@ -25,6 +25,8 @@ namespace Sapphire::Entity class Player : public Chara { public: + using AchievementDataList = std::map< uint32_t, uint32_t >; + using AchievementList = std::array< uint8_t, 2048 / 8 >; // up to 2048 achievements using TitleList = std::array< uint8_t, 48 >; using HowToList = std::array< uint8_t, 34 >; using MinionList = std::array< uint8_t, 40 >; @@ -365,7 +367,13 @@ namespace Sapphire::Entity /*! send the players title list */ void sendTitleList(); - /*! set number of gear sets */ + /*! get player's achievement list */ + AchievementList& getAchievementList(); + + /*! get player's achievement data list */ + AchievementDataList& getAchievementDataList(); + + /*! set number of gear sets */ void setMaxGearSets( uint8_t amount ); /*! get number of gear sets */ @@ -860,6 +868,8 @@ namespace Sapphire::Entity uint8_t status; } m_retainerInfo[8]{}; + AchievementList m_achievementList{}; + AchievementDataList m_achievementData{}; uint16_t m_activeTitle{}; TitleList m_titleList{}; HowToList m_howTo{}; diff --git a/src/world/Manager/AchievementMgr.cpp b/src/world/Manager/AchievementMgr.cpp new file mode 100644 index 000000000..a82940bd3 --- /dev/null +++ b/src/world/Manager/AchievementMgr.cpp @@ -0,0 +1,192 @@ +#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; +} + diff --git a/src/world/Manager/AchievementMgr.h b/src/world/Manager/AchievementMgr.h new file mode 100644 index 000000000..ffc8838a1 --- /dev/null +++ b/src/world/Manager/AchievementMgr.h @@ -0,0 +1,122 @@ +#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(); + + template < auto AchievementType > + void progressAchievementByType( Entity::Player& player, int32_t argument, uint32_t progressCount = 1 ) + { + progressAchievement< decltype( AchievementType ), AchievementType >( std::move( player ), argument, progressCount ); + } + + bool hasAchievementUnlocked( Entity::Player& player, uint32_t achievementId ); + + void unlockAchievement( 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; + + std::shared_ptr< Excel::ExcelStruct< Excel::Achievement > > getAchievementDetail( uint32_t achvId ) const; + const std::vector< uint32_t >& getAchievementIdByType( Common::Achievement::Type type ) const; + const std::vector< uint32_t >& getAchievementIdByType( uint32_t type ) const; + + Common::AchievementDataKey getKeyFromType( Common::Achievement::Type achvType, int32_t argument ); + + void handleLinkedAchievementsForId( 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& achvDataList = player.getAchievementDataList(); + + auto dataKey = getKeyFromType( Common::Achievement::Type::General, subtype ); + + if( !achvDataList.count( dataKey.u32 ) ) + achvDataList[ dataKey.u32 ] = 0; + + achvDataList[ dataKey.u32 ] += progressCount; + + 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 ] <= achvDataList[ 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 level ) + { + auto& achvDataList = player.getAchievementDataList(); + + auto dataKey = getKeyFromType( Common::Achievement::Type::Classjob, classJob ); + + if( !achvDataList.count( dataKey.u32 ) ) + achvDataList[ dataKey.u32 ] = 0; + + achvDataList[ 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 ] <= achvDataList[ dataKey.u32 ] ) + unlockAchievement( player, achvId ); + + } + } +} diff --git a/src/world/Manager/DebugCommandMgr.cpp b/src/world/Manager/DebugCommandMgr.cpp index db0886eb6..5452170b7 100644 --- a/src/world/Manager/DebugCommandMgr.cpp +++ b/src/world/Manager/DebugCommandMgr.cpp @@ -32,20 +32,23 @@ #include "Territory/HousingZone.h" #include "Territory/InstanceContent.h" #include "Territory/QuestBattle.h" + #include "Manager/TerritoryMgr.h" #include "Manager/PlayerMgr.h" +#include "Manager/AchievementMgr.h" +#include "Manager/WarpMgr.h" +#include "Manager/LinkshellMgr.h" +#include "Manager/RNGMgr.h" + #include "Event/EventDefs.h" #include "ContentFinder/ContentFinder.h" -#include "Manager/LinkshellMgr.h" #include "Linkshell/Linkshell.h" -#include "Manager/WarpMgr.h" - #include "WorldServer.h" #include "Session.h" -#include + using namespace Sapphire::Network; using namespace Sapphire::Network::Packets; @@ -554,12 +557,32 @@ void DebugCommandMgr::add( char* data, Entity::Player& player, std::shared_ptr< pSession->getZoneConnection()->queueOutPacket( effectPacket ); } + else if( subCommand == "achv" ) + { + uint32_t achvId; + + sscanf( params.c_str(), "%u", &achvId ); + + auto& achvMgr = Common::Service< Manager::AchievementMgr >::ref(); + + achvMgr.unlockAchievement( player, achvId ); + } + else if( subCommand == "achvGeneral" ) + { + uint32_t achvSubtype; + uint32_t progress; + + sscanf( params.c_str(), "%u %u", &achvSubtype, &progress ); + + auto& achvMgr = Common::Service< Manager::AchievementMgr >::ref(); + + achvMgr.progressAchievementByType< Common::Achievement::Type::General >( player, achvSubtype, progress ); + } else { PlayerMgr::sendUrgent( player, "{0} is not a valid ADD command.", subCommand ); } - } void DebugCommandMgr::get( char* data, Entity::Player& player, std::shared_ptr< DebugCommand > command ) diff --git a/src/world/Manager/PlayerMgr.cpp b/src/world/Manager/PlayerMgr.cpp index 634d8df79..1b4cc5e9d 100644 --- a/src/world/Manager/PlayerMgr.cpp +++ b/src/world/Manager/PlayerMgr.cpp @@ -8,6 +8,7 @@ #include #include +#include #include "Script/ScriptMgr.h" #include "WorldServer.h" @@ -77,6 +78,37 @@ void PlayerMgr::onSendStateFlags( Entity::Player& player, bool updateInRange ) static_cast< uint8_t >( player.getOnlineStatus() ) ), true ); } +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 ) ); + + server.queueForPlayer( player.getCharacterId(), achvPacket ); +} + +void PlayerMgr::onSendAchievementProgress( Entity::Player& player, uint32_t achievementId ) +{ + auto& server = Common::Service< World::WorldServer >::ref(); + auto& achvMgr = Common::Service< Manager::AchievementMgr >::ref(); + + auto achvProgress = achvMgr.getAchievementDataById( player, achievementId ); + + auto pAchvProgressPacket = makeActorControl( player.getId(), AchievementSetRate, achievementId, achvProgress.first, achvProgress.second ); + server.queueForPlayer( player.getCharacterId(), pAchvProgressPacket ); +} + +void PlayerMgr::onUnlockAchievement( Entity::Player& player, uint32_t achievementId ) +{ + auto& server = Common::Service< World::WorldServer >::ref(); + + onSendAchievementList( player ); + + server.queueForPlayer( player.getCharacterId(), makeActorControl( player.getId(), AchievementComplete, achievementId ) ); + server.queueForPlayer( player.getCharacterId(), makeActorControl( player.getId(), AchievementObtainMsg, achievementId ) ); +} + void PlayerMgr::onSendStats( Entity::Player& player ) { std::array< uint32_t, 50 > statParams; @@ -144,8 +176,23 @@ void PlayerMgr::onLevelUp( Entity::Player& player ) player.sendToInRangeSet( makeActorControl( player.getId(), LevelUpEffect, static_cast< uint8_t >( player.getClass() ), player.getLevel(), player.getLevel() - 1 ), true ); + + auto& achvMgr = Common::Service< World::Manager::AchievementMgr >::ref(); + achvMgr.progressAchievementByType< Common::Achievement::Type::Classjob >( player, static_cast< uint8_t >( player.getClass() ), player.getLevel() ); } +void PlayerMgr::onSetLevelForClass( Entity::Player& player, Common::ClassJob classJob ) +{ + auto& server = Common::Service< World::WorldServer >::ref(); + auto& achvMgr = Common::Service< World::Manager::AchievementMgr >::ref(); + + server.queueForPlayer( player.getCharacterId(), makeActorControlSelf( player.getId(), Network::ActorControl::ClassJobUpdate, + static_cast< uint8_t >( classJob ), player.getLevelForClass( classJob ) ) ); + + achvMgr.progressAchievementByType< Common::Achievement::Type::Classjob >( player, static_cast< uint8_t >( classJob ), player.getLevel() ); +} + + void PlayerMgr::onGainExp( Entity::Player& player, uint32_t exp ) { auto& server = Common::Service< World::WorldServer >::ref(); diff --git a/src/world/Manager/PlayerMgr.h b/src/world/Manager/PlayerMgr.h index efa12e849..91c73406c 100644 --- a/src/world/Manager/PlayerMgr.h +++ b/src/world/Manager/PlayerMgr.h @@ -22,12 +22,20 @@ class PlayerMgr void onChangeClass( Sapphire::Entity::Player& player ); + void onSendAchievementList( Sapphire::Entity::Player& player ); + + void onSendAchievementProgress( Sapphire::Entity::Player& player, uint32_t achievementId ); + + void onUnlockAchievement( Sapphire::Entity::Player& player, uint32_t achievementId ); + void onPlayerHpMpTpChanged( Sapphire::Entity::Player& player ); void onPlayerItemLevelUpdate( Sapphire::Entity::Player& player ); void onLevelUp( Sapphire::Entity::Player& player ); + void onSetLevelForClass( Sapphire::Entity::Player& player, Common::ClassJob classJob ); + void onGainExp( Sapphire::Entity::Player& player, uint32_t exp ); void onUnlockOrchestrion( Sapphire::Entity::Player& player, uint8_t songId, uint32_t itemId ); diff --git a/src/world/Network/Handlers/GMCommandHandlers.cpp b/src/world/Network/Handlers/GMCommandHandlers.cpp index e6574a0b4..27b745ef2 100644 --- a/src/world/Network/Handlers/GMCommandHandlers.cpp +++ b/src/world/Network/Handlers/GMCommandHandlers.cpp @@ -539,7 +539,7 @@ void Sapphire::Network::GameConnection::gmCommandHandler( const Packets::FFXIVAR else { auto& warpMgr = Common::Service< WarpMgr >::ref(); - warpMgr.requestMoveTerritory( *targetPlayer, WarpType::WARP_TYPE_GM, teriMgr.getZoneByTerritoryTypeId( param1 )->getGuId(), targetPlayer->getPos(), 0); + warpMgr.requestMoveTerritory( *targetPlayer, WarpType::WARP_TYPE_GM, teriMgr.getZoneByTerritoryTypeId( param1 )->getGuId(), targetPlayer->getPos(), 0 ); } PlayerMgr::sendServerNotice( player, "{0} was warped to zone {1}", targetPlayer->getName(), param1, pZone->getName() ); diff --git a/src/world/Network/Handlers/PacketCommandHandler.cpp b/src/world/Network/Handlers/PacketCommandHandler.cpp index 2bd1e14fb..a1970b705 100644 --- a/src/world/Network/Handlers/PacketCommandHandler.cpp +++ b/src/world/Network/Handlers/PacketCommandHandler.cpp @@ -600,7 +600,16 @@ void Sapphire::Network::GameConnection::commandHandler( const Packets::FFXIVARR_ player.setIsLogin( false ); break; } - + case PacketCommand::ACHIEVEMENT_REQUEST_RATE: + { + Service< World::Manager::PlayerMgr >::ref().onSendAchievementProgress( player, param11 ); + break; + } + case PacketCommand::ACHIEVEMENT_REQUEST: + { + Service< World::Manager::PlayerMgr >::ref().onSendAchievementList( player ); + break; + } case PacketCommand::TELEPO_INQUIRY: // Teleport { diff --git a/src/world/WorldServer.cpp b/src/world/WorldServer.cpp index 5c49ebd3a..b1ac88737 100644 --- a/src/world/WorldServer.cpp +++ b/src/world/WorldServer.cpp @@ -19,7 +19,6 @@ #include "Session.h" #include "Manager/TerritoryMgr.h" -#include "Manager/LinkshellMgr.h" #include "Manager/TaskMgr.h" #include "Task/TestTask.h" @@ -29,6 +28,7 @@ #include #include #include +#include "Manager/AchievementMgr.h" #include "Manager/LinkshellMgr.h" #include "Manager/TerritoryMgr.h" #include "Manager/HousingMgr.h" @@ -186,6 +186,16 @@ void WorldServer::run( int32_t argc, char* argv[] ) } Common::Service< Manager::LinkshellMgr >::set( pLsMgr ); + auto pAchvMgr = std::make_shared< Manager::AchievementMgr >(); + + Logger::info( "AchievementMgr: Caching data" ); + if( !pAchvMgr->cacheAchievements() ) + { + Logger::fatal( "Unable to cache achievements!" ); + return; + } + Common::Service< Manager::AchievementMgr >::set( pAchvMgr ); + Logger::info( "Setting up InstanceObjectCache" ); auto pInstanceObjCache = std::make_shared< Sapphire::InstanceObjectCache >(); Common::Service< Sapphire::InstanceObjectCache >::set( pInstanceObjCache );