352 lines
18 KiB
C
352 lines
18 KiB
C
|
///////////////////////////////////////////////////////////////////
|
||
|
//*-------------------------------------------------------------*//
|
||
|
//| 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<gjScore*> m_apScore; //!< semi-cached score entries
|
||
|
std::vector<gjScore*> 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 <typename T> 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 <typename T> 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 <typename T> 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 <typename T> int __FetchScores(const bool& bOnlyUser, const int& iLimit, gjScoreList* papOutput, GJ_NETWORK_OUTPUT(gjScoreList));
|
||
|
template <typename T> 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 <typename T> 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 <typename T> 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 <typename T> 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 <typename T> 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 <typename T> 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_ */
|