diff --git a/Plugins/GamejoltAPI/Binaries/Win64/UE4Editor-GameJoltPlugin.dll b/Plugins/GamejoltAPI/Binaries/Win64/UE4Editor-GameJoltPlugin.dll new file mode 100644 index 0000000..4c24687 Binary files /dev/null and b/Plugins/GamejoltAPI/Binaries/Win64/UE4Editor-GameJoltPlugin.dll differ diff --git a/Plugins/GamejoltAPI/Binaries/Win64/UE4Editor-GameJoltPlugin.pdb b/Plugins/GamejoltAPI/Binaries/Win64/UE4Editor-GameJoltPlugin.pdb new file mode 100644 index 0000000..28667de Binary files /dev/null and b/Plugins/GamejoltAPI/Binaries/Win64/UE4Editor-GameJoltPlugin.pdb differ diff --git a/Plugins/GamejoltAPI/Binaries/Win64/UE4Editor.modules b/Plugins/GamejoltAPI/Binaries/Win64/UE4Editor.modules new file mode 100644 index 0000000..7c48218 --- /dev/null +++ b/Plugins/GamejoltAPI/Binaries/Win64/UE4Editor.modules @@ -0,0 +1,7 @@ +{ + "BuildId": "13144385", + "Modules": + { + "GameJoltPlugin": "UE4Editor-GameJoltPlugin.dll" + } +} \ No newline at end of file diff --git a/Plugins/GamejoltAPI/GameJoltPlugIn.uplugin b/Plugins/GamejoltAPI/GameJoltPlugIn.uplugin new file mode 100644 index 0000000..690ed81 --- /dev/null +++ b/Plugins/GamejoltAPI/GameJoltPlugIn.uplugin @@ -0,0 +1,23 @@ +{ + "FileVersion": 3, + "Version": 14, + "VersionName": "1.8", + "FriendlyName": "GameJolt Plugin", + "Description": " This plugin allows you to communicate with the GameJolt-Servers to use Scoreboards, Sessions, Cloud-Data-Storage and more.", + "Category": "GameJolt", + "CreatedBy": "FreezerNick", + "CreatedByURL": "https://gamejolt.com/@FreezerNick", + "DocsURL": "https://gitlab.com/f2p-entertainment/plugins/ue4/gj/ue4-gamejoltapi/-/wikis/home", + "MarketplaceURL": "", + "SupportURL": "mailto:incoming+f2p-entertainment-plugins-ue4-gj-ue4-gamejoltapi-5318268-issue-@incoming.gitlab.com", + "EngineVersion": "4.25.0", + "CanContainContent": false, + "Installed": true, + "Modules": [ + { + "Name": "GameJoltPlugin", + "Type": "Runtime", + "LoadingPhase": "Default" + } + ] +} \ No newline at end of file diff --git a/Plugins/GamejoltAPI/Resources/Icon128.png b/Plugins/GamejoltAPI/Resources/Icon128.png new file mode 100644 index 0000000..5a857e8 Binary files /dev/null and b/Plugins/GamejoltAPI/Resources/Icon128.png differ diff --git a/Plugins/GamejoltAPI/Source/GameJoltPlugin/Classes/UEGameJoltAPI.h b/Plugins/GamejoltAPI/Source/GameJoltPlugin/Classes/UEGameJoltAPI.h new file mode 100644 index 0000000..a967eea --- /dev/null +++ b/Plugins/GamejoltAPI/Source/GameJoltPlugin/Classes/UEGameJoltAPI.h @@ -0,0 +1,717 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Interfaces/IHttpRequest.h" +#include "Serialization/JsonSerializer.h" +#include "Serialization/JsonWriter.h" +#include "Dom/JsonValue.h" +#include "UEGameJoltAPI.generated.h" + +/* Represents all possible requests */ +UENUM(BlueprintType) +enum class EGameJoltComponentEnum : uint8 +{ + GJ_USER_AUTH UMETA(DisplayName = "Authorize User"), + GJ_USER_AUTOLOGIN UMETA(DisplayName = "Automatic Login"), + GJ_USER_FETCH UMETA(DisplayName = "Fetch Current User"), + GJ_USERS_FETCH UMETA(DisplayName = "Fetch Users"), + GJ_USER_FRIENDLIST UMETA(DisplayName = "Fetch Friendlist"), + GJ_SESSION_OPEN UMETA(DisplayName = "Open Session"), + GJ_SESSION_PING UMETA(DisplayName = "Ping Session"), + GJ_SESSION_CLOSE UMETA(DisplayName = "Close Session"), + GJ_SESSION_CHECK UMETA(DisplayName = "Check Session"), + GJ_TROPHIES_FETCH UMETA(DisplayName = "Fetch Trophies"), + GJ_TROPHIES_ADD UMETA(DisplayName = "Reward Trophy"), + GJ_TROHIES_REMOVE UMETA(DisplayName = "Remove Rewarded Trophy"), + GJ_SCORES_FETCH UMETA(DisplayName = "Fetch Scores"), + GJ_SCORES_ADD UMETA(DisplayName = "Add Score"), + GJ_SCORES_TABLE UMETA(DisplayName = "Fetch Tables"), + GJ_SCORES_RANK UMETA(DisplayName = "Fetch Rank of Highscore"), + GJ_DATASTORE_FETCH UMETA(DisplayName = "Fetch Data"), + GJ_DATASTORE_SET UMETA(DisplayName = "Set Data"), + GJ_DATASTORE_UPDATE UMETA(DisplayName = "Update Data"), + GJ_DATASTORE_REMOVE UMETA(DisplayName = "Fetch Keys"), + GJ_OTHER UMETA(DisplayName = "Other"), + GJ_TIME UMETA(DisplayName = "Fetch Server Time") +}; + +/* Represents the possible selections for "Fetch Trophies" (all, achieved, unachieved) */ +UENUM(BlueprintType) +enum class EGameJoltAchievedTrophies : uint8 +{ + GJ_ACHIEVEDTROPHY_BLANK UMETA(DisplayName = "All Trophies"), + GJ_ACHIEVEDTROPHY_USER UMETA(DisplayName = "User Achieved Trophies"), + GJ_ACHIEVEDTROPHY_GAME UMETA(DisplayName = "Unachieved Trophies") +}; + + +/** Represents the possible values for the status of a session + * https://gamejolt.com/game-api/doc/sessions/ping + */ +UENUM(BlueprintType) +enum class ESessionStatus : uint8 +{ + Active, + Idle +}; + +UENUM(BlueprintType) +enum class EDataStore : uint8 +{ + Global, + User +}; + +UENUM(BlueprintType) +enum class EDataOperation : uint8 +{ + add UMETA(DisplayName = "Add"), + substract UMETA(DisplayName = "Substract"), + multiply UMETA(DisplayName = "Multiply"), + divide UMETA(DisplayName = "Divide"), + append UMETA(DisplayName = "Append"), + prepend UMETA(DisplayName = "Prepend") +}; + +/* Contains all available information about a user */ +USTRUCT(BlueprintType) +struct FUserInfo +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "User ID") + int32 S_User_ID; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "User type") + FString User_Type; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Username") + FString User_Name; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "User Avatar") + FString User_AvatarURL; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "User Signed up") + FString Signed_up; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "User Last Logged in") + FString Last_Logged_in; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "User Status") + FString status; +}; + +/* Contains all information about a trophy */ +USTRUCT(BlueprintType) +struct FTrophyInfo +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trophy ID") + int32 Trophy_ID; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trophy's Name") + FString Name; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trophy's Description") + FString Description; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trophy's Difficulty") + FString Difficulty; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trophy's Image URL") + FString image_url; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Achieved Time") + FString achieved; +}; + +/* Contains all information about an entry in a scoreboard */ +USTRUCT(BlueprintType) +struct FScoreInfo +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FString ScoreString; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + int32 ScoreSort; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FString ExtraData; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FString UserName; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + int32 UserID; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FString Guest; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FString UnixTimestamp; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + struct FDateTime TimeStamp; + + FScoreInfo() + { + TimeStamp = FDateTime::Now(); + ScoreSort = 0; + UserID = 0; + } +}; + +/* Contains all information about a scoreboard */ +USTRUCT(BlueprintType) +struct FScoreTableInfo +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Scoreboard Table ID") + int32 Id; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Scoreboard Table Name") + FString Name; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Scoreboard Table Description") + FString Description; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Scoreboard Table Primary") + FString Primary; + +}; + +/* Generates a delegate for the OnGetResult event */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnGetResult); + +/* Generates a delegate for the OnFailed event */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnFailed); + +#pragma region Specific Delegate Declaration + +/* Authorize User */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnUserAuthorized, bool, bIsLoggedIn); +/* Automatic Login */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAutoLogin, bool, bIsLoggedIn); +/* Get Current User Info */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnUserFetched, FUserInfo, CurrentUserInfo); +/* Get User Info*/ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnUsersFetched, const TArray&, UserInfo); +/* Get Friendlist */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnFriendlistFetched, const TArray&, Friendlist); +/* Open Session */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSessionOpened, bool, bIsSessionOpen); +/* Ping Session */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSessionPinged, bool, bIsSessionStillOpen); +/* Close Session */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSessionClosed, bool, bIsSessionClosed); +/* Check Session */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSessionChecked, bool, bIsSessionStillOpen); +/* Fetch Trophies */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTrophiesFetched, TArray, Trophies); +/* Remove Trophy */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTrophyRemoved, bool, bWasRemoved); +/* Add Score */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnScoreAdded, bool, bWasScoreAdded); +/* Fetch Scoreboard */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnScoreboardFetched, const TArray&, Scores); +/* Fetch Scoreboard Table */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnScoreboardTableFetched, TArray, ScoreboardTable); +/* Fetch High-Score Rank */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRankFetched, int32, Rank); +/* Fetch Time */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTimeFetched, struct FDateTime, ServerTime); + +#pragma endregion + +/** + * Class to use the GameJoltAPI + * Is also internally used by an UUEGameJoltAPI instance as a carrier for response data +*/ +UCLASS(BlueprintType, Blueprintable) +class GAMEJOLTPLUGIN_API UUEGameJoltAPI : public UObject +{ + GENERATED_UCLASS_BODY() + +private: + + /** + * Callback for IHttpRequest::OnProcessRequestComplete() + * @param Request HTTP request pointer + * @param Response Response pointer + * @param bWasSuccessful Whether the request was successful or not + */ + void OnReady(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); + + /* Reset Data*/ + void Reset(); + + void WriteObject(TSharedRef> writer, FString key, FJsonValue* value); + +public: + + UObject* contextObject; + + /* Prevents crashes in Get-Functions */ + UPROPERTY(Transient) + class UWorld* World; + + /* Allows usage of the World-Property */ + virtual class UWorld* GetWorld() const override; + + /* The username of the guest profile */ + UPROPERTY(BlueprintReadWrite, Category = "GameJolt|User") + FString Guest_username; + + /* Whether a user is currently logged in. Treated as a guest if false */ + UPROPERTY(BlueprintReadOnly, Category = "GameJolt|User") + bool bIsLoggedIn; + + /* An enum representing the last request send. Local 'Get' nodes don't count */ + UPROPERTY(BlueprintReadWrite, Category = "GameJolt") + EGameJoltComponentEnum LastActionPerformed; + + /* The actual field data */ + TSharedPtr Data; + + /* Contains the actual page content, as a string */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "GameJolt|Request") + FString Content; + + /* Event which triggers when the content has been retrieved */ + UPROPERTY(BlueprintAssignable, Category = "GameJolt|Events") + FOnGetResult OnGetResult; + + /* Event which triggers when the request failed */ + UPROPERTY(BlueprintAssignable, Category = "GameJolt|Events") + FOnFailed OnFailed; + +#pragma region Specific Events + + UPROPERTY(BlueprintAssignable, Category = "GameJolt|Events|Specific") + FOnUserAuthorized OnUserAuthorized; + + UPROPERTY(BlueprintAssignable, Category = "GameJolt|Events|Specific") + FOnAutoLogin OnAutoLogin; + + UPROPERTY(BlueprintAssignable, Category = "GameJolt|Events|Specific") + FOnUserFetched OnUserFetched; + + UPROPERTY(BlueprintAssignable, Category = "GameJolt|Events|Specific") + FOnUsersFetched OnUsersFetched; + + UPROPERTY(BlueprintAssignable, Category = "GameJolt|Events|Specific") + FOnFriendlistFetched OnFriendlistFetched; + + UPROPERTY(BlueprintAssignable, Category = "GameJolt|Events|Specific") + FOnSessionOpened OnSessionOpened; + + UPROPERTY(BlueprintAssignable, Category = "GameJolt|Events|Specific") + FOnSessionPinged OnSessionPinged; + + UPROPERTY(BlueprintAssignable, Category = "GameJolt|Events|Specific") + FOnSessionClosed OnSessionClosed; + + UPROPERTY(BlueprintAssignable, Category = "GameJolt|Events|Specific") + FOnSessionChecked OnSessionChecked; + + UPROPERTY(BlueprintAssignable, Category = "GameJolt|Events|Specific") + FOnTrophiesFetched OnTrophiesFetched; + + UPROPERTY(BlueprintAssignable, Category = "GameJolt|Events|Specific") + FOnTrophyRemoved OnTrophyRemoved; + + UPROPERTY(BlueprintAssignable, Category = "GameJolt|Events|Specific") + FOnScoreAdded OnScoreAdded; + + UPROPERTY(BlueprintAssignable, Category = "GameJolt|Events|Specific") + FOnScoreboardFetched OnScoreboardFetched; + + UPROPERTY(BlueprintAssignable, Category = "GameJolt|Events|Specific") + FOnScoreboardTableFetched OnScoreboardTableFetched; + + UPROPERTY(BlueprintAssignable, Category = "GameJolt|Events|Specific") + FOnRankFetched OnRankFetched; + + UPROPERTY(BlueprintAssignable, Category = "GameJolt|Events|Specific") + FOnTimeFetched OnTimeFetched; + +#pragma endregion + + /* Creates new data from the input string */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "From String"), Category = "GameJolt|Request") + void FromString(const FString& dataString); + + /** + * Creates a new instance of the UUEGameJoltAPI class, for use in Blueprint graphs. + * @param WorldContextObject The current context (default to self / this) + * @return A pointer to the newly created post data + **/ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Create GameJolt API Data", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "GameJolt") + static UUEGameJoltAPI* Create(UObject* WorldContextObject); + + /* GameID */ + UPROPERTY(BlueprintReadOnly, meta = (DisplayName = "Your Game ID"), Category = "GameJolt") + int32 Game_ID; + + /* Private Key */ + UPROPERTY(BlueprintReadOnly, meta = (DisplayName = "Your Game Private Key"), Category = "GameJolt") + FString Game_PrivateKey; + + /* Username */ + UPROPERTY(BlueprintReadOnly, meta = (DisplayName = "Players Username"), Category = "GameJolt|User") + FString UserName; + +private: + /* Token */ + UPROPERTY() + FString UserToken; + +public: + + /* Properties for HTTP-Request*/ + UPROPERTY(BlueprintReadWrite, meta = (DisplayName = "GameJolt API Server"), Category = "GameJolt|Request") + FString GJAPI_SERVER; + + UPROPERTY(BlueprintReadWrite, meta = (DisplayName = "GameJolt API Root"), Category = "GameJolt|Request") + FString GJAPI_ROOT; + + UPROPERTY(BlueprintReadWrite, meta = (DisplayName = "GameJolt API Version"), Category = "GameJolt|Request") + FString GJAPI_VERSION; + /* End of Properties */ + + /* Public Functions */ + + /** + * Sets information needed for all requests + * You can find these values in the GameJolt API section of your game's dashboard + * @param PrivateKey The private key of your game + * @param GameID The id of your game + * @param AutoLogin Whether to check for passed credentials by the GameJolt client or not + * @return Whether the .gj-crendential file was found or not. Also false if AutoLogin is false + **/ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Init", AdvancedDisplay=2), Category = "GameJolt") + bool Init(const int32 GameID, const FString PrivateKey, const bool AutoLogin); + +private: + + void AutoLogin(const FString Username, const FString Token); + + +#pragma region Session + + /** + * Opens a session. You'll have to ping it manually with a timer + * @return True if the request succeded, false if not + **/ + UFUNCTION(BlueprintCallable, meta = (DislayName = "Open Session"), Category = "GameJolt|Sessions") + bool OpenSession(); + + /** + * Pings the Session. Every 30 to 60 seconds is good. + * @param SessionStatus The status of the session. Can be "Active" or "Idle" + * @return True if the request succeded, false if not + **/ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Ping Session"), Category = "GameJolt|Sessions") + bool PingSession(ESessionStatus SessionStatus); + + /** + * Closes the session + * @return True if the request succeded, false if not + **/ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Close Session"), Category = "GameJolt|Sessions") + bool CloseSession(); + + /** + * Fetches the current session status + * @return True if the request succeded, false if not + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Fetch Session Status"), Category = "GameJolt|Sessions") + bool CheckSession(); + + /** + * Gets the current session status + * @return Whether the session is open or not. Also false if any error occurred + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get Session Status"), Category = "GameJolt|Sessions") + bool GetSessionStatus(); + +#pragma endregion + + /** + * Gets the time of the GameJolt servers + * @return True if the request succeded, false if not + **/ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Fetch Server Time"), Category = "GameJolt|Misc") + bool FetchServerTime(); + + /** + * Puts the requested server time in a readable format + * UUEGameJoltAPI::FetchServerTime has to be called before this function + * @return The server time in a FDateTime struct + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Read Server Time"), Category = "GameJolt|Misc") + struct FDateTime ReadServerTime(); + +#pragma region User + + /** + * Sends a request to authentificate the user + * Call UUEGameJoltAPI::isUserAuthorize / Is User Login to check whether the authorization was successful or not + * @param Name The username - case insensitive + * @param Token The token - case insensitive + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Login"), Category = "GameJolt|User") + void Login(const FString Name, const FString Token); + + /** + * Checks if the authentification was succesful + * @return True if the user could be logged in, false if not + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Is User Login"), Category = "GameJolt|User") + bool isUserAuthorize(); + + /* Resets user related properties */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Logoff User"), Category = "GameJolt|User") + void LogOffUser(); + + /** + * Gets information about the current user + * @return True if it the request succeded and false if it failed + **/ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Fetch Current User Info"), Category = "GameJolt|User") + bool FetchUser(); + + /** + * Fetches an array of users + * @param Users An array (int32) representing the user ids + * @return True if the request succeded, false if not + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Fetch Users"), Category = "GameJolt|User") + bool FetchUsers(const TArray Users); + + /** + * Gets a single or an array of users and puts them in an array of FUserInfo structs + * @return An array with the FUserInfo structs + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get User Info"), Category = "GameJolt|User") + TArray GetUserInfo(); + + /** + * Fetches the friendlist of the current user + * @return True if the request could be send, false if not + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Fetch Friendlist"), Category = "GameJolt|User") + bool FetchFriendlist(); + + /** + * Returns the fetched friendlist + * @warning Call FetchFriendlist first + * @return The user ids of all friends + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get Friendlist"), Category = "GameJolt|User") + TArray GetFriendlist(); + +#pragma endregion + +#pragma region Trophies + + /** + * Awards the current user a trophy + * @return True if the request succeded, false if not + **/ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Reward Trophies"), Category = "GameJolt|Trophies") + bool RewardTrophy(const int32 Trophy_ID); + + /** + * Gets information for all trophies + * This is meant for the use in Blueprints + * It's just a wrapper around FetchTrophies with an empty TArray as an parameter + * You can call UUEGameJoltAPI::FetchTrophies directly + **/ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Fetch All Trophies"), Category = "GameJolt|Trophies") + void FetchAllTrophies(const EGameJoltAchievedTrophies AchievedType); + + /** + * Gets information for the selected trophies + * @param AchievedType Whether only achieved, unachieved or all trophies should be fetched + * @param Tropies_ID An array of trophy IDs. An empty array will return all trophies + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Fetch Trophies"), Category = "GameJolt|Trophies") + void FetchTrophies(const EGameJoltAchievedTrophies AchievedType, const TArray Trophy_IDs); + + /** + * Gets the trophy information from the fetched trophies + * @return Array of FTrophyInfo structs for all fetched trophies + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get Trophies"), Category = "GameJolt|Trophies") + TArray GetTrophies(); + + /** + * Unachieved the specified trophy for the curernt user + * @param Trophy_ID The ID of the trophy to be unachieved + * @return Whether the request could be send or not + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove Rewarded Trophy"), Category = "GameJolt|Trophies") + bool RemoveRewardedTrophy(const int32 Trophy_ID); + + /** + * Checks the success of a trophy removal + * @return Whether the trophy was successfuly remove or not + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Check Trophy Removal Status"), Category = "GameJolt|Trophies") + bool GetTrophyRemovalStatus(); + +#pragma endregion + +#pragma region Scores + + /** + * Returns a list of scores either for a user or globally for a game + * @param ScoreLimit The amount of scores you want to fetch. Default is 10, maximum is 100 + * @param Table_id The ID of the score table + * @param BetterThan Fetch only scores better than this score sort value + * @param WorseThan Fetch only scores worse than this score sort value + * @return True if the request succeded, false if not + **/ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Fetch Scoreboard"), Category = "GameJolt|Scoreboard") + bool FetchScoreboard(const int32 ScoreLimit, const int32 Table_id, const int32 BetterThan, const int32 WorseThan); + + /** + * Gets the list of scores fetched with FetchScoreboard + * @return An array of FScoreInfo structs for all entries + **/ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get Scoreboard"), Category = "GameJolt|Scoreboard") + TArray GetScoreboard(); + + /** + * Adds an entry to a scoreboard + * @param UserScore A String value associated with the score. Example: "234 Jumps". + * @param UserScore_Sort An integer sorting value associated with the score. All sorting will work off of this number. Example: "234". + * @param GuestUser The guest's name. Leave blank if you're storing for a user. + * @param extra_data If there's any extra data you would like to store (as a string), you can use this variable. This data is never shown to the user. + * @param table_id The id of the high score table that you want to submit to. If left blank the score will be submitted to the primary high score table. + * @return True if the request succeded, false if not + **/ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Score to Scoreboard"), Category = "GameJolt|Scoreboard") + bool AddScore(const FString UserScore, const int32 UserScore_Sort, const FString GuestUser, const FString extra_data, const int32 table_id); + + /** + * Returns a list of high score tables for a game. + * @return True if it the request succeded and false if it failed + **/ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Fetch Scoreboard Table"), Category = "GameJolt|Scoreboard") + bool FetchScoreboardTable(); + + /** + * Gets a list of high score tables for a game and puts them in an array of FScoreTableInfo structs + * @return A array of FScoreTableInfo structs + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get Scoreboard Table"), Category = "GameJolt|Scoreboard") + TArray GetScoreboardTable(); + + /** + * Fetches the rank of the specified score + * Use "Get Rank of Score" / GetRank or the OnGetRank delegate to read the results + * @param Score The numeric score value to look for + * @param TableID The ID of the scoreboard to search. '0' means primary table + * @return Whether the request could be send successfully or not + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Fetch Rank of Score"), Category = "GameJolt|Scoreboard") + bool FetchRank(const int32 Score, const int32 TableID); + + /** + * Gets the rank of a highscore from the response data + * + * If the score is not represented by any rank on the score table, the request will return the rank that is closest to the requested score. + * + * @warning Make sure to call "Fetch Rank of Score" / FetchRank before this + * @return The rank of the score + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Rank of Score"), Category = "GameJolt|Scoreboard") + int32 GetRank(); + +#pragma endregion + +#pragma region Data-Store + + /** + * Either posts data for a new key or changes data for an existing one. + * @param Type Whether to store the key/value pair for all users (global) or for the current user (user) + * @param Key The key/label for the data + * @param Data The actual data to store + */ + UFUNCTION(BlueprintCallable) + void SetData(EDataStore Type, const FString Key, const FString Data); + + /** + * Tries to fetch the data stored under the specified key + * @param Type Whether to fetch a global key/value pair or a key/value pair stored for the current user + * @param Key The key/label of the data + */ + UFUNCTION(BlueprintCallable) + void FetchData(EDataStore Type, FString Key); + + /** + * Updates already stored data + * @param Type Whether to update a global key/value pair or a key/value pair stored of the current user + * @param Key The key of the data to update + * @param Operation The operation that should be performed on the data + * @param Value The value for the selected operation + */ + UFUNCTION(BlueprintCallable) + void UpdateData(EDataStore Type, const FString Key, EDataOperation Operation, const FString Value); + + /** + * Deletes the data stored under the specified key + * @param Type Whether to remove a global key/value pair or a key/value pair stored for the current user + * @param Key The key of the data to remove + */ + UFUNCTION(BlueprintCallable) + void RemoveData(EDataStore Type, const FString Key); + + /** + * Gets the fetched data and converts them to a string or an integer (if possible) + * @param Success Whether the data was found + * @param DataAsString The fetched data as a string + * @param DataAsInt The fetched data as an integer (0 if conversion was not possible) + */ + UFUNCTION(BlueprintCallable) + void GetData(bool& Success, FString& DataAsString, int32& DataAsInt); + +#pragma endregion + +#pragma region Utility + + /* Sends Request */ + UFUNCTION(Blueprintcallable, meta = (Displayname = " Send Request"), Category = "GameJolt|Request|Advanced") + bool SendRequest(const FString& output, FString url, bool bAppendUserInfo = true); + + /** Gets nested post data from the object with the specified key + * @param key The key of the post data value + * @return The value as an UUEGameJoltAPI object reference / pointer + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Data Field", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "GameJolt|Request|Advanced") + UUEGameJoltAPI* GetObject(const FString& key); + + /** Gets a string from the object with the specified key + * @param key The key of the string value + * @return The value as a string + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get String Field"), Category = "GameJolt|Request|Advanced") + FString GetString(const FString& key) const; + + /** Gets a bool from the object with the specified key + * @param key The key of the bool value + * @return The value as a bool + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Bool Field"), Category = "GameJolt|Request|Advanced") + bool GetBool(const FString& key) const; + + /** Gets an integer from the object with the specified key + * @param key The key of the integer value + * @return The value as an integer + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Int Field"), Category = "GameJolt|Request|Advanced") + int32 GetInt(const FString& key) const; + + /** + * Gets a string array of all keys from the post data + * @return An array with all keys + */ + UFUNCTION(Blueprintpure, meta = (Displayname = "Get Object Keys", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "GameJolt|Request|Advanced") + TArray GetObjectKeys(UObject* WorldContextObject); + + /** + * Gets an array fromt the post data + * @param key The key of the array + * @return The array assigned to the key + **/ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Object Array Field", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "GameJolt|Request|Advanced") + TArray GetObjectArray(UObject* WorldContextObject, const FString& key); + +#pragma endregion + +}; \ No newline at end of file diff --git a/Plugins/GamejoltAPI/Source/GameJoltPlugin/GameJoltPlugin.Build.cs b/Plugins/GamejoltAPI/Source/GameJoltPlugin/GameJoltPlugin.Build.cs new file mode 100644 index 0000000..c22bf64 --- /dev/null +++ b/Plugins/GamejoltAPI/Source/GameJoltPlugin/GameJoltPlugin.Build.cs @@ -0,0 +1,40 @@ +using UnrealBuildTool; +using System.IO; + +namespace UnrealBuildTool.Rules +{ + public class GameJoltPlugin : ModuleRules + { + public GameJoltPlugin(ReadOnlyTargetRules Target) : base (Target) + { + + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "Classes")); + + PrivateIncludePaths.Add(Path.Combine(ModuleDirectory, "Private")); + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "HTTP", + "CoreUObject", + "Engine", + "Json", + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Engine", + "Core", + "CoreUObject", + "HTTP", + "JSON", + } + ); + } + } +} \ No newline at end of file diff --git a/Plugins/GamejoltAPI/Source/GameJoltPlugin/Private/GameJoltPluginModule.cpp b/Plugins/GamejoltAPI/Source/GameJoltPlugin/Private/GameJoltPluginModule.cpp new file mode 100644 index 0000000..f921833 --- /dev/null +++ b/Plugins/GamejoltAPI/Source/GameJoltPlugin/Private/GameJoltPluginModule.cpp @@ -0,0 +1,16 @@ + +#include "GameJoltPluginModule.h" + +DEFINE_LOG_CATEGORY(GJAPI); + +void GameJoltPlugin::StartupModule() +{ + +} + +void GameJoltPlugin::ShutdownModule() +{ + +} + +IMPLEMENT_MODULE(FDefaultGameModuleImpl, GameJoltPlugin); \ No newline at end of file diff --git a/Plugins/GamejoltAPI/Source/GameJoltPlugin/Private/GameJoltPluginModule.h b/Plugins/GamejoltAPI/Source/GameJoltPlugin/Private/GameJoltPluginModule.h new file mode 100644 index 0000000..777390e --- /dev/null +++ b/Plugins/GamejoltAPI/Source/GameJoltPlugin/Private/GameJoltPluginModule.h @@ -0,0 +1,17 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +DECLARE_LOG_CATEGORY_EXTERN(GJAPI, Log, All); + +class GAMEJOLTPLUGIN_API GameJoltPlugin : public IModuleInterface +{ +private: + +public: + GameJoltPlugin(); + + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; \ No newline at end of file diff --git a/Plugins/GamejoltAPI/Source/GameJoltPlugin/Private/UEGameJoltAPI.cpp b/Plugins/GamejoltAPI/Source/GameJoltPlugin/Private/UEGameJoltAPI.cpp new file mode 100644 index 0000000..42bd0f2 --- /dev/null +++ b/Plugins/GamejoltAPI/Source/GameJoltPlugin/Private/UEGameJoltAPI.cpp @@ -0,0 +1,862 @@ +#include "UEGameJoltAPI.h" +#include "Engine/Engine.h" +#include "HttpModule.h" +#include "Interfaces/IHttpResponse.h" +#include "Dom/JsonObject.h" +#include "Serialization/JsonReader.h" +#include "GameJoltPluginModule.h" +#include "Misc/DateTime.h" +#include "Engine/World.h" +#include "Misc/Paths.h" +#include "Misc/FileHelper.h" + +/* Constructor */ +UUEGameJoltAPI::UUEGameJoltAPI(const class FObjectInitializer& PCIP) : Super(PCIP) +{ + Reset(); + bIsLoggedIn = false; + GJAPI_SERVER = "api.gamejolt.com"; + GJAPI_ROOT = "/api/game/"; + GJAPI_VERSION = "v1_2"; + Game_ID = 0; + Game_PrivateKey = ""; + LastActionPerformed = EGameJoltComponentEnum::GJ_USER_AUTH; +} + +/* Prevents crashes within 'Get...' functions */ +UWorld* UUEGameJoltAPI::GetWorld() const +{ + return World; +} + +/* Sets information needed for all requests */ +bool UUEGameJoltAPI::Init(const int32 GameID, const FString PrivateKey, const bool AutoLogin = false) +{ + Game_ID = GameID; + Game_PrivateKey = PrivateKey; + if(!AutoLogin) + { + UE_LOG(GJAPI, Log, TEXT("Autologin is turned off!")); + return false; + } + + if(!FPaths::FileExists(FPaths::Combine(FPaths::ProjectDir(), TEXT(".gj-credentials")))) + return false; + + TArray strings; + FFileHelper::LoadFileToStringArray(strings, *FPaths::Combine(FPaths::ProjectDir(), TEXT(".gj-credentials"))); + this->AutoLogin(strings[1], strings[2]); + return true; +} + +void UUEGameJoltAPI::AutoLogin(const FString Name, const FString Token) +{ + FString output; + UserName = Name; + UserToken = Token; + LastActionPerformed = EGameJoltComponentEnum::GJ_USER_AUTOLOGIN; + SendRequest(output, "/users/auth/?"); +} + +/* Gets the time of the GameJolt servers */ +bool UUEGameJoltAPI::FetchServerTime() +{ + FString output; + LastActionPerformed = EGameJoltComponentEnum::GJ_TIME; + return SendRequest(output, "/time/?", false); +} + +/* Puts the requested server time in a readable format */ +FDateTime UUEGameJoltAPI::ReadServerTime() +{ + UUEGameJoltAPI* responseField = NULL; + responseField = GetObject("response"); + if (responseField == NULL) + { + UE_LOG(GJAPI, Error, TEXT("responseField Return Null")); + return FDateTime(); + } + if(!responseField->GetBool("success")) + { + UE_LOG(GJAPI, Error, TEXT("Can't read time: Request failed!")); + if(responseField->GetString("message") != "") + { + UE_LOG(GJAPI, Error, TEXT("Error message: %s"), *responseField->GetString("message")); + } + return FDateTime(); + } + int32 Year = responseField->GetInt("year"); + int32 Month = responseField->GetInt("month"); + int32 Day = responseField->GetInt("day"); + int32 Hour = responseField->GetInt("hour"); + int32 Minute = responseField->GetInt("minute"); + int32 Second = responseField->GetInt("second"); + + return FDateTime(Year, Month, Day, Hour, Minute, Second); +} + +/* Creates a new instance of the UUEGameJoltAPI class, for use in Blueprint graphs. */ +UUEGameJoltAPI* UUEGameJoltAPI::Create(UObject* WorldContextObject) { + // Get the world object from the context + UWorld* World = GEngine->GetWorldFromContextObjectChecked(WorldContextObject); + // Construct the object and return it + UUEGameJoltAPI* fieldData = NewObject((UUEGameJoltAPI*)GetTransientPackage(), UUEGameJoltAPI::StaticClass()); + fieldData->contextObject = WorldContextObject; + fieldData->World = World; + return fieldData; +} + +/* Sends a request to authentificate the user */ +void UUEGameJoltAPI::Login(const FString name, const FString token) +{ + FString output; + FString GameIDString = FString::FromInt(Game_ID); + LastActionPerformed = EGameJoltComponentEnum::GJ_USER_AUTH; + UserName = name; + UserToken = token; + SendRequest(output, "/users/auth/?"); +} + +/* Checks if the authentification was succesful */ +bool UUEGameJoltAPI::isUserAuthorize() +{ + bool outAuthorize; + UUEGameJoltAPI* responseField = NULL; + responseField = GetObject("response"); + if (responseField == NULL) + { + UE_LOG(GJAPI, Error, TEXT("responseField Return Null")); + return false; + } + outAuthorize = responseField->GetBool("success"); + if (!outAuthorize) + { + bIsLoggedIn = false; + UE_LOG(GJAPI, Error, TEXT("Couldn't authenticate user. Message: %s"), *responseField->GetString("message")); + return false; + } + + bIsLoggedIn = true; + return true; +} + +/* Gets information the current user */ +bool UUEGameJoltAPI::FetchUser() +{ + bool ret = false; + FString output; + LastActionPerformed = EGameJoltComponentEnum::GJ_USER_FETCH; + ret = SendRequest(output, "/users/?username=" + UserName, false); + if (!ret) + { + UE_LOG(GJAPI, Error, TEXT("Could not fetch user.")); + return false; + } + return true; +} + +/* Fetches an array of users */ +bool UUEGameJoltAPI::FetchUsers(const TArray Users) +{ + FString output; + LastActionPerformed = EGameJoltComponentEnum::GJ_USERS_FETCH; + FString UserIDs = ""; + for(const int32 UserID : Users) + UserIDs.Append(FString::FromInt(UserID) + ","); + return SendRequest(output, "/users/?user_id=" + UserIDs, false); +} + +/* Fetches the friendlist of the current user */ +bool UUEGameJoltAPI::FetchFriendlist() +{ + FString output; + LastActionPerformed = EGameJoltComponentEnum::GJ_USER_FRIENDLIST; + return SendRequest(output, "/friends/?"); +} + +/* Gets the friendlist */ +TArray UUEGameJoltAPI::GetFriendlist() +{ + TArray returnArray = GetObject("response")->GetObjectArray(GetObject("response"), "friends"); + TArray returnIDs; + for(int i = 0; i < returnArray.Num(); i++) + returnIDs.Add(returnArray[i]->GetInt("friend_id")); + return returnIDs; +} + +/* Resets user related properties */ +void UUEGameJoltAPI::LogOffUser() +{ + bIsLoggedIn = false; + UserName = ""; + UserToken = ""; +} + +/* Opens a session */ +bool UUEGameJoltAPI::OpenSession() +{ + FString output; + FString GameIDString; + GameIDString = FString::FromInt(Game_ID); + LastActionPerformed = EGameJoltComponentEnum::GJ_SESSION_OPEN; + return SendRequest(output, "/sessions/open/?"); +} + +/* Pings the session */ +bool UUEGameJoltAPI::PingSession(ESessionStatus SessionStatus) +{ + FString output; + FString SessionString = SessionStatus == ESessionStatus::Active ? FString("active") : FString("idle"); + FString GameIDString = FString::FromInt(Game_ID); + LastActionPerformed = EGameJoltComponentEnum::GJ_SESSION_PING; + return SendRequest(output, "/sessions/ping/?status=" + SessionString); +} + +/* Closes the session */ +bool UUEGameJoltAPI::CloseSession() +{ + FString output; + FString GameIDString; + GameIDString = FString::FromInt(Game_ID); + LastActionPerformed = EGameJoltComponentEnum::GJ_SESSION_CLOSE; + return SendRequest(output, "/sessions/close/?"); +} + +/* Fetches the session status */ +bool UUEGameJoltAPI::CheckSession() +{ + FString output; + LastActionPerformed = EGameJoltComponentEnum::GJ_SESSION_CHECK; + return SendRequest(output, "/sessions/check/?"); +} + +/* Gets the session status */ +bool UUEGameJoltAPI::GetSessionStatus() +{ + UUEGameJoltAPI* Response = GetObject("response"); + if(!Response) + { + UE_LOG(GJAPI, Error, TEXT("Response invalid in GetSessionStatus. Was ist called to early?")); + return false; + } + return Response->GetBool("success"); +} + +/* Gets an array of users and puts them in an array of FUserInfo structs */ +TArray UUEGameJoltAPI::GetUserInfo() +{ + TArray returnArray = GetObject("response")->GetObjectArray(GetObject("response"), "users"); + + TArray returnUserInfo; + + FUserInfo tempUser; + + for (int i = 0; i< returnArray.Num(); i++) + { + + tempUser.S_User_ID = returnArray[i]->GetInt("id"); + tempUser.User_Name = returnArray[i]->GetString("username"); + tempUser.User_Type = returnArray[i]->GetString("type"); + tempUser.User_AvatarURL = returnArray[i]->GetString("avatar_url"); + tempUser.Signed_up = returnArray[i]->GetString("signed_up"); + tempUser.Last_Logged_in = returnArray[i]->GetString("last_logged_in"); + tempUser.status = returnArray[i]->GetString("status"); + returnUserInfo.Add(tempUser); + } + + return returnUserInfo; +} + +/* Awards the current user a trophy */ +bool UUEGameJoltAPI::RewardTrophy(const int32 Trophy_ID) +{ + + bool ret = true; + FString output; + FString GameIDString; + FString TrophyIDString; + GameIDString = FString::FromInt(Game_ID); + if (!bIsLoggedIn) + { + UE_LOG(GJAPI, Error, TEXT("User is not logged in")); + return false; + } + TrophyIDString = FString::FromInt(Trophy_ID); + LastActionPerformed = EGameJoltComponentEnum::GJ_TROPHIES_ADD; + ret = SendRequest(output, "/trophies/add-achieved/?trophy_id=" + TrophyIDString); + + + return true; +} + +/* Gets information for all trophies */ +void UUEGameJoltAPI::FetchAllTrophies(const EGameJoltAchievedTrophies AchievedType) +{ + TArray Trophies; + FetchTrophies(AchievedType, Trophies); +} + +/* Gets information for the selected trophies */ +void UUEGameJoltAPI::FetchTrophies(const EGameJoltAchievedTrophies AchievedType, const TArray Trophy_IDs) +{ + TArray returnTrophies; + bool ret = true; + FString output; + FString TrophyIDString; + FString AchievedString; + + if (!bIsLoggedIn) + { + UE_LOG(GJAPI, Error, TEXT("User is not logged in!")); + return; + } + + LastActionPerformed = EGameJoltComponentEnum::GJ_TROPHIES_FETCH; + if(AchievedType == EGameJoltAchievedTrophies::GJ_ACHIEVEDTROPHY_GAME){ + + AchievedString ="false"; + } + else + { + AchievedString = "true"; + } + + for (int32 i = 0; i < Trophy_IDs.Num(); i++){ + TrophyIDString += FString::FromInt(Trophy_IDs[i]); + if (i != Trophy_IDs.Num()-1) + { + TrophyIDString += TEXT(","); + } + } + if (AchievedType == EGameJoltAchievedTrophies::GJ_ACHIEVEDTROPHY_BLANK)//if We Want to get all trophies + { + ret = SendRequest(output, "/trophies/?" + (Trophy_IDs.Num() > 0 ? "&trophy_id=" : "" + TrophyIDString)); + } + else //if We Want to get what trophies the User achieved have Not Achieved + { + ret = SendRequest(output, "/trophies/?achieved=" + AchievedString + + (Trophy_IDs.Num() > 0 ? "&trophy_id=" : "" + TrophyIDString)); + } + + if (!ret) + { + UE_LOG(GJAPI, Error, TEXT("Could not fetch trophies.")); + return; + } + + return; +} + +/* Gets the trophy information from the fetched trophies */ +TArray UUEGameJoltAPI::GetTrophies() +{ + TArray returnTrophy; + TArray returnArray = GetObject("response")->GetObjectArray(GetObject("response"), "trophies"); + FTrophyInfo tempTrophies; + for (int i = 0; i< returnArray.Num(); i++) + { + + tempTrophies.Trophy_ID = returnArray[i]->GetInt("id"); + tempTrophies.Name = returnArray[i]->GetString("title"); + tempTrophies.Description = returnArray[i]->GetString("description"); + tempTrophies.Difficulty = returnArray[i]->GetString("difficulty"); + tempTrophies.image_url = returnArray[i]->GetString("image_url"); + tempTrophies.achieved = returnArray[i]->GetString("achieved"); + + returnTrophy.Add(tempTrophies); + } + + return returnTrophy; +} + +/* Unachieves a trophy */ +bool UUEGameJoltAPI::RemoveRewardedTrophy(const int32 Trophy_ID) +{ + FString output; + LastActionPerformed = EGameJoltComponentEnum::GJ_TROHIES_REMOVE; + return SendRequest(output, "/trophies/remove-achieved/?trophy_id=" + FString::FromInt(Trophy_ID)); +} + +/* Checks if the trophy removel was successful */ +bool UUEGameJoltAPI::GetTrophyRemovalStatus() +{ + UUEGameJoltAPI* Response = GetObject("response"); + if(!Response) + { + UE_LOG(GJAPI, Error, TEXT("Response invalid in GetTrophyRemovalStatus. Was ist called to early?")); + return false; + } + return Response->GetBool("success"); +} + +/* Returns a list of scores either for a user or globally for a game */ +bool UUEGameJoltAPI::FetchScoreboard(const int32 ScoreLimit, const int32 Table_id, const int32 BetterThan, const int32 WorseThan) +{ + TArray returnTrophies; + bool ret = true; + FString output; + FString GameIDString; + FString TableIDString; + FString ScoreLimitString; + + GameIDString = FString::FromInt(Game_ID); + TableIDString = FString::FromInt(Table_id); + ScoreLimitString = FString::FromInt(ScoreLimit); + LastActionPerformed = EGameJoltComponentEnum::GJ_SCORES_FETCH; + + ret = SendRequest(output, TEXT("/scores/?game_id=") + GameIDString + + (!UserName.IsEmpty() || !bIsLoggedIn ? "&username=" : "") + UserName + + (bIsLoggedIn ? "&user_token=" : "") + UserToken + + (ScoreLimit > 0 ? "&limit=" + ScoreLimitString : "") + + (Table_id > 0 ? "&table_id=" + TableIDString : "") + + (BetterThan > 0 ? "&better_than=" + FString::FromInt(BetterThan) : "") + + (WorseThan > 0 ? "&worse_than=" + FString::FromInt(WorseThan) : "")); + + if (!ret) + { + UE_LOG(GJAPI, Error, TEXT("Could not fetch scoreboard.")); + return false; + } + + return true; +} + +/* Gets the list of scores fetched with FetchScoreboard */ +TArray UUEGameJoltAPI::GetScoreboard() +{ + TArray returnScoreInfo; + TArray returnArray = GetObject("response")->GetObjectArray(GetObject("response"), "scores"); + FScoreInfo tempScore; + for (int i = 0; i < returnArray.Num(); i++) + { + tempScore.ScoreSort = returnArray[i]->GetInt("sort"); + tempScore.ScoreString = returnArray[i]->GetString("score"); + tempScore.ExtraData = returnArray[i]->GetString("extra_data"); + tempScore.UserName = returnArray[i]->GetString("user"); + tempScore.UserID = returnArray[i]->GetInt("user_id"); + tempScore.Guest = returnArray[i]->GetString("guest"); + tempScore.UnixTimestamp = returnArray[i]->GetString("stored"); + tempScore.TimeStamp = FDateTime::FromUnixTimestamp(returnArray[i]->GetInt("stored")); + returnScoreInfo.Add(tempScore); + } + return returnScoreInfo; +} + +/* Adds an entry to a scoreboard */ +bool UUEGameJoltAPI::AddScore(const FString UserScore, const int32 UserScore_Sort, const FString GuestUser, const FString extra_data, const int32 table_id) +{ + bool ret = true; + FString output; + FString GameIDString; + FString TableIDString; + + GameIDString = FString::FromInt(Game_ID); + TableIDString = FString::FromInt(table_id); + LastActionPerformed = EGameJoltComponentEnum::GJ_SCORES_ADD; + ret = SendRequest(output, "/scores/add/?score=" + UserScore + + TEXT("&sort=") + FString::FromInt(UserScore_Sort) + + (!UserName.IsEmpty() || bIsLoggedIn ? "&username=" : "") + UserName + + (bIsLoggedIn ? "&user_token=" : "") + UserToken + + (!bIsLoggedIn ? "&guest=" : "") + GuestUser + + (!extra_data.IsEmpty() ? "&extra_data=" : "") + extra_data + + (table_id > 0 ? "&table_id=" : "") + (table_id > 0 ? TableIDString : "")); + if (!ret) + { + UE_LOG(GJAPI, Error, TEXT("Failed to add user's score")); + return false; + } + + return true; +} + +/* Fetches all scoreboard tables */ +bool UUEGameJoltAPI::FetchScoreboardTable() +{ + bool ret = true; + FString output; + FString GameIDString; + + GameIDString = FString::FromInt(Game_ID); + LastActionPerformed = EGameJoltComponentEnum::GJ_SCORES_TABLE; + + ret = SendRequest(output, "/scores/tables/?"); + + if (!ret) + { + UE_LOG(GJAPI, Error, TEXT("Could not fetch scoreboard table")); + return false; + } + + return true; +} + +/* Creates an array of FScoreTableInfo structs for all scoreboards of the game */ +TArray UUEGameJoltAPI::GetScoreboardTable() +{ + TArray returnTableinfo; + FScoreTableInfo tempTable; + TArray returnArray = GetObject("response")->GetObjectArray(GetObject("response"), "tables"); + for (int i = 0; i < returnArray.Num(); i++) + { + tempTable.Id = returnArray[i]->GetInt("id"); + tempTable.Name = returnArray[i]->GetString("name"); + tempTable.Description = returnArray[i]->GetString("description"); + tempTable.Primary = returnArray[i]->GetString("primary"); + returnTableinfo.Add(tempTable); + } + + return returnTableinfo; +} + +/* Fetches the rank of a highscore */ +bool UUEGameJoltAPI::FetchRank(const int32 Score, const int32 TableID = 0) +{ + LastActionPerformed = EGameJoltComponentEnum::GJ_SCORES_RANK; + FString output; + return SendRequest(output, "/scores/get-rank/?sort=" + FString::FromInt(Score) + ((TableID != 0) ? ("&table_id=" + FString::FromInt(TableID)) : "")); +} + +/* Gets the rank of a highscore from the response */ +int32 UUEGameJoltAPI::GetRank() +{ + UUEGameJoltAPI* response = GetObject("response"); + if(!response) + { + UE_LOG(GJAPI, Error, TEXT("Response in GetRank is invalid! Was it called to early? LastActionPerformed is %s"), *UEnum::GetValueAsString(LastActionPerformed)); + return 0; + } + return GetObject("response")->GetInt("rank"); +} + +#pragma region Data-Store + +void UUEGameJoltAPI::SetData(EDataStore Type, FString key, FString data) +{ + FString output; + LastActionPerformed = EGameJoltComponentEnum::GJ_DATASTORE_SET; + SendRequest(output, "/data-store/set/?key=" + key + "&data=" + data, Type == EDataStore::User); +} + +void UUEGameJoltAPI::FetchData(EDataStore Type, FString key) +{ + FString output; + LastActionPerformed = EGameJoltComponentEnum::GJ_DATASTORE_FETCH; + SendRequest(output, "/data-store/?key=" + key, Type == EDataStore::User); +} + +void UUEGameJoltAPI::UpdateData(EDataStore Type, FString key, EDataOperation Operation, FString value) +{ + FString output; + LastActionPerformed = EGameJoltComponentEnum::GJ_DATASTORE_UPDATE; + SendRequest(output, "/data-store/update/?key=" + key + "&value=" + value + "&operation=" + UEnum::GetValueAsString(Operation).RightChop(16), Type == EDataStore::User); +} + +void UUEGameJoltAPI::RemoveData(EDataStore Type, FString key) +{ + FString output; + LastActionPerformed = EGameJoltComponentEnum::GJ_DATASTORE_REMOVE; + SendRequest(output, "/data-store/remove/?key=" + key, Type == EDataStore::User); +} + +void UUEGameJoltAPI::GetData(bool& Success, FString& DataAsString, int32& DataAsInt) +{ + DataAsString = ""; + DataAsInt = 0; + UUEGameJoltAPI* response = GetObject("response"); + if(!response) + { + Success = false; + return; + } + Success = response->GetBool("success"); + if(!Success) + return; + + DataAsString = response->GetString("data"); + DataAsInt = response->GetInt("data"); +} + +#pragma endregion + +/* Gets nested post data from the object with the specified key */ +UUEGameJoltAPI* UUEGameJoltAPI::GetObject(const FString& key) +{ + UUEGameJoltAPI* fieldObj = NULL; + // Try to get the object field from the data + const TSharedPtr *outPtr; + if (!Data->TryGetObjectField(*key, outPtr)) { + // Throw an error and return NULL when the key could not be found + UE_LOG(GJAPI, Error, TEXT("Entry '%s' not found in the field data!"), *key); + return NULL; + } + + // Create a new field data object and assign the data + fieldObj = UUEGameJoltAPI::Create(contextObject); + fieldObj->Data = *outPtr; + + // Return the newly created object + return fieldObj; +} + +/* Gets a string field */ +FString UUEGameJoltAPI::GetString(const FString& key) const +{ + FString outString; + if (!Data->TryGetStringField(*key, outString)) + { + UE_LOG(GJAPI, Error, TEXT("Entry '%s' not found in the field data!"), *key); + return ""; + } + + return outString; +} + +/* Gets a bool field */ +bool UUEGameJoltAPI::GetBool(const FString& key)const +{ + bool outBool; + if (!Data->TryGetBoolField(*key,outBool)) + { + UE_LOG(GJAPI, Error, TEXT("Entry '%s' not found in the field data!"), *key); + return false; + } + return outBool; +} + +/* Gets an integer field */ +int32 UUEGameJoltAPI::GetInt(const FString& key) const +{ + int32 outInt; + if (!Data->TryGetNumberField(*key, outInt)) + { + UE_LOG(GJAPI, Error, TEXT("Entry '%s' not found in the field data!"), *key); + return 0; + } + return outInt; +} + +/* Gets a string array of all keys from the post data */ +TArray UUEGameJoltAPI::GetObjectKeys(UObject* WorldContextObject) +{ + TArray stringArray; + + for (auto currJsonValue = Data->Values.CreateConstIterator(); currJsonValue; ++currJsonValue) { + stringArray.Add((*currJsonValue).Key); + } + + // Return the array, will be empty if unsuccessful + return stringArray; +} + +/* Gets an array of post data */ +TArray UUEGameJoltAPI::GetObjectArray(UObject* WorldContextObject, const FString& key) +{ + TArray objectArray; + + // Try to fetch and assign the array to the array pointer + const TArray> *arrayPtr; + if (Data->TryGetArrayField(*key, arrayPtr)) { + // Iterate through the input array and create new post data objects for every entry and add them to the objectArray + for (int32 i = 0; i < arrayPtr->Num(); i++) { + UUEGameJoltAPI* pageData = Create(WorldContextObject); + pageData->Data = (*arrayPtr)[i]->AsObject(); + objectArray.Add(pageData); + } + } + else { + // Throw an error, since the value with the supplied key could not be found + UE_LOG(GJAPI, Error, TEXT("Array entry '%s' not found in the field data!"), *key); + } + + // Return the array, will be empty if unsuccessful + + return objectArray; +} + +/* Sends a request */ +bool UUEGameJoltAPI::SendRequest(const FString& output, FString url, bool bAppendUserInfo) +{ + if (Game_PrivateKey == TEXT("")) + { + UE_LOG(GJAPI, Error, TEXT("You must put in your game's private key before you can use any of the API functions.")); + return false; + } + + if(Game_ID == 0) + { + UE_LOG(GJAPI, Error, TEXT("You must put in your game's ID before you can use any of the API functions")); + return false; + } + + FString outStr; + TSharedRef> JsonWriter = TJsonWriterFactory::Create(&outStr); + //Start writing the response + WriteObject(JsonWriter, "", new FJsonValueObject(Data)); + JsonWriter->Close(); + + //Create URL First + url = TEXT("https://") + GJAPI_SERVER + GJAPI_ROOT + GJAPI_VERSION + url + "&game_id=" + FString::FromInt(Game_ID); + + if(bAppendUserInfo) + url += "&username=" + UserName + "&user_token=" + UserToken; + + FString signature(FMD5::HashAnsiString(*(url + Game_PrivateKey))); + url += TEXT("&signature=") + signature; + UE_LOG(GJAPI, Log, TEXT("%s"), *url); + + + TSharedRef HttpRequest = FHttpModule::Get().CreateRequest(); + HttpRequest->SetVerb("POST"); + HttpRequest->SetURL(url); + HttpRequest->SetHeader("Content-Type", "application/json"); + HttpRequest->SetContentAsString(output); + HttpRequest->OnProcessRequestComplete().BindUObject(this, &UUEGameJoltAPI::OnReady); + HttpRequest->ProcessRequest(); + + return true; + +} + +/* Writes data */ +void UUEGameJoltAPI::WriteObject(TSharedRef> writer, FString key, FJsonValue* value) { + if (value->Type == EJson::String) { + // Write simple string entry, don't a key when it isn't set + if (key.Len() > 0) { + writer->WriteValue(key, value->AsString()); + } + else { + writer->WriteValue(value->AsString()); + } + } + else if (value->Type == EJson::Object) { + // Write object entry + if (key.Len() > 0) { + writer->WriteObjectStart(key); + } + else { + writer->WriteObjectStart(); + } + + // Loop through all the values in the object data + TSharedPtr objectData = value->AsObject(); + for (auto objectValue = objectData->Values.CreateIterator(); objectValue; ++objectValue) { + // Using recursion to write the key and value to the writer + WriteObject(writer, objectValue.Key(), objectValue.Value().Get()); + } + + writer->WriteObjectEnd(); + } + else if (value->Type == EJson::Array) { + // Process array entry + writer->WriteArrayStart(key); + + TArray> objectArray = value->AsArray(); + for (int32 i = 0; i < objectArray.Num(); i++) { + // Use recursion with an empty key to process all the values in the array + WriteObject(writer, "", objectArray[i].Get()); + } + + writer->WriteArrayEnd(); + } +} + +/* Creates data from a string */ +void UUEGameJoltAPI::FromString(const FString& dataString) { + TSharedRef> JsonReader = TJsonReaderFactory::Create(dataString); + + // Deserialize the JSON data + bool isDeserialized = FJsonSerializer::Deserialize(JsonReader, Data); + + if (!isDeserialized || !Data.IsValid()) { + UE_LOG(GJAPI, Error, TEXT("JSON data is invalid! Input:\n'%s'"), *dataString); + return; + } + + // Assign the request content + Content = dataString; +} + +/* Callback for IHttpRequest::OnProcessRequestComplete() */ +void UUEGameJoltAPI::OnReady(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) { + if (!bWasSuccessful) { + UE_LOG(GJAPI, Warning, TEXT("Response was invalid! Please check the URL.")); + + // Broadcast the failed event + OnFailed.Broadcast(); + return; + } + + // Process the string + FromString(Response->GetContentAsString()); + + if(!GetObject("response") || (GetObject("response")->GetBool("success") == false && LastActionPerformed != EGameJoltComponentEnum::GJ_SESSION_CHECK)) + { + OnFailed.Broadcast(); + return; + } + + switch(LastActionPerformed) + { + case EGameJoltComponentEnum::GJ_USER_AUTH: + OnUserAuthorized.Broadcast(isUserAuthorize()); + break; + case EGameJoltComponentEnum::GJ_USER_AUTOLOGIN: + OnAutoLogin.Broadcast(isUserAuthorize()); + break; + case EGameJoltComponentEnum::GJ_USER_FETCH: + OnUserFetched.Broadcast(GetUserInfo()[0]); + break; + case EGameJoltComponentEnum::GJ_USERS_FETCH: + OnUsersFetched.Broadcast(GetUserInfo()); + break; + case EGameJoltComponentEnum::GJ_USER_FRIENDLIST: + OnFriendlistFetched.Broadcast(GetFriendlist()); + break; + case EGameJoltComponentEnum::GJ_SESSION_OPEN: + OnSessionOpened.Broadcast(GetSessionStatus()); + break; + case EGameJoltComponentEnum::GJ_SESSION_PING: + OnSessionPinged.Broadcast(GetSessionStatus()); + break; + case EGameJoltComponentEnum::GJ_SESSION_CLOSE: + OnSessionClosed.Broadcast(GetSessionStatus()); + break; + case EGameJoltComponentEnum::GJ_SESSION_CHECK: + OnSessionChecked.Broadcast(GetSessionStatus()); + break; + case EGameJoltComponentEnum::GJ_TROPHIES_FETCH: + OnTrophiesFetched.Broadcast(GetTrophies()); + break; + case EGameJoltComponentEnum::GJ_TROHIES_REMOVE: + OnTrophyRemoved.Broadcast(GetTrophyRemovalStatus()); + break; + case EGameJoltComponentEnum::GJ_SCORES_ADD: + OnScoreAdded.Broadcast(GetObject("response")->GetBool("success")); + break; + case EGameJoltComponentEnum::GJ_SCORES_FETCH: + OnScoreboardFetched.Broadcast(GetScoreboard()); + break; + case EGameJoltComponentEnum::GJ_SCORES_TABLE: + OnScoreboardTableFetched.Broadcast(GetScoreboardTable()); + break; + case EGameJoltComponentEnum::GJ_SCORES_RANK: + OnRankFetched.Broadcast(GetRank()); + break; + case EGameJoltComponentEnum::GJ_TIME: + OnTimeFetched.Broadcast(ReadServerTime()); + break; + } + // Broadcast the result event + OnGetResult.Broadcast(); + return; +} + +/* Resets the saved data */ +void UUEGameJoltAPI::Reset() +{ + if (Data.IsValid()) + Data.Reset(); + + // Created a new JSON Object + Data = MakeShareable(new FJsonObject()); +} \ No newline at end of file