/////////////////////////////////////////////////////////////////// //*-------------------------------------------------------------*// //| Part of the Game Jolt API C++ Library (http://gamejolt.com) |// //*-------------------------------------------------------------*// //| Released under the zlib License |// //| More information available in the readme file |// //*-------------------------------------------------------------*// /////////////////////////////////////////////////////////////////// #pragma once #ifndef _GJ_GUARD_SCORE_H_ #define _GJ_GUARD_SCORE_H_ // **************************************************************** /*! Score table object class.\n * http://gamejolt.com/api/doc/game/scores/ * \brief Score Table Object */ class gjScoreTable final { private: int m_iID; //!< ID of the score table std::string m_sTitle; //!< title/name of the score table std::string m_sDescription; //!< description text of the score table int m_iSortDir; //!< sort direction of the score table (see #GJ_SORT_DIRECTION) bool m_bPrimary; //!< primary status static gjScoreTable* s_pPrimary; //!< pointer to the primary score table std::vector m_apScore; //!< semi-cached score entries std::vector m_apVerify; //!< verification list with temporary score entries (helper) gjAPI* m_pAPI; //!< main interface access pointer public: gjScoreTable(const gjData& aScoreTableData, gjAPI* pAPI)noexcept; ~gjScoreTable(); /*! \name Fetch Scores Request */ //! @{ /*! Fetch and semi-cache specific score entries of this score table through an API request. * \pre Login maybe required * \note \b -Now blocks, \b -Call uses non-blocking callbacks * \param bOnlyUser Fetch only score entries from the current main user (Login required) * \param iLimit Max number of fetched score entries * \return **GJ_OK** on success\n * **GJ_REQUEST_FAILED** if request was unsuccessful\n * **GJ_NOT_CONNECTED** if connection/login is missing\n * **GJ_NO_DATA_FOUND** if no scores were found\n * (see #GJ_ERROR) */ inline int FetchScoresNow(const bool& bOnlyUser, const int& iLimit, gjScoreList* papOutput) {if(!papOutput) return GJ_INVALID_INPUT; return this->__FetchScores(bOnlyUser, iLimit, papOutput, GJ_NETWORK_NULL_API(gjScoreList));} template inline int FetchScoresCall(const bool& bOnlyUser, const int& iLimit, GJ_NETWORK_OUTPUT(gjScoreList)) {return this->__FetchScores(bOnlyUser, iLimit, NULL, GJ_NETWORK_OUTPUT_FW);} //! @} /*! \name Add Score Request */ //! @{ /*! Add a new score entry to this score table through an API request.\n * May does nothing, if score entry was already added (compares name and sort).\n * May does nothing, if main user has a better highscore. * \pre Login maybe required * \note \b -Now blocks, \b -Call uses non-blocking callbacks * \param sScore Score string * \param iSort Numerical sort value of the score * \param sExtraData Extra data associated with the score * \param sGuestName Name of a guest user (leave empty to use the current main user, Login required) * \return **GJ_OK** on success\n * **GJ_REQUEST_FAILED** if request was unsuccessful\n * **GJ_REQUEST_CANCELED** if score entry was already added or if main user has a better highscore\n * **GJ_INVALID_INPUT** if score string is empty or sort value is zero\n * **GJ_NOT_CONNECTED** if connection/login is missing\n * (see #GJ_ERROR) */ inline int AddScoreNow(const std::string& sScore, const int& iSort, const std::string& sExtraData, const std::string& sGuestName) {return this->__AddScore(sScore, iSort, sExtraData, sGuestName, true, GJ_NETWORK_NULL_API(gjScorePtr));} inline int AddScoreCall(const std::string& sScore, const int& iSort, const std::string& sExtraData, const std::string& sGuestName) {return this->__AddScore(sScore, iSort, sExtraData, sGuestName, false, GJ_NETWORK_NULL_API(gjScorePtr));} template inline int AddScoreCall(const std::string& sScore, const int& iSort, const std::string& sExtraData, const std::string& sGuestName, GJ_NETWORK_OUTPUT(gjScorePtr)) {return this->__AddScore(sScore, iSort, sExtraData, sGuestName, false, GJ_NETWORK_OUTPUT_FW);} //! @} /*! \name Add Score Request Base64 */ //! @{ /*! Like \link AddScoreNow AddScore\endlink\n * Allows to send extra data in binary form. * \pre Login maybe required * \note \b -Now blocks, \b -Call uses non-blocking callbacks * \param sScore Score string * \param iSort Numerical sort value of the score * \param pExtraData Extra data in binary form associated with the score * \param iExtraSize Size of the extra data * \param sGuestName Name of a guest user (leave empty to use the current main user, Login required) * \return **GJ_OK** on success\n * **GJ_REQUEST_FAILED** if request was unsuccessful\n * **GJ_REQUEST_CANCELED** if score entry was already added or if main user has a better highscore\n * **GJ_INVALID_INPUT** if score string is empty or sort value is zero\n * **GJ_NOT_CONNECTED** if connection/login is missing\n * (see #GJ_ERROR) */ inline int AddScoreBase64Now(const std::string& sScore, const int& iSort, void* pExtraData, const size_t& iExtraSize, const std::string& sGuestName) {return this->__AddScoreBase64(sScore, iSort, pExtraData, iExtraSize, sGuestName, true, GJ_NETWORK_NULL_API(gjScorePtr));} inline int AddScoreBase64Call(const std::string& sScore, const int& iSort, void* pExtraData, const size_t& iExtraSize, const std::string& sGuestName) {return this->__AddScoreBase64(sScore, iSort, pExtraData, iExtraSize, sGuestName, false, GJ_NETWORK_NULL_API(gjScorePtr));} template inline int AddScoreBase64Call(const std::string& sScore, const int& iSort, void* pExtraData, const size_t& iExtraSize, const std::string& sGuestName, GJ_NETWORK_OUTPUT(gjScorePtr)) {return this->__AddScoreBase64(sScore, iSort, pExtraData, iExtraSize, sGuestName, false, GJ_NETWORK_OUTPUT_FW);} //! @} /*! \name Get Attributes */ //! @{ inline const int& GetID()const {return m_iID;} //!< \copybrief m_iID inline const std::string& GetTitle()const {return m_sTitle;} //!< \copybrief m_sTitle inline const std::string& GetDescription()const {return m_sDescription;} //!< \copybrief m_sDescription inline const int& GetSortDirection()const {return m_iSortDir;} //!< \copybrief m_iSortDir /*! */ //! @} /*! \name Get Static Attributes */ //! @{ static inline gjScoreTable* GetPrimary() {return s_pPrimary;} //!< \copybrief s_pPrimary /*! */ //! @} /*! \name Check Status */ //! @{ inline bool IsPrimary()const {return m_bPrimary;} //!< \copybrief m_bPrimary //! @} private: DISABLE_COPY(gjScoreTable) /*! \name Superior Request Functions */ //! @{ template int __FetchScores(const bool& bOnlyUser, const int& iLimit, gjScoreList* papOutput, GJ_NETWORK_OUTPUT(gjScoreList)); template int __AddScore(const std::string& sScore, const int& iSort, const std::string& sExtraData, const std::string& sGuestName, const bool& bNow, GJ_NETWORK_OUTPUT(gjScorePtr)); template int __AddScoreBase64(const std::string& sScore, const int& iSort, void* pExtraData, const size_t& iExtraSize, const std::string& sGuestName, const bool& bNow, GJ_NETWORK_OUTPUT(gjScorePtr)); //! @} /*! \name Management Functions */ //! @{ int __CheckCache(const bool bOnlyUser, const int& iLimit, gjScoreList* papOutput); int __Process(const std::string& sData, void* pAdd, gjScoreList* papOutput); //! @} /*! \name Callback Functions */ //! @{ int __AddScoreCallback(const std::string& sData, void* pAdd, gjScorePtr* pOutput); //! @} }; // **************************************************************** /*! Score entry class.\n * http://gamejolt.com/api/doc/game/scores/ * \brief Score Entry Object */ class gjScore final { private: std::string m_sScore; //!< score string int m_iSort; //!< numerical sort value of the score std::string m_sExtraData; //!< extra data associated with the score int m_iUserID; //!< ID of the user, 0 if guest std::string m_sUserName; //!< display name of the user or guest std::string m_sDate; //!< time string when the score was logged (e.g. 4 weeks ago) gjScoreTable* m_pScoreTable; //!< associated score table gjAPI* m_pAPI; //!< main interface access pointer public: gjScore(const gjData& aScoreData, gjScoreTable* pScoreTable, gjAPI* pAPI)noexcept; /*! \name Fetch User Request */ //! @{ /*! Fetch and cache the associated user through an API request. * \note \b -Now blocks, \b -Call uses non-blocking callbacks * \return **GJ_OK** on success\n * **GJ_REQUEST_FAILED** if request was unsuccessful\n * **GJ_NOT_CONNECTED** if connection/login is missing\n * (see #GJ_ERROR) */ inline int FetchUserNow(gjUserPtr* ppOutput) {return m_pAPI->InterUser()->FetchUserNow(this->IsGuest() ? -1 : m_iUserID, ppOutput);} template inline int FetchUserCall(GJ_NETWORK_OUTPUT(gjUserPtr)) {return m_pAPI->InterUser()->FetchUserCall(this->IsGuest() ? -1 : m_iUserID, GJ_NETWORK_OUTPUT_FW);} //! @} /*! \name Get Attributes */ //! @{ inline const std::string& GetScore()const {return m_sScore;} //!< \copybrief m_sScore inline const int& GetSort()const {return m_iSort;} //!< \copybrief m_iSort inline const std::string& GetExtraData()const {return m_sExtraData;} //!< \copybrief m_sExtraData inline const int& GetUserID()const {return m_iUserID;} //!< \copybrief m_iUserID inline const std::string& GetUserName()const {return m_sUserName;} //!< \copybrief m_sUserName inline const std::string& GetDate()const {return m_sDate;} //!< \copybrief m_sDate inline gjScoreTable* GetScoreTable()const {return m_pScoreTable;} //!< \copybrief m_pScoreTable /*! */ //! @} /*! \name Get Base64 Attributes */ //! @{ inline int GetExtraDataBase64(void* pTarget, const size_t& iSize)const {if(!pTarget || iSize <= 0) return GJ_INVALID_INPUT; base64_decode(m_sExtraData.c_str(), (unsigned char*)pTarget, iSize); return GJ_OK;} //!< \copybrief m_sExtraData /*! */ //! @} /*! \name Check Status */ //! @{ inline bool IsGuest()const {return m_iUserID ? false : true;} //!< guest status //! @} private: DISABLE_COPY(gjScore) }; // **************************************************************** /*! \name Sorting Callbacks */ //! @{ bool SortAscending(const gjScore* i, const gjScore* j); bool SortDescending(const gjScore* i, const gjScore* j); //! @} // **************************************************************** /* fetch and semi-cache specific score entries of this score table */ template int gjScoreTable::__FetchScores(const bool& bOnlyUser, const int& iLimit, gjScoreList* papOutput, GJ_NETWORK_OUTPUT(gjScoreList)) { if(!m_pAPI->IsUserConnected() && bOnlyUser) return GJ_NOT_CONNECTED; if(iLimit <= 0) return GJ_INVALID_INPUT; const bool bNow = papOutput ? true : false; if(bOnlyUser && m_iSortDir) { // check for cached score entries gjScoreList apCache; if(this->__CheckCache(bOnlyUser, iLimit, &apCache) == GJ_OK) { if(bNow) (*papOutput) = apCache; else (pOutputObj->*OutputCallback)(apCache, pOutputData); return GJ_OK; } } // only scores from the main user or all scores const std::string sUserData = bOnlyUser ? "&username=" + m_pAPI->GetProcUserName() + "&user_token=" + m_pAPI->GetProcUserToken() : ""; // send get score request std::string sResponse; if(m_pAPI->SendRequest("/scores/" "?game_id=" + m_pAPI->GetProcGameID() + "&table_id=" + gjAPI::UtilIntToString(m_iID) + "&limit=" + gjAPI::UtilIntToString(iLimit) + sUserData, bNow ? &sResponse : NULL, this, &gjScoreTable::__Process, NULL, GJ_NETWORK_OUTPUT_FW)) return GJ_REQUEST_FAILED; if(bNow) return this->__Process(sResponse, NULL, papOutput); return GJ_OK; } // **************************************************************** /* add score entry to this score table */ template int gjScoreTable::__AddScore(const std::string& sScore, const int& iSort, const std::string& sExtraData, const std::string& sGuestName, const bool& bNow, GJ_NETWORK_OUTPUT(gjScorePtr)) { if(sScore == "" || iSort == 0) return GJ_INVALID_INPUT; const bool bGuest = (sGuestName != "") ? true : false; if(!m_pAPI->IsUserConnected() && !bGuest) return GJ_NOT_CONNECTED; if(m_iSortDir && !bGuest) { // check for cached best score and cancel request gjScoreList apBestScore; if(this->__CheckCache(true, 1, &apBestScore) == GJ_OK) { if(((m_iSortDir == GJ_SORT_DESC) && (iSort <= apBestScore[0]->GetSort())) || ((m_iSortDir == GJ_SORT_ASC) && (iSort >= apBestScore[0]->GetSort()))) return GJ_REQUEST_CANCELED; } } // check if specific score entry is already available and cancel request const std::string sUserName = bGuest ? sGuestName : m_pAPI->GetUserName(); FOR_EACH(it, m_apScore) { gjScore* pScore = (*it); if((pScore->GetUserID() == -1) == bGuest && pScore->GetUserName() == sUserName && pScore->GetSort() == iSort) { // specific score entry already available return GJ_REQUEST_CANCELED; } } // create temporary score entry gjData asScoreData; if(!bGuest) { // fetch cached main user gjUserPtr pMainUser; const int iError = m_pAPI->InterUser()->FetchUserNow(0, &pMainUser); if(iError) return iError; asScoreData["user_id"] = gjAPI::UtilIntToString(pMainUser->GetID()); asScoreData["user"] = pMainUser->GetName(); } asScoreData["score"] = sScore; asScoreData["sort"] = gjAPI::UtilIntToString(iSort); asScoreData["extra_data"] = sExtraData; asScoreData["stored"] = GJ_API_TEXT_NOW; asScoreData["guest"] = sGuestName; // add score entry to verification list gjScore* pVerify = new gjScore(asScoreData, this, m_pAPI); m_apVerify.push_back(pVerify); // use user data or guest name const std::string sUserData = bGuest ? "&guest=" + gjAPI::UtilEscapeString(sGuestName) : "&username=" + m_pAPI->GetProcUserName() + "&user_token=" + m_pAPI->GetProcUserToken(); // send add score request std::string sResponse; if(m_pAPI->SendRequest("/scores/add/" "?game_id=" + m_pAPI->GetProcGameID() + "&table_id=" + gjAPI::UtilIntToString(m_iID) + "&score=" + gjAPI::UtilEscapeString(sScore) + "&sort=" + gjAPI::UtilIntToString(iSort) + "&extra_data=" + gjAPI::UtilEscapeString(sExtraData) + sUserData, bNow ? &sResponse : NULL, this, &gjScoreTable::__AddScoreCallback, pVerify, GJ_NETWORK_OUTPUT_FW)) return GJ_REQUEST_FAILED; if(bNow) return this->__AddScoreCallback(sResponse, pVerify, NULL); return GJ_OK; } // **************************************************************** /* add score entry to this score table with Base64 extra data */ template int gjScoreTable::__AddScoreBase64(const std::string& sScore, const int& iSort, void* pExtraData, const size_t& iExtraSize, const std::string& sGuestName, const bool& bNow, GJ_NETWORK_OUTPUT(gjScorePtr)) { if(!pExtraData || iExtraSize <= 0) return GJ_INVALID_INPUT; const size_t iNeed = base64_needed(iExtraSize); // convert binary data to Base64 string char* pcBase64 = new char[iNeed]; base64_encode((unsigned char*)pExtraData, iExtraSize, pcBase64, iNeed); // execute add score function with Base64 string const int iReturn = this->__AddScore(sScore, iSort, pcBase64, sGuestName, bNow, GJ_NETWORK_OUTPUT_FW); SAFE_DELETE_ARRAY(pcBase64) return iReturn; } #endif /* _GJ_GUARD_SCORE_H_ */