└── addons
└── godot-playfab
├── docs
├── .gdignore
├── images
│ ├── login-steam-setup-1.png
│ ├── login-steam-setup-2.png
│ └── login-steam-godot-installation.png
├── Events
│ ├── 3-flushing.md
│ ├── README.md
│ ├── 1-configuration.md
│ └── 2-sending.md
├── Logins
│ └── README.md
├── 4-connecting-signals.md
├── 2-usage.md
├── Steam
│ ├── README.md
│ ├── manual-integration.md
│ └── godot-steam-manual.md
├── 3-basic-requests.md
├── 1-initial-setup.md
└── README.md
├── .gitignore
├── PlayFab.gd.uid
├── PlayFabHttp.gd.uid
├── JsonSerializable.gd.uid
├── Models
├── EntityKey.gd.uid
├── ApiErrorWrapper.gd.uid
├── EventContents.gd.uid
├── LoginResult.gd.uid
├── UserTitleInfo.gd.uid
├── EntityTokenResponse.gd.uid
├── GetTitleDataRequest.gd.uid
├── GetTitleDataResult.gd.uid
├── UserAccountInfo.gd.uid
├── WriteEventsRequest.gd.uid
├── EventContentsCollection.gd.uid
├── LoginWithCustomIdRequest.gd.uid
├── LoginWithSteamRequest.gd.uid
├── RegisterPlayFabUserResult.gd.uid
├── LoginWithEmailAddressRequest.gd.uid
├── PlayerProfileViewConstraints.gd.uid
├── RegisterPlayFabUserRequest.gd.uid
├── AbstractJsonSerializableCollection.gd.uid
├── GetPlayerCombinedInfoRequestParams.gd.uid
├── GetPlayerCombinedInfoResultPayload.gd.uid
├── EventContentsCollection.gd
├── GetTitleDataResult.gd
├── ApiErrorWrapper.gd
├── EntityTokenResponse.gd
├── WriteEventsRequest.gd
├── LoginWithEmailAddressRequest.gd
├── RegisterPlayFabUserResult.gd
├── LoginWithSteamRequest.gd
├── GetTitleDataRequest.gd
├── LoginResult.gd
├── LoginWithCustomIdRequest.gd
├── UserTitleInfo.gd
├── RegisterPlayFabUserRequest.gd
├── EventContents.gd
├── GetPlayerCombinedInfoResultPayload.gd
├── AbstractJsonSerializableCollection.gd
├── PlayerProfileViewConstraints.gd
├── GetPlayerCombinedInfoRequestParams.gd
├── UserAccountInfo.gd
└── EntityKey.gd
├── PlayFabClient.gd.uid
├── PlayFabConstants.gd.uid
├── PlayFabEditor.gd.uid
├── PlayFabEvent.gd.uid
├── PlayFabManager.gd.uid
├── Scenes
├── PlayFabMainScreen.gd.uid
├── PlayFabMainScreen.tscn
└── PlayFabMainScreen.gd
├── lib
└── binogure-studio
│ └── godot-uuid
│ ├── test.gd.uid
│ ├── uuid.gd.uid
│ ├── README.md
│ ├── LICENSE
│ ├── uuid.gd
│ └── test.gd
├── PlayFabClientConfig
├── PlayFabClientConfig.gd.uid
├── PlayFabClientConfigLoader.gd.uid
├── PlayFabClientConfig.gd
└── PlayFabClientConfigLoader.gd
├── icon.png
├── icon_16x16.png
├── plugin.cfg
├── PlayFabConstants.gd
├── PlayFabClient.gd
├── README.md
├── icon.png.import
├── icon_16x16.png.import
├── LICENSE
├── PlayFabManager.gd
├── PlayFabEditor.gd
├── JsonSerializable.gd
├── PlayFabHttp.gd
├── PlayFabEvent.gd
├── PlayFab.gd
└── CHANGELOG.md
/addons/godot-playfab/docs/.gdignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/addons/godot-playfab/.gitignore:
--------------------------------------------------------------------------------
1 | temp/
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/PlayFab.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bb51i18eldin1
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/PlayFabHttp.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dmv0ghbe0s0t7
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/JsonSerializable.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c4a0il4ngkxlw
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/EntityKey.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dsm071d7iwvsp
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/PlayFabClient.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c5tbokou88qbn
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/PlayFabConstants.gd.uid:
--------------------------------------------------------------------------------
1 | uid://4jtf0km243l7
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/PlayFabEditor.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c6jvsbltxl2li
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/PlayFabEvent.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cegrrsdkbujbn
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/PlayFabManager.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bincuw4pj5p2j
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/ApiErrorWrapper.gd.uid:
--------------------------------------------------------------------------------
1 | uid://j4hr5djparmj
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/EventContents.gd.uid:
--------------------------------------------------------------------------------
1 | uid://buu7jy5umvb3x
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/LoginResult.gd.uid:
--------------------------------------------------------------------------------
1 | uid://b10y5qmd7yjnc
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/UserTitleInfo.gd.uid:
--------------------------------------------------------------------------------
1 | uid://4p16qmiufe3i
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/EntityTokenResponse.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dnl2pqpra3yd8
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/GetTitleDataRequest.gd.uid:
--------------------------------------------------------------------------------
1 | uid://db5a64dtfrpbv
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/GetTitleDataResult.gd.uid:
--------------------------------------------------------------------------------
1 | uid://5mtg5d50gcg6
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/UserAccountInfo.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cti4x8llwxrcl
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/WriteEventsRequest.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dmtq1k24nlv7o
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Scenes/PlayFabMainScreen.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c1kf0y6v7e05n
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/EventContentsCollection.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dquhesx0kpby2
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/LoginWithCustomIdRequest.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cyu1bc6gp6722
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/LoginWithSteamRequest.gd.uid:
--------------------------------------------------------------------------------
1 | uid://b0m4gu8tk00kf
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/RegisterPlayFabUserResult.gd.uid:
--------------------------------------------------------------------------------
1 | uid://jlvalxfk1myc
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/LoginWithEmailAddressRequest.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bjbaqxncpng2p
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/PlayerProfileViewConstraints.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bewr675d0mgoh
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/RegisterPlayFabUserRequest.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dvm4jqtgcpa8e
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/lib/binogure-studio/godot-uuid/test.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c063ovvcfuhrg
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/lib/binogure-studio/godot-uuid/uuid.gd.uid:
--------------------------------------------------------------------------------
1 | uid://drbxcc00qyn3b
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/AbstractJsonSerializableCollection.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cegrvy0ms6xk7
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/GetPlayerCombinedInfoRequestParams.gd.uid:
--------------------------------------------------------------------------------
1 | uid://mugrwu32v5su
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/GetPlayerCombinedInfoResultPayload.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cv38mnemjjs5o
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/PlayFabClientConfig/PlayFabClientConfig.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cqv0k6cthopiy
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/PlayFabClientConfig/PlayFabClientConfigLoader.gd.uid:
--------------------------------------------------------------------------------
1 | uid://q2w1818o60xj
2 |
--------------------------------------------------------------------------------
/addons/godot-playfab/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Structed/godot-playfab/HEAD/addons/godot-playfab/icon.png
--------------------------------------------------------------------------------
/addons/godot-playfab/icon_16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Structed/godot-playfab/HEAD/addons/godot-playfab/icon_16x16.png
--------------------------------------------------------------------------------
/addons/godot-playfab/docs/images/login-steam-setup-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Structed/godot-playfab/HEAD/addons/godot-playfab/docs/images/login-steam-setup-1.png
--------------------------------------------------------------------------------
/addons/godot-playfab/docs/images/login-steam-setup-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Structed/godot-playfab/HEAD/addons/godot-playfab/docs/images/login-steam-setup-2.png
--------------------------------------------------------------------------------
/addons/godot-playfab/docs/images/login-steam-godot-installation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Structed/godot-playfab/HEAD/addons/godot-playfab/docs/images/login-steam-godot-installation.png
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/EventContentsCollection.gd:
--------------------------------------------------------------------------------
1 | extends AbstractJsonSerializableCollection
2 | class_name EventContentsCollection
3 |
4 | func _init():
5 | _item_type = EventContents
6 |
--------------------------------------------------------------------------------
/addons/godot-playfab/plugin.cfg:
--------------------------------------------------------------------------------
1 | [plugin]
2 |
3 | name="godot-playfab"
4 | description="Integration with Azure PlayFab - playfab.com"
5 | author="Johannes Ebner"
6 | version="1.3.1"
7 | script="PlayFabEditor.gd"
8 |
--------------------------------------------------------------------------------
/addons/godot-playfab/PlayFabConstants.gd:
--------------------------------------------------------------------------------
1 | extends Object
2 | class_name PlayFabConstants
3 |
4 |
5 | const SETTING_PLAYFAB_TITLE_ID = "playfab/title_id"
6 | const SETTING_PLAYFAB_MANAGER_PROCESS_MODE = "playfab/manager_process_mode"
7 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/GetTitleDataResult.gd:
--------------------------------------------------------------------------------
1 | extends JsonSerializable
2 | class_name GetTitleDataResult
3 |
4 | # a dictionary object of key / value pairs
5 | var Data: Dictionary
6 |
7 |
8 | func _get_type_for_property(property_name: String) -> String:
9 | match property_name:
10 | _:
11 | pass
12 |
13 | push_error("Could not find mapping for property: " + property_name)
14 | return super._get_type_for_property(property_name)
15 |
--------------------------------------------------------------------------------
/addons/godot-playfab/PlayFabClient.gd:
--------------------------------------------------------------------------------
1 | @icon("res://addons/godot-playfab/icon.png")
2 |
3 | extends PlayFab
4 | class_name PlayFabClient
5 |
6 | func _ready():
7 | super._ready()
8 |
9 | # Retrieves the key-value store of custom title settings
10 | # @param request_data: GetTitleDataRequest
11 | # @param callback: Callable
12 | func get_title_data(request_data: GetTitleDataRequest, callback: Callable):
13 | _post_with_session_auth(request_data, "/Client/GetTitleData", callback)
14 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/ApiErrorWrapper.gd:
--------------------------------------------------------------------------------
1 | extends RefCounted
2 | class_name ApiErrorWrapper
3 |
4 | # Numerical HTTP code
5 | var code: int
6 |
7 | # Playfab error code
8 | var error: String
9 |
10 | # Numerical PlayFab error code
11 | var errorCode: int
12 |
13 | # Detailed description of individual issues with the request object
14 | var errorDetails
15 |
16 | # Description for the PlayFab errorCode
17 | var errorMessage: String
18 |
19 | # String HTTP code
20 | var status: String
21 |
--------------------------------------------------------------------------------
/addons/godot-playfab/docs/Events/3-flushing.md:
--------------------------------------------------------------------------------
1 | # Flushing Events
2 | Events will be automatically sent if either of the thresholds in the [Configuration](./Configuration.md) is met.
3 |
4 | However, you can also force flush the cache:
5 |
6 |
7 | ## Flush Telemetry Event Batch
8 | ````gdscript
9 | $PlayFab._flush_telemetry_event_batch()
10 | ````
11 |
12 | ## Flush PlayStream Event Batch
13 | ````gdscript
14 | $PlayFab._flush_playstream_event_batch()
15 | ````
16 |
17 | ⬅️ [2 - Sending Events](2-sending.md) | [Events Overview](README.md) ⬆️
18 |
--------------------------------------------------------------------------------
/addons/godot-playfab/README.md:
--------------------------------------------------------------------------------
1 | # User Documentation
2 | This is the "user" or "game developer" documentation - if you are a developer seeking to *integrate* `godot-playfab` into your game or application, this is for you!
3 |
4 | # Topics
5 | * [Feature Overview](docs/README.md)
6 | * [Initial Setup](docs/initial-setup.md)
7 | * [Using `godot-playfab` in your Game](docs/usage.md)
8 | * [Basic Requests](docs/basic-requests.md)
9 | * [Connecting Signals](docs/connecting-signals.md)
10 | * [Events](docs/Events/README.md)
11 | * [Steam integration](docs/Steam/README.md)
12 |
--------------------------------------------------------------------------------
/addons/godot-playfab/lib/binogure-studio/godot-uuid/README.md:
--------------------------------------------------------------------------------
1 | uuid - static uuid generator for Godot Engine
2 | ===========================================
3 |
4 | The *uuid* class is a GDScript 'static' class that provides a unique identifier generation for [Godot Engine](https://godotengine.org).
5 |
6 | Usage
7 | -----
8 |
9 | Copy the `uuid.gd` file in your project folder, and preload it using a constant.
10 |
11 | ```gdscript
12 | const uuid_util = preload('res://uuid.gd')
13 |
14 | func _init():
15 | print(uuid_util.v4())
16 | ```
17 |
18 | Licensing
19 | ---------
20 |
21 | MIT (See license file for more informations)
22 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/EntityTokenResponse.gd:
--------------------------------------------------------------------------------
1 | extends JsonSerializable
2 | class_name EntityTokenResponse
3 |
4 | ## The entity id and type.
5 | var Entity: EntityKey
6 |
7 | ## The token used to set X-EntityToken for all entity based API calls.
8 | var EntityToken: String
9 |
10 | ## The time the token will expire, if it is an expiring token, in UTC.
11 | var TokenExpiration: String
12 |
13 | func _get_type_for_property(property_name: String) -> String:
14 | match property_name:
15 | "Entity":
16 | return "EntityKey"
17 |
18 | push_error("Could not find mapping for property: " + property_name)
19 | return super._get_type_for_property(property_name)
20 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/WriteEventsRequest.gd:
--------------------------------------------------------------------------------
1 | extends JsonSerializable
2 | class_name WriteEventsRequest
3 |
4 | # Collection of events to write to PlayStream.
5 | var Events: EventContentsCollection
6 |
7 | # The optional custom tags associated with the request (e.g. build number, external trace identifiers, etc.).
8 | var CustomTags: Dictionary
9 |
10 |
11 | func _init():
12 | Events = EventContentsCollection.new()
13 |
14 |
15 | func _get_type_for_property(property_name: String) -> String:
16 | match property_name:
17 | "Events":
18 | return "EventContentsCollection"
19 | _:
20 | pass
21 |
22 | push_error("Could not find mapping for property: " + property_name)
23 | return super._get_type_for_property(property_name)
24 |
25 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/LoginWithEmailAddressRequest.gd:
--------------------------------------------------------------------------------
1 | extends JsonSerializable
2 | class_name LoginWithEmailAddressRequest
3 |
4 | # https://docs.microsoft.com/en-us/rest/api/playfab/client/authentication/login-with-email-address?view=playfab-rest
5 |
6 | # Email address for the account.
7 | var Email: String
8 |
9 | # Password for the PlayFab account (6-100 characters)
10 | var Password: String
11 |
12 | # Unique identifier for the title, found in the Settings > Game Properties section of the PlayFab developer site when a title has been selected.
13 | var TitleId: String
14 |
15 | # The optional custom tags associated with the request (e.g. build number, external trace identifiers, etc.).
16 | var CustomTags: Dictionary #object
17 |
18 | # Flags for which pieces of info to return for the user.
19 | var InfoRequestParameters: GetPlayerCombinedInfoRequestParams
20 |
--------------------------------------------------------------------------------
/addons/godot-playfab/docs/Logins/README.md:
--------------------------------------------------------------------------------
1 | # Logins
2 |
3 | ## Anonymous
4 | The simplest way to get started with PlayFab is to use Anonymous Login.
5 | It allows users to log in without any credentials.
6 | This is particularly useful for quick testing or when you want to provide an experience without requiring
7 | users to create an account.
8 |
9 | ## Custom ID
10 | Custom ID Login allows users to log in using a custom identifier, with an optional password.
11 | This method is useful when you want to manage user identities yourself, such as using a username or
12 | email address.
13 |
14 | ## Steam
15 | godot-playfab provides integration with Steam for user authentication.
16 | This allows users to log in using their Steam accounts, leveraging Steam's authentication system.
17 |
18 | For more info, refer to the [godot-playfab Steam documentation](../Steam/README.md).
19 |
20 |
--------------------------------------------------------------------------------
/addons/godot-playfab/icon.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://blhn11y3b7sgj"
6 | path="res://.godot/imported/icon.png-9c1a4befef51eb6f77f948cb8c9e8bf5.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/godot-playfab/icon.png"
14 | dest_files=["res://.godot/imported/icon.png-9c1a4befef51eb6f77f948cb8c9e8bf5.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 |
--------------------------------------------------------------------------------
/addons/godot-playfab/icon_16x16.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://dqmr6ags56s74"
6 | path="res://.godot/imported/icon_16x16.png-0afc886ccf5120fe1aa739d480333f18.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/godot-playfab/icon_16x16.png"
14 | dest_files=["res://.godot/imported/icon_16x16.png-0afc886ccf5120fe1aa739d480333f18.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 |
--------------------------------------------------------------------------------
/addons/godot-playfab/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022, 2023 Johannes Ebner
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/addons/godot-playfab/lib/binogure-studio/godot-uuid/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Xavier Sellier
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/RegisterPlayFabUserResult.gd:
--------------------------------------------------------------------------------
1 | extends JsonSerializable
2 | class_name RegisterPlayFabUserResult
3 |
4 | ## Each account must have a unique email address in the PlayFab service. Once created, the account may be associated with additional accounts (Steam, Facebook, Game Center, etc.), allowing for added social network lists and achievements systems.
5 |
6 | ## If LoginTitlePlayerAccountEntity flag is set checked the login request the title_player_account will also be logged in and returned.
7 | var EntityToken: EntityTokenResponse
8 |
9 | ## PlayFab unique identifier for this newly created account.
10 | var PlayFabId: String
11 |
12 | ## Unique token identifying the user and game at the server level, for the current session.
13 | var SessionTicket: String
14 |
15 | ## Settings specific to this user.
16 | var SettingsForUser#: UserSettings
17 |
18 | ## PlayFab unique user name.
19 | var Username: String
20 |
21 | func _get_type_for_property(property_name: String) -> String:
22 | match property_name:
23 | "EntityToken":
24 | return "EntityTokenResponse"
25 |
26 | push_error("Could not find mapping for property: " + property_name)
27 | return super._get_type_for_property(property_name)
28 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/LoginWithSteamRequest.gd:
--------------------------------------------------------------------------------
1 | extends JsonSerializable
2 | class_name LoginWithSteamRequest
3 |
4 | # Unique identifier for the title, found in the Settings > Game Properties section of the PlayFab developer site when a title has been selected.
5 | var TitleId: String
6 |
7 | # Automatically create a PlayFab account if one is not currently linked to this ID.
8 | var CreateAccount: bool
9 |
10 | # The optional custom tags associated with the request (e.g. build number, external trace identifiers, etc.).
11 | var CustomTags: Dictionary
12 |
13 | # Base64 encoded body that is encrypted with the Title's public RSA key (Enterprise Only).
14 | var EncryptedRequest: String
15 |
16 | # Flags for which pieces of info to return for the user.
17 | var InfoRequestParameters: GetPlayerCombinedInfoRequestParams
18 |
19 | # Player secret that is used to verify API request signatures (Enterprise Only).
20 | var PlayerSecret: String
21 |
22 | # Authentication token for the user, returned as a byte array from Steam, and converted to a string (for example, the byte 0x08 should become "08").
23 | var SteamTicket: String
24 |
25 | # True if ticket was generated using ISteamUser::GetAuthTicketForWebAPI() using "AzurePlayFab" as the identity string. False if the ticket was generated with ISteamUser::GetAuthSessionTicket().
26 | var TicketIsServiceSpecific: bool
27 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/GetTitleDataRequest.gd:
--------------------------------------------------------------------------------
1 | extends JsonSerializable
2 | class_name GetTitleDataRequest
3 |
4 | ## This API is designed to return title specific values which can be read, but not written to, by the client.
5 | ## For example, a developer could choose to store values which modify the user experience,
6 | ## such as enemy spawn rates, weapon strengths, movement speeds, etc. This allows a developer
7 | ## to update the title without the need to create, test, and ship a new build. If the player
8 | ## belongs to an experiment variant that uses title data overrides, the overrides are applied
9 | ## automatically and returned with the title data. Note that there may up to a minute delay
10 | ## in between updating title data and this API call returning the newest value.
11 |
12 | # Specific keys to search for in the title data (leave null to get all keys)
13 | var Keys: Array = []
14 |
15 | # Optional field that specifies the name of an override. This value is ignored when used by the game client; otherwise, the overrides are applied automatically to the title data.
16 | var OverrideLabel: String = ""
17 |
18 |
19 | func _get_type_for_property(property_name: String) -> String:
20 | match property_name:
21 | _:
22 | pass
23 |
24 | push_error("Could not find mapping for property: " + property_name)
25 | return super._get_type_for_property(property_name)
26 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/LoginResult.gd:
--------------------------------------------------------------------------------
1 | extends JsonSerializable
2 | class_name LoginResult
3 |
4 | ## If LoginTitlePlayerAccountEntity flag is set checked the login request the title_player_account will also be logged in and returned.
5 | var EntityToken: EntityTokenResponse
6 |
7 | # Results for requested info.
8 | var InfoResultPayload: GetPlayerCombinedInfoResultPayload
9 |
10 | ## The time of this user's previous login. If there was no previous login, then it's DateTime.MinValue
11 | var LastLoginTime: float
12 |
13 | ## True if the account was newly created checked this login.
14 | var NewlyCreated: bool
15 |
16 | ## Player's unique PlayFabId.
17 | var PlayFabId: String
18 |
19 | ## Unique token authorizing the user and game at the server level, for the current session.
20 | var SessionTicket: String
21 |
22 | ## Settings specific to this user.
23 | var SettingsForUser#: UserSettings
24 |
25 | #The experimentation treatments for this user at the time of login.
26 | var TreatmentAssignment#: TreatmentAssignment
27 |
28 | func _get_type_for_property(property_name: String) -> String:
29 | match property_name:
30 | "EntityToken":
31 | return "EntityTokenResponse"
32 | "InfoResultPayload":
33 | return "GetPlayerCombinedInfoResultPayload"
34 |
35 | push_error("Could not find mapping for property: " + property_name)
36 | return super._get_type_for_property(property_name)
37 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/LoginWithCustomIdRequest.gd:
--------------------------------------------------------------------------------
1 | extends JsonSerializable
2 | class_name LoginWithCustomIdRequest
3 |
4 | # Unique identifier for the title, found in the Settings > Game Properties section of the PlayFab developer site when a title has been selected.
5 | var TitleId: String
6 |
7 | # Automatically create a PlayFab account if one is not currently linked to this ID.
8 | var CreateAccount: bool
9 |
10 | # Custom unique identifier for the user, generated by the title.
11 | var CustomId: String
12 |
13 | # The optional custom tags associated with the request (e.g. build number, external trace identifiers, etc.).
14 | var CustomTags: Dictionary
15 |
16 | # Base64 encoded body that is encrypted with the Title's public RSA key (Enterprise Only).
17 | var EncryptedRequest: String
18 |
19 | # Flags for which pieces of info to return for the user.
20 | var InfoRequestParameters: GetPlayerCombinedInfoRequestParams
21 |
22 | # Player secret that is used to verify API request signatures (Enterprise Only).
23 | var PlayerSecret: String
24 |
25 |
26 | func _get_type_for_property(property_name: String) -> String:
27 | match property_name:
28 | "InfoRequestParameters":
29 | return "GetPlayerCombinedInfoRequestParams"
30 | _:
31 | pass
32 |
33 | push_error("Could not find mapping for property: " + property_name)
34 | return super._get_type_for_property(property_name)
35 |
36 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/UserTitleInfo.gd:
--------------------------------------------------------------------------------
1 | extends JsonSerializable
2 | class_name UserTitleInfo
3 |
4 | # URL to the player's avatar.
5 | var AvatarUrl: String
6 |
7 | # timestamp indicating when the user was first associated with this game (this can differ significantly from when the user first registered with PlayFab)
8 | var Created: String
9 |
10 | # name of the user, as it is displayed in-game
11 | var DisplayName: String
12 |
13 | # timestamp indicating when the user first signed into this game (this can differ from the Created timestamp, as other events, such as issuing a beta key to the user, can associate the title to the user)
14 | var FirstLogin: String
15 |
16 | # timestamp for the last user login for this title
17 | var LastLogin: String
18 |
19 | # source by which the user first joined the game, if known
20 | var Origination: String #UserOrigination (enum)
21 |
22 | # Title player account entity for this user
23 | var TitlePlayerAccount: EntityKey
24 |
25 | # boolean indicating whether or not the user is currently banned for a title
26 | var isBanned: bool
27 |
28 | func _get_type_for_property(property_name: String) -> String:
29 | match property_name:
30 | "Origination":
31 | return "UserOrigination"
32 | "TitlePlayerAccount":
33 | return "EntityKey"
34 |
35 | push_error("Could not find mapping for property: " + property_name)
36 | return super._get_type_for_property(property_name)
37 |
--------------------------------------------------------------------------------
/addons/godot-playfab/docs/4-connecting-signals.md:
--------------------------------------------------------------------------------
1 | # Connecting Signals
2 | There are a few Signals that you can & should connect to:
3 |
4 | | Signal Name | Purpose |
5 | |-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
6 | | api_error | Emitted when a PlayFab API (HTTP status code 4xx) error occurs. Will receive a LoginResult as parameter.
Use to handle validation errors etc. |
7 | | server_error | Emitted when a Server Error (HTTP status code 5xx) occurs when querying PlayFab. Will receive the request path as parameter. |
8 | | json_parse_error | Emitted when the returned JSON could not be properly parsed. This should generally not be needed to be connected. |
9 | | registered | Emitted when a new User is registered. You should only connect this in the Scene where you do Player Registrations. |
10 | | logged_in | Emitted when a Player successfully logs in. |
11 |
12 | ⬅️ [3 - Basic Requests](3-basic-requests.md) | [Events](Events/README.md) ➡️
13 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/RegisterPlayFabUserRequest.gd:
--------------------------------------------------------------------------------
1 | extends JsonSerializable
2 | class_name RegisterPlayFabUserRequest
3 |
4 | # REQUIRED Unique identifier for the title, found in the Settings > Game Properties section of the PlayFab developer site when a title has been selected.
5 | var TitleId: String
6 |
7 | # The optional custom tags associated with the request (e.g. build number, external trace identifiers, etc.).
8 | var CustomTags: Dictionary
9 |
10 | # An optional parameter for setting the display name for this title (3-25 characters).
11 | var DisplayName: String
12 |
13 | # User email address attached to their account
14 | var Email: String
15 |
16 | # Base64 encoded body that is encrypted with the Title's public RSA key (Enterprise Only).
17 | var EncryptedRequest: String
18 |
19 | # Flags for which pieces of info to return for the user.
20 | var InfoRequestParameters: GetPlayerCombinedInfoRequestParams
21 |
22 | # Password for the PlayFab account (6-100 characters)
23 | var Password: String
24 |
25 | # Player secret that is used to verify API request signatures (Enterprise Only).
26 | var PlayerSecret: String
27 |
28 | # An optional parameter that specifies whether both the username and email parameters are required. If true, both parameters are required; if false, the user must supply either the username or email parameter. The default value is true.
29 | var RequireBothUsernameAndEmail: bool
30 |
31 | # PlayFab username for the account (3-20 characters)
32 | var Username: String
33 |
34 |
--------------------------------------------------------------------------------
/addons/godot-playfab/docs/2-usage.md:
--------------------------------------------------------------------------------
1 | # Using `godot-playfab` in your Game
2 |
3 | ## In Code
4 | > ℹ️ This is the preferred way of using `godot-playfab`!
5 |
6 | * Use the global `PlayFabManager.client` to call the `/Client/` APIs - which is most of PlayFab's APIs.
7 | * Use the global `PlayFabManager.event` to call [Event APIs](Events/README.md).
8 | * For everything that's not covered, you can use "[basic requests](3-basic-requests.md)" to call any other APIs.
9 |
10 | > ℹ️ Please have a look at the example Scenes on how these are being used.
11 |
12 | ## In the Editor
13 | In any scene you want to use `godot-playfab`, just place a `PlayFabClient` node into your scene.
14 |
15 | You can use an arbitrary number of `PlayFabClient` nodes. Each will get their configuration values from
16 | `PlayFabClientConfig` in the `PlayFabManager` singleton, which you should have already set up
17 | (see [Initial Setup](initial-setup.md))
18 |
19 | ## Authentication
20 | Each of these clients can make their own requests, and each of those clients can do events in parallel.
21 |
22 | They all use the same authentication state, so if you log in with one client, the other clients will also be authenticated. Authentication state is shared globally within `PlayFabClientConfig` and cached on disk in an encrypted format.
23 |
24 | If you check `PlayFabClientConfig.is_logged_in` it will also check, if the session token is still valid for a good amount of time. It will return `false` if the session token is expired or about to expire, so you can then log in again.
25 |
26 |
27 | ⬅️ [1 - Initial Setup](1-initial-setup.md) | [3 - Basic Requests](3-basic-requests.md) ➡️
28 |
--------------------------------------------------------------------------------
/addons/godot-playfab/docs/Events/README.md:
--------------------------------------------------------------------------------
1 | # Events
2 |
3 | ## Events Documentation Page Index
4 | 1. [Configuration](Configuration.md)
5 | 2. [Sending Events](Sending.md)
6 | 3. [Flushing Events](Flushing.md)
7 |
8 | ## PlayStream & Telemetry
9 | > See the [Official Documentation](https://docs.microsoft.com/en-us/gaming/playfab/features/automation/playstream-events/)
10 |
11 | PlayStream and Telemetry events are - from a integration perspective - the same.
12 | The only difference when sending either event type is the different API name.
13 |
14 | **The difference is in pricing!**
15 |
16 | ## TL;DR; - what should I use?
17 | Telemetry Events - unless you need to react upon these events in near-real-time.
18 | Telemetry Events are a lot less expensive!
19 |
20 | ## Differences
21 | ### PlayStream Events
22 |
23 | PlayStream events...
24 | * Near-real-time
25 | * will appear in the GameStream
26 | * can be reacted upon e.g.
27 | * execute script
28 | * send notification
29 | * give item etc.
30 | * Can be worked with in the Data & Analytics features of PlayFab
31 | * Cost **more than double** than Telemetry events!
32 | * Otherwise have the same properties as Telemetry Events
33 |
34 |
35 | ### Telemetry Events
36 | * Bypass the PlayStream
37 | * Can be worked with in the Data & Analytics features of PlayFab
38 |
39 | # Usage
40 | Instead of (or in addition to any) `PlayFabClient` Nodes, drop a `PlayFabEvent` node in your scene!
41 | You just need to make sure, a login was done before, so the EntityToken is available for Authentication
42 |
43 | ⬆️️ [User Documentation](../README.md) | [1 - Configuration for Events](1-configuration.md) ➡️
44 |
--------------------------------------------------------------------------------
/addons/godot-playfab/docs/Steam/README.md:
--------------------------------------------------------------------------------
1 | # Steam integration
2 |
3 | 1. [Prerequisites](#prerequisites)
4 | 2. [Setup](#setup)
5 | 3. [API](#api)
6 | 4. [Example](#example)
7 | 5. [Example using **GodotSteam**](#example-using-godotsteam)
8 |
9 | ## Prerequisites
10 |
11 | Before beginning, you should have:
12 | - A PlayFab Title
13 | - A Steam App Id
14 | - A Steam Publisher Web API Key
15 | - Follow [Creating a Publisher Web API Key](https://partner.steamgames.com/doc/webapi_overview/auth#create_publisher_key) in the Steamworks documentation in order to generate it.
16 |
17 | ## Setup
18 |
19 | To enable support for Steam authorization, PlayFab requires you to enable the Steam add-on.
20 |
21 | 1. Go to your Overview page of your PlayFab Title.
22 | 2. Select the **Add-ons** menu item.
23 | 3. In the list of available **Add-ons**, locate and select Steam
24 |
25 | 
26 |
27 | 1. Click on the "Install Steam"
28 | 1. Enter your Steam Application ID
29 | 2. Enter your Steam Publisher Web API Key created earlier
30 | 3. Click on the "Install Steam" again
31 |
32 | 
33 |
34 | ## Godot Steam Integration
35 | If you want to use the Steam integration, please switch to the [`integrate-steam` branch](https://github.com/Structed/godot-playfab/tree/integrate-steam).
36 | It holds an integration between godot-playfab and [GodotSteam](https://godotsteam.com/).
37 |
38 | If you want to do a manual integration, please check the [Manual Setup](../Steam/manual-integration.md) documentation (not recommended).
39 | However, it's a good read to understand how the Steam login works under the hood.
40 |
41 |
42 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/EventContents.gd:
--------------------------------------------------------------------------------
1 | extends JsonSerializable
2 | class_name EventContents
3 |
4 | # The optional custom tags associated with the event (e.g. build number, external trace identifiers, etc.). Before an event is written, this collection and the base request custom tags will be merged, but not overriden. This enables the caller to specify static tags and per event tags.
5 | var CustomTags: Dictionary
6 |
7 | # Entity associated with the event. If null, the event will apply to the calling entity.
8 | # **You should not set the Entity** unless you need to.
9 | var Entity: EntityKey
10 |
11 | # The namespace in which the event is defined. Allowed namespaces can vary by API.
12 | var EventNamespace: String
13 |
14 | # The name of this event.
15 | var Name: String
16 |
17 | # The original unique identifier associated with this event before it was posted to PlayFab. The value might differ from the EventId value, which is assigned when the event is received by the server.
18 | var OriginalId: String
19 |
20 | # The time (in UTC) associated with this event when it occurred. If specified, this value is stored in the OriginalTimestamp property of the PlayStream event.
21 | var OriginalTimestamp: String
22 |
23 | # Arbitrary data associated with the event. `PayloadJSON` is not implemented.
24 | # If you ***need*** to use `PayloadJSON`, please use a verbaitim request instead.
25 | var Payload: Dictionary
26 |
27 |
28 | func _get_type_for_property(property_name: String) -> String:
29 | match property_name:
30 | "Entity":
31 | return "EntityKey"
32 | _:
33 | pass
34 |
35 | push_error("Could not find mapping for property: " + property_name)
36 | return super._get_type_for_property(property_name)
37 |
38 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/GetPlayerCombinedInfoResultPayload.gd:
--------------------------------------------------------------------------------
1 | extends JsonSerializable
2 | class_name GetPlayerCombinedInfoResultPayload
3 |
4 | # Account information for the user. This is always retrieved.
5 | var AccountInfo: UserAccountInfo
6 |
7 | # Inventories for each character for the user.
8 | var CharacterInventories: Array
9 |
10 | # List of characters for the user.
11 | var CharacterList: Array
12 |
13 | # The profile of the players. This profile is not guaranteed to be up-to-date. For a new player, this profile will not exist.
14 | var PlayerProfile#: PlayerProfileModel
15 |
16 | # List of statistics for this player.
17 | var PlayerStatistics: Array
18 |
19 | # Title data for this title.
20 | var TitleData#: object
21 |
22 | # User specific custom data.
23 | var UserData#: UserDataRecord
24 |
25 | # The version of the UserData that was returned.
26 | var UserDataVersion#: number
27 |
28 | # Array of inventory items in the user's current inventory.
29 | var UserInventory: Array
30 |
31 | # User specific read-only data.
32 | var UserReadOnlyData#: UserDataRecord
33 |
34 | # The version of the Read-Only UserData that was returned.
35 | var UserReadOnlyDataVersion#: number
36 |
37 | # Dictionary of virtual currency balance(s) belonging to the user.
38 | var UserVirtualCurrency#: object
39 |
40 | # Dictionary of remaining times and timestamps for virtual cu
41 | var UserVirtualCurrencyRechargeTimes#: VirtualCurrencyRechargeTime
42 |
43 |
44 | func _get_type_for_property(property_name: String) -> String:
45 | match property_name:
46 | "AccountInfo":
47 | return "UserAccountInfo"
48 |
49 | push_error("Could not find mapping for property: " + property_name)
50 | return super._get_type_for_property(property_name)
51 |
--------------------------------------------------------------------------------
/addons/godot-playfab/docs/3-basic-requests.md:
--------------------------------------------------------------------------------
1 | # Basic Requests
2 |
3 | ## Overview
4 | While `godot-playfab`'s aim is to provide convenient pre-built requests and models, we can likely never support the whole API surface.
5 | Especially, when new APIs appear, `godot-playfab` will always lag a bit behind.
6 |
7 | This requires the ability to write "generic" requests, where you can easily specify the path and request parameters and can parse the returned JSON by yourself.
8 |
9 | ## APIs
10 | `godot-playfab` provides the following APIs to do generic requests:
11 |
12 | | API Name | Description |
13 | |---------------------------|----------------|
14 | | `PlayFab.post_dict_auth` | Takes a Dictionary as request body parameters. Will work on APIs that require authentication. You need to specify which Authentication type should be used (SessionTicket or EntityToken) |
15 | | `PlayFab.post_dict` | Takes a Dictionary as request body parameters. Will only work on APIs that do not require authentication. There is likely not a lot of reasons why you would need this method, as there are no requests that do not require authentication - except authentication calls. |
16 |
17 | ## Examples
18 | ### `PlayFab.post_dict_auth`
19 |
20 | ```gdscript
21 | func GetTitleData():
22 | var dict = {
23 | "keys": [
24 | "BarKey"
25 | ]
26 | }
27 |
28 | PlayFabManager.client.post_dict_auth(dict,"/Client/GetTitleData", PlayFab.AUTH_TYPE.SESSION_TICKET, funcref(self, "_on_get_title_data"))
29 |
30 | func _on_get_title_data(response):
31 | print_debug(JSON.print(response.data, "\t"))
32 |
33 | ```
34 |
35 | Check out the [RequestBuilder.gd](/Scenes/RequestBuilder.gd) in the Scenes folder
36 |
37 |
38 |
39 | ⬅️ [2 - Usage](2-usage.md) | [3 - Connecting Signals](4-connecting-signals.md) ➡️
40 |
--------------------------------------------------------------------------------
/addons/godot-playfab/PlayFabClientConfig/PlayFabClientConfig.gd:
--------------------------------------------------------------------------------
1 | @icon("res://addons/godot-playfab/icon.png")
2 |
3 | extends RefCounted
4 | class_name PlayFabClientConfig
5 |
6 | # Timeout for the sesion token
7 | const TOKEN_TIMEOUT = 23 * 3600
8 |
9 | ## The Client Session ticket. Used for /Client API
10 | var session_ticket: String : set = _set_session_ticket
11 |
12 | ## The Master Player Account ID, aka "PlayFab ID"
13 | var master_player_account_id: String
14 |
15 | ## Object holding the Entity Token, as well as the EntityKey (ID, Type) of the logged in Entity (usually title_player_account)
16 | var entity_token: EntityTokenResponse = EntityTokenResponse.new()
17 |
18 | # One of `LoginType`. Denotes the authentication method used for the login saved here
19 | var login_type = LoginType.LOGIN_NONE
20 |
21 | ## User Identifier - Email, UUID etc. This is dependent checked the Login Type
22 | ## LOGIN_EMAIL - Email
23 | ## LOGIN_CUSTOM_ID - UUID
24 | ## LOGIN_STEAM - SteamTicket
25 | var login_id = ""
26 |
27 | # Last Login timestamp - when tokens were refreshed
28 | var login_timestamp = 0
29 |
30 | enum LoginType { LOGIN_NONE, LOGIN_EMAIL, LOGIN_CUSTOM_ID, LOGIN_STEAM }
31 |
32 | # Whether the user should stay logged in
33 | var stay_logged_in = false
34 |
35 |
36 | # Checks whether the account is considered logged in
37 | func is_logged_in() -> bool:
38 | if session_ticket.is_empty() || is_login_token_expired():
39 | return false
40 |
41 | return true
42 |
43 |
44 | # Validates whether the login token has expired (based checked time)
45 | func is_login_token_expired() -> bool:
46 | var elapsed_time = Time.get_unix_time_from_system() - login_timestamp
47 |
48 | if elapsed_time < 0 || elapsed_time > TOKEN_TIMEOUT:
49 | return true
50 |
51 | return false
52 |
53 |
54 | # Sets the session_ticket and updates the login_timestamp
55 | func _set_session_ticket(value: String):
56 | login_timestamp = Time.get_unix_time_from_system()
57 | session_ticket = value
58 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/AbstractJsonSerializableCollection.gd:
--------------------------------------------------------------------------------
1 | # This is a wrapper class around an Array
2 | extends JsonSerializable
3 | class_name AbstractJsonSerializableCollection
4 |
5 |
6 | # Holds the items
7 | var _Items: Array
8 |
9 | # Specifies the type of the items in `_Items`, which in turn must inherit JsonSerializable.
10 | # @example: Given a collection `StoreItemCollection`, by convention, `_Items` would be a collection of `StoreItem`.
11 | # Thus, in the extending class you would add a constructor initialization like so:
12 | # ```
13 | # extends AbstractJsonSerializableCollection
14 | # class_name StoreItemCollection
15 | #
16 | # func _init():
17 | # _item_type = StoreItem
18 | # ```
19 | #
20 | var _item_type
21 |
22 | # Appends an element at the end of the array (alias of push_back).
23 | func append(item: JsonSerializable):
24 | _Items.append(item)
25 |
26 |
27 | # Returns the number of elements in the array.
28 | func size() -> int:
29 | return _Items.size()
30 |
31 | # Clears the array. This is equivalent to using resize with a size of 0.
32 | func clear():
33 | _Items.clear();
34 |
35 |
36 | ## MASSIVE HACK 😬
37 | # Casts this collection and sub-objects to an Array of Dictionaries, recursively.
38 | # The method name is only `to_dict` to keep the API compatible with `JsonSerializable`
39 | # The return value is an ARRAY!
40 | # @returns Array: An array of all the Dictionaries, marshaled from objects inheriting from `JsonSerializable`
41 | func to_dict():
42 | var index = 0
43 | var dict = [] # Actually, Array of items! It's called dict to keep the API compatible with JsonSerializable
44 | for item in _Items:
45 | dict.append((item as JsonSerializable).to_dict())
46 | index += 1
47 |
48 | return dict
49 |
50 |
51 | # Rehydrates a collection and appropriate sub-objects from a Dictionary, recursively
52 | func from_dict(data, instance: JsonSerializable):
53 | var index = 0
54 | for item in data:
55 | var nested_instance = _item_type.new()
56 | nested_instance.from_dict(item, nested_instance)
57 | _Items.append(nested_instance)
58 | index += 1
59 |
--------------------------------------------------------------------------------
/addons/godot-playfab/docs/Events/1-configuration.md:
--------------------------------------------------------------------------------
1 | # Configuration for Events
2 | When sending events, you may want to batch batch requests you send to save on API requests/open connections.
3 |
4 | In order for you to be able to flexibly change those based on your needs wherever you are in your game, each `PlayFab` node exposes two settings:
5 |
6 | | Name | Variable | Description |
7 | |---------------------|-----------------------------|---------------------------------------------------------------------------------------------------|
8 | | Event Batch Size | event_batch_size | Determines how many events are batched before they are sent automatically |
9 | | Event Batch Timeout | event_batch_timeout_seconds | Determines after how many seconds the batch will be flushed - disregarding the current batch size |
10 | | Use Local Time | use_local_time | Defines whether local time is set on the event (true), or server time should be used (false) |
11 |
12 | ## Considerations
13 | > If your game crashes while having batched, unsent events, these will be lost!
14 |
15 | > If you use multiple `PlayFab` nodes, you may set these values independently
16 |
17 | Suppose you want to implement a logger of which sends Telemetry Events for debugging purposes. But you would also like to write general PlayStream events to react upon.
18 | You could then configure two different `PlayFab` objects with different batching configurations.
19 |
20 | ### Example:
21 | #### Logger
22 | Log Telemetry Events immediately
23 |
24 | | Name | Value |
25 | |---------------------|--------------|
26 | | Event Batch Size | 1 |
27 | | Event Batch Timeout | 10 (default) |
28 |
29 | #### PlayStream Events
30 | Sending small PlayStream events, we don't really care when/if they are being sent
31 |
32 | | Name | Value |
33 | |---------------------|--------------|
34 | | Event Batch Size | 5 (default) |
35 | | Event Batch Timeout | 10 (default) |
36 |
37 | ⬅️ [Events Overview](README.md) | [2 - Sending Events](2-sending.md) ➡️
38 |
--------------------------------------------------------------------------------
/addons/godot-playfab/docs/1-initial-setup.md:
--------------------------------------------------------------------------------
1 | # Initial Setup
2 | These steps only need to be done once:
3 |
4 | ## Downloading the addon
5 | Grab the latest stable release from either the [Godot Asset Library](https://godotengine.org/asset-library/asset/1321) or [GitHub release](https://github.com/Structed/godot-playfab/releases).
6 |
7 | > ⚠️ If you're using the AssetLib version:
8 | >
9 | > make sure to only select the `addons/godot-playfab` folder when importing the addon into your project.
10 | > All the other files are only for development or examples and can/should therefore be ignored.
11 |
12 | ## Enabling the addon
13 | * Copy the folder `/addons/godot-playfab` in your own project's `/addons/` directory
14 | * In the `Plugins` tab of your Godot project's Project Settings, `"`godot-playfab`"` should now show up. Enable it.
15 |
16 | If you close and re-open Project Settings, the General tab will now have a "PlayFab" option at the bottom of the left-hand sidebar.
17 |
18 | ## Setting the Title Id
19 | * Go to [PlayFab](https://playfab.com), log in and copy your Title's ID
20 | * In your Godot editor, open Project Settings
21 | * Set the Title Id in `Playfab --> Title Id`
22 |
23 | > ⚠️ If you cannot find the setting, just add it yourself!
24 | > * Key: `playfab/title_id`
25 | > * In the platform dropdown menu, select `(All)`
26 | > * In the type dropdown menu, select `String`
27 | > * Last, click the `Add` button
28 | > After you added the setting, you can set the Title ID as described above.
29 |
30 | Next: [Using `godot-playfab` in your Game](usage.md)
31 |
32 | # Configuration
33 | This is only for information. No action is required.
34 | On autoload of the `PlayFabManager`, an encrypted `PlayFab Client Config` file will be created in `user://playfab_client_config.tres`.
35 | See the [File paths in Godot projects documentation](https://docs.godotengine.org/en/stable/tutorials/io/data_paths.html).
36 |
37 | This Config is used to store transient client data PlayFab needs to work across scenes, like the current PlayFab `Session Ticket`.
38 |
39 | # Example Project
40 | Clone the full repo for a full example project where you can find out how different features are implemented and how you can make more sophisticated calls!
41 |
42 | ⬅️ [Feature overview](README.md) | [Using `godot-playfab` in your Game](2-usage.md) ➡️
43 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/PlayerProfileViewConstraints.gd:
--------------------------------------------------------------------------------
1 | extends JsonSerializable
2 | class_name PlayerProfileViewConstraints
3 |
4 | # Whether to show player's avatar URL. Defaults to false
5 | var ShowAvatarUrl: bool
6 |
7 | # Whether to show the banned until time. Defaults to false
8 | var ShowBannedUntil: bool
9 |
10 | # Whether to show campaign attributions. Defaults to false
11 | var ShowCampaignAttributions: bool
12 |
13 | # Whether to show contact email addresses. Defaults to false
14 | var ShowContactEmailAddresses: bool
15 |
16 | # Whether to show the created date. Defaults to false
17 | var ShowCreated: bool
18 |
19 | # Whether to show the display name. Defaults to false
20 | var ShowDisplayName: bool
21 |
22 | # Whether to show player's experiment variants. Defaults to false
23 | var ShowExperimentVariants: bool
24 |
25 | # Whether to show the last login time. Defaults to false
26 | var ShowLastLogin: bool
27 |
28 | # Whether to show the linked accounts. Defaults to false
29 | var ShowLinkedAccounts: bool
30 |
31 | # Whether to show player's locations. Defaults to false
32 | var ShowLocations: bool
33 |
34 | # Whether to show player's membership information. Defaults to false
35 | var ShowMemberships: bool
36 |
37 | # Whether to show origination. Defaults to false
38 | var ShowOrigination: bool
39 |
40 | # Whether to show push notification registrations. Defaults to false
41 | var ShowPushNotificationRegistrations: bool
42 |
43 | # Reserved for future development
44 | var ShowStatistics: bool
45 |
46 | # Whether to show tags. Defaults to false
47 | var ShowTags: bool
48 |
49 | # Whether to show the total value to date in usd. Defaults to false
50 | var ShowTotalValueToDateInUsd: bool
51 |
52 | # Whether to show the values to date. Defaults to false
53 | var ShowValuesToDate: bool
54 |
55 | func show_all():
56 | ShowAvatarUrl = true
57 | ShowBannedUntil = true
58 | ShowCampaignAttributions = true
59 | ShowContactEmailAddresses = true
60 | ShowCreated = true
61 | ShowDisplayName = true
62 | ShowExperimentVariants = true
63 | ShowLastLogin = true
64 | ShowLinkedAccounts = true
65 | ShowLocations = true
66 | ShowMemberships = true
67 | ShowOrigination = true
68 | ShowPushNotificationRegistrations = true
69 | ShowStatistics = true
70 | ShowTags = true
71 | ShowTotalValueToDateInUsd = true
72 | ShowValuesToDate = true
73 |
--------------------------------------------------------------------------------
/addons/godot-playfab/PlayFabManager.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | # This is script must be auto-loaded as `PlayFabManager`.
3 | # Use it as a global state/config manager for PlayFab data, like login persistence.
4 |
5 | # Handles saving/loading of the `PlayFabClientConfig`
6 | var _client_config_loader = PlayFabClientConfigLoader.new()
7 |
8 | # **READONLY**
9 | # The Tile ID to use for this project. Will be pulled from ProjectSettings.
10 | # **DO NOT** manually override!
11 | var title_id: String
12 |
13 | # Holds information for the PlayFab client, e.g. login data.
14 | # **Call `save_client_config()` after changing/updating it to persist.
15 | var client_config: PlayFabClientConfig
16 |
17 | # Represents the PlayFab Client API
18 | # https://docs.microsoft.com/en-us/rest/api/playfab/client/?view=playfab-rest
19 | var client : PlayFabClient = PlayFabClient.new()
20 |
21 | # Represents the PlayFab `Event` API
22 | # see https://docs.microsoft.com/en-us/rest/api/playfab/events/?view=playfab-rest
23 | var event: PlayFabEvent = PlayFabEvent.new()
24 |
25 |
26 | # Retrieves the `title_id` from `ProjectSettings`
27 | func _init():
28 | if ProjectSettings.has_setting(PlayFabConstants.SETTING_PLAYFAB_TITLE_ID) && ProjectSettings.get_setting(PlayFabConstants.SETTING_PLAYFAB_TITLE_ID) != "":
29 | title_id = ProjectSettings.get_setting(PlayFabConstants.SETTING_PLAYFAB_TITLE_ID)
30 | else:
31 | push_error("Title Id was not set in ProjectSettings: %s" % PlayFabConstants.SETTING_PLAYFAB_TITLE_ID)
32 |
33 |
34 | # Called when the node enters the scene tree for the first time.
35 | func _ready():
36 | var manager_process_mode := Node.PROCESS_MODE_INHERIT
37 | if ProjectSettings.has_setting(PlayFabConstants.SETTING_PLAYFAB_MANAGER_PROCESS_MODE):
38 | manager_process_mode = int(ProjectSettings.get_setting(PlayFabConstants.SETTING_PLAYFAB_MANAGER_PROCESS_MODE))
39 | set_process_mode(manager_process_mode)
40 | add_child(client)
41 | add_child(event)
42 | client_config = _client_config_loader.load(title_id)
43 |
44 |
45 | # Saves the client config to a file
46 | func save_client_config():
47 | _client_config_loader.save(title_id, client_config)
48 |
49 |
50 | # Forgets the login
51 | func forget_login():
52 | _client_config_loader.clear(title_id)
53 | reload()
54 |
55 |
56 | # Reloads the config
57 | func reload():
58 | client_config = _client_config_loader.load(title_id)
59 |
--------------------------------------------------------------------------------
/addons/godot-playfab/docs/README.md:
--------------------------------------------------------------------------------
1 | # Feature Overview
2 |
3 | `godot-playfab` is a Godot addon that provides easy access to the [PlayFab](https://playfab.com/)
4 | Game Backend-as-a-Service (BaaS) platform for Godot Engine games/applications.
5 |
6 |
7 |
8 |
9 |
10 | ## Authentication
11 | `godot-playfab` supports multiple authentication methods to log in users:
12 |
13 | - **Anonymous Login**: Allows users to log in without any credentials.
14 | - **Custom ID Login**: Users can log in using a custom identifier, with optional password.
15 | - **Steam Login**: Integrates with Steam for user authentication.
16 |
17 | It also takes care of some behind the scenes fundamentals:
18 | - **Session Management**: Manages session tokens and handles re-authentication as needed.
19 | - **Signal-Based Callbacks**: Signals for handling login success and failure.
20 | - **Example Implementations**: The example project includes implementations for each login method.
21 |
22 | ## Events & Analytics
23 | With the `PlayFabEvents` API, you can send telemetry and PlayStream events.
24 | These events can be used in PlayFab's Data & Analytics features.
25 |
26 | Events can be sent in two ways:
27 | - **Batched Send**: To save cost/requests, Events are batched and sent when a configured threshold is met or manually flushed.
28 | - **Direct Write**: Events are sent immediately, suitable for urgent events, mainly used for PlayStream events.
29 |
30 | ## Steam integration
31 | godot-playfab has a built-in integration with [GodotSteam](https://godotsteam.com/).
32 |
33 | > ⚠️ Steam integration is hosted on the [`integrate-steam`](https://github.com/Structed/godot-playfab/tree/integrate-steam) branch of the repository. Make sure to check it out if you want to use Steam login.
34 | >
35 | > You can easily enable Steam login in your game by following the steps in the [Steam integration](Steam/README.md) documentation.
36 |
37 |
38 | ## Example Project
39 | If you clone the full repository, you get a full example project where you can find out how different features
40 | are implemented.
41 |
42 | You can absolutely use the example project as a starting point for your own game! In fact, I encourage you to do so!
43 |
44 | Start with the default scene in `Scenes/Main.tscn` to see how to use `godot-playfab` in your game.
45 |
46 | ⬅️ [User Documentation](../README.md) | [Initial Setup](1-initial-setup.md) ➡️
47 |
--------------------------------------------------------------------------------
/addons/godot-playfab/docs/Steam/manual-integration.md:
--------------------------------------------------------------------------------
1 | ## Manual Setup
2 | > ⚠️ The following steps are there if you do not want to use the Steam integration provided in the `integrate-steam` branch.
3 |
4 | ### API
5 |
6 | The [PlayFab](/addons/godot-playfab/PlayFab.gd) Node class implements the `login_with_steam` function:
7 |
8 | ```gdscript
9 | func login_with_steam(steam_auth_ticket: String, is_auth_ticket_for_api: bool, create_account: bool, info_request_parameters: GetPlayerCombinedInfoRequestParams) -> void:
10 | ```
11 |
12 | - **steam_auth_ticket**: String that contains the steam authentication ticket in hexadecimal format
13 | - **is_auth_ticket_for_api**: `true` if the authentication ticket was generated for an API, `false` otherwise
14 | - **create_account**: `true` if you want that it to create a Player Account for your Title
15 | - **info_request_parameters**: Flags for which piece of info to return for the user
16 |
17 | ### Example
18 |
19 | This example below show you how to call the function seen above with the different callbacks needed.
20 |
21 | ```gdscript
22 | extends Node
23 |
24 | func _ready() -> void:
25 | PlayFabManager.client.logged_in.connect(_on_logged_in)
26 | PlayFabManager.client.api_error.connect(_on_api_error)
27 | PlayFabManager.client.server_error.connect(_on_server_error)
28 |
29 | func _on_logged_in(login_result: LoginResult) -> void:
30 | print("Playfab Login: %s" % login_result)
31 |
32 | func _on_api_error(error_wrapper: ApiErrorWrapper) -> void:
33 | print("Playfab API Error: %s" % error_wrapper.errorMessage)
34 |
35 | func _on_server_error(error_wrapper: ApiErrorWrapper) -> void:
36 | print("Playfab Server Error: %s" % error_wrapper.errorMessage)
37 |
38 | func login(ticket: String, is_auth_ticket_for_api: bool) -> void:
39 | var combined_info_request_params = GetPlayerCombinedInfoRequestParams.new()
40 | combined_info_request_params.show_all()
41 | var player_profile_view_constraints = PlayerProfileViewConstraints.new()
42 | combined_info_request_params.ProfileConstraints = player_profile_view_constraints
43 | PlayFabManager.client.login_with_steam(ticket, is_auth_ticket_for_api, true, combined_info_request_params)
44 | ```
45 |
46 | ### Example using **GodotSteam**
47 |
48 | Given that the example is a bit long, a dedicated page as been created for it and can be found [here](godot-steam-manual.md)
49 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Scenes/PlayFabMainScreen.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=2 format=3 uid="uid://5fda86xbwxbu"]
2 |
3 | [ext_resource type="Script" uid="uid://c1kf0y6v7e05n" path="res://addons/godot-playfab/Scenes/PlayFabMainScreen.gd" id="2"]
4 |
5 | [node name="MainScreen" type="Control"]
6 | layout_mode = 3
7 | anchors_preset = 15
8 | anchor_right = 1.0
9 | anchor_bottom = 1.0
10 | grow_horizontal = 2
11 | grow_vertical = 2
12 | size_flags_vertical = 3
13 | script = ExtResource("2")
14 |
15 | [node name="FileDialog" type="FileDialog" parent="."]
16 | filters = PackedStringArray("*.gd")
17 | show_hidden_files = true
18 |
19 | [node name="VBoxContainer" type="VBoxContainer" parent="."]
20 | layout_mode = 0
21 | anchor_right = 1.0
22 | anchor_bottom = 1.0
23 |
24 | [node name="Label" type="Label" parent="VBoxContainer"]
25 | layout_mode = 2
26 | text = "Convert Model Text to a model
27 |
28 | 1. Type in class name
29 | 2. Paste text below
30 | 3. Click \"Save Model\""
31 |
32 | [node name="ClassNameContainer" type="HBoxContainer" parent="VBoxContainer"]
33 | layout_mode = 2
34 |
35 | [node name="Label" type="Label" parent="VBoxContainer/ClassNameContainer"]
36 | layout_mode = 2
37 | text = "Class Name:"
38 |
39 | [node name="LineEdit" type="LineEdit" parent="VBoxContainer/ClassNameContainer"]
40 | layout_mode = 2
41 | size_flags_horizontal = 3
42 |
43 | [node name="SaveModel" type="Button" parent="VBoxContainer/ClassNameContainer"]
44 | layout_mode = 2
45 | text = "Save As"
46 |
47 | [node name="Button" type="Button" parent="VBoxContainer/ClassNameContainer"]
48 | layout_mode = 2
49 | text = "Save Direct"
50 |
51 | [node name="Input" type="TextEdit" parent="VBoxContainer"]
52 | layout_mode = 2
53 | size_flags_horizontal = 3
54 | size_flags_vertical = 3
55 |
56 | [node name="ErrorPopupDialog" type="Popup" parent="."]
57 |
58 | [node name="Label" type="Label" parent="ErrorPopupDialog"]
59 | anchors_preset = 8
60 | anchor_left = 0.5
61 | anchor_top = 0.5
62 | anchor_right = 0.5
63 | anchor_bottom = 0.5
64 | offset_left = -177.0
65 | offset_top = -38.0
66 | offset_right = 177.0
67 | offset_bottom = 38.0
68 | text = "Please first enter a class name!"
69 |
70 | [connection signal="pressed" from="VBoxContainer/ClassNameContainer/SaveModel" to="." method="_on_SaveModel_pressed"]
71 | [connection signal="pressed" from="VBoxContainer/ClassNameContainer/Button" to="." method="_on_save_direct_pressed"]
72 |
--------------------------------------------------------------------------------
/addons/godot-playfab/PlayFabEditor.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends EditorPlugin
3 |
4 | const MainPanel = preload("res://addons/godot-playfab/Scenes/PlayFabMainScreen.tscn")
5 |
6 | var main_panel_instance
7 |
8 | func _init():
9 |
10 | add_custom_project_setting(PlayFabConstants.SETTING_PLAYFAB_TITLE_ID, "", TYPE_STRING, PROPERTY_HINT_PLACEHOLDER_TEXT, "Retieve from PlayFab Game Manager")
11 |
12 | var process_mode_hint = ",".join([
13 | "Inherit:%d" % Node.PROCESS_MODE_INHERIT,
14 | "Disabled:%d" % Node.PROCESS_MODE_DISABLED,
15 | "Pausable:%d" % Node.PROCESS_MODE_PAUSABLE,
16 | "WhenPaused:%d" % Node.PROCESS_MODE_WHEN_PAUSED,
17 | "Always:%d" % Node.PROCESS_MODE_ALWAYS,
18 | ])
19 | add_custom_project_setting(PlayFabConstants.SETTING_PLAYFAB_MANAGER_PROCESS_MODE, Node.PROCESS_MODE_INHERIT, TYPE_INT, PROPERTY_HINT_ENUM, process_mode_hint)
20 |
21 | var error: int = ProjectSettings.save()
22 | if error: push_error("Encountered error %d when saving project settings." % error)
23 |
24 |
25 | func _enter_tree():
26 | add_autoload_singleton("PlayFabManager", "res://addons/godot-playfab/PlayFabManager.gd")
27 |
28 | main_panel_instance = MainPanel.instantiate()
29 | # Add the main panel to the editor's main viewport.
30 | get_editor_interface().get_editor_main_screen().add_child(main_panel_instance)
31 | # Hide the main panel. Very much required.
32 | _make_visible(false)
33 |
34 |
35 | func _exit_tree():
36 | remove_autoload_singleton("PlayFabManager")
37 |
38 | if main_panel_instance:
39 | main_panel_instance.queue_free()
40 |
41 | func _has_main_screen():
42 | return true
43 |
44 |
45 | func _make_visible(visible):
46 | if main_panel_instance:
47 | main_panel_instance.visible = visible
48 |
49 |
50 | func _get_plugin_name():
51 | return "PlayFab"
52 |
53 |
54 | func _get_plugin_icon():
55 | return load("res://addons/godot-playfab/icon_16x16.png")
56 |
57 |
58 | func add_custom_project_setting(name: String, default_value, type: int, hint: int = PROPERTY_HINT_NONE, hint_string: String = "") -> void:
59 |
60 | if ProjectSettings.has_setting(name): return
61 |
62 | var setting_info: Dictionary = {
63 | "name": name,
64 | "type": type,
65 | "hint": hint,
66 | "hint_string": hint_string
67 | }
68 |
69 | ProjectSettings.set_setting(name, default_value)
70 | ProjectSettings.add_property_info(setting_info)
71 | ProjectSettings.set_initial_value(name, default_value)
72 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/GetPlayerCombinedInfoRequestParams.gd:
--------------------------------------------------------------------------------
1 | extends JsonSerializable
2 | class_name GetPlayerCombinedInfoRequestParams
3 |
4 | # Whether to get character inventories. Defaults to false.
5 | var GetCharacterInventories: bool
6 |
7 | # Whether to get the list of characters. Defaults to false.
8 | var GetCharacterList: bool
9 |
10 | # Whether to get player profile. Defaults to false. Has no effect for a new player.
11 | var GetPlayerProfile: bool
12 |
13 | # Whether to get player statistics. Defaults to false.
14 | var GetPlayerStatistics: bool
15 |
16 | # Whether to get title data. Defaults to false.
17 | var GetTitleData: bool
18 |
19 | # Whether to get the player's account Info. Defaults to false
20 | var GetUserAccountInfo: bool
21 |
22 | # Whether to get the player's custom data. Defaults to false
23 | var GetUserData: bool
24 |
25 | # Whether to get the player's inventory. Defaults to false
26 | var GetUserInventory: bool
27 |
28 | # Whether to get the player's read only data. Defaults to false
29 | var GetUserReadOnlyData: bool
30 |
31 | # Whether to get the player's virtual currency balances. Defaults to false
32 | var GetUserVirtualCurrency: bool
33 |
34 | # Specific statistics to retrieve. Leave null to get all keys. Has no effect if GetPlayerStatistics is false
35 | var PlayerStatisticNames: Array
36 |
37 | # Specifies the properties to return from the player profile. Defaults to returning the player's display name.
38 | var ProfileConstraints: PlayerProfileViewConstraints
39 |
40 | # Specific keys to search for in the custom data. Leave null to get all keys. Has no effect if GetTitleData is false
41 | var TitleDataKeys: Array
42 |
43 | # Specific keys to search for in the custom data. Leave null to get all keys. Has no effect if GetUserData is false
44 | var UserDataKeys: Array
45 |
46 | # Specific keys to search for in the custom data. Leave null to get all keys. Has no effect if GetUserReadOnlyData is false
47 | var UserReadOnlyDataKeys: Array
48 |
49 | func show_all():
50 | GetCharacterInventories = true
51 | GetCharacterList = true
52 | GetPlayerProfile = true
53 | GetPlayerStatistics = true
54 | GetTitleData = true
55 | GetUserAccountInfo = true
56 | GetUserData = true
57 | GetUserInventory = true
58 | GetUserReadOnlyData = true
59 | GetUserVirtualCurrency = true
60 |
61 | # ProfileConstraints = ProfileConstraints.new()
62 | # ProfileConstraints.show_all()
63 |
--------------------------------------------------------------------------------
/addons/godot-playfab/docs/Events/2-sending.md:
--------------------------------------------------------------------------------
1 | # Sending Events
2 |
3 | There are multiple ways of how you can write events:
4 |
5 | * Batched Send
6 | * Direct write
7 |
8 | ## Batched Send
9 | should be used if you either want to:
10 | * Save HTTP overhead traffic (by sending fewer individual event write requests)
11 | * Ingest large numbers of events
12 |
13 | PlayFab has API limits for # of requests and size of events.
14 | Thus, it is **generally recommended** to batch events as much as possible to future-proof your game.
15 |
16 | ### Batching Defaults
17 | However, `godot-playfab` sets defaults default on batch-sending to provide an easy to sue user experience for game developers.
18 | You can change these defaults to fit your needs - see [Configuration](Configuration.md).
19 |
20 |
21 | ## Direct Write
22 | should only be used if you:
23 | * need to immediately send an event
24 | * are sure you are not sending too many events
25 |
26 | # API
27 | The [PlayFabEvent](/addons/godot-playfab/PlayFabEvent.gd) Node class covers the PlayFab Event API.
28 |
29 | Examples below assume:
30 | ```gdscript
31 | var payload = {
32 | "foo": "bar"
33 | }
34 | var event_name = "title_player_event"
35 | var callback = "_on_write_events_request_completed"
36 | ```
37 |
38 | > :warning: **event_name** must be non-empty and contain only alphanumeric letters, dash(-), and underscore(_).
39 |
40 | ## Batched Send
41 | > **Note**
42 | >
43 | > Events will be first batched, and only sent when:
44 | > * a [configured Flush Threshold](Configuration.md) is met , or
45 | > * the batch is [flushed manually](Flushing.md).
46 | >
47 | ### Telemetry Event
48 | Batch a Telemetry Event:
49 | ```gdscript
50 | $PlayFabEvent.batch_title_player_telemetry_event(event_name, payload, Callable(self, callback))
51 | ```
52 |
53 | ### PlayStream Event
54 | Batch a PlayStream Event:
55 | ```gdscript
56 | $PlayFabEvent.batch_title_player_playstream_event(event_name, payload, Callable(self, callback))
57 | ```
58 |
59 |
60 | ## Direct Write
61 |
62 | ### Telemetry Event
63 | Write a Telemetry Event:
64 | ````gdscript
65 | $PlayFabEvent.write_title_player_telemetry_event(event_name, payload, Callable(self, callback))
66 | ````
67 |
68 | ### PlayStream Event
69 | Write a PlayStream Event:
70 | ````gdscript
71 | $PlayFabEvent.write_title_player_playstream_event(event_name, payload, Callable(self, callback))
72 | ````
73 |
74 | ⬅️ [1 - Configuration for Events](1-configuration.md) | [3 - Flushing Events](3-flushing.md) ➡️
75 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/UserAccountInfo.gd:
--------------------------------------------------------------------------------
1 | extends JsonSerializable
2 | class_name UserAccountInfo
3 |
4 | # User Android device information, if an Android device has been linked
5 | var AndroidDeviceInfo#: UserAndroidDeviceInfo
6 |
7 | # Sign in with Apple account information, if an Apple account has been linked
8 | var AppleAccountInfo#: UserAppleIdInfo
9 |
10 | # Timestamp indicating when the user account was created
11 | var Created: String
12 |
13 | # Custom ID information, if a custom ID has been assigned
14 | var CustomIdInfo#: UserCustomIdInfo
15 |
16 | # User Facebook information, if a Facebook account has been linked
17 | var FacebookInfo#: UserFacebookInfo
18 |
19 | # Facebook Instant Games account information, if a Facebook Instant Games account has been linked
20 | var FacebookInstantGamesIdInfo#: UserFacebookInstantGamesIdInfo
21 |
22 | # User Gamecenter information, if a Gamecenter account has been linked
23 | var GameCenterInfo#: UserGameCenterInfo
24 |
25 | # User Google account information, if a Google account has been linked
26 | var GoogleInfo#: UserGoogleInfo
27 |
28 | # User iOS device information, if an iOS device has been linked
29 | var IosDeviceInfo#: UserIosDeviceInfo
30 |
31 | # User Kongregate account information, if a Kongregate account has been linked
32 | var KongregateInfo#: UserKongregateInfo
33 |
34 | # Nintendo Switch account information, if a Nintendo Switch account has been linked
35 | var NintendoSwitchAccountInfo#: UserNintendoSwitchAccountIdInfo
36 |
37 | # Nintendo Switch device information, if a Nintendo Switch device has been linked
38 | var NintendoSwitchDeviceIdInfo#: UserNintendoSwitchDeviceIdInfo
39 |
40 | # OpenID Connect information, if any OpenID Connect accounts have been linked
41 | var OpenIdInfo: Array
42 |
43 | # Unique identifier for the user account
44 | var PlayFabId: String
45 |
46 | # Personal information for the user which is considered more sensitive
47 | var PrivateInfo#: UserPrivateAccountInfo
48 |
49 | # User PSN account information, if a PSN account has been linked
50 | var PsnInfo#: UserPsnInfo
51 |
52 | # User Steam information, if a Steam account has been linked
53 | var SteamInfo#: UserSteamInfo
54 |
55 | # Title-specific information for the user account
56 | var TitleInfo: UserTitleInfo
57 |
58 | # User Twitch account information, if a Twitch account has been linked
59 | var TwitchInfo#: UserTwitchInfo
60 |
61 | # User account name in the PlayFab service
62 | var Username: String
63 |
64 | # User XBox account information, if a XBox account has been linked
65 | var XboxInfo#: UserXboxInfo
66 |
67 | func _get_type_for_property(property_name: String) -> String:
68 | match property_name:
69 | "TitleInfo":
70 | return "UserTitleInfo"
71 |
72 | push_error("Could not find mapping for property: " + property_name)
73 | return super._get_type_for_property(property_name)
74 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Models/EntityKey.gd:
--------------------------------------------------------------------------------
1 | extends JsonSerializable
2 | class_name EntityKey
3 |
4 | ## Combined entity type and ID structure which uniquely identifies a single entity.
5 | ## See: https://docs.microsoft.com/en-us/gaming/playfab/features/data/entities/available-built-in-entity-types
6 | ## Entity keys identify entities in most of the newer API methods.
7 | ## You use the value of the EntityKey.Type field to determine the type of value to set in the ID field.
8 | ## Note:
9 | ## ENTITY KEYS ARE CASE SENSITIVE.
10 |
11 | ## The namespace entity refers to all global information for all titles within your studio.
12 | ## Note:
13 | ## Changes to this entity are not reflected in real time.
14 | ## Set the ID field to your game's Publisher ID. To retrieve your Publisher ID:
15 | ## * Sign in to Game Manager.
16 | ## * In the upper left-hand corner of Game Manger, select the gear icon.
17 | ## * Select Title Settings.
18 | ## * Select API Features.
19 | ## The Publisher ID is displayed in the API ACCESS section.
20 | const ENTITY_TYPE_NAMESPACE = "namespace"
21 |
22 | ## The title entity refers to all global information for that title.
23 | ## Note:
24 | ## Changes to this entity are not reflected in real time.
25 | ## Set the ID field to your game's Title ID. To retrieve the Title ID:
26 | ## * Sign in to Game Manager.
27 | ## * In the upper left-hand corner of Game Manger, select the gear icon.
28 | ## * Select Title Settings.
29 | ## * Select API Features.
30 | ## The Title ID is displayed in the API ACCESS section.
31 | const ENTITY_TYPE_TITLE = "title"
32 |
33 | ## The master_player_account is a player entity that is shared by all titles within your studio.
34 | ## Set the ID field to the LoginResult.PlayFabId from the classic API. To retrieve the LoginResult, call one of the login methods in Client Authentication.
35 | const ENTITY_TYPE_MASTER_PLAYER_ACCOUNT = "master_player_account"
36 |
37 | ## For most developers, title_player_account represents the player in the most traditional way.
38 | ## Set the ID field to LoginResult.EntityToken.Entity.Id in the client API, or GetEntityTokenResponse.Entity.Id in the authentication API.
39 | ## To retrieve the LoginResult, call one of the login methods in Client Authentication. To retrieve the GetEntityTokenResponse, call Get Entity Token.
40 | const ENTITY_TYPE_TITLE_PLAYER_ACCOUNT = "title_player_account"
41 |
42 | ## The character entity is a sub-entity of title_player_account, and is a direct mirror of Characters in the Classic APIs.
43 | ## Set the ID field to any characterId from result.Characters[i].CharacterId.
44 | const ENTITY_TYPE_CHARACTER = "character"
45 |
46 | ## The group entity is a container for other entities. It is currently limited to players and characters.
47 | ## Set the ID field to the result.Group.Id if you are creating a group, or the result.Groups[i].Group.Id when listing your memberships.
48 | const ENTITY_TYPE_GROUP = "group"
49 |
50 | ## The service entity is reserved for internal use.
51 | const ENTITY_TYPE_SERVICE = "service"
52 |
53 | # Unique ID of the entity.
54 | var Id: String
55 |
56 | # Entity type - one of ENTITY_TYPE_*. See https://docs.microsoft.com/gaming/playfab/features/data/entities/available-built-in-entity-types
57 | var Type: String
58 |
59 |
--------------------------------------------------------------------------------
/addons/godot-playfab/Scenes/PlayFabMainScreen.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends Control
3 |
4 | var editor_resource_filesystem_cached
5 |
6 | func _ready():
7 | # Needed, so can ater refresh the "FileSystem" panel of the Editor
8 | editor_resource_filesystem_cached = EditorPlugin.new().get_editor_interface().get_resource_filesystem()
9 |
10 | func _on_SaveModel_pressed():
11 |
12 | if !guard_class_name_set():
13 | return
14 |
15 | var file_dialog = $FileDialog
16 | file_dialog.current_file = $VBoxContainer/ClassNameContainer/LineEdit.text + ".gd"
17 | file_dialog.show()
18 | file_dialog.connect("file_selected",Callable(self,"_on_file_selected").bind(),CONNECT_ONE_SHOT)
19 |
20 |
21 | func _on_save_direct_pressed():
22 |
23 | if !guard_class_name_set():
24 | return
25 |
26 | var file_name = $VBoxContainer/ClassNameContainer/LineEdit.text + ".gd"
27 | var file_path = "res://addons/godot-playfab/Models/" + file_name
28 | _on_file_selected(file_path)
29 |
30 |
31 | func _on_file_selected(file_path: String):
32 |
33 | var model = to_model($VBoxContainer/ClassNameContainer/LineEdit.text, $VBoxContainer/Input.text)
34 | var file = FileAccess.open(file_path, FileAccess.WRITE)
35 | file.store_string(model)
36 |
37 | # Refresh the "FileSystem" panel
38 | editor_resource_filesystem_cached.scan()
39 |
40 | print("Saved model to file path: \"%s\"" % file_path)
41 |
42 |
43 | func guard_class_name_set() -> bool:
44 | if $VBoxContainer/ClassNameContainer/LineEdit.text.is_empty():
45 | $ErrorPopupDialog/Label.text = "Please first enter a Class Name!"
46 | $ErrorPopupDialog.popup_centered(Vector2(0,0))
47 | return false
48 |
49 | return true
50 |
51 |
52 | static func to_model(object_name: String, input: String) -> String:
53 | var lines = input.split("\n", true)
54 | lines.push_back("") # Hack: add an empty line at the bottom so below logic works & is simpler :-) Otherwise, the last prop would not be written
55 |
56 | var props = []
57 | var current_prop = ""
58 | var prop_line = 0
59 | for line in lines:
60 |
61 | var str_line = (line as String).strip_edges()
62 |
63 | if not str_line.is_empty():
64 | match prop_line:
65 | 0: # Variable name
66 | current_prop = "var " + str_line
67 | 1: # Type
68 | str_line = fix_type(str_line)
69 | if not str_line.is_empty() and not str_line.begins_with("#"):
70 | current_prop += ": %s" % str_line
71 | else:
72 | current_prop = str_line
73 | 2: # Comment
74 | current_prop = "# %s\n%s" % [str_line, current_prop]
75 |
76 | prop_line += 1
77 | else:
78 | props.append(current_prop)
79 | prop_line = 0
80 |
81 | var model = "extends JsonSerializable\nclass_name " + object_name + "\n\n"
82 | for prop in props:
83 | model += prop + "\n\n"
84 |
85 | # TODO: Find a way to generate the mapping for props automatically!
86 | model += """
87 | func _get_type_for_property(property_name: String) -> String:
88 | match property_name:
89 | # "":
90 | # return ""
91 | _:
92 | pass
93 |
94 | push_error("Could not find mapping for property: " + property_name)
95 | return super._get_type_for_property(property_name)
96 |
97 | """
98 | return model
99 |
100 | static func fix_type(type: String) -> String:
101 |
102 | match type:
103 | "string":
104 | return "String"
105 | "boolean":
106 | return "bool"
107 | _:
108 | if type.ends_with("]"):
109 | return "Array"
110 | return type
111 |
--------------------------------------------------------------------------------
/addons/godot-playfab/PlayFabClientConfig/PlayFabClientConfigLoader.gd:
--------------------------------------------------------------------------------
1 | extends RefCounted
2 | class_name PlayFabClientConfigLoader
3 |
4 | # Handles serialization/deserialization of a `PlayFabClientConfig` to `ConfigFile`
5 |
6 |
7 | # Whether to encrypt the config file
8 | # Will only work in Debug Mode
9 | const DEBUG_DO_NOT_ENCRYPT = false # Only works checked debug builds
10 |
11 | # Section to write key/value paris to
12 | const SECTION_NAME = "PlayFab"
13 |
14 | # **Accessibility: protected/virtual**
15 | # Path3D to load/save the ConfigFile from
16 | # Should only be overridden in tests or extending classes
17 | var _load_path = "user://playfab_client_config.cfg"
18 |
19 | # **Accessibility: private**
20 | # Holds the ConfigFile instance
21 | var _config: ConfigFile = ConfigFile.new()
22 |
23 | # An errors array.
24 | # Will get filled if there are errors during loading of the ConfigFile
25 | var errors = []
26 |
27 |
28 | # Saves properties from @paramref `new_config`
29 | # to an encrypted ConfigFile
30 | # @param password: String - The password used to encrypt the ConfigFile
31 | # @param new_config: PlayFabClientConfig - Object to retrieve key/values from
32 | func save(password: String, new_config: PlayFabClientConfig):
33 | _set_values(new_config)
34 | _save(password)
35 |
36 |
37 | # Actual file system save logic
38 | # @param password: String - The password used to encrypt the ConfigFile
39 | func _save(password: String):
40 | if OS.is_debug_build() && DEBUG_DO_NOT_ENCRYPT:
41 | _config.save(_load_path)
42 | else:
43 | _config.save_encrypted_pass(_load_path, password)
44 |
45 |
46 | # Loads an encrypted ConfigFile from disk and returns a `PlayFabClientConfig`
47 | # with all properties set from values of ConfigFile
48 | # @paramref password: String - Password used for file enxryption
49 | func load(password: String) -> PlayFabClientConfig:
50 | _config = ConfigFile.new()
51 | var new_config = PlayFabClientConfig.new()
52 | var err: int
53 |
54 | if OS.is_debug_build() && DEBUG_DO_NOT_ENCRYPT:
55 | err = _config.load(_load_path)
56 | else:
57 | err = _config.load_encrypted_pass(_load_path, password)
58 |
59 | # If the file didn't load, ignore it.
60 | if err != OK:
61 | if err == ERR_FILE_NOT_FOUND:
62 | print_debug("No config file found. After login, it will be created at \"%s\"." % _load_path)
63 | else:
64 | var error_message = "Config file didn't load. Error code: %f" % err
65 | print_debug(error_message)
66 | errors.append(error_message)
67 |
68 | return PlayFabClientConfig.new()
69 |
70 | _get_values(new_config)
71 |
72 | return new_config
73 |
74 |
75 | # Clears the config\
76 | # @param password: String - The password used to encrypt the ConfigFile
77 | func clear(password: String):
78 | _config.clear()
79 | _save(password)
80 |
81 |
82 | # Sets all property values from @paramref `new_config`checked `_config`
83 | # @param new_config: PlayFabClientConfig - object to get property values from
84 | func _set_values(new_config: PlayFabClientConfig):
85 | var props = new_config.get_property_list()
86 |
87 | for i in range(3, props.size()):
88 | var name = props[i].name
89 | _config.set_value(SECTION_NAME, name, new_config.get(name))
90 |
91 |
92 | # Retrieves all keys from ConfigFile and sets them
93 | # checked the corresponding properties of @paramref `new_config`
94 | # @param new_config: PlayFabClientConfig - object to set properties checked
95 | func _get_values(new_config: PlayFabClientConfig):
96 | var props = new_config.get_property_list()
97 |
98 | for i in range(3, props.size()):
99 | var name = props[i].name
100 | if _config.has_section_key(SECTION_NAME, name):
101 | var value = _config.get_value(SECTION_NAME, name)
102 | new_config.set(name, value)
103 |
--------------------------------------------------------------------------------
/addons/godot-playfab/JsonSerializable.gd:
--------------------------------------------------------------------------------
1 | extends RefCounted
2 | class_name JsonSerializable
3 |
4 | # **VIRTUAL**
5 | #
6 | # Returns the type of a property of this class
7 | # Will **always** `push_error` and return an empty string in this base class! Should be overridden.
8 | # @param property_name: String - The name of the property to lookup a type for
9 | # @returns - The type's name
10 | func _get_type_for_property(property_name: String):
11 | push_error("No mapping for property " + property_name)
12 | return ""
13 |
14 | # Marshals an object - recursively - into a dictionary
15 | # @returns Dictionary - A Dictionary representation of this object instance
16 | func to_dict() -> Dictionary:
17 |
18 | var dict = {}
19 | var props = get_property_list()
20 |
21 | # Skipping the first 3 items because they are metadata we do not need
22 | for prop in props:
23 | var name = prop["name"] # The name of the property on the object. Will be used to access its's value
24 | var type = prop["type"] # The godot built-in type (Array, Object etc)
25 | var usage = prop["usage"] # is a combination of PropertyUsageFlags.
26 |
27 | # If it's not PROPERTY_USAGE_SCRIPT_VARIABLE, it's not an actual property and we can ignore it
28 | if (usage & PROPERTY_USAGE_SCRIPT_VARIABLE) != PROPERTY_USAGE_SCRIPT_VARIABLE:
29 | continue
30 |
31 | if (type == TYPE_OBJECT):
32 | # Get the value of the property
33 | var sub_prop = get(name)
34 | if sub_prop == null:
35 | # Actually set null if null
36 | dict[name] = null
37 | elif sub_prop.has_method("to_dict"):
38 | # Handle recursive property
39 | dict[name] = sub_prop.to_dict()
40 | else:
41 | var type_name = sub_prop.get_class()
42 | # No to_dict method - likely an error!
43 | # If it is a builtin class, however, a special handler needs to be iomplemented here.
44 | #push_error("If '%s' is not a builtin class, please implement a to_dict() method! If it IS a builtin class, a special handler needs to be implemented in JsonSerializable." % type_name)
45 | print_debug("If '%s' is not a builtin class, please implement a to_dict() method! If it IS a builtin class, a special handler needs to be implemented in JsonSerializable." % type_name)
46 | dict[name] = type_name
47 | else:
48 | # Get the value of the property
49 | dict[name] = get(name)
50 |
51 | return dict
52 |
53 | # Demarshals a Dictionary - recursively - into an object of a specific class instance.
54 | # @param data: Dictionary - The Dictionary to demarshal
55 | # @param instance: JsonSerializable: An instance of a class implementing JsonSerializable.
56 | # @returns void
57 | func from_dict(data: Dictionary, instance: JsonSerializable):
58 |
59 | var props = instance.get_property_list()
60 | for key in data.keys():
61 |
62 | var type
63 | for prop in props:
64 | if prop["name"] == key:
65 | type = prop["type"]
66 | break
67 |
68 | # If basic data type - just set it
69 | if type != TYPE_OBJECT:
70 | instance.set(key, data[key])
71 | elif data[key] == null:
72 | instance.set(key, null)
73 | else:
74 | # If object data type, instantiate object
75 | var type_name = instance._get_type_for_property(key)
76 | var nested_instance = instance.get_class_instance(type_name)
77 | # fill properties
78 | nested_instance.from_dict(data[key], nested_instance)
79 | instance.set(key, nested_instance)
80 |
81 |
82 | # Instantiate a class by name
83 | # @param name: String - A class name
84 | # @returns RefCounted - The instance reference
85 | func get_class_instance(name: String):
86 | var config = ConfigFile.new()
87 | config.load("res://.godot/global_script_class_cache.cfg")
88 | var classes = config.get_value('', 'list', [])
89 | for element in classes:
90 | if element["class"] == name:
91 | return load(element["path"]).new()
92 |
93 |
94 | push_error("Class \"" + name + "\" could not be found")
95 | return null
96 |
--------------------------------------------------------------------------------
/addons/godot-playfab/PlayFabHttp.gd:
--------------------------------------------------------------------------------
1 | @icon("res://addons/godot-playfab/icon.png")
2 |
3 | extends Node
4 | class_name PlayFabHttp
5 |
6 |
7 | ## Emitted when a JSON parse error occurs. Will receive a JSONResult as parameter.
8 | ## @param json_result: JSONResult
9 | signal json_parse_error(json_result)
10 |
11 | ## Emitted when a PlayFab API (HTTP status code 4xx) error occurs. Will receive a LoginResult as parameter.
12 | ## @param api_error_wrapper: ApiErrorWrapper
13 | signal api_error(api_error_wrapper)
14 |
15 | ## Emitted when a Server Error (HTTP status code 5xx) occurs when querying PlayFab. Will receive the request path as parameter.
16 | ## @param path: String
17 | signal server_error(path)
18 |
19 |
20 | var _http: HTTPRequest
21 | var _request_in_progress = false
22 | var _title_id: String
23 | var _base_uri = "playfabapi.com"
24 | var _response_compression_enabled = true # Whether to use response compression (gzip). If false, will send no `Accept-Encoding` header. If true, An `Accept-Encoding: gzip` header will be sent, and responses decoded with gzip.
25 | var _response_compression_max_output_bytes = -1 # -1 is unlimited, but this could be very large! If you change this, be aware there is no error handling implemented to catch if the output size is too small! See https://docs.godotengine.org/en/3.5/classes/class_poolbytearray.html#class-poolbytearray-method-decompress-dynamic
26 |
27 |
28 | func _ready():
29 | _http = HTTPRequest.new()
30 | add_child(_http)
31 |
32 |
33 | func _dict_to_header_array(dict: Dictionary):
34 | if dict.size() < 1:
35 | return []
36 |
37 | var array = []
38 | for key in dict.keys():
39 | var value = "%s: %s" % [key, dict[key]]
40 | array.append(value)
41 |
42 | return array
43 |
44 |
45 | func _get_api_url() -> String:
46 | return "https://%s.%s" % [ _title_id, _base_uri ]
47 |
48 |
49 | func _http_request(request_method: int, body: Dictionary, path: String, callback: Callable, additional_headers: Dictionary = {}):
50 | # Create a new HTTPRequest instance for each request
51 | var http_request = HTTPRequest.new()
52 | add_child(http_request)
53 |
54 | var json = JSON.stringify(body)
55 | var headers = [
56 | "Content-Type: application/json",
57 | "Content-Length: " + str(json.length()),
58 | ]
59 |
60 | if _response_compression_enabled:
61 | headers.append("Accept-Encoding: gzip")
62 |
63 | headers.append_array(_dict_to_header_array(additional_headers))
64 |
65 | var request_uri = "%s%s" % [ _get_api_url(), path]
66 | var error = http_request.request(request_uri, headers, request_method, json)
67 |
68 | if error != OK:
69 | push_error("An error occurred in the HTTP request.")
70 | return
71 |
72 | # Use await to wait for *this specific request* to complete
73 | var args = await http_request.request_completed
74 |
75 | # After the request completes, remove the node
76 | http_request.queue_free()
77 |
78 | var response_result = args[0] as int
79 | var response_code = args[1] as int
80 | var response_headers = args[2] as PackedStringArray
81 | var response_body = args[3] as PackedByteArray
82 |
83 | var response_body_string = response_body.get_string_from_utf8()
84 | var test_json_conv = JSON.new()
85 | var parse_error = test_json_conv.parse(response_body_string)
86 | var json_parse_result = test_json_conv.data
87 |
88 | if parse_error != OK:
89 | emit_signal("json_parse_error", json_parse_result)
90 | return
91 | if response_code >= 200 and response_code < 400:
92 | if callback != null:
93 | if callback.is_valid():
94 | callback.call(json_parse_result)
95 | else:
96 | push_error("Response calback " + callback.get_method() + " is no longer valid! Make sure, a script is only removed after all requests returned!")
97 | return
98 | elif response_code >= 400:
99 | var apiErrorWrapper = ApiErrorWrapper.new()
100 | for key in json_parse_result.keys():
101 | apiErrorWrapper.set(key, json_parse_result[key])
102 | emit_signal("api_error", apiErrorWrapper)
103 | return
104 | if response_code >= 500:
105 | emit_signal("server_error", path)
106 | return
107 |
--------------------------------------------------------------------------------
/addons/godot-playfab/lib/binogure-studio/godot-uuid/uuid.gd:
--------------------------------------------------------------------------------
1 | # Note: The code might not be as pretty it could be, since it's written
2 | # in a way that maximizes performance. Methods are inlined and loops are avoided.
3 | class_name UUID extends Node
4 |
5 | const BYTE_MASK: int = 0b11111111
6 |
7 | static func uuidbin():
8 | randomize()
9 | # 16 random bytes with the bytes on index 6 and 8 modified
10 | return [
11 | randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK,
12 | randi() & BYTE_MASK, randi() & BYTE_MASK, ((randi() & BYTE_MASK) & 0x0f) | 0x40, randi() & BYTE_MASK,
13 | ((randi() & BYTE_MASK) & 0x3f) | 0x80, randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK,
14 | randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK,
15 | ]
16 |
17 | static func uuidbinrng(rng: RandomNumberGenerator):
18 | rng.randomize()
19 | return [
20 | rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK,
21 | rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, ((rng.randi() & BYTE_MASK) & 0x0f) | 0x40, rng.randi() & BYTE_MASK,
22 | ((rng.randi() & BYTE_MASK) & 0x3f) | 0x80, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK,
23 | rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK,
24 | ]
25 |
26 | static func v4():
27 | # 16 random bytes with the bytes on index 6 and 8 modified
28 | var b = uuidbin()
29 | return '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x' % [
30 | # low
31 | b[0], b[1], b[2], b[3],
32 |
33 | # mid
34 | b[4], b[5],
35 |
36 | # hi
37 | b[6], b[7],
38 |
39 | # clock
40 | b[8], b[9],
41 |
42 | # clock
43 | b[10], b[11], b[12], b[13], b[14], b[15]
44 | ]
45 |
46 | static func v4_rng(rng: RandomNumberGenerator):
47 | # 16 random bytes with the bytes on index 6 and 8 modified
48 | var b = uuidbinrng(rng)
49 | return '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x' % [
50 | # low
51 | b[0], b[1], b[2], b[3],
52 |
53 | # mid
54 | b[4], b[5],
55 |
56 | # hi
57 | b[6], b[7],
58 |
59 | # clock
60 | b[8], b[9],
61 |
62 | # clock
63 | b[10], b[11], b[12], b[13], b[14], b[15]
64 | ]
65 |
66 | var _uuid: Array
67 |
68 | func _init(rng := RandomNumberGenerator.new()) -> void:
69 | _uuid = uuidbinrng(rng)
70 |
71 | func as_array() -> Array:
72 | return _uuid.duplicate()
73 |
74 | func as_dict(big_endian := true) -> Dictionary:
75 | if big_endian:
76 | return {
77 | "low" : (_uuid[0] << 24) + (_uuid[1] << 16) + (_uuid[2] << 8 ) + _uuid[3],
78 | "mid" : (_uuid[4] << 8 ) + _uuid[5],
79 | "hi" : (_uuid[6] << 8 ) + _uuid[7],
80 | "clock": (_uuid[8] << 8 ) + _uuid[9],
81 | "node" : (_uuid[10] << 40) + (_uuid[11] << 32) + (_uuid[12] << 24) + (_uuid[13] << 16) + (_uuid[14] << 8 ) + _uuid[15]
82 | }
83 | else:
84 | return {
85 | "low" : _uuid[0] + (_uuid[1] << 8 ) + (_uuid[2] << 16) + (_uuid[3] << 24),
86 | "mid" : _uuid[4] + (_uuid[5] << 8 ),
87 | "hi" : _uuid[6] + (_uuid[7] << 8 ),
88 | "clock": _uuid[8] + (_uuid[9] << 8 ),
89 | "node" : _uuid[10] + (_uuid[11] << 8 ) + (_uuid[12] << 16) + (_uuid[13] << 24) + (_uuid[14] << 32) + (_uuid[15] << 40)
90 | }
91 |
92 | func as_string() -> String:
93 | return '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x' % [
94 | # low
95 | _uuid[0], _uuid[1], _uuid[2], _uuid[3],
96 |
97 | # mid
98 | _uuid[4], _uuid[5],
99 |
100 | # hi
101 | _uuid[6], _uuid[7],
102 |
103 | # clock
104 | _uuid[8], _uuid[9],
105 |
106 | # node
107 | _uuid[10], _uuid[11], _uuid[12], _uuid[13], _uuid[14], _uuid[15]
108 | ]
109 |
110 | func is_equal(other) -> bool:
111 | # Godot Engine compares Array recursively
112 | # There's no need for custom comparison here.
113 | return _uuid == other._uuid
114 |
--------------------------------------------------------------------------------
/addons/godot-playfab/lib/binogure-studio/godot-uuid/test.gd:
--------------------------------------------------------------------------------
1 | extends SceneTree
2 |
3 | # To run this script
4 | # godot -s test.gd
5 |
6 | const NUMBER_OF_TESTS = 500000
7 | const NUMBER_OF_OBJECTS = 50000 # enough to test and to not run out of memory
8 |
9 | var uuid_util = preload('uuid.gd')
10 |
11 | func benchmark_raw():
12 | print('Benchmarking ...')
13 |
14 | var begin = Time.get_unix_time_from_system()
15 |
16 | var index := 0
17 | while index < NUMBER_OF_TESTS:
18 | uuid_util.v4()
19 | index += 1
20 |
21 | var duration = 1.0 * Time.get_unix_time_from_system() - begin
22 |
23 | print('uuid/sec: %.02f avg time: %.4fus total time: %.2fs' % [
24 | NUMBER_OF_TESTS / duration,
25 | (duration / NUMBER_OF_TESTS) * 1000000,
26 | duration
27 | ])
28 | print('Benchmark done')
29 |
30 | func benchmark_raw_rng():
31 | print('Benchmarking ...')
32 |
33 | var rng = RandomNumberGenerator.new()
34 | var begin = Time.get_unix_time_from_system()
35 | var index = 0
36 |
37 | while index < NUMBER_OF_TESTS:
38 | uuid_util.v4_rng(rng)
39 | index += 1
40 |
41 | var duration = 1.0 * Time.get_unix_time_from_system() - begin
42 |
43 | print('uuid/sec: %.02f avg time: %.4fus total time: %.2fs' % [
44 | NUMBER_OF_TESTS / duration,
45 | (duration / NUMBER_OF_TESTS) * 1000000,
46 | duration
47 | ])
48 | print('Benchmark done')
49 |
50 | func benchmark_obj():
51 | print('Benchmarking ...')
52 |
53 | var begin = Time.get_unix_time_from_system()
54 | var index = 0
55 |
56 | while index < NUMBER_OF_TESTS:
57 | uuid_util.new().free() # immediately freeing does not seem to add much overhead
58 | index += 1
59 |
60 | var duration = 1.0 * Time.get_unix_time_from_system() - begin
61 |
62 | print('uuid/sec: %.02f avg time: %.4fus total time: %.2fs' % [
63 | NUMBER_OF_TESTS / duration,
64 | (duration / NUMBER_OF_TESTS) * 1000000,
65 | duration
66 | ])
67 | print('Benchmark done')
68 |
69 | func benchmark_obj_rng():
70 | print('Benchmarking ...')
71 |
72 | var rng = RandomNumberGenerator.new()
73 | var begin = Time.get_unix_time_from_system()
74 | var index = 0
75 |
76 | while index < NUMBER_OF_TESTS:
77 | uuid_util.new(rng).free() # immediately freeing does not seem to add much overhead
78 | index += 1
79 |
80 | var duration = 1.0 * Time.get_unix_time_from_system() - begin
81 |
82 | print('uuid/sec: %.02f avg time: %.4fus total time: %.2fs' % [
83 | NUMBER_OF_TESTS / duration,
84 | (duration / NUMBER_OF_TESTS) * 1000000,
85 | duration
86 | ])
87 | print('Benchmark done')
88 |
89 | func benchmark_obj_to_dict():
90 | print('Setting up benchmark ...')
91 | var uuids = []
92 | var index = 0
93 |
94 | while index < NUMBER_OF_OBJECTS:
95 | uuids.push_back(uuid_util.new())
96 | index += 1
97 |
98 | print('Benchmarking ...')
99 | var begin = Time.get_unix_time_from_system()
100 |
101 | for uuid in uuids:
102 | uuid.as_dict()
103 |
104 | var duration = 1.0 * Time.get_unix_time_from_system() - begin
105 |
106 | print('uuid/sec: %.02f avg time: %.4fus total time: %.2fs' % [
107 | NUMBER_OF_OBJECTS / duration,
108 | (duration / NUMBER_OF_OBJECTS) * 1000000,
109 | duration
110 | ])
111 | print('Cleaning up ...')
112 |
113 | for uuid in uuids:
114 | uuid.free()
115 | print('Benchmark done')
116 |
117 | func benchmark_obj_to_str():
118 | print('Setting up benchmark ...')
119 | var uuids = []
120 | var index = 0
121 |
122 | while index < NUMBER_OF_OBJECTS:
123 | uuids.push_back(uuid_util.new())
124 | index += 1
125 |
126 | print('Benchmarking ...')
127 | var begin = Time.get_unix_time_from_system()
128 |
129 | for uuid in uuids:
130 | uuid.as_string()
131 |
132 | var duration = 1.0 * Time.get_unix_time_from_system() - begin
133 |
134 | print('uuid/sec: %.02f avg time: %.4fus total time: %.2fs' % [
135 | NUMBER_OF_OBJECTS / duration,
136 | (duration / NUMBER_OF_OBJECTS) * 1000000,
137 | duration
138 | ])
139 | print('Cleaning up ...')
140 |
141 | for uuid in uuids:
142 | uuid.free()
143 |
144 | print('Benchmark done')
145 |
146 | func benchmark_comp_raw():
147 | print('Setting up benchmark ...')
148 | var uuids = []
149 | var index = 0
150 |
151 | while index < NUMBER_OF_OBJECTS:
152 | uuids.push_back(uuid_util.v4())
153 | index += 1
154 |
155 | index = 0
156 |
157 | var collisions = 0
158 |
159 | print('Benchmarking ...')
160 | var begin = Time.get_unix_time_from_system()
161 |
162 | while index < (NUMBER_OF_OBJECTS - 1):
163 | var uuid1 = uuids[index]
164 | var sub_index = index + 1
165 |
166 | while sub_index < NUMBER_OF_OBJECTS:
167 | if uuid1 == uuids[sub_index]:
168 | # Don't print anything since it slows down the benchmark
169 | collisions += 1
170 |
171 | sub_index += 1
172 | index += 1
173 |
174 | var duration = 1.0 * Time.get_unix_time_from_system() - begin
175 |
176 | print("%s collisions detected" % [collisions])
177 | print("%s total comparison operations" % [NUMBER_OF_OBJECTS])
178 | print('uuid/sec: %.02f avg time: %.4fus total time: %.2fs' % [
179 | NUMBER_OF_OBJECTS / duration,
180 | (duration / NUMBER_OF_OBJECTS) * 1000000,
181 | duration
182 | ])
183 | print('Benchmark done')
184 |
185 | func benchmark_comp_obj():
186 | print('Setting up benchmark ...')
187 | var uuids = []
188 | var index = 0
189 |
190 | while index < NUMBER_OF_OBJECTS:
191 | uuids.push_back(uuid_util.new())
192 | index += 1
193 |
194 | index = 0
195 | var collisions = 0
196 |
197 | print('Benchmarking ...')
198 | var begin = Time.get_unix_time_from_system()
199 |
200 | while index < (NUMBER_OF_OBJECTS - 1):
201 | var uuid1 = uuids[index]
202 | var sub_index = index + 1
203 |
204 | while sub_index < NUMBER_OF_OBJECTS:
205 | if uuid1.is_equal(uuids[sub_index]):
206 | # Don't print anything since it slows down the benchmark
207 | collisions += 1
208 |
209 | sub_index += 1
210 | index += 1
211 |
212 | var duration = 1.0 * Time.get_unix_time_from_system() - begin
213 |
214 | print("%s collisions detected" % [collisions])
215 | print("%s total comparison operations" % [NUMBER_OF_OBJECTS])
216 | print('uuid/sec: %.02f avg time: %.4fus total time: %.2fs' % [
217 | NUMBER_OF_OBJECTS / duration,
218 | (duration / NUMBER_OF_OBJECTS) * 1000000,
219 | duration
220 | ])
221 | print('Cleaning up ...')
222 |
223 | for uuid in uuids:
224 | uuid.free()
225 |
226 | print('Benchmark done')
227 |
228 | func detect_collision():
229 | print('Detecting collision ...')
230 |
231 | var number_of_collision = 0
232 | var generated_uuid = {}
233 | var index = 0
234 |
235 | while index < NUMBER_OF_TESTS:
236 | var key = uuid_util.v4()
237 |
238 | if generated_uuid.has(key):
239 | number_of_collision += 1
240 |
241 | else:
242 | generated_uuid[key] = true
243 |
244 | index += 1
245 |
246 | print('Number of collision: %s' % [ number_of_collision ])
247 | print('Collision detection done')
248 |
249 | func detect_collision_with_rng():
250 | print('Detecting collision with rng ...')
251 |
252 | var rng = RandomNumberGenerator.new()
253 | var number_of_collision = 0
254 | var generated_uuid = {}
255 | var index = 0
256 |
257 | while index < NUMBER_OF_TESTS:
258 | var key = uuid_util.v4_rng(rng)
259 |
260 | if generated_uuid.has(key):
261 | number_of_collision += 1
262 |
263 | else:
264 | generated_uuid[key] = true
265 |
266 | index += 1
267 |
268 | print('Number of collision: %s' % [ number_of_collision ])
269 | print('Collision detection done')
270 |
271 | func test_is_equal():
272 | var uuid_1 = uuid_util.new()
273 | var uuid_2 = uuid_util.new()
274 | var uuid_3 = uuid_util.new()
275 |
276 | uuid_3._uuid = uuid_1.as_array()
277 |
278 | print('Testing is_equal function')
279 |
280 | if uuid_2.is_equal(uuid_1):
281 | print('"is_equal" ain\'t working correctly (different uuid are identicals)')
282 |
283 | elif not uuid_3.is_equal(uuid_1):
284 | print('"is_equal" ain\'t working correctly (duplicated array not identical)')
285 |
286 | print('Done.')
287 |
288 | func _init():
289 | test_is_equal()
290 | detect_collision()
291 | detect_collision_with_rng()
292 |
293 | print("\n---------------- Raw ----------------")
294 | benchmark_raw()
295 |
296 | print("\n---------------- Raw with rng ----------------")
297 | benchmark_raw_rng()
298 |
299 | print("\n---------------- Simple object ----------------")
300 | benchmark_obj()
301 |
302 | print("\n---------------- Obj with rng ----------------")
303 | benchmark_obj_rng()
304 |
305 | print("\n---------------- Obj to dict ----------------")
306 | benchmark_obj_to_dict()
307 |
308 | print("\n---------------- Obj to string ----------------")
309 | benchmark_obj_to_str()
310 |
311 | print("\n---------------- Compare raw ----------------")
312 | benchmark_comp_raw()
313 |
314 | print("\n---------------- Compare obj ----------------")
315 | benchmark_comp_obj()
316 |
317 | quit()
318 |
--------------------------------------------------------------------------------
/addons/godot-playfab/PlayFabEvent.gd:
--------------------------------------------------------------------------------
1 | @icon("res://addons/godot-playfab/icon.png")
2 |
3 | extends PlayFab
4 | class_name PlayFabEvent
5 |
6 |
7 | # Emitted when the PlayStream Event batch was flushed
8 | # @param event_ids: The IDs of the events written
9 | signal event_batch_playstream_flushed(event_ids)
10 |
11 | # Emitted when the Telemetry Event batch was flushed
12 | # @param event_ids: The IDs of the events written
13 | signal event_batch_telemetry_flushed(event_ids)
14 |
15 | # Defines the maximum batch size. This is dictated by the PlayFab API
16 | const max_batch_size = 200
17 |
18 | # Defines the event batch size threshold
19 | @export var event_batch_size: int = 5
20 |
21 | # Defines the wait time (in seconds) until batches are flushed
22 | @export var event_batch_timeout_seconds: int = 10
23 |
24 | # Defines whether local time is set checked the event (true),
25 | # or server time should be used (false)
26 | @export var use_local_time: bool = true
27 |
28 |
29 | var playstream_event_batch = EventContentsCollection.new()
30 | var telemetry_event_batch = EventContentsCollection.new()
31 |
32 | func _ready():
33 | super._ready()
34 |
35 | var timer = Timer.new()
36 | timer.process_mode = Timer.TIMER_PROCESS_IDLE
37 | timer.wait_time = event_batch_timeout_seconds
38 | timer.one_shot = false
39 | timer.autostart = true
40 | timer.connect("timeout",Callable(self,"_flush_playstream_event_batch"))
41 | timer.connect("timeout",Callable(self,"_flush_telemetry_event_batch"))
42 | add_child(timer)
43 |
44 |
45 | func _process(_delta):
46 | _flush_batches_on_batch_size_met()
47 |
48 |
49 | # Write batches of entity based events to as Telemetry events (bypass PlayStream).
50 | # Prefer using `batch_title_player_telemetry_event`
51 | # @Visibility: Public
52 | # @param request_data: WriteEventsRequest - Request object, holding multiple requests.
53 | # @callback: Callable (optional) - Optional callback function, receiving a Dictionary with the returned Event ID.
54 | func event_telemetry_write_events(request_data: WriteEventsRequest, callback: Callable = func(): pass):
55 | _post_with_entity_auth(request_data, "/Event/WriteTelemetryEvents", callback)
56 |
57 |
58 | # Write batches of entity based events to PlayStream.
59 | # Prefer using `batch_title_player_playstream_event`
60 | # @Visibility: Public
61 | # @param request_data: WriteEventsRequest - Request object, holding multiple requests.
62 | # @callback: Callable (optional) - Optional callback function, receiving a Dictionary with the returned Event ID.
63 | func event_playstream_write_events(request_data: WriteEventsRequest, callback: Callable = func(): pass):
64 | _post_with_entity_auth(request_data, "/Event/WriteEvents", callback)
65 |
66 |
67 | # Directly write a Telemetry Event
68 | # @Visibility: Public
69 | # @param event_name: String - The Event's name
70 | # @param payload: Dictionary - A dictionary to send as event payload
71 | # @param callback: Callable (optional) - A callback, providing a Dictionary containing the Event ID.
72 | # @param event_namespace: String (optional) - The namespace of the Event must be 'custom' or start with 'custom.'.
73 | func write_title_player_telemetry_event(event_name: String, payload: Dictionary, callback: Callable = func(): pass, event_namespace = "custom.%s" % _title_id):
74 | var event = _assemble_event(event_name, payload, event_namespace)
75 | # We send a batch of events here!
76 | var request = WriteEventsRequest.new()
77 | request.Events.append(event)
78 | event_telemetry_write_events(request, callback)
79 |
80 |
81 | # Directly write a PlayStream Event
82 | # @Visibility: Public
83 | # @param event_name: String - The Event's name
84 | # @param payload: Dictionary - A dictionary to send as event payload
85 | # @param callback: Callable (optional) - A callback, providing a Dictionary containing the Event ID.
86 | # @param event_namespace: String (optional) - The namespace of the Event must be 'custom' or start with 'custom.'.
87 | func write_title_player_playstream_event(event_name: String, payload: Dictionary, callback: Callable = func(): pass, event_namespace = "custom.%s" % _title_id):
88 | var event = _assemble_event(event_name, payload, event_namespace)
89 | # We send a batch of events here!
90 | var request = WriteEventsRequest.new()
91 | request.Events.append(event)
92 | event_playstream_write_events(request, callback)
93 |
94 |
95 | # Batch a Telemetry Event for later sending
96 | # @Visibility: Public
97 | # @param event_name: String - The Event's name
98 | # @param payload: Dictionary - A dictionary to send as event payload
99 | # @param callback: Callable (optional) - A callback, providing a Dictionary containing the Event ID.
100 | # @param event_namespace: String (optional) - The namespace of the Event must be 'custom' or start with 'custom.'.
101 | func batch_title_player_telemetry_event(event_name: String, payload: Dictionary, callback: Callable = func(): pass, event_namespace = "custom.%s" % _title_id):
102 | var event = _assemble_event(event_name, payload, event_namespace)
103 | if (telemetry_event_batch.size() < max_batch_size):
104 | telemetry_event_batch.append(event)
105 | else:
106 | push_warning("godot-playfab: dropping event as the telemetry event maximum per batch (%s) was reached." % max_batch_size)
107 |
108 |
109 | # Batch a PlayStream Event for later sending
110 | # @Visibility: Public
111 | # @param event_name: String - The Event's name
112 | # @param payload: Dictionary - A dictionary to send as event payload
113 | # @param callback: Callable (optional) - A callback, providing a Dictionary containing the Event ID.
114 | # @param event_namespace: String (optional) - The namespace of the Event must be 'custom' or start with 'custom.'.
115 | func batch_title_player_playstream_event(event_name: String, payload: Dictionary, callback: Callable = func(): pass, event_namespace = "custom.%s" % _title_id):
116 | var event = _assemble_event(event_name, payload, event_namespace)
117 | if (playstream_event_batch.size() < max_batch_size):
118 | playstream_event_batch.append(event)
119 | else:
120 | push_warning("godot-playfab: dropping event as the playstream event maximum per batch (%s) was reached." % max_batch_size)
121 |
122 | # Assembles event data
123 | # @Visibility: Private
124 | # @param event_name: String - The Event's name
125 | # @param payload: Dictionary - A dictionary to send as event payload
126 | # @param callback: Callable (optional) - A callback, providing a Dictionary containing the Event ID.
127 | # @param event_namespace: String (optional) - The namespace of the Event must be 'custom' or start with 'custom.'.
128 | func _assemble_event(event_name: String, payload: Dictionary, event_namespace = "custom.%s" % _title_id) -> EventContents:
129 | var event = EventContents.new()
130 | event.Name = event_name
131 | event.EventNamespace = event_namespace
132 | event.Payload = payload
133 |
134 | if use_local_time:
135 | event.OriginalTimestamp = Time.get_datetime_string_from_system(true) # Use UTC
136 |
137 | # Event can also have an Entity, which is a type/id combo.
138 | # If omitted, the event will be sent in the "current" Entity's context
139 | # Usually, this means `title_player_account`, as you are logged in with it in a client.
140 |
141 | return event
142 |
143 |
144 | # Triggers batch flush if configured threshold is met
145 | # @Visibility: Private
146 | func _flush_batches_on_batch_size_met():
147 | if playstream_event_batch.size() >= event_batch_size:
148 | _flush_playstream_event_batch()
149 | elif telemetry_event_batch.size() >= event_batch_size:
150 | _flush_telemetry_event_batch()
151 |
152 | # Flushes the PlayStream event batch
153 | # @Visibility: Private
154 | func _flush_playstream_event_batch():
155 | if playstream_event_batch.size() < 1:
156 | print_debug("No playstream events to flush")
157 | return
158 |
159 | var request = WriteEventsRequest.new()
160 | request.Events = playstream_event_batch
161 | event_playstream_write_events(request, Callable(self, "_on_playstream_batch_flush"))
162 | playstream_event_batch.clear()
163 | print_debug("Flushed playstream batch")
164 |
165 |
166 | # Flushes the Telemetry event batch
167 | # @Visibility: Private
168 | func _flush_telemetry_event_batch():
169 | if telemetry_event_batch.size() < 1:
170 | print_debug("No telemetry events to flush")
171 | return
172 |
173 | var request = WriteEventsRequest.new()
174 | request.Events = telemetry_event_batch
175 | event_telemetry_write_events(request, Callable(self, "_on_telemetry_batch_flush"))
176 | telemetry_event_batch.clear()
177 | print_debug("Flushed telemetry batch")
178 |
179 |
180 | # Callback for after PlayStream batch flush
181 | # @Visibility: Private
182 | func _on_playstream_batch_flush(response: Dictionary):
183 | var event_ids: Array = response["data"]["AssignedEventIds"]
184 | print_debug("Flushed %s PlayStream events" % event_ids.size())
185 | emit_signal("event_batch_playstream_flushed", event_ids)
186 |
187 |
188 | # Callback for after Telemetry batch flush
189 | # @Visibility: Private
190 | func _on_telemetry_batch_flush(response: Dictionary):
191 | var event_ids: Array = response["data"]["AssignedEventIds"]
192 | print_debug("Flushed %s Telemetry events" % event_ids.size())
193 | emit_signal("event_batch_telemetry_flushed", event_ids)
194 |
--------------------------------------------------------------------------------
/addons/godot-playfab/docs/Steam/godot-steam-manual.md:
--------------------------------------------------------------------------------
1 | # Login with Steam using [GodotSteam](https://godotsteam.com/)
2 |
3 | 1. [Introduction](#introduction)
4 | 2. [Setup](#setup)
5 | 3. [Environments](#environments)
6 | 4. [Initialization](#initialization)
7 | 5. [Create Steam Auth Session Ticket](#create-steam-auth-session-ticket)
8 | 6. [Create Steam Auth Ticket For Web API](#create-steam-auth-ticket-for-web-api)
9 | 7. [Convert Steam Auth Ticket (Session and Web API)](#convert-steam-auth-ticket-session-and-web-api)
10 | 8. [Cancel Steam Auth Ticket (Session and Web API)](#cancel-steam-auth-ticket-session-and-web-api)
11 | 9. [Altogether](#altogether)
12 | 10. [Troubleshooting](#troubleshooting)
13 |
14 | ## Introduction
15 |
16 | This page is an advanced example that will show you how to login with Steam on PlayFab using the third party [GodotSteam](https://godotsteam.com/) plugin.
17 |
18 | Please check their documentation on how to use GodotSteam.
19 |
20 | ## Example
21 | You can check out a working example in the [godot-playfab `integrate-steam` branch](https://github.com/Structed/godot-playfab/tree/integrate-steam).
22 |
23 | ## Setup
24 |
25 | There are a couple of different ways to install GodotSteam but for this example, we gonna used the GDExtension available for Godot 4.x.
26 |
27 | > :warning: Be careful to install the normal extension and **not** the server extension
28 |
29 | 
30 |
31 |
32 | ## Environments
33 |
34 | When the game is run through the Steam client, it already knows which game you are playing. However, during development and testing, you must supply a valid App ID somehow. Typically, if you do not already have an app ID, you can use App ID `480` which is Valve's *SpaceWar* example game.
35 |
36 | There are three ways to set the App ID. For this example, we will use one of them. If you want to see the other, check [GodotSteam (Initializing Steam)](https://godotsteam.com/tutorials/initializing/).
37 |
38 | > :warning: Don't forget to replace **STEAM_APP_ID** by a valid String that contains your App ID.
39 |
40 | The easiest way to set your AppID, is to create a file called `steam_appid.txt` in your project root folder (the same folder as your `project.godot` file) and put the App ID in that file.
41 |
42 | ## Initialization
43 |
44 | Then, we need to initialize Steamworks using the code below:
45 |
46 | ```gdscript
47 | func _ready() -> void:
48 | var result : Dictionary = Steam.steamInitEx(false) # Set to true if you want some local user's data
49 | if result.status > 0:
50 | print("Failure to initialize Steam with status %s" % result.status)
51 | ```
52 |
53 | ## Create Steam Auth Session Ticket
54 |
55 | Once the initialization is done, you can create a Steam Auth Session Ticket.
56 | This type of ticket is mainly used by games in order to authenticate players.
57 | In the case of PlayFab, it will associate players with their corresponding event entries.
58 | To do it, we use a synchronous method but a callback exists and tell you whether getting the ticket was successful (result should be 1).
59 |
60 | ```gdscript
61 | var steam_auth_ticket : Dictionary
62 |
63 | func _ready() -> void:
64 | Steam.get_auth_session_ticket_response.connect(_on_get_auth_sesssion_ticket)
65 |
66 | func _process(delta: float) -> void:
67 | Steam.run_callbacks()
68 |
69 | func create_auth_session_ticket() -> void:
70 | steam_auth_ticket = Steam.getAuthSessionTicket()
71 |
72 | func _on_get_auth_sesssion_ticket(auth_ticket_id: int, result: int) -> void:
73 | print("Auth Session Ticket (%s) return with result %s" % [auth_ticket_id, result])
74 | ```
75 |
76 | ## Create Steam Auth Ticket For Web API
77 |
78 | You can also create a Steam Auth Ticket for Web API.
79 | This type of ticket is used for external API in order to have info about a game.
80 | To do it, we use an asynchronous method. It is necessary to link to the callback in order to retrieve the ticket information.
81 |
82 | > :warning: Steam require an authorized identity for this type of ticket. In the case of PlayFab, the identity is **"AzurePlayfab"**
83 |
84 | ```gdscript
85 | var steam_auth_ticket : Dictionary
86 |
87 | func _ready() -> void:
88 | Steam.get_ticket_for_web_api.connect(_on_get_auth_ticket_for_web_api_response)
89 |
90 | func _process(delta: float) -> void:
91 | Steam.run_callbacks()
92 |
93 | func create_auth_ticket_for_web_api() -> void:
94 | Steam.getAuthTicketForWebApi("AzurePlayFab")
95 |
96 | func _on_get_auth_ticket_for_web_api_response(auth_ticket: int, result: int, ticket_size: int, ticket_buffer: Array) -> void:
97 | print("Auth Ticket for Web API (%s) return with the result %s" % [auth_ticket, result])
98 | steam_auth_ticket.id = auth_ticket
99 | steam_auth_ticket.buffer = ticket_buffer
100 | steam_auth_ticket.size = ticket_size
101 | ```
102 |
103 | ## Convert Steam Auth Ticket (Session and Web API)
104 |
105 | Once a Steam Auth Ticket is retrieved successfully, we can login into PlayFab.
106 | However, PlayFab want a specific string. To do that, we need to convert the steam buffer into a string with hexadecimal.
107 |
108 | ```gdscript
109 | func convert_auth_ticket() -> String:
110 | var ticket: String = ""
111 | for number in steam_auth_ticket.buffer:
112 | ticket += "%02X" % number
113 | return ticket
114 | ```
115 |
116 | ## Cancel Steam Auth Ticket (Session and Web API)
117 |
118 | Any Steam Auth Ticket also need to be canceled before leaving the application.
119 |
120 | ```gdscript
121 | func _exit_tree() -> void:
122 | if steam_auth_ticket.size > 0:
123 | cancel_auth_ticket()
124 |
125 | func cancel_auth_ticket() -> void:
126 | Steam.cancelAuthTicket(steam_auth_ticket.id)
127 | ```
128 |
129 | ## Altogether
130 |
131 | Finally, putting it together with the previous example that you can find [here](manual-integration.md) should give you something like below:
132 |
133 | > :warning: Don't forget to replace **STEAM_APP_ID** by a valid String that contains your App ID.
134 |
135 | ```gdscript
136 | extends Node
137 |
138 | var steam_auth_ticket : Dictionary
139 |
140 | func _init() -> void:
141 | # Set steam environment only in editor because Steam would already know which game you are playing
142 | if OS.has_feature("editor"):
143 | OS.set_environment("SteamAppId", STEAM_APP_ID)
144 | OS.set_environment("SteamGameId", STEAM_APP_ID)
145 |
146 | func _ready() -> void:
147 | PlayFabManager.client.logged_in.connect(_on_logged_in)
148 | PlayFabManager.client.api_error.connect(_on_api_error)
149 | PlayFabManager.client.server_error.connect(_on_server_error)
150 | Steam.get_auth_session_ticket_response.connect(_on_get_auth_sesssion_ticket)
151 | Steam.get_ticket_for_web_api.connect(_on_get_auth_ticket_for_web_api_response)
152 |
153 | var result : Dictionary = Steam.steamInitEx(false) # Set to true if you want some local user's data
154 | if result.status > 0:
155 | print("Failure to initialize Steam with status %s" % result.status)
156 | else:
157 | create_auth_session_ticket();
158 | #create_auth_ticket_for_web_api(); Use this line instead if you need Steam Auth Ticket for Web Api
159 |
160 | func _process(delta: float) -> void:
161 | Steam.run_callbacks()
162 |
163 | func _exit_tree() -> void:
164 | if steam_auth_ticket.size > 0:
165 | cancel_auth_ticket()
166 |
167 | func _on_logged_in(login_result: LoginResult) -> void:
168 | print("Playfab Login: %s" % login_result)
169 |
170 | func _on_api_error(error_wrapper: ApiErrorWrapper) -> void:
171 | print("Playfab API Error: %s" % error_wrapper.errorMessage)
172 |
173 | func _on_server_error(error_wrapper: ApiErrorWrapper) -> void:
174 | print("Playfab Server Error: %s" % error_wrapper.errorMessage)
175 |
176 | func login(ticket: String, is_auth_ticket_for_api: bool) -> void:
177 | var combined_info_request_params = GetPlayerCombinedInfoRequestParams.new()
178 | combined_info_request_params.show_all()
179 | var player_profile_view_constraints = PlayerProfileViewConstraints.new()
180 | combined_info_request_params.ProfileConstraints = player_profile_view_constraints
181 | PlayFabManager.client.login_with_steam(ticket, is_auth_ticket_for_api, true, combined_info_request_params)
182 |
183 | func cancel_auth_ticket() -> void:
184 | Steam.cancelAuthTicket(steam_auth_ticket.id)
185 |
186 | func create_auth_session_ticket() -> void:
187 | steam_auth_ticket = Steam.getAuthSessionTicket()
188 |
189 | func create_auth_ticket_for_web_api() -> void:
190 | Steam.getAuthTicketForWebApi("AzurePlayFab")
191 |
192 | func convert_auth_ticket() -> String:
193 | var ticket: String = ""
194 | for number in steam_auth_ticket.buffer:
195 | ticket += "%02X" % number
196 | return ticket
197 |
198 | func _on_get_auth_sesssion_ticket(auth_ticket_id: int, result: int) -> void:
199 | print("Auth Session Ticket (%s) return with result %s" % [auth_ticket_id, result])
200 | if result == 1:
201 | login(convert_auth_ticket(), false)
202 |
203 | func _on_get_auth_ticket_for_web_api_response(auth_ticket: int, result: int, ticket_size: int, ticket_buffer: Array) -> void:
204 | print("Auth Ticket for Web API (%s) return with the result %s" % [auth_ticket, result])
205 | steam_auth_ticket.id = auth_ticket
206 | steam_auth_ticket.buffer = ticket_buffer
207 | steam_auth_ticket.size = ticket_size
208 | if result == 1:
209 | login(convert_auth_ticket(), true)
210 | ```
211 |
212 | ## :warning: Troubleshooting
213 |
214 | They are many possible errors, but the most common ones are:
215 | - Steam is not launched on your device
216 | - Steam App Id is not correct
217 | - Steam add-ons is not enabled in the PlayFab Title
218 |
219 | If you still have an error, check the debugger, [GodotSteam (Initializing Steam)](https://godotsteam.com/tutorials/initializing/) or [GodoSteam (Authentication)](https://godotsteam.com/tutorials/authentication/).
220 |
--------------------------------------------------------------------------------
/addons/godot-playfab/PlayFab.gd:
--------------------------------------------------------------------------------
1 | @icon("res://addons/godot-playfab/icon.png")
2 |
3 | extends PlayFabHttp
4 | class_name PlayFab
5 |
6 | ## Arguments: RegisterPlayFabUserResult
7 | signal registered(RegisterPlayFabUserResult)
8 |
9 | ## Emitted when the player logged in successfully
10 | ## @param login_result: LoginResult
11 | signal logged_in(login_result)
12 |
13 | enum AUTH_TYPE {SESSION_TICKET, ENTITY_TOKEN}
14 |
15 |
16 | func _init():
17 |
18 | if ProjectSettings.has_setting(PlayFabConstants.SETTING_PLAYFAB_TITLE_ID) && ProjectSettings.get_setting(PlayFabConstants.SETTING_PLAYFAB_TITLE_ID) != "":
19 | _title_id = ProjectSettings.get_setting(PlayFabConstants.SETTING_PLAYFAB_TITLE_ID)
20 | else:
21 | push_error("Title Id was not set in ProjectSettings: %s" % PlayFabConstants.SETTING_PLAYFAB_TITLE_ID)
22 |
23 |
24 | func _ready():
25 | super._ready()
26 | connect("logged_in",Callable(self,"_on_logged_in"))
27 |
28 |
29 | func _on_logged_in(login_result: LoginResult):
30 | # Setting SessionTicket for subsequent client requests
31 | PlayFabManager.client_config.session_ticket = login_result.SessionTicket
32 | PlayFabManager.client_config.master_player_account_id = login_result.PlayFabId
33 | PlayFabManager.client_config.entity_token = login_result.EntityToken
34 | PlayFabManager.save_client_config()
35 |
36 |
37 | func register_email_password(username: String, email: String, password: String, info_request_parameters: GetPlayerCombinedInfoRequestParams):
38 | var request_params = RegisterPlayFabUserRequest.new()
39 | request_params.TitleId = _title_id
40 | request_params.DisplayName = username
41 | request_params.Username = username
42 | request_params.Email = email
43 | request_params.Password = password
44 | request_params.InfoRequestParameters = info_request_parameters
45 | request_params.RequireBothUsernameAndEmail = true
46 |
47 | var result = _post(request_params, "/Client/RegisterPlayFabUser", Callable(self, "_on_register_email_password"))
48 |
49 |
50 | func login_with_email(email: String, password: String, custom_tags: Dictionary, info_request_parameters: GetPlayerCombinedInfoRequestParams):
51 | PlayFabManager.client_config.login_type = PlayFabClientConfig.LoginType.LOGIN_EMAIL
52 | PlayFabManager.client_config.login_id = email
53 |
54 | var request_params = LoginWithEmailAddressRequest.new()
55 | request_params.TitleId = _title_id
56 | request_params.Email = email
57 | request_params.Password = password
58 | request_params.CustomTags = custom_tags
59 | request_params.InfoRequestParameters = info_request_parameters
60 |
61 | var result = _post(request_params, "/Client/LoginWithEmailAddress", _on_login)
62 |
63 |
64 | func login_with_custom_id(custom_id: String, create_user: bool, info_request_parameters: GetPlayerCombinedInfoRequestParams):
65 | PlayFabManager.client_config.login_type = PlayFabClientConfig.LoginType.LOGIN_CUSTOM_ID
66 | PlayFabManager.client_config.login_id = custom_id
67 |
68 | var request_params = LoginWithCustomIdRequest.new()
69 | request_params.TitleId = _title_id
70 | request_params.CustomId = custom_id
71 | request_params.CreateAccount = create_user
72 | request_params.InfoRequestParameters = info_request_parameters
73 |
74 | var result = _post(request_params, "/Client/LoginWithCustomID", _on_login)
75 |
76 | ## Login into PlayFab using an user authentication token generated by SteamAPI.[br]
77 | ## [param steam_auth_ticket]: Generated steam authentication token for the user (String composed of hexadecimal)[br]
78 | ## [param is_auth_ticket_for_api]: True if ticket was generated using GetAuthTicketForWebAPI(), false if it was generated using GetAuthSessionTicket[br]
79 | ## [param create_account]: Automatically create a PlayFab account if one is not currently linked to this ID[br]
80 | ## [param info_request_parameters]: Flags for which pieces of info to return for the user
81 | func login_with_steam(steam_auth_ticket: String, is_auth_ticket_for_api: bool, create_account: bool, info_request_parameters: GetPlayerCombinedInfoRequestParams) -> void:
82 | PlayFabManager.client_config.login_type = PlayFabClientConfig.LoginType.LOGIN_STEAM
83 | PlayFabManager.client_config.login_id = steam_auth_ticket
84 |
85 | var request_params = LoginWithSteamRequest.new()
86 | request_params.TitleId = _title_id
87 | request_params.CreateAccount = create_account
88 | request_params.InfoRequestParameters = info_request_parameters
89 | request_params.SteamTicket = steam_auth_ticket
90 | request_params.TicketIsServiceSpecific = is_auth_ticket_for_api
91 |
92 | var result = _post(request_params, "/Client/LoginWithSteam", _on_login)
93 |
94 | # Anonymous login with a GUID as username
95 | func login_anonymous():
96 | var combined_info_request_params = GetPlayerCombinedInfoRequestParams.new()
97 | combined_info_request_params.show_all()
98 | var player_profile_view_constraints = PlayerProfileViewConstraints.new()
99 | combined_info_request_params.ProfileConstraints = player_profile_view_constraints
100 |
101 | PlayFabManager.client.login_with_custom_id(UUID.v4(), true, combined_info_request_params)
102 |
103 | func _on_register_email_password(result: Dictionary):
104 | var register_result = RegisterPlayFabUserResult.new()
105 | register_result.from_dict(result["data"], register_result)
106 |
107 | emit_signal("registered", register_result)
108 |
109 |
110 | func _on_login(result: Dictionary):
111 | var login_result = LoginResult.new()
112 | login_result.from_dict(result["data"], login_result)
113 |
114 | emit_signal("logged_in", login_result)
115 |
116 | func _post_with_session_auth(body: JsonSerializable, path: String, callback: Callable, additional_headers: Dictionary = {}) -> bool:
117 | var result = _add_auth_headers(additional_headers, AUTH_TYPE.SESSION_TICKET)
118 | if !result:
119 | return false
120 |
121 | var dict = body.to_dict()
122 | _http_request(HTTPClient.METHOD_POST, dict, path, callback, additional_headers)
123 | return true
124 |
125 | # General request method for endpoints which require Entity-Token-Auth.
126 | # You should use this to provide convenience methods for requests to specific resources.
127 | #
128 | # @visibility: internal
129 | # @param body: JsonSerializable - A data model valid for the request to be made
130 | # @param path: String - The request path, e.g. `/Client/GetTitleData`
131 | # @param callback: Callable - A callback which will be called once the request **succeeds**
132 | # @param additional_headers: Dictionary (optional) - Additional headers to be sent with the request
133 | # @ returns: bool - False if the player is not logged in - true if the request was sent.
134 | func _post_with_entity_auth(body: JsonSerializable, path: String, callback: Callable, additional_headers: Dictionary = {}) -> bool:
135 | var result = _add_auth_headers(additional_headers, AUTH_TYPE.ENTITY_TOKEN)
136 | if !result:
137 | return false
138 |
139 | var dict = body.to_dict()
140 | _http_request(HTTPClient.METHOD_POST, dict, path, callback, additional_headers)
141 | return true
142 |
143 |
144 | func _post(body: JsonSerializable, path: String, callback: Callable, additional_headers: Dictionary = {}):
145 | var dict = body.to_dict()
146 | _http_request(HTTPClient.METHOD_POST, dict, path, callback, additional_headers)
147 |
148 |
149 | # General (POST) request method for endpoints which require Authentication.
150 | # You should use this to provide custom requests by passing parameters as a Dictionary.
151 | #
152 | # @visibility: internal
153 | # @param body: Dictionary - A Dictionary representing the request body
154 | # @param path: String - The request path, e.g. `/Client/GetTitleData`
155 | # @param auth_type: PlayFab.AUTH_TYPE - One of `PlayFab.AUTH_TYPE`
156 | # @param callback: Callable - A callback which will be called once the request **succeeds**
157 | # @param additional_headers: Dictionary (optional) - Additional headers to be sent with the request
158 | func post_dict_auth(body: Dictionary, path: String, auth_type, callback: Callable, additional_headers: Dictionary = {}):
159 | _add_auth_headers(additional_headers, auth_type)
160 | _http_request(HTTPClient.METHOD_POST, body, path, callback, additional_headers)
161 |
162 |
163 | # General (POST) request method for endpoints which **DO NOT** require Authentication.
164 | # However, you can add auth parameters yourself via appropriate headers.
165 | # You should use this to provide custom requests by passing parameters as a Dictionary.
166 | #
167 | # @visibility: internal
168 | # @param body: Dictionary - A Dictionary representing the request body
169 | # @param path: String - The request path, e.g. `/Client/GetTitleData`
170 | # @param callback: Callable - A callback which will be called once the request **succeeds**
171 | # @param additional_headers: Dictionary (optional) - Additional headers to be sent with the request
172 | func post_dict(body: Dictionary, path: String, callback: Callable, additional_headers: Dictionary = {}):
173 | _http_request(HTTPClient.METHOD_POST, body, path, callback, additional_headers)
174 |
175 |
176 | # Adds PlayFab specific authentication headers depending checked the `auth_type` provided.
177 | #
178 | # @visibility: internal
179 | # @param additional_headers: Dictionary - Authentication headers will be appended to this Dictionary
180 | # @param auth_type: PlayFab.AUTH_TYPE - One of `PlayFab.AUTH_TYPE`
181 | # @return bool: - Whethr the player is authenticated. True if authenticated.
182 | func _add_auth_headers(additional_headers: Dictionary, auth_type) -> bool:
183 | if !PlayFabManager.client_config.is_logged_in():
184 | push_error("Player is not logged in.")
185 | return false
186 |
187 | if auth_type == AUTH_TYPE.SESSION_TICKET:
188 | additional_headers["X-Authorization"] = PlayFabManager.client_config.session_ticket
189 | elif auth_type == AUTH_TYPE.ENTITY_TOKEN:
190 | additional_headers["X-EntityToken"] = PlayFabManager.client_config.entity_token.EntityToken
191 | else:
192 | push_error("auth_type \"" + auth_type + "\" is invalid")
193 |
194 | return true
195 |
--------------------------------------------------------------------------------
/addons/godot-playfab/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### [1.3.1](https://github.com/Structed/godot-playfab/compare/1.3.0...1.3.1) (2024-12-19)
2 |
3 |
4 | ### Bug Fixes
5 |
6 | * issue [#148](https://github.com/Structed/godot-playfab/issues/148) ([249de48](https://github.com/Structed/godot-playfab/commit/249de485c0249154eff5a4eaf70f3df9883973e8))
7 |
8 |
9 | ### Tests
10 |
11 | * added tests for UUID library ([cd45d33](https://github.com/Structed/godot-playfab/commit/cd45d33e5f80ed9234d85965749cb60bb3bad29c))
12 |
13 |
14 | ### Code Refactoring
15 |
16 | * fixed tabs ([7fb5dbc](https://github.com/Structed/godot-playfab/commit/7fb5dbc0fbfecb6e4c3c6f8ea9516adb1d31d0ec))
17 | * fixing tabs again ([bfab71c](https://github.com/Structed/godot-playfab/commit/bfab71ccfd17321bb4cf6f3871985fa96fd9df56))
18 | * moved library to originally named folders ([f870241](https://github.com/Structed/godot-playfab/commit/f870241b9dbd471cd8e70b94cb7488d5865b130a))
19 |
20 |
21 |
22 | ## [1.3.0](https://github.com/Structed/godot-playfab/compare/1.2.0...1.3.0) (2024-12-03)
23 |
24 |
25 | ### Features
26 |
27 | * **login:** Login With Steam ([#132](https://github.com/Structed/godot-playfab/issues/132)) ([29bd210](https://github.com/Structed/godot-playfab/commit/29bd210e33924ca4cdc27517f879c4ddd03c84cf))
28 |
29 |
30 | ### Bug Fixes
31 |
32 | * **docs:** documentation (add warning in order to avoid other developers to send invalid events) ([#129](https://github.com/Structed/godot-playfab/issues/129)) ([0c116fb](https://github.com/Structed/godot-playfab/commit/0c116fb1e83c25ce6e248055927d7ccb9616ad59))
33 | * **PlayFabEvent:** Events are dropped if batch size exceeds the playfab api limits (fixes [#147](https://github.com/Structed/godot-playfab/issues/147)) ([121be3b](https://github.com/Structed/godot-playfab/commit/121be3bbd9edfba97f23a371019e397a2808c1e2))
34 |
35 |
36 | ### Documentation
37 |
38 | * **maintainer:** Add Commit message format documentation ([11e44b1](https://github.com/Structed/godot-playfab/commit/11e44b17fda6fc6d7a1317fa02036e0d9ec2998d))
39 | * **steam:** fixed link to godotsteam page ([a45c018](https://github.com/Structed/godot-playfab/commit/a45c018e7541376e5f6ab4a85b8bf047ac802fdd))
40 | * **steam:** moved images to user docs ([20bc925](https://github.com/Structed/godot-playfab/commit/20bc92501529c5ccd6c68686aaf5030544f74823))
41 | * **user:** Moved user docs to addons/godot-playfab ([#127](https://github.com/Structed/godot-playfab/issues/127)) ([ebd47e7](https://github.com/Structed/godot-playfab/commit/ebd47e7e682413d0b3a74a5ee5af979f9f68ce52))
42 |
43 |
44 |
45 | ## [1.2.0](https://github.com/Structed/godot-playfab/compare/1.1.0...1.2.0) (2023-10-12)
46 |
47 |
48 | ### Features
49 |
50 | * **Auth:** Anonymous login ([#126](https://github.com/Structed/godot-playfab/issues/126)) ([d3339b1](https://github.com/Structed/godot-playfab/commit/d3339b1ddd01508a89c3b5593277f738c27fe264))
51 |
52 |
53 |
54 | ## [1.1.0](https://github.com/Structed/godot-playfab/compare/1.0.1...1.1.0) (2023-07-30)
55 |
56 |
57 | ### Features
58 |
59 | * **platform:** Upgrade to Godot 4.1 ([#121](https://github.com/Structed/godot-playfab/issues/121)) ([42c2af4](https://github.com/Structed/godot-playfab/commit/42c2af43ac5c7e71ef27095530f66aafe9b4f5da))
60 | * **setup:** add programmatic autoload in PlayFabEditor plugin script ([#120](https://github.com/Structed/godot-playfab/issues/120)) ([d34d7fe](https://github.com/Structed/godot-playfab/commit/d34d7fe9875765e0132b411d93fc6018d5b4e9e4))
61 |
62 |
63 | ### Bug Fixes
64 |
65 | * add description on how to add Title ID ([69b09b1](https://github.com/Structed/godot-playfab/commit/69b09b15f8e513d6a8161aa654d232486f442c47))
66 |
67 |
68 |
69 | ### [1.0.1](https://github.com/Structed/godot-playfab/compare/1.0.0...1.0.1) (2023-06-13)
70 |
71 |
72 | ### Bug Fixes
73 |
74 | * **serializer:** hardcoded ignoring of first 3 items of properties on to be serialized object ([#118](https://github.com/Structed/godot-playfab/issues/118)) ([0da7b4d](https://github.com/Structed/godot-playfab/commit/0da7b4d380eaa092006fee2093c436d4814c8503))
75 |
76 |
77 |
78 | ## [1.0.0](https://github.com/Structed/godot-playfab/compare/0.3.4...1.0.0) (2023-06-03)
79 |
80 |
81 | ### ⚠ BREAKING CHANGES
82 |
83 | * **core:** Update for Godot 4 stable
84 |
85 | Upgraded to Godot 4. Thanks to: @MikeSchulze for GDUnit his GH Action workflow examples - https://github.com/MikeSchulze/gdUnit4 @bitbrain for his GH Action workflow examples in https://github.com/bitbrain/beehave
86 |
87 | fix(example): Update Events demo scene for Godot 4
88 | fix(example): crash issue on writing telemetry events
89 | ci(pipeline): workflow_dispatch to be in correct indent level
90 | ci(pipeline): Update GDUnit4 actions
91 | ci(pipeline): Run unit tests on Godot 4.02 & 4.0.3
92 | ci(pipeline): Separate Godot 4 & 3 branches
93 | ci(pipeline): Separate Godot 4 workflows
94 | ci(pipeline): set new asset ID for new Asset in AssetLib
95 | ci(pipeline): Exclude LoginIntegrationTest (broken)
96 | ci(pipeline): Removed defunct & unused plugin refresher
97 | test(pipeline): Switch test framework to GDUnit4
98 | docs(readme): Added users of godot-playfab to README.md
99 | docs(license)Updated licenses
100 |
101 | ### Features
102 |
103 | * **core:** Godot 4 upgrade ([0d39b88](https://github.com/Structed/godot-playfab/commit/0d39b88faab532aced5259fbe2af8ca119636425))
104 |
105 |
106 | ### Changes
107 |
108 | * add note note to highlight Godot4 prerelease availability ([fef9a1d](https://github.com/Structed/godot-playfab/commit/fef9a1d3344c7beb7f1305551df477b9e17d2971))
109 |
110 |
111 | ### Continuous Integration
112 |
113 | * bump mathieudutour/github-tag-action to v6.1 ([7491b16](https://github.com/Structed/godot-playfab/commit/7491b16eb92adfacb9882004378d183f2e38877b))
114 |
115 |
116 |
117 | ### [0.3.4](https://github.com/Structed/godot-playfab/compare/0.3.3...0.3.4) (2023-01-25)
118 |
119 |
120 | ### Bug Fixes
121 |
122 | * Implementation to check for GZip Accept-Encoding header ([#101](https://github.com/Structed/godot-playfab/issues/101)) ([d4c1f15](https://github.com/Structed/godot-playfab/commit/d4c1f15c2d99c75028e159004cebdb75b02416e8))
123 |
124 |
125 | ### Continuous Integration
126 |
127 | * enable release job on branch ([e90d587](https://github.com/Structed/godot-playfab/commit/e90d5873dfb771e64b6629307244f4e46078292c))
128 | * fix step name when using changelog output ([39269b5](https://github.com/Structed/godot-playfab/commit/39269b54b5a4fb880de9f5391c44edaae0a2bdb9))
129 | * USe release notes from and also create prereleases ([3e7b942](https://github.com/Structed/godot-playfab/commit/3e7b942e5428d8bda80edaa5eec320a33e53f2b7))
130 |
131 |
132 |
133 | ### [0.3.3](https://github.com/Structed/godot-playfab/compare/0.3.2...0.3.3) (2023-01-23)
134 |
135 |
136 | ### Bug Fixes
137 |
138 | * Do not assume GZip compressed response. ([#99](https://github.com/Structed/godot-playfab/issues/99)) ([1110d8e](https://github.com/Structed/godot-playfab/commit/1110d8e750ad41d337beb65dd181562998a37371))
139 |
140 |
141 |
142 | ### [0.3.2](https://github.com/Structed/godot-playfab/compare/0.3.1...0.3.2) (2023-01-22)
143 |
144 |
145 | ### Bug Fixes
146 |
147 | * .editorconfig ([79300cf](https://github.com/Structed/godot-playfab/commit/79300cf1764472a87d0d9ea68b7cb631b7406106))
148 | * PlayFabEvent._assemble_event() did not properly accept the parameter ([#95](https://github.com/Structed/godot-playfab/issues/95)) ([1ad4e3f](https://github.com/Structed/godot-playfab/commit/1ad4e3f89c85816256239dd760d0b3017a604239))
149 |
150 |
151 | ### Continuous Integration
152 |
153 | * fix whitespace ([f4d53df](https://github.com/Structed/godot-playfab/commit/f4d53dfa03cc32f8e439705a77740412073b7a1b))
154 | * fix yaml ([10f9fb9](https://github.com/Structed/godot-playfab/commit/10f9fb97afbfb9a07ad0d32fc9ca8772d1f5208c))
155 | * remove unused env variable ([f991a2b](https://github.com/Structed/godot-playfab/commit/f991a2bd500e5c6c3aa03acfc576480477b5011f))
156 | * updated/upgraded workflows ([3ffab3a](https://github.com/Structed/godot-playfab/commit/3ffab3a7acbc09230e9f47aac0d35a3323505aa6))
157 | * using PAT again ([3a42d0f](https://github.com/Structed/godot-playfab/commit/3a42d0ff781e1fc364a0b3d130efcc9296d4fe40))
158 |
159 |
160 |
161 | ## [0.3.0](https://github.com/Structed/godot-playfab/compare/0.2.0...0.3.0) (2022-12-18)
162 |
163 |
164 | ### Features
165 |
166 | * **pencil:** Add response compression (gzip) ([#87](https://github.com/Structed/godot-playfab/issues/87)) ([248972c](https://github.com/Structed/godot-playfab/commit/248972cdaf9891e5fd8a3308471831967ee00c9c))
167 |
168 |
169 | ### Bug Fixes
170 |
171 | * **pencil:** Do not require callbacks for Events API ([#90](https://github.com/Structed/godot-playfab/issues/90)) ([71d0a92](https://github.com/Structed/godot-playfab/commit/71d0a929938428af305c2b2e9eb01a2a800f7153))
172 |
173 |
174 |
175 | ## [0.2.0](https://github.com/Structed/godot-playfab/compare/0.1.0...0.2.0) (2022-11-21)
176 |
177 |
178 | ### Features
179 |
180 | * **pencil:** Add Default Theme ([#64](https://github.com/Structed/godot-playfab/issues/64)) ([72bce50](https://github.com/Structed/godot-playfab/commit/72bce508cb3ff08a4dc5fddab35c2d82b301ef4c))
181 |
182 |
183 | ### Bug Fixes
184 |
185 | * **pencil:** 55 update documentation ([#78](https://github.com/Structed/godot-playfab/issues/78)) ([161dd26](https://github.com/Structed/godot-playfab/commit/161dd2666e63f05cde49afc77623c976a462eb4e))
186 | * **pencil:** Added missing in to specify . Otherwise, deserialization would fail. ([e2d73c1](https://github.com/Structed/godot-playfab/commit/e2d73c10bd5c07f5920278c2a7a2087cb89c139e))
187 | * **pencil:** Anonymous Login button does not turn green after successful anonymous login [#81](https://github.com/Structed/godot-playfab/issues/81) ([#82](https://github.com/Structed/godot-playfab/issues/82)) ([090dc2c](https://github.com/Structed/godot-playfab/commit/090dc2c61f6b8c2413de0b1f2afbd7db5dfa69ed))
188 | * **pencil:** fix code docs ([47223ef](https://github.com/Structed/godot-playfab/commit/47223ef624efcb6e83af3ac85e8f76fb737844c8))
189 | * **pencil:** Fix demo scene file ([a2a244f](https://github.com/Structed/godot-playfab/commit/a2a244f3ef1741cc08f49688196607ad6b982400))
190 | * **pencil:** Login with incorrect credentials stalls the demo [#76](https://github.com/Structed/godot-playfab/issues/76) ([#77](https://github.com/Structed/godot-playfab/issues/77)) ([b4f25fb](https://github.com/Structed/godot-playfab/commit/b4f25fb05d35463ea8bf8d974a6b8a3dccd85f38))
191 | * **pencil:** Update demo scene gif ([#80](https://github.com/Structed/godot-playfab/issues/80)) ([71c4b15](https://github.com/Structed/godot-playfab/commit/71c4b157babbaf037a6981ec5caca0955e702caf))
192 |
193 |
194 |
195 | ## [0.1.0](https://github.com/Structed/godot-playfab/compare/0.0.6...0.1.0) (2022-11-07)
196 |
197 |
198 | ### Features
199 |
200 | * **pencil:** 65 timestamp of events ([#66](https://github.com/Structed/godot-playfab/issues/66)) ([34d0be7](https://github.com/Structed/godot-playfab/commit/34d0be7e9babb685b8ce9398a3ed5398f3be5a5f))
201 | * **pencil:** 72 add discord shield ([#73](https://github.com/Structed/godot-playfab/issues/73)) ([5e666ba](https://github.com/Structed/godot-playfab/commit/5e666ba07ca8736644208c0be5cbadf561d63321))
202 |
203 |
204 | ### Bug Fixes
205 |
206 | * **pencil:** Add missing icon.png.import ([97f9589](https://github.com/Structed/godot-playfab/commit/97f958986b0c69d76ce16664b42749e1a1f683b5))
207 | * **pencil:** Add test step for Gopdot 3.4.4 ([#70](https://github.com/Structed/godot-playfab/issues/70)) ([7135bfe](https://github.com/Structed/godot-playfab/commit/7135bfe9bc1628572c38451aa773e0ecd3258839))
208 | * **pencil:** Use Godot 3.5 stable ([#69](https://github.com/Structed/godot-playfab/issues/69)) ([74d06cc](https://github.com/Structed/godot-playfab/commit/74d06ccedb246d64a7be1493050baaaebb98dd0a))
209 |
210 |
211 |
212 | ### [0.0.5](https://github.com/Structed/godot-playfab/compare/v0.0.4...0.0.5) (2022-07-17)
213 |
214 |
215 | ### Bug Fixes
216 |
217 | * **pencil:** release workflow ([1be05de](https://github.com/Structed/godot-playfab/commit/1be05deec0e2e9523d8e5d8156ebfb6a5218a1c3))
218 | * changelog ([23b34c6](https://github.com/Structed/godot-playfab/commit/23b34c62a9feb9d275a065ee4f1f258ac20aed62))
219 | * do not check on whether to update plugin.cfg ([e62bd1e](https://github.com/Structed/godot-playfab/commit/e62bd1ec61266b75e3d3b715a76b1f17e67def31))
220 | * do not restrict on develop branch ([7cc878e](https://github.com/Structed/godot-playfab/commit/7cc878e739ef26ba17558e6a401a42bdeab1a5ad))
221 | * dry run (do not create a tag) if not on main ([dbb3a68](https://github.com/Structed/godot-playfab/commit/dbb3a6806a335d8e1b2e952bdaca3a6f6fa9c258))
222 | * enable itch ([c416eff](https://github.com/Structed/godot-playfab/commit/c416effbc14ac0a729184ff661e2db501258092a))
223 | * introducing PAT ([b235453](https://github.com/Structed/godot-playfab/commit/b235453112bdf71ef2227321e892555456ecf5cd))
224 | * make releases public by default ([edb6414](https://github.com/Structed/godot-playfab/commit/edb64145aee4e590072dc1f17ed99027a9b9103d))
225 | * only run itch on main ([109c61f](https://github.com/Structed/godot-playfab/commit/109c61f9f1d0571d0e858f5d815fdd7789fe82f8))
226 | * only run on branch main ([61d5a3a](https://github.com/Structed/godot-playfab/commit/61d5a3a008c6d0a0cb63a97485d5164e690d1986))
227 | * quoting banch names ([764772e](https://github.com/Structed/godot-playfab/commit/764772e1fe32e52b93116ca014c27b09875358df))
228 | * re-enable itch release ([3309664](https://github.com/Structed/godot-playfab/commit/3309664429bb1cbfe93ec67527c2ce0a360806d0))
229 | * remove test-changelog ([d00ffab](https://github.com/Structed/godot-playfab/commit/d00ffab3112866d771c0dd0bc1a931f2e8ea2449))
230 | * reset version ([f20d4d1](https://github.com/Structed/godot-playfab/commit/f20d4d10073054fc6c8abc19fcb23e8abd9ac713))
231 | * reset version ([7cafbf2](https://github.com/Structed/godot-playfab/commit/7cafbf250e4cee4930c04566e92a430c47231c42))
232 | * run asset lib release only on main ([f9d0f1e](https://github.com/Structed/godot-playfab/commit/f9d0f1e59bd67f029ac8f6f3946442b0565a95a5))
233 | * run main workflow on main and develop ([956e328](https://github.com/Structed/godot-playfab/commit/956e328c9948c96cd16af661fea6828316091ceb))
234 | * test again ([c07672d](https://github.com/Structed/godot-playfab/commit/c07672df773a3144c392005f7511bf218dca5ed1))
235 | * upload of artifact with proper version ([ad83470](https://github.com/Structed/godot-playfab/commit/ad834707812a16c322688551e6d8c8abc83fb634))
236 | * use calculate version ([bf0d538](https://github.com/Structed/godot-playfab/commit/bf0d5388dc042da87697c7e041ab085b84c39f21))
237 | * version for itch build ([9baade6](https://github.com/Structed/godot-playfab/commit/9baade6e20aedf8e096cce57260165dd93649b24))
238 | * version of mathieudutour/github-tag-action ([5304ba4](https://github.com/Structed/godot-playfab/commit/5304ba46ea552c6cf30aecf3718b1984f9dd876d))
239 |
240 |
241 |
242 | ### [0.0.13](https://github.com/Structed/godot-playfab/compare/0.0.12...0.0.13) (2022-07-17)
243 |
244 |
245 | ### Bug Fixes
246 |
247 | * re-enable itch release ([3309664](https://github.com/Structed/godot-playfab/commit/3309664429bb1cbfe93ec67527c2ce0a360806d0))
248 |
249 |
250 |
251 | ### [0.0.12](https://github.com/Structed/godot-playfab/compare/0.0.11...0.0.12) (2022-07-16)
252 |
253 |
254 | ### Bug Fixes
255 |
256 | * try version AssetLib HandleBars ([e0148fb](https://github.com/Structed/godot-playfab/commit/e0148fb9f6c6c7d83d4f77d8fcc539c998b5d8eb))
257 |
258 |
259 |
260 | ### [0.0.11](https://github.com/Structed/godot-playfab/compare/0.0.10...0.0.11) (2022-07-16)
261 |
262 |
263 | ### Bug Fixes
264 |
265 | * accidentially committed failing yaml ([9e21f7d](https://github.com/Structed/godot-playfab/commit/9e21f7d463d8a95aeb0ad254c4a5ea20bbc01cd4))
266 |
267 |
268 |
269 | ### [0.0.10](https://github.com/Structed/godot-playfab/compare/0.0.9...0.0.10) (2022-07-16)
270 |
271 |
272 | ### Bug Fixes
273 |
274 | * step name in HB ([2e0baa5](https://github.com/Structed/godot-playfab/commit/2e0baa5e406d407f0e318132d57bd660110f267c))
275 | * update ([82496a3](https://github.com/Structed/godot-playfab/commit/82496a3b5d8e8932e7e10dfc7825bab5c6a626c4))
276 |
277 |
278 |
279 | ### [0.0.9](https://github.com/Structed/godot-playfab/compare/0.0.8...0.0.9) (2022-07-16)
280 |
281 |
282 | ### Bug Fixes
283 |
284 | * version ion Handlebars for AssetLib ([83b2401](https://github.com/Structed/godot-playfab/commit/83b2401a9cd31eb116191222a7cca485c5aa888f))
285 |
286 |
287 |
288 | ### [0.0.8](https://github.com/Structed/godot-playfab/compare/0.0.7...0.0.8) (2022-07-16)
289 |
290 |
291 | ### Bug Fixes
292 |
293 | * handlebars template for AssetLib export ([ff1bda7](https://github.com/Structed/godot-playfab/commit/ff1bda72acf82eadf2f4c562d44889c7c6400e95))
294 |
295 |
296 |
297 | ### [0.0.7](https://github.com/Structed/godot-playfab/compare/0.0.6...0.0.7) (2022-07-16)
298 |
299 |
300 | ### Bug Fixes
301 |
302 | * only run on branch main ([61d5a3a](https://github.com/Structed/godot-playfab/commit/61d5a3a008c6d0a0cb63a97485d5164e690d1986))
303 | * run main workflow on main and develop ([956e328](https://github.com/Structed/godot-playfab/commit/956e328c9948c96cd16af661fea6828316091ceb))
304 |
305 |
306 |
307 | ### [0.0.6](https://github.com/Structed/godot-playfab/compare/0.0.5...0.0.6) (2022-07-15)
308 |
309 |
310 | ### Bug Fixes
311 |
312 | * dry run (do not create a tag) if not on main ([dbb3a68](https://github.com/Structed/godot-playfab/commit/dbb3a6806a335d8e1b2e952bdaca3a6f6fa9c258))
313 | * only run itch on main ([109c61f](https://github.com/Structed/godot-playfab/commit/109c61f9f1d0571d0e858f5d815fdd7789fe82f8))
314 |
315 |
316 |
317 | ### [0.0.5](https://github.com/Structed/godot-playfab/compare/v0.0.4...0.0.5) (2022-07-15)
318 |
319 |
320 | ### Bug Fixes
321 |
322 | * **pencil:** release workflow ([1be05de](https://github.com/Structed/godot-playfab/commit/1be05deec0e2e9523d8e5d8156ebfb6a5218a1c3))
323 | * changelog ([23b34c6](https://github.com/Structed/godot-playfab/commit/23b34c62a9feb9d275a065ee4f1f258ac20aed62))
324 | * do not check on whether to update plugin.cfg ([e62bd1e](https://github.com/Structed/godot-playfab/commit/e62bd1ec61266b75e3d3b715a76b1f17e67def31))
325 | * do not restrict on develop branch ([7cc878e](https://github.com/Structed/godot-playfab/commit/7cc878e739ef26ba17558e6a401a42bdeab1a5ad))
326 | * enable itch ([c416eff](https://github.com/Structed/godot-playfab/commit/c416effbc14ac0a729184ff661e2db501258092a))
327 | * introducing PAT ([b235453](https://github.com/Structed/godot-playfab/commit/b235453112bdf71ef2227321e892555456ecf5cd))
328 | * quoting banch names ([764772e](https://github.com/Structed/godot-playfab/commit/764772e1fe32e52b93116ca014c27b09875358df))
329 | * remove test-changelog ([d00ffab](https://github.com/Structed/godot-playfab/commit/d00ffab3112866d771c0dd0bc1a931f2e8ea2449))
330 | * reset version ([f20d4d1](https://github.com/Structed/godot-playfab/commit/f20d4d10073054fc6c8abc19fcb23e8abd9ac713))
331 | * reset version ([7cafbf2](https://github.com/Structed/godot-playfab/commit/7cafbf250e4cee4930c04566e92a430c47231c42))
332 | * run asset lib release only on main ([f9d0f1e](https://github.com/Structed/godot-playfab/commit/f9d0f1e59bd67f029ac8f6f3946442b0565a95a5))
333 | * test again ([c07672d](https://github.com/Structed/godot-playfab/commit/c07672df773a3144c392005f7511bf218dca5ed1))
334 | * upload of artifact with proper version ([ad83470](https://github.com/Structed/godot-playfab/commit/ad834707812a16c322688551e6d8c8abc83fb634))
335 | * use calculate version ([bf0d538](https://github.com/Structed/godot-playfab/commit/bf0d5388dc042da87697c7e041ab085b84c39f21))
336 | * version for itch build ([9baade6](https://github.com/Structed/godot-playfab/commit/9baade6e20aedf8e096cce57260165dd93649b24))
337 | * version of mathieudutour/github-tag-action ([5304ba4](https://github.com/Structed/godot-playfab/commit/5304ba46ea552c6cf30aecf3718b1984f9dd876d))
338 |
--------------------------------------------------------------------------------