From fdf5114634acf5ed4718eae8c0bd4c9a048f3163 Mon Sep 17 00:00:00 2001 From: tudor <7089284+tudddorrr@users.noreply.github.com> Date: Sat, 22 Aug 2026 11:01:02 +0100 Subject: [PATCH] 1.0 migration guides for godot and unity --- content/docs/godot/install.mdx | 5 + content/docs/godot/meta.json | 1 + content/docs/godot/upgrading-to-1.0.mdx | 269 +++++++++++++++++++++++ content/docs/unity/install.mdx | 5 + content/docs/unity/meta.json | 1 + content/docs/unity/upgrading-to-1.0.mdx | 278 ++++++++++++++++++++++++ 6 files changed, 559 insertions(+) create mode 100644 content/docs/godot/upgrading-to-1.0.mdx create mode 100644 content/docs/unity/upgrading-to-1.0.mdx diff --git a/content/docs/godot/install.mdx b/content/docs/godot/install.mdx index 507bb5e..3b29235 100644 --- a/content/docs/godot/install.mdx +++ b/content/docs/godot/install.mdx @@ -21,6 +21,11 @@ The Godot Asset Library is the recommended way to download Godot plugins. Open t You can download the latest version of the Godot plugin from our [itch.io page](https://sleepystudios.itch.io/talo-godot). Simply unzip the plugin and copy the `addons/talo` folder into your project. + + Upgrading from an older version of the plugin? Read the [upgrading to 1.0 + guide](/docs/godot/upgrading-to-1.0) for the breaking changes. + + ## Enable the plugin diff --git a/content/docs/godot/meta.json b/content/docs/godot/meta.json index 8232a00..08d9cba 100644 --- a/content/docs/godot/meta.json +++ b/content/docs/godot/meta.json @@ -1,6 +1,7 @@ { "pages": [ "install", + "upgrading-to-1.0", "settings-reference", "exporting", "request-verification", diff --git a/content/docs/godot/upgrading-to-1.0.mdx b/content/docs/godot/upgrading-to-1.0.mdx new file mode 100644 index 0000000..ba9f681 --- /dev/null +++ b/content/docs/godot/upgrading-to-1.0.mdx @@ -0,0 +1,269 @@ +--- +description: Upgrade the Talo Godot plugin to 1.0 and migrate code from v0.49.1 and earlier. +title: Upgrading to 1.0 +--- +## How to upgrade + +1. Download the latest release from the [Godot Asset Library](https://godotengine.org/asset-library/asset/2936), [itch.io](https://sleepystudios.itch.io/talo-godot) or [GitHub releases](https://github.com/TaloDev/godot/releases). +2. Replace your existing `addons/talo` folder with the new one. +3. Go through each section below and update any code that uses the affected APIs. + +## Identifying players + +### Player aliases + +`identify()`, `identify_steam()`, `identify_google_play_games()`, `identify_game_center()` and `identify_offline()` return a `TaloPlayerAlias` instead of a `TaloPlayer` ([#223](https://github.com/TaloDev/godot/pull/223)). + +You can access the underlying player via `alias.player`: + +```gdscript +# before +var player: TaloPlayer = await Talo.players.identify("username", "bob") +func _on_identified(_player: TaloPlayer) -> void: ... + +# after +var alias: TaloPlayerAlias = await Talo.players.identify("username", "bob") +func _on_identified(player_alias: TaloPlayerAlias) -> void: ... +``` + +### Identification errors + +The `identification_failed` signal now passes a `TaloIdentifyError` with a `code` enum (`UNKNOWN_ERROR`, `IDENTIFIER_PROFANITY`, `IDENTIFIER_TAKEN`) ([#224](https://github.com/TaloDev/godot/pull/224)): + +```gdscript +Talo.players.identification_failed.connect(func (error: TaloIdentifyError): + push_error("Identification failed: %s" % error.code) + go_to_login() +) +``` + +## Player props + +### Awaitable prop updates + +All six prop mutators (`set_prop`, `delete_prop`, `set_prop_array`, `delete_prop_array`, `insert_into_prop_array`, `remove_from_prop_array`) now return a signal you can `await`, which resolves with a new `PlayerUpdateResult` (`success`, `rejected_props`) after the debounce settles ([#232](https://github.com/TaloDev/godot/pull/232)): + +```gdscript +# fire-and-forget (still works - the return value can be ignored) +Talo.current_player.set_prop("xp", "100") + +# await the settle result +var result: PlayersAPI.PlayerUpdateResult = await Talo.current_player.set_prop("xp", "100") +# result.success - whether the update was successful +# result.rejected_props - which props Talo rejected (if any) + +# a local update (no network request); returns a valid result +var local := Talo.current_player.set_prop("xp", "100", false) +``` + +### Rejected props + +The `Talo.players.props_rejected` signal has been removed - rejected props come back on the result ([#233](https://github.com/TaloDev/godot/pull/233)). + +### Trailing-only debounce + +Debounce timers are now **trailing-only** - the leading-edge mode and its `leading` constructor param were removed ([#234](https://github.com/TaloDev/godot/pull/234)). Pending player and save updates are now flushed when the game quits, so queued/in-flight updates are no longer dropped. + +## Player authentication + +### Result objects + +Every auth method (`register`, `login`, `verify`, `change_password`, `forgot_password`, `migrate_account`, etc.) now returns a **result object** instead of a bare `Error`. + +Errors now live inside this result object and `Talo.player_auth.last_error` has been removed ([#228](https://github.com/TaloDev/godot/pull/228)): + +```gdscript +# before +var res := await Talo.player_auth.register(username.text, password.text, email.text) +if res != OK: + match Talo.player_auth.last_error.get_code(): + TaloAuthError.ErrorCode.IDENTIFIER_TAKEN: + validation_label.text = "Username is already taken" + _: + validation_label.text = Talo.player_auth.last_error.get_string() + +# after +var res := await Talo.player_auth.register(username.text, password.text, email.text, verification_enabled) +if not res.success: + match res.error.code: + TaloPlayerAuthError.ErrorCode.IDENTIFIER_TAKEN: + validation_label.text = "Username is already taken" + _: + validation_label.text = res.error.message +``` + +### Login verification + +The `LoginResult` enum has been removed. `login()` returns a `PlayerAuthLoginResult` - check `verification_required` instead: + +```gdscript +# before +var res := await Talo.player_auth.login(username.text, password.text) +match res: + Talo.player_auth.LoginResult.VERIFICATION_REQUIRED: + verification_required.emit() + Talo.player_auth.LoginResult.FAILED: ... + +# after +var res := await Talo.player_auth.login(username.text, password.text) +if res.verification_required: + verification_required.emit() +elif not res.success: + match res.error.code: ... +``` + +## Leaderboards + +### Current player entries + +The deprecated `get_entries_for_current_player()` and `get_cached_entries_for_current_player()` have been removed ([#220](https://github.com/TaloDev/godot/pull/220), [#221](https://github.com/TaloDev/godot/pull/221)). + +Use the options-based methods with `player_id` or `alias_id` filtering instead: + +```gdscript +# before +var entries := Talo.leaderboards.get_entries_for_current_player(internal_name) +var cached := Talo.leaderboards.get_cached_entries_for_current_player(internal_name) + +# after +var options := Talo.leaderboards.GetEntriesOptions.new() +options.player_id = Talo.current_player.id +var res := await Talo.leaderboards.get_entries(internal_name, options) + +var cached_options := Talo.leaderboards.GetCachedEntriesOptions.new() +cached_options.player_id = Talo.current_player.id +var cached := Talo.leaderboards.get_cached_entries(internal_name, cached_options) +``` + +### Rejected entry props + +The `props_rejected` signal has been removed. `add_entry()` now returns an `AddEntryResult` with `success`, `entry`, `updated` and `rejected_props` ([#233](https://github.com/TaloDev/godot/pull/233), [#235](https://github.com/TaloDev/godot/pull/235)): + +```gdscript +# before +Talo.leaderboards.props_rejected.connect(_on_props_rejected) +func _on_props_rejected(rejected_props: Array[TaloRejectedProp]): + for prop in rejected_props: + print("Rejected prop '%s': %s (%s)" % [prop.key, prop.message, prop.error]) + +# after +var res := await Talo.leaderboards.add_entry(internal_name, score) +if not res.success: + for prop in res.rejected_props: + print("Rejected prop '%s': %s (%s)" % [prop.key, prop.message, prop.code]) +``` + +## Channels + +### Updating channels + +`update()` now takes an `UpdateChannelOptions` object instead of positional arguments. `create()` and `update()` now return a `ChannelUpsertResult` (`success`, `channel`, `rejected_props`) ([#231](https://github.com/TaloDev/godot/pull/231)): + +```gdscript +# before +var channel := await Talo.channels.create(options) +await Talo.channels.update(channel_id, "new name", 123, { team = "red" }) + +# after +var options := Talo.channels.UpdateChannelOptions.new() +options.name = "new name" +options.new_owner_alias_id = 123 +options.props = { team = "red" } +var result := await Talo.channels.update(channel_id, options) +if not result.success: + for prop in result.rejected_props: ... + print(result.channel.name) +``` + +`UpdateChannelOptions` also adds tri-state toggles (`auto_cleanup`, `private`, `temporary_membership`) so you can leave a field unchanged or explicitly set it to `true`/`false`. + +### Rejected channel props + +The `channel_props_rejected` signal has been removed - rejected props come back on the result object ([#233](https://github.com/TaloDev/godot/pull/233)). + +The `channel_storage_props_failed_to_set` signal still exists but now emits `Array[TaloRejectedProp]` instead of `Array[TaloChannelStoragePropError]` ([#222](https://github.com/TaloDev/godot/pull/222)): + +```gdscript +# before +func _on_failed(channel: TaloChannel, failed_props: Array[TaloChannelStoragePropError]): + for prop in failed_props: + print("%s: %s (%s)" % [prop.key, prop.message, prop.error]) + +# after +func _on_failed(channel: TaloChannel, failed_props: Array[TaloRejectedProp]): + for prop in failed_props: + print("%s: %s (%s)" % [prop.key, prop.message, prop.code]) +``` + +## Saves + +### Updating the current save + +`update_current_save()` now returns a `SaveUpdateResult` (`success`, `save`) on **both** the debounced content-sync path and the immediate rename path ([#232](https://github.com/TaloDev/godot/pull/232)). + +### Updating a save + +`update_save(save, new_name)` is unchanged in signature but now returns `null` on a non-200 response (previously it returned the local save, masking failures) ([#232](https://github.com/TaloDev/godot/pull/232)). + +## Feedback + +### Sending feedback + +`send()` now returns a `FeedbackSendResult` (`success`, `rejected_props`) instead of `void`, and the `props_rejected` signal has been removed ([#233](https://github.com/TaloDev/godot/pull/233)): + +```gdscript +# before +await Talo.feedback.send(category, comment, props) + +# after +var result := await Talo.feedback.send(category, comment, props) +if not result.success: + for prop in result.rejected_props: + print("Rejected prop '%s': %s (%s)" % [prop.key, prop.message, prop.code]) +``` + +## Error classes + +### Error class renames + +- `TaloAuthError` has been renamed to `TaloPlayerAuthError` ([#225](https://github.com/TaloDev/godot/pull/225)). +- `get_code()` / `get_string()` have been replaced by the typed `code: ErrorCode` and `message: String` properties ([#226](https://github.com/TaloDev/godot/pull/226)). +- `.error` has been renamed to `.code` on every error class, and `TaloRejectedProp.RejectionReason` is now `TaloRejectedProp.ErrorCode` ([#227](https://github.com/TaloDev/godot/pull/227)). + +```gdscript +# before +prop.error # TaloRejectedProp.RejectionReason.PROP_VALUE_TOO_LONG +Talo.player_auth.last_error.get_string() + +# after +prop.code # TaloRejectedProp.ErrorCode.PROP_VALUE_TOO_LONG +res.error.message # TaloPlayerAuthError.message +``` + +### Channel storage errors + +`TaloChannelStoragePropError` has been removed - channel storage failures now use the shared `TaloRejectedProp` class ([#222](https://github.com/TaloDev/godot/pull/222)). + +## Quick reference + +| Removed / renamed | Replacement | +| ----------------------------------------------------------------- | ------------------------------------------------------------------------- | +| `Talo.players.identify()` → `TaloPlayer` | `Talo.players.identify()` → `TaloPlayerAlias` (use `.player`) | +| `Talo.players.props_rejected` | `PlayerUpdateResult.rejected_props` | +| Prop editing on players, e.g. `set_prop()`, returns `void` | `Talo.current_player.set_prop()` is awaitable → `PlayerUpdateResult` | +| `TaloDebounceTimer(..., leading)` | leading mode removed (trailing-only) | +| `Talo.player_auth.last_error` | `.error` on the returned result | +| `Talo.player_auth.LoginResult` enum | `PlayerAuthLoginResult` + `verification_required` | +| `Talo.leaderboards.get_entries_for_current_player()` | `Talo.leaderboards.get_entries()` + `player_id`/`alias_id` options | +| `Talo.leaderboards.get_cached_entries_for_current_player()` | `Talo.leaderboards.get_cached_entries()` + `player_id`/`alias_id` options | +| `Talo.leaderboards.props_rejected` | `AddEntryResult.rejected_props` | +| `Talo.channels.update(id, name, owner, props)` | `Talo.channels.update(id, UpdateChannelOptions)` | +| `Talo.channels.channel_props_rejected` | `ChannelUpsertResult.rejected_props` | +| `Talo.saves.update_current_save()` → `TaloGameSave` | `Talo.saves.update_current_save()` → `SaveUpdateResult` | +| `Talo.saves.update_save()` returns local save on failure | `Talo.saves.update_save()` returns `null` on non-200 | +| `Talo.feedback.props_rejected` | `FeedbackSendResult.rejected_props` | +| `TaloAuthError` | `TaloPlayerAuthError` | +| `get_code()` / `get_string()` on `TaloPlayerAuthError` | `.code` / `.message` | +| `TaloRejectedProp.RejectionReason` | `TaloRejectedProp.ErrorCode` | +| `.error` property on error classes (e.g. `TaloRejectedProp`) | `.code` (e.g. `prop.error` → `prop.code`) | +| `TaloChannelStoragePropError` | `TaloRejectedProp` | \ No newline at end of file diff --git a/content/docs/unity/install.mdx b/content/docs/unity/install.mdx index 323f6f9..8fdf67f 100644 --- a/content/docs/unity/install.mdx +++ b/content/docs/unity/install.mdx @@ -27,6 +27,11 @@ You can download the latest version of the Unity package from our [itch.io page] Once downloaded, you can open the `talo.unitypackage` file to import it into your project. + + Upgrading from an older version of the package? Read the [upgrading to 1.0 + guide](/docs/unity/upgrading-to-1.0) for the breaking changes. + + ## Generate an API key Visit [the Talo dashboard](https://dashboard.trytalo.com), login or create an account (and confirm your email address), and visit the API Keys page. diff --git a/content/docs/unity/meta.json b/content/docs/unity/meta.json index 8b881cd..3d283d0 100644 --- a/content/docs/unity/meta.json +++ b/content/docs/unity/meta.json @@ -1,6 +1,7 @@ { "pages": [ "install", + "upgrading-to-1.0", "settings-reference", "dev-data", "request-verification", diff --git a/content/docs/unity/upgrading-to-1.0.mdx b/content/docs/unity/upgrading-to-1.0.mdx new file mode 100644 index 0000000..3c07872 --- /dev/null +++ b/content/docs/unity/upgrading-to-1.0.mdx @@ -0,0 +1,278 @@ +--- +description: Upgrade the Talo Unity package to 1.0 and migrate code from v0.60.2 and earlier. +title: Upgrading to 1.0 +--- +## How to upgrade + +1. Download the latest release from the [Unity Asset Store](https://assetstore.unity.com/packages/tools/game-toolkits/talo-game-services-292832), [itch.io](https://sleepystudios.itch.io/talo-unity) or [GitHub releases](https://github.com/TaloDev/unity/releases). +2. Re-import the package over your existing install (Asset Store: re-download from `Window > My Assets`; itch.io: re-open the `.unitypackage`). +3. Go through each section below and update any code that uses the affected APIs. + +## Identifying players + +### Player aliases + +`Identify()`, `IdentifySteam()`, `IdentifyGooglePlayGames()` and `IdentifyGameCenter()` now return a `PlayerAlias` instead of a `Player`, and the `OnIdentified` event passes a `PlayerAlias` ([#223](https://github.com/TaloDev/unity/pull/223)). + +You can access the underlying player via `alias.player`: + +```csharp +// before +var player = await Talo.Players.Identify("username", "bob"); +Debug.Log(player.id); + +// after +var alias = await Talo.Players.Identify("username", "bob"); +Debug.Log(alias.player.id); +``` + +### Identification errors + +The `OnIdentificationFailed` event now passes an `IdentifyException` with an `ErrorCode` enum (`UNKNOWN_ERROR`, `IDENTIFIER_PROFANITY`, `IDENTIFIER_TAKEN`) instead of firing with no arguments ([#224](https://github.com/TaloDev/unity/pull/224)): + +```csharp +// before +Talo.Players.OnIdentificationFailed += () => GoToLogin(); + +// after +Talo.Players.OnIdentificationFailed += (ex) => +{ + Debug.LogError($"Identification failed: {ex.ErrorCode}"); + GoToLogin(); +}; +``` + +## Player props + +### Awaitable prop updates + +All six prop mutators (`SetProp`, `DeleteProp`, `SetPropArray`, `DeletePropArray`, `InsertIntoPropArray`, `RemoveFromPropArray`) now return a `Task` that resolves with a new `PlayerUpdateResult` (`Success`, `RejectedProps`) after the debounce settles ([#227](https://github.com/TaloDev/unity/pull/227)): + +```csharp +// fire-and-forget (still works - the return value can be ignored) +Talo.CurrentPlayer.SetProp("level", "5"); + +// await the settle result +var result = await Talo.CurrentPlayer.SetProp("level", "5"); +// result.Success - whether the update was successful +// result.RejectedProps - which props Talo rejected (if any) + +// a local update (no network request); returns a valid result +var local = await Talo.CurrentPlayer.SetProp("level", "5", false); +``` + +### Rejected props + +The `Talo.Players.OnPropsRejected` event has been removed - rejected props come back on the result ([#228](https://github.com/TaloDev/unity/pull/228)). + +### Trailing-only debounce + +The debounce timer is now **trailing-only** - the leading-edge mode has been removed, so the first call no longer fires immediately ([#229](https://github.com/TaloDev/unity/pull/229)). Pending player and save updates are now flushed when the game quits, so queued updates are no longer dropped. + +## Leaderboards + +### Current player entries + +The deprecated `GetEntriesForCurrentPlayer()`, `GetCachedEntriesForCurrentPlayer()` and the `GetEntries(name, page, aliasId, includeArchived)` overloads have been removed ([#219](https://github.com/TaloDev/unity/pull/219), [#220](https://github.com/TaloDev/unity/pull/220), [#221](https://github.com/TaloDev/unity/pull/221)). + +Use the options-based methods with `aliasId` or `playerId` filtering instead: + +```csharp +// before +var res = await Talo.Leaderboards.GetEntriesForCurrentPlayer(internalName); +var cached = Talo.Leaderboards.GetCachedEntriesForCurrentPlayer(internalName); +var paged = await Talo.Leaderboards.GetEntries(internalName, 0, aliasId, true); + +// after +var res = await Talo.Leaderboards.GetEntries(internalName, new GetEntriesOptions +{ + aliasId = Talo.CurrentAlias.id +}); +var cached = Talo.Leaderboards.GetCachedEntries(internalName, new GetCachedEntriesOptions +{ + aliasId = Talo.CurrentAlias.id +}); +var paged = await Talo.Leaderboards.GetEntries(internalName, new GetEntriesOptions +{ + page = 0, + aliasId = aliasId, + includeArchived = true +}); +``` + +### Rejected entry props + +The `OnPropsRejected` event has been removed. `AddEntry()` now returns an `AddEntryResult` (`Success`, `Entry`, `Updated`, `RejectedProps`) instead of a `(LeaderboardEntry, bool)` tuple ([#228](https://github.com/TaloDev/unity/pull/228)): + +```csharp +// before +(LeaderboardEntry entry, bool updated) = await Talo.Leaderboards.AddEntry(internalName, score, ("team", "red")); +Debug.Log(entry.position); + +// after +var result = await Talo.Leaderboards.AddEntry(internalName, score, ("team", "red")); +if (!result.Success) +{ + foreach (var prop in result.RejectedProps) + { + Debug.Log($"Rejected prop '{prop.key}': {prop.message} ({prop.error})"); + } + return; +} +Debug.Log(result.Entry.position); +``` + +## Stats + +### `GetStat()` removed + +The deprecated `Talo.Stats.GetStat()` method has been removed. Use `Talo.Stats.Find()` instead ([#215](https://github.com/TaloDev/unity/pull/215)): + +```csharp +// before +var stat = await Talo.Stats.GetStat("gold-collected"); + +// after +var stat = await Talo.Stats.Find("gold-collected"); +``` + +## Channels + +### Deprecated methods removed + +The deprecated `GetChannels(int page)`, `CreatePrivate()` and `Create(string name, ...)` overloads have been removed ([#216](https://github.com/TaloDev/unity/pull/216), [#217](https://github.com/TaloDev/unity/pull/217), [#218](https://github.com/TaloDev/unity/pull/218)): + +```csharp +// before +var res = await Talo.Channels.GetChannels(page); +var privateChannel = await Talo.Channels.CreatePrivate("secret-guild", autoCleanup: true); +var guild = await Talo.Channels.Create("guild", true, ("team", "red")); + +// after +var res = await Talo.Channels.GetChannels(new GetChannelsOptions { page = page }); +var privateResult = await Talo.Channels.Create(new CreateChannelOptions +{ + name = "secret-guild", + autoCleanup = true, + isPrivate = true +}); +var guildResult = await Talo.Channels.Create(new CreateChannelOptions +{ + name = "guild", + autoCleanup = true, + props = new[] { ("team", "red") } +}); +``` + +### Updating channels + +`Update()` now takes an `UpdateChannelOptions` object instead of positional arguments. `Create()` and `Update()` now return a `ChannelUpsertResult` (`Success`, `Channel`, `RejectedProps`) instead of a `Channel` ([#226](https://github.com/TaloDev/unity/pull/226), [#228](https://github.com/TaloDev/unity/pull/228)): + +```csharp +// before +await Talo.Channels.Update(channelId, "new name", newOwnerAliasId, ("team", "red")); + +// after +var options = new UpdateChannelOptions +{ + name = "new name", + newOwnerAliasId = newOwnerAliasId, + props = new[] { ("team", "red") } +}; +var result = await Talo.Channels.Update(channelId, options); +if (!result.Success) +{ + foreach (var prop in result.RejectedProps) { ... } + return; +} +``` + +`UpdateChannelOptions` also adds nullable toggles - `autoCleanup`, `isPrivate` and `temporaryMembership` are `bool?` - so you can leave a field unchanged (`null`) or explicitly set it to `true`/`false`. `name` (empty string) and `newOwnerAliasId` (`-1`) work the same way. + +### Channel storage errors + +`ChannelStoragePropError` has been removed. The `OnChannelStoragePropsFailedToSet` event now passes `RejectedProp[]` instead of `ChannelStoragePropError[]` ([#222](https://github.com/TaloDev/unity/pull/222)): + +```csharp +// before +Talo.Channels.OnChannelStoragePropsFailedToSet += (Channel channel, ChannelStoragePropError[] errors) => +{ + foreach (var prop in errors) Debug.Log($"{prop.key}: {prop.message} ({prop.error})"); +}; + +// after +Talo.Channels.OnChannelStoragePropsFailedToSet += (Channel channel, RejectedProp[] errors) => +{ + foreach (var prop in errors) Debug.Log($"{prop.key}: {prop.message} ({prop.error})"); +}; +``` + +`RejectedProp` keeps the same shape (`key`, `error`, `message`), so only the type annotation changes. + +## Saves + +### Updating the current save + +`UpdateCurrentSave()` now returns a `SaveUpdateResult` (`Success`, `Save`) on **both** the debounced content-sync path and the immediate rename path (previously the rename path returned a `GameSave`) ([#227](https://github.com/TaloDev/unity/pull/227)): + +```csharp +// rename path (immediate, same shape as the content-sync path): +var result = await Talo.Saves.UpdateCurrentSave("new name"); +// result.Success - whether the PATCH round-trip OK +// result.Save - the save that was synced (null on failure) +``` + +## Feedback + +### Sending feedback + +`Send()` now returns a `FeedbackSendResult` (`Success`, `RejectedProps`) instead of `void`, and the `OnPropsRejected` event has been removed ([#228](https://github.com/TaloDev/unity/pull/228)): + +```csharp +// before +await Talo.Feedback.Send(category, comment, ("version", "1.2")); + +// after +var result = await Talo.Feedback.Send(category, comment, ("version", "1.2")); +if (!result.Success) +{ + foreach (var prop in result.RejectedProps) + { + Debug.Log($"Rejected prop '{prop.key}': {prop.message} ({prop.error})"); + } +} +``` + +## Exception handling + +`PlayerAuthException.ErrorCode` and `SocketException.ErrorCode` now fall back to `API_ERROR` when the server returns an unrecognised code, instead of throwing ([#225](https://github.com/TaloDev/unity/pull/225)). + +## Quick reference + + +| Removed / renamed | Replacement | +| -------------------------------------------------------------------- | -------------------------------------------------------------------------- | +| `Talo.Players.Identify*()` → `Player` | `Talo.Players.Identify*()` → `PlayerAlias` (use `.player`) | +| `Talo.Players.OnIdentified(Player)` | `Talo.Players.OnIdentified(PlayerAlias)` | +| `Talo.Players.OnIdentificationFailed` (no args) | `Talo.Players.OnIdentificationFailed(IdentifyException)` | +| `Talo.Players.OnPropsRejected` | `PlayerUpdateResult.RejectedProps` | +| Prop editing on players, e.g. `SetProp()`, returns `void` | `Talo.CurrentPlayer.SetProp()` is awaitable → `Task` | +| Leading debounce (first call sends immediately) | Trailing-only: all calls coalesce into one request after the window | +| `Talo.Leaderboards.GetEntriesForCurrentPlayer()` | `Talo.Leaderboards.GetEntries()` + `aliasId`/`playerId` options | +| `Talo.Leaderboards.GetCachedEntriesForCurrentPlayer()` | `Talo.Leaderboards.GetCachedEntries()` + `aliasId`/`playerId` options | +| `Talo.Leaderboards.GetEntries(name, page, aliasId, includeArchived)` | `Talo.Leaderboards.GetEntries(name, GetEntriesOptions)` | +| `Talo.Leaderboards.AddEntry()` → `(LeaderboardEntry, bool)` tuple | `Talo.Leaderboards.AddEntry()` → `AddEntryResult` (`.Entry`, `.Updated`) | +| `Talo.Leaderboards.OnPropsRejected` | `AddEntryResult.RejectedProps` | +| `Talo.Stats.GetStat(name)` | `Talo.Stats.Find(name)` | +| `Talo.Channels.GetChannels(page)` | `Talo.Channels.GetChannels(new GetChannelsOptions { page = page })` | +| `Talo.Channels.CreatePrivate(name, ...)` | `Talo.Channels.Create(new CreateChannelOptions { isPrivate = true, ... })` | +| `Talo.Channels.Create(name, autoCleanup, props)` | `Talo.Channels.Create(new CreateChannelOptions { ... })` | +| `Talo.Channels.Update(id, name, ownerId, props)` | `Talo.Channels.Update(id, new UpdateChannelOptions { ... })` | +| `Talo.Channels.Create()`/`Update()` returned `Channel` | `ChannelUpsertResult` (use `.Channel`, check `.Success`) | +| `Talo.Channels.OnChannelPropsRejected` | `ChannelUpsertResult.RejectedProps` | +| `ChannelStoragePropError` | `RejectedProp` | +| `Talo.Saves.UpdateCurrentSave()` → `GameSave` | `Talo.Saves.UpdateCurrentSave()` → `SaveUpdateResult` (both paths) | +| `Talo.Feedback.Send()` → `void` | `Talo.Feedback.Send()` → `FeedbackSendResult` | +| `Talo.Feedback.OnPropsRejected` | `FeedbackSendResult.RejectedProps` | + +