└── addons └── godot-playfab ├── .gitignore ├── CHANGELOG.md ├── JsonSerializable.gd ├── LICENSE ├── Models ├── AbstractJsonSerializableCollection.gd ├── ApiErrorWrapper.gd ├── EntityKey.gd ├── EntityTokenResponse.gd ├── EventContents.gd ├── EventContentsCollection.gd ├── GetPlayerCombinedInfoRequestParams.gd ├── GetPlayerCombinedInfoResultPayload.gd ├── GetTitleDataRequest.gd ├── GetTitleDataResult.gd ├── LoginResult.gd ├── LoginWithCustomIdRequest.gd ├── LoginWithEmailAddressRequest.gd ├── LoginWithSteamRequest.gd ├── PlayerProfileViewConstraints.gd ├── RegisterPlayFabUserRequest.gd ├── RegisterPlayFabUserResult.gd ├── UserAccountInfo.gd ├── UserTitleInfo.gd └── WriteEventsRequest.gd ├── PlayFab.gd ├── PlayFabClient.gd ├── PlayFabClientConfig ├── PlayFabClientConfig.gd └── PlayFabClientConfigLoader.gd ├── PlayFabConstants.gd ├── PlayFabEditor.gd ├── PlayFabEvent.gd ├── PlayFabHttp.gd ├── PlayFabManager.gd ├── README.md ├── Scenes ├── PlayFabMainScreen.gd └── PlayFabMainScreen.tscn ├── docs ├── .gdignore ├── Events │ ├── Configuration.md │ ├── Flushing.md │ ├── README.md │ └── Sending.md ├── Logins │ ├── README.md │ ├── login-steam-godotsteam.md │ └── login-steam.md ├── basic-requests.md ├── connecting-signals.md ├── images │ ├── login-steam-godot-installation.png │ ├── login-steam-setup-1.png │ └── login-steam-setup-2.png ├── initial-setup.md └── usage.md ├── icon.png ├── icon.png.import ├── icon_16x16.png ├── icon_16x16.png.import ├── lib └── binogure-studio │ └── godot-uuid │ ├── LICENSE │ ├── README.md │ ├── test.gd │ └── uuid.gd └── plugin.cfg /addons/godot-playfab/.gitignore: -------------------------------------------------------------------------------- 1 | temp/ 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/EventContentsCollection.gd: -------------------------------------------------------------------------------- 1 | extends AbstractJsonSerializableCollection 2 | class_name EventContentsCollection 3 | 4 | func _init(): 5 | _item_type = EventContents 6 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/PlayFabConstants.gd: -------------------------------------------------------------------------------- 1 | extends Object 2 | class_name PlayFabConstants 3 | 4 | 5 | const SETTING_PLAYFAB_TITLE_ID = "playfab/title_id" 6 | -------------------------------------------------------------------------------- /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 error: int = ProjectSettings.save() 13 | if error: push_error("Encountered error %d when saving project settings." % error) 14 | 15 | 16 | func _enter_tree(): 17 | add_autoload_singleton("PlayFabManager", "res://addons/godot-playfab/PlayFabManager.gd") 18 | 19 | main_panel_instance = MainPanel.instantiate() 20 | # Add the main panel to the editor's main viewport. 21 | get_editor_interface().get_editor_main_screen().add_child(main_panel_instance) 22 | # Hide the main panel. Very much required. 23 | _make_visible(false) 24 | 25 | 26 | func _exit_tree(): 27 | remove_autoload_singleton("PlayFabManager") 28 | 29 | if main_panel_instance: 30 | main_panel_instance.queue_free() 31 | 32 | func _has_main_screen(): 33 | return true 34 | 35 | 36 | func _make_visible(visible): 37 | if main_panel_instance: 38 | main_panel_instance.visible = visible 39 | 40 | 41 | func _get_plugin_name(): 42 | return "PlayFab" 43 | 44 | 45 | func _get_plugin_icon(): 46 | return load("res://addons/godot-playfab/icon_16x16.png") 47 | 48 | 49 | func add_custom_project_setting(name: String, default_value, type: int, hint: int = PROPERTY_HINT_NONE, hint_string: String = "") -> void: 50 | 51 | if ProjectSettings.has_setting(name): return 52 | 53 | var setting_info: Dictionary = { 54 | "name": name, 55 | "type": type, 56 | "hint": hint, 57 | "hint_string": hint_string 58 | } 59 | 60 | ProjectSettings.set_setting(name, default_value) 61 | ProjectSettings.add_property_info(setting_info) 62 | ProjectSettings.set_initial_value(name, default_value) 63 | -------------------------------------------------------------------------------- /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():): 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():): 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():, 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():, 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():, 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():, 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/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 | var json = JSON.stringify(body) 51 | #print_debug(JSON.stringify(body, "\t")) 52 | var headers = [ 53 | "Content-Type: application/json", 54 | "Content-Length: " + str(json.length()), 55 | ] 56 | 57 | if _response_compression_enabled: 58 | headers.append("Accept-Encoding: gzip") 59 | 60 | headers.append_array(_dict_to_header_array(additional_headers)) 61 | 62 | while (_request_in_progress): 63 | await _http.get_tree().process_frame 64 | 65 | _request_in_progress = true 66 | var request_uri = "%s%s" % [ _get_api_url(), path] 67 | var error = _http.request(request_uri, headers, request_method, json) 68 | if error != OK: 69 | push_error("An error occurred in the HTTP request.") 70 | return 71 | 72 | var args = await _http.request_completed 73 | # TODO: Perhaps build response object? 74 | var response_result = args[0] as int 75 | var response_code = args[1] as int 76 | var response_headers = args[2] as PackedStringArray 77 | var response_body = args[3] as PackedByteArray 78 | _request_in_progress = false 79 | 80 | var response_body_string = response_body.get_string_from_utf8() 81 | var test_json_conv = JSON.new() 82 | var parse_error = test_json_conv.parse(response_body_string) 83 | var json_parse_result = test_json_conv.data 84 | #print_debug("JSON Parse result: %s" % JSON.stringify(json_parse_result, "\t")) 85 | 86 | if parse_error != OK: 87 | emit_signal("json_parse_error", json_parse_result) 88 | return 89 | if response_code >= 200 and response_code < 400: 90 | if callback != null: 91 | if callback.is_valid(): 92 | callback.call(json_parse_result) 93 | else: 94 | push_error("Response calback " + callback.get_method() + " is no longer valid! Make sure, a script is only removed after all requests returned!") 95 | return 96 | elif response_code >= 400: 97 | var apiErrorWrapper = ApiErrorWrapper.new() 98 | for key in json_parse_result.keys(): 99 | apiErrorWrapper.set(key, json_parse_result[key]) 100 | emit_signal("api_error", apiErrorWrapper) 101 | return 102 | if response_code >= 500: 103 | emit_signal("server_error", path) 104 | return 105 | 106 | 107 | func _test_http(body, path: String): 108 | var error = _http.request("https://httpbin.org/post", [], HTTPClient.METHOD_POST, JSON.stringify(body)) 109 | if error != OK: 110 | push_error("An error occurred in the HTTP request.") 111 | -------------------------------------------------------------------------------- /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 | add_child(client) 37 | add_child(event) 38 | client_config = _client_config_loader.load(title_id) 39 | 40 | 41 | # Saves the client config to a file 42 | func save_client_config(): 43 | _client_config_loader.save(title_id, client_config) 44 | 45 | 46 | # Forgets the login 47 | func forget_login(): 48 | _client_config_loader.clear(title_id) 49 | reload() 50 | 51 | 52 | # Reloads the config 53 | func reload(): 54 | client_config = _client_config_loader.load(title_id) 55 | -------------------------------------------------------------------------------- /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 | * [Initial Setup](docs/initial-setup.md) 6 | * [Using `godot-playfab` in your Game](docs/usage.md) 7 | * [Basic Requests](docs/basic-requests.md) 8 | * [Connecting Signals](docs/connecting-signals.md) 9 | * [Events](docs/Events/README.md) 10 | * [Login with Steam](docs/Logins/login-steam.md) 11 | -------------------------------------------------------------------------------- /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/Scenes/PlayFabMainScreen.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://5fda86xbwxbu"] 2 | 3 | [ext_resource type="Script" 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/docs/.gdignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Structed/godot-playfab/14ee81851da6a98ff484a79e674ac1baf114a695/addons/godot-playfab/docs/.gdignore -------------------------------------------------------------------------------- /addons/godot-playfab/docs/Events/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 | Back: [Events - PlayStream & Telemetry](README.md) | Next: [Sending Events](Sending.md) 38 | -------------------------------------------------------------------------------- /addons/godot-playfab/docs/Events/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 | Back: [Sending Events](Sending.md) | Next: [Events Overview](README.md) 18 | -------------------------------------------------------------------------------- /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 | Back: [User Documentation](../README.md) | Next: [Configuration for Events](Configuration.md) 44 | -------------------------------------------------------------------------------- /addons/godot-playfab/docs/Events/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, Calable(self, callback)) 51 | ``` 52 | 53 | ### PlayStream Event 54 | Batch a PlayStream Event: 55 | ```gdscript 56 | $PlayFabEvent.batch_title_player_playstream_event(event_name, payload, Calable(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, Calable(self, callback)) 66 | ```` 67 | 68 | ### PlayStream Event 69 | Write a PlayStream Event: 70 | ````gdscript 71 | $PlayFabEvent.write_title_player_playstream_event(event_name, payload, Calable(self, callback)) 72 | ```` 73 | 74 | Back: [Configuration for Events](Configuration.md) | Next: [Flushing Events](Flushing.md) 75 | -------------------------------------------------------------------------------- /addons/godot-playfab/docs/Logins/README.md: -------------------------------------------------------------------------------- 1 | # Logins 2 | 3 | 1. [Login with Steam](/docs/user/Logins/login-steam.md) 4 | 2. [Login with Steam using **GodotSteam**](/docs/user/Logins/login-steam-godotsteam.md) 5 | -------------------------------------------------------------------------------- /addons/godot-playfab/docs/Logins/login-steam-godotsteam.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-example](https://github.com/Structed/godot-playfab-example) repository. Just load up the `godot-steam` project in that repository! 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 | ![Login Steam Godot Installation](../images/login-steam-godot-installation.png) 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 is 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 | ```gdscript 41 | func _init() -> void: 42 | # Set steam environment only in editor because Steam would already know which game you are playing 43 | if OS.has_feature("editor"): 44 | OS.set_environment("SteamAppId", STEAM_APP_ID) 45 | OS.set_environment("SteamGameId", STEAM_APP_ID) 46 | ``` 47 | 48 | ## Initialization 49 | 50 | Then, we need to initialize Steamworks using the code below: 51 | 52 | ```gdscript 53 | func _ready() -> void: 54 | var result : Dictionary = Steam.steamInitEx(false) # Set to true if you want some local user's data 55 | if result.status > 0: 56 | print("Failure to initialize Steam with status %s" % result.status) 57 | ``` 58 | 59 | ## Create Steam Auth Session Ticket 60 | 61 | Once the initialization is done, you can create a Steam Auth Session Ticket. 62 | This type of ticket is mainly used by games in order to authenticate players. 63 | In the case of PlayFab, it will associate players with their corresponding event entries. 64 | 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). 65 | 66 | ```gdscript 67 | var steam_auth_ticket : Dictionary 68 | 69 | func _ready() -> void: 70 | Steam.get_auth_session_ticket_response.connect(_on_get_auth_sesssion_ticket) 71 | 72 | func _process(delta: float) -> void: 73 | Steam.run_callbacks() 74 | 75 | func create_auth_session_ticket() -> void: 76 | steam_auth_ticket = Steam.getAuthSessionTicket() 77 | 78 | func _on_get_auth_sesssion_ticket(auth_ticket_id: int, result: int) -> void: 79 | print("Auth Session Ticket (%s) return with result %s" % [auth_ticket_id, result]) 80 | ``` 81 | 82 | ## Create Steam Auth Ticket For Web API 83 | 84 | You can also create a Steam Auth Ticket for Web API. 85 | This type of ticket is used for external API in order to have info about a game. 86 | To do it, we use an asynchronous method. It is necessary to link to the callback in order to retrieve the ticket information. 87 | 88 | > :warning: Steam require an authorized identity for this type of ticket. In the case of PlayFab, the identity is **"AzurePlayfab"** 89 | 90 | ```gdscript 91 | var steam_auth_ticket : Dictionary 92 | 93 | func _ready() -> void: 94 | Steam.get_ticket_for_web_api.connect(_on_get_auth_ticket_for_web_api_response) 95 | 96 | func _process(delta: float) -> void: 97 | Steam.run_callbacks() 98 | 99 | func create_auth_ticket_for_web_api() -> void: 100 | Steam.getAuthTicketForWebApi("AzurePlayFab") 101 | 102 | func _on_get_auth_ticket_for_web_api_response(auth_ticket: int, result: int, ticket_size: int, ticket_buffer: Array) -> void: 103 | print("Auth Ticket for Web API (%s) return with the result %s" % [auth_ticket, result]) 104 | steam_auth_ticket.id = auth_ticket 105 | steam_auth_ticket.buffer = ticket_buffer 106 | steam_auth_ticket.size = ticket_size 107 | ``` 108 | 109 | ## Convert Steam Auth Ticket (Session and Web API) 110 | 111 | Once a Steam Auth Ticket is retrieved successfully, we can login into PlayFab. 112 | However, PlayFab want a specific string. To do that, we need to convert the steam buffer into a string with hexadecimal. 113 | 114 | ```gdscript 115 | func convert_auth_ticket() -> String: 116 | var ticket: String = "" 117 | for number in steam_auth_ticket.buffer: 118 | ticket += "%02X" % number 119 | return ticket 120 | ``` 121 | 122 | ## Cancel Steam Auth Ticket (Session and Web API) 123 | 124 | Any Steam Auth Ticket also need to be canceled before leaving the application. 125 | 126 | ```gdscript 127 | func _exit_tree() -> void: 128 | if steam_auth_ticket.size > 0: 129 | cancel_auth_ticket() 130 | 131 | func cancel_auth_ticket() -> void: 132 | Steam.cancelAuthTicket(steam_auth_ticket.id) 133 | ``` 134 | 135 | ## Altogether 136 | 137 | Finally, putting it together with the previous example that you can find [here](/docs/user/Logins/login-steam.md) should give you something like below: 138 | 139 | > :warning: Don't forget to replace **STEAM_APP_ID** by a valid String that contains your App ID. 140 | 141 | ```gdscript 142 | extends Node 143 | 144 | var steam_auth_ticket : Dictionary 145 | 146 | func _init() -> void: 147 | # Set steam environment only in editor because Steam would already know which game you are playing 148 | if OS.has_feature("editor"): 149 | OS.set_environment("SteamAppId", STEAM_APP_ID) 150 | OS.set_environment("SteamGameId", STEAM_APP_ID) 151 | 152 | func _ready() -> void: 153 | PlayFabManager.client.logged_in.connect(_on_logged_in) 154 | PlayFabManager.client.api_error.connect(_on_api_error) 155 | PlayFabManager.client.server_error.connect(_on_server_error) 156 | Steam.get_auth_session_ticket_response.connect(_on_get_auth_sesssion_ticket) 157 | Steam.get_ticket_for_web_api.connect(_on_get_auth_ticket_for_web_api_response) 158 | 159 | var result : Dictionary = Steam.steamInitEx(false) # Set to true if you want some local user's data 160 | if result.status > 0: 161 | print("Failure to initialize Steam with status %s" % result.status) 162 | else: 163 | create_auth_session_ticket(); 164 | #create_auth_ticket_for_web_api(); Use this line instead if you need Steam Auth Ticket for Web Api 165 | 166 | func _process(delta: float) -> void: 167 | Steam.run_callbacks() 168 | 169 | func _exit_tree() -> void: 170 | if steam_auth_ticket.size > 0: 171 | cancel_auth_ticket() 172 | 173 | func _on_logged_in(login_result: LoginResult) -> void: 174 | print("Playfab Login: %s" % login_result) 175 | 176 | func _on_api_error(error_wrapper: ApiErrorWrapper) -> void: 177 | print("Playfab API Error: %s" % error_wrapper.errorMessage) 178 | 179 | func _on_server_error(error_wrapper: ApiErrorWrapper) -> void: 180 | print("Playfab Server Error: %s" % error_wrapper.errorMessage) 181 | 182 | func login(ticket: String, is_auth_ticket_for_api: bool) -> void: 183 | var combined_info_request_params = GetPlayerCombinedInfoRequestParams.new() 184 | combined_info_request_params.show_all() 185 | var player_profile_view_constraints = PlayerProfileViewConstraints.new() 186 | combined_info_request_params.ProfileConstraints = player_profile_view_constraints 187 | PlayFabManager.client.login_with_steam(ticket, is_auth_ticket_for_api, true, combined_info_request_params) 188 | 189 | func cancel_auth_ticket() -> void: 190 | Steam.cancelAuthTicket(steam_auth_ticket.id) 191 | 192 | func create_auth_session_ticket() -> void: 193 | steam_auth_ticket = Steam.getAuthSessionTicket() 194 | 195 | func create_auth_ticket_for_web_api() -> void: 196 | Steam.getAuthTicketForWebApi("AzurePlayFab") 197 | 198 | func convert_auth_ticket() -> String: 199 | var ticket: String = "" 200 | for number in steam_auth_ticket.buffer: 201 | ticket += "%02X" % number 202 | return ticket 203 | 204 | func _on_get_auth_sesssion_ticket(auth_ticket_id: int, result: int) -> void: 205 | print("Auth Session Ticket (%s) return with result %s" % [auth_ticket_id, result]) 206 | if result == 1: 207 | login(convert_auth_ticket(), false) 208 | 209 | func _on_get_auth_ticket_for_web_api_response(auth_ticket: int, result: int, ticket_size: int, ticket_buffer: Array) -> void: 210 | print("Auth Ticket for Web API (%s) return with the result %s" % [auth_ticket, result]) 211 | steam_auth_ticket.id = auth_ticket 212 | steam_auth_ticket.buffer = ticket_buffer 213 | steam_auth_ticket.size = ticket_size 214 | if result == 1: 215 | login(convert_auth_ticket(), true) 216 | ``` 217 | 218 | ## :warning: Troubleshooting 219 | 220 | They are many possible errors, but the most common ones are: 221 | - Steam is not launched on your device 222 | - Steam App Id is not correct 223 | - Steam add-ons is not enabled in the PlayFab Title 224 | 225 | 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/). 226 | -------------------------------------------------------------------------------- /addons/godot-playfab/docs/Logins/login-steam.md: -------------------------------------------------------------------------------- 1 | # Login with Steam 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 | ![Login Steam Setup 1](../images/login-steam-setup-1.png) 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 | ![Login Steam Setup 3](../images/login-steam-setup-2.png) 33 | 34 | ## API 35 | 36 | The [PlayFab](/addons/godot-playfab/PlayFab.gd) Node class implements the `login_with_steam` function: 37 | 38 | ```gdscript 39 | func login_with_steam(steam_auth_ticket: String, is_auth_ticket_for_api: bool, create_account: bool, info_request_parameters: GetPlayerCombinedInfoRequestParams) -> void: 40 | ``` 41 | 42 | - **steam_auth_ticket**: String that contains the steam authentication ticket in hexadecimal format 43 | - **is_auth_ticket_for_api**: `true` if the authentication ticket was generated for an API, `false` otherwise 44 | - **create_account**: `true` if you want that it to create a Player Account for your Title 45 | - **info_request_parameters**: Flags for which piece of info to return for the user 46 | 47 | ## Example 48 | 49 | This example below show you how to call the function seen above with the different callbacks needed. 50 | 51 | ```gdscript 52 | extends Node 53 | 54 | func _ready() -> void: 55 | PlayFabManager.client.logged_in.connect(_on_logged_in) 56 | PlayFabManager.client.api_error.connect(_on_api_error) 57 | PlayFabManager.client.server_error.connect(_on_server_error) 58 | 59 | func _on_logged_in(login_result: LoginResult) -> void: 60 | print("Playfab Login: %s" % login_result) 61 | 62 | func _on_api_error(error_wrapper: ApiErrorWrapper) -> void: 63 | print("Playfab API Error: %s" % error_wrapper.errorMessage) 64 | 65 | func _on_server_error(error_wrapper: ApiErrorWrapper) -> void: 66 | print("Playfab Server Error: %s" % error_wrapper.errorMessage) 67 | 68 | func login(ticket: String, is_auth_ticket_for_api: bool) -> void: 69 | var combined_info_request_params = GetPlayerCombinedInfoRequestParams.new() 70 | combined_info_request_params.show_all() 71 | var player_profile_view_constraints = PlayerProfileViewConstraints.new() 72 | combined_info_request_params.ProfileConstraints = player_profile_view_constraints 73 | PlayFabManager.client.login_with_steam(ticket, is_auth_ticket_for_api, true, combined_info_request_params) 74 | ``` 75 | 76 | ## Example using **GodotSteam** 77 | 78 | Given that the example is a bit long, a dedicated page as been created for it and can be found [here](login-steam-godotsteam.md) 79 | -------------------------------------------------------------------------------- /addons/godot-playfab/docs/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 | Back: [Initial Setup](usage.md) | Next: [Connecting Signals](connecting-signals.md) 40 | -------------------------------------------------------------------------------- /addons/godot-playfab/docs/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 | Back: [Basic Requests](basic-requests.md) | Next: [Events](Events/README.md) 13 | -------------------------------------------------------------------------------- /addons/godot-playfab/docs/images/login-steam-godot-installation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Structed/godot-playfab/14ee81851da6a98ff484a79e674ac1baf114a695/addons/godot-playfab/docs/images/login-steam-godot-installation.png -------------------------------------------------------------------------------- /addons/godot-playfab/docs/images/login-steam-setup-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Structed/godot-playfab/14ee81851da6a98ff484a79e674ac1baf114a695/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/14ee81851da6a98ff484a79e674ac1baf114a695/addons/godot-playfab/docs/images/login-steam-setup-2.png -------------------------------------------------------------------------------- /addons/godot-playfab/docs/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 | Back: [User Documentation](README.md) | Next: [Using `godot-playfab` in your Game](usage.md) 43 | -------------------------------------------------------------------------------- /addons/godot-playfab/docs/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 any PlayFab API. 7 | Please have a look at the example Scenes on how it is being used. 8 | 9 | ## In the Editor 10 | In any scene you want to use `godot-playfab`, just place a `PlayFabClient` node into your scene. 11 | 12 | You can use an arbitrary number of `PlayFabClient` nodes. Each will get their configuration values from `PlayFabClientConfig` in the `PlayFabManager` singleton, which you should have already set up (see [Initial Setup](initial-setup.md)) 13 | 14 | # Working with Events 15 | See [Events](Events/README.md) 16 | 17 | Back: [Initial Setup](initial-setup.md) | Next: [Basic Requests](basic-requests.md) 18 | -------------------------------------------------------------------------------- /addons/godot-playfab/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Structed/godot-playfab/14ee81851da6a98ff484a79e674ac1baf114a695/addons/godot-playfab/icon.png -------------------------------------------------------------------------------- /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: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Structed/godot-playfab/14ee81851da6a98ff484a79e674ac1baf114a695/addons/godot-playfab/icon_16x16.png -------------------------------------------------------------------------------- /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/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/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/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/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/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 | --------------------------------------------------------------------------------