diff --git a/deps/mysqlConnector/PreparedStatement.cpp b/deps/mysqlConnector/PreparedStatement.cpp index ad65ec96e..a841afff9 100644 --- a/deps/mysqlConnector/PreparedStatement.cpp +++ b/deps/mysqlConnector/PreparedStatement.cpp @@ -393,7 +393,7 @@ bool Mysql::PreparedStatement::execute() bool Mysql::PreparedStatement::execute( const std::string &sql ) { - throw std::runtime_error("PreparedStatement::execute( const std::string &sql ) Not implemented"); + throw std::runtime_error( "PreparedStatement::execute( const std::string &sql ) Not implemented" ); return false; } @@ -485,7 +485,7 @@ void Mysql::PreparedStatement::setInt( uint32_t parameterIndex, int32_t value ) void Mysql::PreparedStatement::setUInt( uint32_t parameterIndex, uint32_t value ) { if( parameterIndex == 0 || parameterIndex > m_paramCount ) - throw std::runtime_error( "PreparedStatement::setInt: invalid 'parameterIndex'" ); + throw std::runtime_error( "PreparedStatement::setUInt: invalid 'parameterIndex'" ); --parameterIndex; { @@ -546,7 +546,7 @@ void Mysql::PreparedStatement::setInt64( uint32_t parameterIndex, int64_t value void Mysql::PreparedStatement::setUInt64( uint32_t parameterIndex, uint64_t value ) { if( parameterIndex == 0 || parameterIndex > m_paramCount ) - throw std::runtime_error( "PreparedStatement::setInt64: invalid 'parameterIndex'" ); + throw std::runtime_error( "PreparedStatement::setUInt64: invalid 'parameterIndex'" ); --parameterIndex; { diff --git a/sql/migrations/20230702121200_ChangeFcFieldSizes.sql b/sql/migrations/20230207121200_ChangeFcFieldSizes.sql similarity index 100% rename from sql/migrations/20230702121200_ChangeFcFieldSizes.sql rename to sql/migrations/20230207121200_ChangeFcFieldSizes.sql diff --git a/sql/migrations/20230802121200_ChangeFcMembersFieldSizes.sql b/sql/migrations/20230208121200_ChangeFcMembersFieldSizes.sql similarity index 100% rename from sql/migrations/20230802121200_ChangeFcMembersFieldSizes.sql rename to sql/migrations/20230208121200_ChangeFcMembersFieldSizes.sql diff --git a/sql/migrations/20230305161241_AddAchievement.sql b/sql/migrations/20230305161241_AddAchievement.sql new file mode 100644 index 000000000..82e3c9161 --- /dev/null +++ b/sql/migrations/20230305161241_AddAchievement.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS charainfoachievement ( + `CharacterId` BIGINT(20) NOT NULL, + `UnlockList` blob, + `ProgressData` blob, + `HistoryList` blob, + `IS_DELETE` int(3) DEFAULT 0, + `IS_NOT_ACTIVE_FLG` int(3) DEFAULT 0, + `UPDATE_DATE` datetime, + PRIMARY KEY (`CharacterId`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; diff --git a/sql/migrations/20230305162627_RemovePlayerAchievement.sql b/sql/migrations/20230305162627_RemovePlayerAchievement.sql new file mode 100644 index 000000000..8a594d984 --- /dev/null +++ b/sql/migrations/20230305162627_RemovePlayerAchievement.sql @@ -0,0 +1 @@ +ALTER TABLE `charainfo` DROP `Achievement`; \ No newline at end of file diff --git a/src/api/PlayerMinimal.cpp b/src/api/PlayerMinimal.cpp index e1ab32e22..c761ac043 100644 --- a/src/api/PlayerMinimal.cpp +++ b/src/api/PlayerMinimal.cpp @@ -286,6 +286,19 @@ void PlayerMinimal::saveAsNew() stmtBlacklist->setBinary( 2, blIds ); g_charaDb.directExecute( stmtBlacklist ); + // Achievement related + auto stmtAchv = g_charaDb.getPreparedStatement( Db::ZoneDbStatements::CHARA_ACHIEV_INS ); + std::vector< uint8_t > unlock( 2048 / 8, 0 ); + std::vector< uint8_t > progressData( 8, 0 ); + std::vector< uint8_t > history( 5 * 4, 0 ); + + stmtAchv->setUInt64( 1, m_characterId ); + stmtAchv->setBinary( 2, unlock ); + stmtAchv->setBinary( 3, progressData ); + stmtAchv->setBinary( 4, history ); + g_charaDb.directExecute( stmtAchv ); + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// SET UP INVENTORIES createInvDbContainer( InventoryType::Bag0 ); diff --git a/src/common/Database/ZoneDbConnection.cpp b/src/common/Database/ZoneDbConnection.cpp index 0adc2ff69..10f9f854e 100644 --- a/src/common/Database/ZoneDbConnection.cpp +++ b/src/common/Database/ZoneDbConnection.cpp @@ -29,7 +29,7 @@ void Sapphire::Db::ZoneDbConnection::doPrepareStatements() "IsNewAdventurer, TerritoryType, TerritoryId, PosX, PosY, PosZ, PosR, " "OTerritoryType, OTerritoryId, OPosX, OPosY, OPosZ, OPosR, GuardianDeity, " "BirthDay, BirthMonth, Class, Status, TotalPlayTime, FirstClass, HomePoint, " - "FavoritePoint, RestPoint, StartTown, ActiveTitle, TitleList, Achievement, " + "FavoritePoint, RestPoint, StartTown, ActiveTitle, TitleList, " "Aetheryte, HowTo, Minions, Mounts, Orchestrion, EquippedMannequin, ConfigFlags, " "QuestCompleteFlags, OpeningSequence, QuestTracking, GrandCompany, " "GrandCompanyRank, Discovery, GMRank, EquipDisplayFlags, Unlocks, CFPenaltyUntil, " @@ -46,7 +46,7 @@ void Sapphire::Db::ZoneDbConnection::doPrepareStatements() "TerritoryType = ?, TerritoryId = ?, PosX = ?, PosY = ?, PosZ = ?, PosR = ?, " "OTerritoryType = ?, OTerritoryId = ?, OPosX = ?, OPosY = ?, OPosZ = ?, OPosR = ?, " "Class = ?, Status = ?, TotalPlayTime = ?, HomePoint = ?, FavoritePoint = ?, RestPoint = ?, " - "ActiveTitle = ?, TitleList = ?, Achievement = ?, Aetheryte = ?, HowTo = ?, Minions = ?, Mounts = ?, Orchestrion = ?, " + "ActiveTitle = ?, TitleList = ?, Aetheryte = ?, HowTo = ?, Minions = ?, Mounts = ?, Orchestrion = ?, " "EquippedMannequin = ?, ConfigFlags = ?, QuestCompleteFlags = ?, OpeningSequence = ?, " "QuestTracking = ?, GrandCompany = ?, GrandCompanyRank = ?, Discovery = ?, GMRank = ?, EquipDisplayFlags = ?, Unlocks = ?, " "CFPenaltyUntil = ?, Pose = ? WHERE CharacterId = ?;", @@ -107,8 +107,6 @@ void Sapphire::Db::ZoneDbConnection::doPrepareStatements() CONNECTION_ASYNC ); prepareStatement( CHARA_UP_TITLE, "UPDATE charainfo SET ActiveTitle = ? WHERE CharacterId = ?;", CONNECTION_ASYNC ); prepareStatement( CHARA_UP_TITLELIST, "UPDATE charainfo SET TitleList = ? WHERE CharacterId = ?;", CONNECTION_ASYNC ); - prepareStatement( CHARA_UP_ACHIEVEMENTS, "UPDATE charainfo SET Achievement = ? WHERE CharacterId = ?;", - CONNECTION_ASYNC ); prepareStatement( CHARA_UP_AETHERYTE, "UPDATE charainfo SET Aetheryte = ? WHERE CharacterId = ?;", CONNECTION_ASYNC ); prepareStatement( CHARA_UP_HOWTO, "UPDATE charainfo SET HowTo = ? WHERE CharacterId = ?;", CONNECTION_ASYNC ); prepareStatement( CHARA_UP_MINIONS, "UPDATE charainfo SET Minions = ? WHERE CharacterId = ?;", CONNECTION_ASYNC ); @@ -224,6 +222,24 @@ void Sapphire::Db::ZoneDbConnection::doPrepareStatements() "WHERE CharacterId = ?;", CONNECTION_SYNC ); + /// CHARA ACHIEVEMENT + prepareStatement( CHARA_ACHIEV_INS, + "INSERT INTO charainfoachievement ( CharacterId, UnlockList, ProgressData, HistoryList, UPDATE_DATE ) " + " VALUES ( ?, ?, ?, ?, NOW() );", + CONNECTION_SYNC ); + + prepareStatement( CHARA_ACHIEV_UP, "UPDATE charainfoachievement " + " SET UnlockList = ?," + " ProgressData = ?," + " HistoryList = ?" + " WHERE CharacterId = ?;", + CONNECTION_ASYNC ); + + prepareStatement( CHARA_ACHIEV_SEL, "SELECT UnlockList, ProgressData, HistoryList FROM charainfoachievement " + "WHERE CharacterId = ?;", + CONNECTION_SYNC ); + + /// CHARA FRIENDLIST prepareStatement( CHARA_FRIENDLIST_INS, "INSERT INTO charainfofriendlist ( CharacterId, CharacterIdList, InviteDataList, UPDATE_DATE ) " @@ -240,7 +256,7 @@ void Sapphire::Db::ZoneDbConnection::doPrepareStatements() "WHERE CharacterId = ?;", CONNECTION_SYNC ); - /// CHARA FRIENDLIST + /// CHARA BLACKLIST prepareStatement( CHARA_BLACKLIST_INS, "INSERT INTO charainfoblacklist ( CharacterId, CharacterIdList, UPDATE_DATE ) " " VALUES ( ?, ?, NOW() );", diff --git a/src/common/Database/ZoneDbConnection.h b/src/common/Database/ZoneDbConnection.h index 789cc29d8..d1ed49ca8 100644 --- a/src/common/Database/ZoneDbConnection.h +++ b/src/common/Database/ZoneDbConnection.h @@ -38,7 +38,6 @@ namespace Sapphire::Db CHARA_UP_FAVOPOINT, CHARA_UP_TITLE, CHARA_UP_TITLELIST, - CHARA_UP_ACHIEVEMENTS, CHARA_UP_AETHERYTE, CHARA_UP_HOWTO, CHARA_UP_MINIONS, @@ -89,6 +88,10 @@ namespace Sapphire::Db CHARA_BLACKLIST_UP, CHARA_BLACKLIST_SEL, + CHARA_ACHIEV_INS, + CHARA_ACHIEV_UP, + CHARA_ACHIEV_SEL, + CHARA_LINKSHELL_INS, ZONE_SEL_BNPCS, diff --git a/src/world/Actor/Player.h b/src/world/Actor/Player.h index 3c307f706..01202cb63 100644 --- a/src/world/Actor/Player.h +++ b/src/world/Actor/Player.h @@ -545,6 +545,9 @@ namespace Sapphire::Entity /*! unload player from logout */ void unload(); + /*! load achievement data */ + bool loadAchievements(); + /*! load active class data */ bool loadClassData(); @@ -672,6 +675,8 @@ namespace Sapphire::Entity void updateDbBlacklist(); + void updateDbAchievement(); + void updateDbChara() const; /////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/world/Actor/PlayerSql.cpp b/src/world/Actor/PlayerSql.cpp index a7c49368a..58d3123ca 100644 --- a/src/world/Actor/PlayerSql.cpp +++ b/src/world/Actor/PlayerSql.cpp @@ -144,7 +144,7 @@ bool Sapphire::Entity::Player::loadFromDb( uint64_t characterId ) res->free(); - if( !loadActiveQuests() || !loadClassData() || !loadSearchInfo() || !loadHuntingLog() || !loadFriendList() || !loadBlacklist() ) + if( !loadActiveQuests() || !loadClassData() || !loadSearchInfo() || !loadHuntingLog() || !loadFriendList() || !loadBlacklist() || !loadAchievements() ) { Logger::error( "chara#{0} data corrupt!", m_characterId ); } @@ -212,6 +212,49 @@ bool Sapphire::Entity::Player::loadActiveQuests() } +bool Sapphire::Entity::Player::loadAchievements() +{ + auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref(); + auto stmt = db.getPreparedStatement( Db::ZoneDbStatements::CHARA_ACHIEV_SEL ); + + stmt->setUInt64( 1, m_characterId ); + auto res = db.query( stmt ); + + while( res->next() ) + { + auto unlock = res->getBlobVector( "UnlockList" ); + auto progressData = res->getBlobVector( "ProgressData" ); + auto history = res->getBlobVector( "HistoryList" ); + + // todo: throw this in util (used in LS etc) + auto func = []( std::unordered_map< uint32_t, uint32_t >& outData, std::vector< char >& inData ) + { + if( !inData.empty() ) + { + size_t entryCount = inData.size() / sizeof( uint32_t ); + + + for( auto i = 0; i < entryCount; ++i ) + { + auto key = *reinterpret_cast< const uint32_t* >( &inData[ i * 4 ] ); + i += 1; + auto val = *reinterpret_cast< const uint32_t* >( &inData[ i * 4 ] ); + outData[ key ] = val; + } + } + }; + + std::unordered_map< uint32_t, uint32_t > progressMap; + func( progressMap, progressData ); + + memcpy( reinterpret_cast< char* >( m_achievementData.unlockList.data() ), unlock.data(), unlock.size() ); + m_achievementData.progressData = progressMap; + memcpy( reinterpret_cast< char* >( m_achievementData.history.data() ), history.data(), history.size() ); + } + + return true; +} + bool Sapphire::Entity::Player::loadClassData() { auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref(); @@ -304,6 +347,9 @@ void Sapphire::Entity::Player::updateSql() ////// Blacklist updateDbBlacklist(); + ////// Achievement + updateDbAchievement(); + ///// Store last write syncLastDBWrite(); } @@ -377,63 +423,61 @@ void Sapphire::Entity::Player::updateDbChara() const std::vector< uint8_t > titleListVec( sizeof( m_titleList ) ); stmt->setBinary( 37, titleListVec ); - std::vector< uint8_t > achievementVec( 16 ); - stmt->setBinary( 38, achievementVec ); std::vector< uint8_t > aetheryteVec( m_aetheryte.size() ); memcpy( aetheryteVec.data(), m_aetheryte.data(), m_aetheryte.size() ); - stmt->setBinary( 39, aetheryteVec ); + stmt->setBinary( 38, aetheryteVec ); std::vector< uint8_t > howToVec( sizeof( m_howTo ) ); memcpy( howToVec.data(), m_howTo.data(), m_howTo.size() ); - stmt->setBinary( 40, howToVec ); + stmt->setBinary( 39, howToVec ); std::vector< uint8_t > minionsVec( sizeof( m_minionGuide ) ); memcpy( minionsVec.data(), m_minionGuide.data(), m_minionGuide.size() ); - stmt->setBinary( 41, minionsVec ); + stmt->setBinary( 40, minionsVec ); std::vector< uint8_t > mountsVec( sizeof( m_mountGuide ) ); memcpy( mountsVec.data(), m_mountGuide.data(), m_mountGuide.size() ); - stmt->setBinary( 42, mountsVec ); + stmt->setBinary( 41, mountsVec ); std::vector< uint8_t > orchestrionVec( m_orchestrion.size() ); memcpy( orchestrionVec.data(), m_orchestrion.data(), m_orchestrion.size() ); - stmt->setBinary( 42, mountsVec ); + stmt->setBinary( 42, orchestrionVec ); - stmt->setInt( 44, m_equippedMannequin ); // EquippedMannequin + stmt->setInt( 43, m_equippedMannequin ); // EquippedMannequin - stmt->setInt( 45, 0 ); // DisplayFlags + stmt->setInt( 44, 0 ); // DisplayFlags std::vector< uint8_t > questCompleteVec( m_questCompleteFlags.size() ); memcpy( questCompleteVec.data(), m_questCompleteFlags.data(), m_questCompleteFlags.size() ); - stmt->setBinary( 46, questCompleteVec ); + stmt->setBinary( 45, questCompleteVec ); - stmt->setInt( 47, m_openingSequence ); + stmt->setInt( 46, m_openingSequence ); std::vector< uint8_t > questTrackerVec( sizeof( m_questTracking ) ); memcpy( questTrackerVec.data(), m_questTracking.data(), sizeof( m_questTracking ) ); - stmt->setBinary( 48, questTrackerVec ); + stmt->setBinary( 47, questTrackerVec ); - stmt->setInt( 49, m_gc ); // DisplayFlags + stmt->setInt( 48, m_gc ); // DisplayFlags - stmt->setBinary( 50, { m_gcRank[ 0 ], m_gcRank[ 1 ], m_gcRank[ 2 ] } ); + stmt->setBinary( 49, { m_gcRank[ 0 ], m_gcRank[ 1 ], m_gcRank[ 2 ] } ); std::vector< uint8_t > discoveryVec( m_discovery.size() ); memcpy( discoveryVec.data(), m_discovery.data(), m_discovery.size() ); - stmt->setBinary( 51, discoveryVec ); + stmt->setBinary( 50, discoveryVec ); - stmt->setInt( 52, m_gmRank ); + stmt->setInt( 51, m_gmRank ); - stmt->setInt( 53, m_configFlags ); + stmt->setInt( 52, m_configFlags ); std::vector< uint8_t > unlockVec( m_unlocks.size() ); memcpy( unlockVec.data(), m_unlocks.data(), m_unlocks.size() ); - stmt->setBinary( 54, unlockVec ); + stmt->setBinary( 53, unlockVec ); - stmt->setInt( 55, m_cfPenaltyUntil ); + stmt->setInt( 54, m_cfPenaltyUntil ); - stmt->setInt( 56, m_pose ); + stmt->setInt( 55, m_pose ); - stmt->setUInt64( 57, m_characterId ); + stmt->setUInt64( 56, m_characterId ); db.execute( stmt ); } @@ -503,6 +547,36 @@ void Sapphire::Entity::Player::updateDbBlacklist() db.execute( stmt ); } +void Sapphire::Entity::Player::updateDbAchievement() +{ + auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref(); + + auto stmt = db.getPreparedStatement( Db::CHARA_ACHIEV_UP ); + + std::vector< int > flattenMap( m_achievementData.progressData.size() * 2 ); + + for( const auto& [ key, val ] : m_achievementData.progressData ) + { + flattenMap.push_back( key ); + flattenMap.push_back( val ); + } + + std::vector< uint8_t > unlockList( sizeof( uint8_t ) * m_achievementData.unlockList.size() ); + std::vector< uint8_t > progressList( sizeof( uint32_t ) * flattenMap.size() ); + std::vector< uint8_t > history( sizeof( uint16_t ) * m_achievementData.history.size() ); + + memcpy( unlockList.data(), m_achievementData.unlockList.data(), unlockList.size() ); + memcpy( progressList.data(), flattenMap.data(), progressList.size() ); + memcpy( history.data(), m_achievementData.history.data(), history.size() ); + + stmt->setBinary( 1, unlockList ); + stmt->setBinary( 2, progressList ); + stmt->setBinary( 3, history ); + stmt->setUInt64( 4, m_characterId ); + db.execute( stmt ); +} + + void Sapphire::Entity::Player::insertDbClass( const uint8_t classJobIndex, uint8_t level ) const { auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref();