mirror of
https://github.com/SapphireServer/Sapphire.git
synced 2025-04-22 20:57:46 +00:00
AchievementMgr framework; styling fixes;
This commit is contained in:
parent
bd1753423e
commit
34213990bc
15 changed files with 664 additions and 13 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -27,6 +27,8 @@ src/tools/bin/generated/*
|
||||||
build/
|
build/
|
||||||
build-*/
|
build-*/
|
||||||
cmake-build-*/
|
cmake-build-*/
|
||||||
|
bin/
|
||||||
|
out/
|
||||||
|
|
||||||
# Prerequisites
|
# Prerequisites
|
||||||
*.d
|
*.d
|
||||||
|
|
|
@ -1000,6 +1000,216 @@ namespace Sapphire::Common
|
||||||
InvincibilityIgnoreDamage,
|
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
|
enum HierarchyType : uint8_t
|
||||||
{
|
{
|
||||||
NONE_2 = 0x0,
|
NONE_2 = 0x0,
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#include "DbWorker.h"
|
#include "DbWorker.h"
|
||||||
#include <MySqlConnector.h>
|
#include <MySqlConnector.h>
|
||||||
#include "Logging/Logger.h"
|
#include "Logging/Logger.h"
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
#include "PreparedStatement.h"
|
#include "PreparedStatement.h"
|
||||||
|
|
||||||
|
|
|
@ -235,11 +235,12 @@ namespace Sapphire::Network::ActorControl
|
||||||
|
|
||||||
ArmoryErrorMsg = 0x201,
|
ArmoryErrorMsg = 0x201,
|
||||||
|
|
||||||
AchievementPopup = 0x203,
|
AchievementSetRate = 0x202,
|
||||||
|
AchievementComplete = 0x203,
|
||||||
SetCutsceneFlag = 0x204,
|
SetCutsceneFlag = 0x204,
|
||||||
|
|
||||||
LogMsg = 0x205, // LogMessage?
|
LogMsg = 0x205, // LogMessage?
|
||||||
AchievementMsg = 0x206,
|
AchievementObtainMsg = 0x206,
|
||||||
|
|
||||||
SetItemLevel = 0x209,
|
SetItemLevel = 0x209,
|
||||||
|
|
||||||
|
|
|
@ -328,6 +328,12 @@ namespace Sapphire::Network::Packets::WorldPackets::Server
|
||||||
uint32_t Result;
|
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
|
struct ZoneProtoDownLetterBoxAppendItemBase
|
||||||
{
|
{
|
||||||
uint32_t CatalogID;
|
uint32_t CatalogID;
|
||||||
|
|
|
@ -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
|
// if it is a teleport in the same zone, we want to do warp instead of moveTerri
|
||||||
bool sameTerritory = getTerritoryTypeId() == data.TerritoryType;
|
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.
|
// TODO: this should be simplified and a type created in server_common/common.h.
|
||||||
if( type == 1 || type == 2 ) // teleport
|
if( type == 1 || type == 2 ) // teleport
|
||||||
{
|
{
|
||||||
|
@ -824,7 +824,7 @@ void Player::setLevelForClass( uint8_t level, Common::ClassJob classjob )
|
||||||
|
|
||||||
m_classArray[ classJobIndex ] = level;
|
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()
|
void Player::sendModel()
|
||||||
|
@ -1315,6 +1315,16 @@ void Player::setTitle( uint16_t titleId )
|
||||||
sendToInRangeSet( makeActorControl( getId(), SetTitle, titleId ), true );
|
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 )
|
void Player::setMaxGearSets( uint8_t amount )
|
||||||
{
|
{
|
||||||
if( amount == 1 )
|
if( amount == 1 )
|
||||||
|
|
|
@ -25,6 +25,8 @@ namespace Sapphire::Entity
|
||||||
class Player : public Chara
|
class Player : public Chara
|
||||||
{
|
{
|
||||||
public:
|
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 TitleList = std::array< uint8_t, 48 >;
|
||||||
using HowToList = std::array< uint8_t, 34 >;
|
using HowToList = std::array< uint8_t, 34 >;
|
||||||
using MinionList = std::array< uint8_t, 40 >;
|
using MinionList = std::array< uint8_t, 40 >;
|
||||||
|
@ -365,6 +367,12 @@ namespace Sapphire::Entity
|
||||||
/*! send the players title list */
|
/*! send the players title list */
|
||||||
void sendTitleList();
|
void sendTitleList();
|
||||||
|
|
||||||
|
/*! get player's achievement list */
|
||||||
|
AchievementList& getAchievementList();
|
||||||
|
|
||||||
|
/*! get player's achievement data list */
|
||||||
|
AchievementDataList& getAchievementDataList();
|
||||||
|
|
||||||
/*! set number of gear sets */
|
/*! set number of gear sets */
|
||||||
void setMaxGearSets( uint8_t amount );
|
void setMaxGearSets( uint8_t amount );
|
||||||
|
|
||||||
|
@ -860,6 +868,8 @@ namespace Sapphire::Entity
|
||||||
uint8_t status;
|
uint8_t status;
|
||||||
} m_retainerInfo[8]{};
|
} m_retainerInfo[8]{};
|
||||||
|
|
||||||
|
AchievementList m_achievementList{};
|
||||||
|
AchievementDataList m_achievementData{};
|
||||||
uint16_t m_activeTitle{};
|
uint16_t m_activeTitle{};
|
||||||
TitleList m_titleList{};
|
TitleList m_titleList{};
|
||||||
HowToList m_howTo{};
|
HowToList m_howTo{};
|
||||||
|
|
192
src/world/Manager/AchievementMgr.cpp
Normal file
192
src/world/Manager/AchievementMgr.cpp
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
122
src/world/Manager/AchievementMgr.h
Normal file
122
src/world/Manager/AchievementMgr.h
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
#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();
|
||||||
|
|
||||||
|
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 );
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
|
||||||
|
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 );
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,20 +32,23 @@
|
||||||
#include "Territory/HousingZone.h"
|
#include "Territory/HousingZone.h"
|
||||||
#include "Territory/InstanceContent.h"
|
#include "Territory/InstanceContent.h"
|
||||||
#include "Territory/QuestBattle.h"
|
#include "Territory/QuestBattle.h"
|
||||||
|
|
||||||
#include "Manager/TerritoryMgr.h"
|
#include "Manager/TerritoryMgr.h"
|
||||||
#include "Manager/PlayerMgr.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 "Event/EventDefs.h"
|
||||||
#include "ContentFinder/ContentFinder.h"
|
#include "ContentFinder/ContentFinder.h"
|
||||||
|
|
||||||
#include "Manager/LinkshellMgr.h"
|
|
||||||
#include "Linkshell/Linkshell.h"
|
#include "Linkshell/Linkshell.h"
|
||||||
|
|
||||||
#include "Manager/WarpMgr.h"
|
|
||||||
|
|
||||||
#include "WorldServer.h"
|
#include "WorldServer.h"
|
||||||
|
|
||||||
#include "Session.h"
|
#include "Session.h"
|
||||||
#include <Manager/RNGMgr.h>
|
|
||||||
|
|
||||||
using namespace Sapphire::Network;
|
using namespace Sapphire::Network;
|
||||||
using namespace Sapphire::Network::Packets;
|
using namespace Sapphire::Network::Packets;
|
||||||
|
@ -554,12 +557,32 @@ void DebugCommandMgr::add( char* data, Entity::Player& player, std::shared_ptr<
|
||||||
|
|
||||||
pSession->getZoneConnection()->queueOutPacket( effectPacket );
|
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
|
else
|
||||||
{
|
{
|
||||||
PlayerMgr::sendUrgent( player, "{0} is not a valid ADD command.", subCommand );
|
PlayerMgr::sendUrgent( player, "{0} is not a valid ADD command.", subCommand );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugCommandMgr::get( char* data, Entity::Player& player, std::shared_ptr< DebugCommand > command )
|
void DebugCommandMgr::get( char* data, Entity::Player& player, std::shared_ptr< DebugCommand > command )
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#include <Manager/TerritoryMgr.h>
|
#include <Manager/TerritoryMgr.h>
|
||||||
#include <Manager/HousingMgr.h>
|
#include <Manager/HousingMgr.h>
|
||||||
|
#include <Manager/AchievementMgr.h>
|
||||||
|
|
||||||
#include "Script/ScriptMgr.h"
|
#include "Script/ScriptMgr.h"
|
||||||
#include "WorldServer.h"
|
#include "WorldServer.h"
|
||||||
|
@ -77,6 +78,37 @@ void PlayerMgr::onSendStateFlags( Entity::Player& player, bool updateInRange )
|
||||||
static_cast< uint8_t >( player.getOnlineStatus() ) ), true );
|
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 )
|
void PlayerMgr::onSendStats( Entity::Player& player )
|
||||||
{
|
{
|
||||||
std::array< uint32_t, 50 > statParams;
|
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.sendToInRangeSet( makeActorControl( player.getId(), LevelUpEffect, static_cast< uint8_t >( player.getClass() ),
|
||||||
player.getLevel(), player.getLevel() - 1 ), true );
|
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 )
|
void PlayerMgr::onGainExp( Entity::Player& player, uint32_t exp )
|
||||||
{
|
{
|
||||||
auto& server = Common::Service< World::WorldServer >::ref();
|
auto& server = Common::Service< World::WorldServer >::ref();
|
||||||
|
|
|
@ -22,12 +22,20 @@ class PlayerMgr
|
||||||
|
|
||||||
void onChangeClass( Sapphire::Entity::Player& player );
|
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 onPlayerHpMpTpChanged( Sapphire::Entity::Player& player );
|
||||||
|
|
||||||
void onPlayerItemLevelUpdate( Sapphire::Entity::Player& player );
|
void onPlayerItemLevelUpdate( Sapphire::Entity::Player& player );
|
||||||
|
|
||||||
void onLevelUp( 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 onGainExp( Sapphire::Entity::Player& player, uint32_t exp );
|
||||||
|
|
||||||
void onUnlockOrchestrion( Sapphire::Entity::Player& player, uint8_t songId, uint32_t itemId );
|
void onUnlockOrchestrion( Sapphire::Entity::Player& player, uint8_t songId, uint32_t itemId );
|
||||||
|
|
|
@ -600,7 +600,16 @@ void Sapphire::Network::GameConnection::commandHandler( const Packets::FFXIVARR_
|
||||||
player.setIsLogin( false );
|
player.setIsLogin( false );
|
||||||
break;
|
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
|
case PacketCommand::TELEPO_INQUIRY: // Teleport
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
#include "Session.h"
|
#include "Session.h"
|
||||||
|
|
||||||
#include "Manager/TerritoryMgr.h"
|
#include "Manager/TerritoryMgr.h"
|
||||||
#include "Manager/LinkshellMgr.h"
|
|
||||||
#include "Manager/TaskMgr.h"
|
#include "Manager/TaskMgr.h"
|
||||||
|
|
||||||
#include "Task/TestTask.h"
|
#include "Task/TestTask.h"
|
||||||
|
@ -29,6 +28,7 @@
|
||||||
#include <Database/ZoneDbConnection.h>
|
#include <Database/ZoneDbConnection.h>
|
||||||
#include <Database/DbWorkerPool.h>
|
#include <Database/DbWorkerPool.h>
|
||||||
#include <Service.h>
|
#include <Service.h>
|
||||||
|
#include "Manager/AchievementMgr.h"
|
||||||
#include "Manager/LinkshellMgr.h"
|
#include "Manager/LinkshellMgr.h"
|
||||||
#include "Manager/TerritoryMgr.h"
|
#include "Manager/TerritoryMgr.h"
|
||||||
#include "Manager/HousingMgr.h"
|
#include "Manager/HousingMgr.h"
|
||||||
|
@ -186,6 +186,16 @@ void WorldServer::run( int32_t argc, char* argv[] )
|
||||||
}
|
}
|
||||||
Common::Service< Manager::LinkshellMgr >::set( pLsMgr );
|
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" );
|
Logger::info( "Setting up InstanceObjectCache" );
|
||||||
auto pInstanceObjCache = std::make_shared< Sapphire::InstanceObjectCache >();
|
auto pInstanceObjCache = std::make_shared< Sapphire::InstanceObjectCache >();
|
||||||
Common::Service< Sapphire::InstanceObjectCache >::set( pInstanceObjCache );
|
Common::Service< Sapphire::InstanceObjectCache >::set( pInstanceObjCache );
|
||||||
|
|
Loading…
Add table
Reference in a new issue