Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,18 @@ jobs:
- uses: chickensoft-games/setup-godot@v2
name: Set up Godot
with:
version: 4.4.1
version: 4.6.1
use-dotnet: false
include-templates: true

- name: Regenerate .godot folder
run: |
godot --editor --headless --quit

- name: Build
run: |
output=$(godot --headless --export-release '${{ matrix.platform }}' 2>&1)
if echo "$output" | grep -q "SCRIPT ERROR"; then
echo "$output"
exit 1
fi
63 changes: 4 additions & 59 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

This is the **Talo Godot Plugin** - a self-hostable game development backend plugin for the Godot Engine (v4.4+). Talo provides leaderboards, player authentication, event tracking, game saves, stats, channels, live config, and more. The plugin is distributed via the Godot Asset Library and GitHub releases.

## Development Commands

### Building & Testing
```bash
# Export for all platforms (runs on push via CI)
godot --headless --export-release 'Windows Desktop'
godot --headless --export-release 'macOS'
godot --headless --export-release 'Linux'
godot --headless --export-release 'Web'

# The CI checks for SCRIPT ERROR in build output and fails if found
```

### Running Samples
The project includes sample scenes demonstrating plugin features. The main scene is configured as `res://addons/talo/samples/playground/playground.tscn`. To test samples, change the main scene in [project.godot](project.godot) or run scenes directly from the editor.

Available samples:
- **Playground**: Text-based playground for testing identify, events, stats, leaderboards
- **Authentication**: Registration/login/account management flow
- **Leaderboards**: Basic leaderboard UI
- **Multi-scene saves**: Persist save data across multiple scenes
- **Persistent buttons**: Simple save/load demo
- **Chat**: Real-time messaging using channels
- **Channel storage**: Shared player data storage
This is the **Talo Godot Plugin** - a self-hostable game development backend plugin for the Godot Engine (v4.6+). Talo provides leaderboards, player authentication, event tracking, game saves, stats, channels, live config, and more. The plugin is distributed via the Godot Asset Library and GitHub releases.

## Architecture

Expand Down Expand Up @@ -70,37 +45,15 @@ The plugin is an **autoload singleton** called `Talo` (defined in [talo_manager.
- Requires ticket creation via [socket_tickets_api.gd](addons/talo/apis/socket_tickets_api.gd)
- Handles player identification with socket token
- Polls in `_process()` loop
- Used by channels, player presence, and custom real-time features
- Used by channels, player presence and player relationships

### API Layer

All API classes extend `TaloAPI` ([apis/api.gd](addons/talo/apis/api.gd)), which provides a `TaloClient` instance. There are 14 API classes:

- [players_api.gd](addons/talo/apis/players_api.gd) - Player identification and management
- [player_auth_api.gd](addons/talo/apis/player_auth_api.gd) - Authentication and sessions
- [events_api.gd](addons/talo/apis/events_api.gd) - Event tracking
- [stats_api.gd](addons/talo/apis/stats_api.gd) - Player and global stats
- [leaderboards_api.gd](addons/talo/apis/leaderboards_api.gd) - Leaderboard management
- [saves_api.gd](addons/talo/apis/saves_api.gd) - Game save operations
- [feedback_api.gd](addons/talo/apis/feedback_api.gd) - Player feedback collection
- [game_config_api.gd](addons/talo/apis/game_config_api.gd) - Live config
- [health_check_api.gd](addons/talo/apis/health_check_api.gd) - Connectivity checks (debounced)
- [player_groups_api.gd](addons/talo/apis/player_groups_api.gd) - Player grouping
- [channels_api.gd](addons/talo/apis/channels_api.gd) - Real-time messaging
- [socket_tickets_api.gd](addons/talo/apis/socket_tickets_api.gd) - Socket authentication
- [player_presence_api.gd](addons/talo/apis/player_presence_api.gd) - Online status
All API classes extend `TaloAPI` ([apis/api.gd](addons/talo/apis/api.gd)), which provides a `TaloClient` instance.

### Entity System

18 entity classes in [addons/talo/entities/](addons/talo/entities/) represent API data models. Key entities:
- `TaloPlayer` - Player data with props
- `TaloPlayerAlias` - Player identity/device association
- `TaloLeaderboardEntry` - Leaderboard scores
- `TaloGameSave` - Saved game state
- `TaloLiveConfig` - Dynamic configuration
- `TaloChannel` - Real-time messaging channels

Most entities extend `TaloLoadable` ([entities/loadable.gd](addons/talo/entities/loadable.gd)) which provides JSON serialization and `from_api()` factory methods.
Entity classes in [addons/talo/entities/](addons/talo/entities/) represent API data models.

### Utilities

Expand Down Expand Up @@ -135,14 +88,6 @@ Talo="*res://addons/talo/talo_manager.gd"

Settings are in [addons/talo/settings.cfg](addons/talo/settings.cfg) - this file is auto-generated and should be filled with the user's access key.

## CI/CD

GitHub Actions workflows in [.github/workflows/](/.github/workflows/):
- **ci.yml** - Runs on every push, exports for all platforms, fails on script errors
- **create-release.yml** - Automates releases
- **tag.yml** - Handles version tagging
- **claude-code-review.yml** - Automated code review

## Important Notes

- **Process mode**: TaloManager uses `PROCESS_MODE_ALWAYS` ([talo_manager.gd](addons/talo/talo_manager.gd:48))
Expand Down
13 changes: 13 additions & 0 deletions addons/talo/apis/events_api.gd
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ class_name EventsAPI extends TaloAPI
##
## @tutorial: https://docs.trytalo.com/docs/godot/events

## Emitted after pending events are flushed.
signal pending_events_flushed

var _queue := []
var _min_queue_size := 10

Expand Down Expand Up @@ -80,9 +83,19 @@ func flush() -> void:
_flush_attempted_during_lock = false
await flush()

pending_events_flushed.emit()

## Clear the queue of events waiting to be flushed.
func clear_queue() -> void:
_queue.clear()
_events_to_flush.clear()
_lock_flushes = false
_flush_attempted_during_lock = false

## Get the number of events waiting to be flushed.
func get_queue_size() -> int:
return _queue.size() + _events_to_flush.size()

## Check to see if events are currently being flushed.
func is_flush_pending() -> bool:
return _lock_flushes
29 changes: 25 additions & 4 deletions addons/talo/apis/leaderboards_api.gd
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,22 @@ class_name LeaderboardsAPI extends TaloAPI

var _entries_manager := TaloLeaderboardEntriesManager.new()

## Get a list of all the entries that have been previously fetched or created for a leaderboard.
func get_cached_entries(internal_name: String) -> Array[TaloLeaderboardEntry]:
return _entries_manager.get_entries(internal_name)
## Get a list of all the entries that have been previously fetched or created for a leaderboard. The options include "alias_id", "player_id" and "alias_service" for additional filtering.
func get_cached_entries(internal_name: String, options := GetCachedEntriesOptions.new()) -> Array[TaloLeaderboardEntry]:
var entries := _entries_manager.get_entries(internal_name).filter(
func (entry: TaloLeaderboardEntry) -> bool:
# filter by alias_id if set
return (options.alias_id == -1 or entry.player_alias.id == options.alias_id) and \
# filter by player_id if set
(options.player_id == "" or entry.player_alias.player.id == options.player_id) and \
# filter by alias_service if set
(options.alias_service == "" or entry.player_alias.service == options.alias_service)
)

return entries

## Get a list of all the entries that have been previously fetched or created for a leaderboard for the current player.
## @deprecated: Use get_cached_entries() with the alias_id or player_id option instead.
func get_cached_entries_for_current_player(internal_name: String) -> Array[TaloLeaderboardEntry]:
if Talo.identity_check() != OK:
return []
Expand All @@ -21,7 +32,7 @@ func get_cached_entries_for_current_player(internal_name: String) -> Array[TaloL
return entry.player_alias.id == Talo.current_alias.id
)

## Get a list of entries for a leaderboard. The options include "page", "alias_id", "player_id", "include_archived", "prop_key", "prop_value", "start_date" and "end_date" for additional filtering.
## Get a list of entries for a leaderboard. The options include "page", "alias_id", "player_id", "include_archived", "prop_key", "prop_value", "start_date", "end_date" and "alias_service" for additional filtering.
func get_entries(internal_name: String, options := GetEntriesOptions.new()) -> EntriesPage:
var url := "/%s/entries?page=%s"
var url_data := [internal_name, options.page]
Expand Down Expand Up @@ -53,6 +64,10 @@ func get_entries(internal_name: String, options := GetEntriesOptions.new()) -> E
url += "&endDate=%s"
url_data.append(options.end_date)

if options.alias_service != "":
url += "&aliasService=%s"
url_data.append(options.alias_service)

var res := await client.make_request(HTTPClient.METHOD_GET, url % url_data)

match res.status:
Expand Down Expand Up @@ -125,3 +140,9 @@ class GetEntriesOptions:
var prop_value: String = ""
var start_date: String = ""
var end_date: String = ""
var alias_service: String = ""

class GetCachedEntriesOptions:
var alias_id: int = -1
var player_id: String = ""
var alias_service: String = ""
4 changes: 2 additions & 2 deletions addons/talo/plugin.cfg
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[plugin]

name="Talo Game Services"
description="Talo (https://trytalo.com) is an open-source game backend. Talo's Godot plugin is the easiest way to add leaderboards, player authentication, socket-based multiplayer and more to your game."
description="Talo (https://trytalo.com) is an open-source game backend. Talo's Godot plugin is the easiest way to add leaderboards, player authentication, peer-to-peer multiplayer and more to your game."
author="trytalo"
version="0.41.1"
version="0.42.0"
script="talo_autoload.gd"
2 changes: 1 addition & 1 deletion addons/talo/samples/authentication/assets/theme.tres
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[gd_resource type="Theme" load_steps=12 format=3 uid="uid://ce2uyi827vc5x"]
[gd_resource type="Theme" format=3 uid="uid://ce2uyi827vc5x"]

[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_m0swq"]
bg_color = Color(0.215686, 0.188235, 0.639216, 1)
Expand Down
28 changes: 14 additions & 14 deletions addons/talo/samples/authentication/authentication.tscn
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[gd_scene load_steps=12 format=3 uid="uid://bwh4ytfs4g7js"]
[gd_scene format=3 uid="uid://bwh4ytfs4g7js"]

[ext_resource type="PackedScene" uid="uid://bjopxp3rhpinb" path="res://addons/talo/samples/authentication/states/register.tscn" id="1_5qw25"]
[ext_resource type="Script" uid="uid://dn2sal6firbmh" path="res://addons/talo/samples/authentication/scripts/authentication.gd" id="1_aqcrf"]
Expand All @@ -12,56 +12,56 @@
[ext_resource type="PackedScene" uid="uid://8aqpg6o0a1xx" path="res://addons/talo/samples/authentication/states/reset_password.tscn" id="9_6wobc"]
[ext_resource type="PackedScene" uid="uid://d17bvaxw1qew4" path="res://addons/talo/samples/authentication/states/delete_account.tscn" id="10_gndcp"]

[node name="Authentication" type="Node2D"]
[node name="Authentication" type="Node2D" unique_id=779908945]
script = ExtResource("1_aqcrf")

[node name="UI" type="Control" parent="."]
[node name="UI" type="Control" parent="." unique_id=1905616596]
layout_mode = 3
anchors_preset = 0
offset_right = 1080.0
offset_bottom = 720.0

[node name="States" type="Control" parent="UI"]
[node name="States" type="Control" parent="UI" unique_id=2125262061]
unique_name_in_owner = true
anchors_preset = 0
offset_right = 40.0
offset_bottom = 40.0

[node name="Login" parent="UI/States" instance=ExtResource("2_fsclp")]
[node name="Login" parent="UI/States" unique_id=863259645 instance=ExtResource("2_fsclp")]
unique_name_in_owner = true

[node name="Register" parent="UI/States" instance=ExtResource("1_5qw25")]
[node name="Register" parent="UI/States" unique_id=1550849126 instance=ExtResource("1_5qw25")]
unique_name_in_owner = true
visible = false

[node name="Verify" parent="UI/States" instance=ExtResource("3_njdp7")]
[node name="Verify" parent="UI/States" unique_id=1739562229 instance=ExtResource("3_njdp7")]
unique_name_in_owner = true
visible = false

[node name="InGame" parent="UI/States" instance=ExtResource("7_sxmru")]
[node name="InGame" parent="UI/States" unique_id=1823958552 instance=ExtResource("7_sxmru")]
unique_name_in_owner = true
visible = false

[node name="ChangeEmail" parent="UI/States" instance=ExtResource("4_vngav")]
[node name="ChangeEmail" parent="UI/States" unique_id=1215911437 instance=ExtResource("4_vngav")]
unique_name_in_owner = true
visible = false

[node name="ChangeIdentifier" parent="UI/States" instance=ExtResource("7_m6xjn")]
[node name="ChangeIdentifier" parent="UI/States" unique_id=1903622653 instance=ExtResource("7_m6xjn")]
unique_name_in_owner = true
visible = false

[node name="ChangePassword" parent="UI/States" instance=ExtResource("5_q15qy")]
[node name="ChangePassword" parent="UI/States" unique_id=143607790 instance=ExtResource("5_q15qy")]
unique_name_in_owner = true
visible = false

[node name="ForgotPassword" parent="UI/States" instance=ExtResource("8_tqrpb")]
[node name="ForgotPassword" parent="UI/States" unique_id=2059359006 instance=ExtResource("8_tqrpb")]
unique_name_in_owner = true
visible = false

[node name="ResetPassword" parent="UI/States" instance=ExtResource("9_6wobc")]
[node name="ResetPassword" parent="UI/States" unique_id=242466536 instance=ExtResource("9_6wobc")]
unique_name_in_owner = true
visible = false

[node name="DeleteAccount" parent="UI/States" instance=ExtResource("10_gndcp")]
[node name="DeleteAccount" parent="UI/States" unique_id=1100626197 instance=ExtResource("10_gndcp")]
unique_name_in_owner = true
visible = false
24 changes: 12 additions & 12 deletions addons/talo/samples/authentication/states/change_email.tscn
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
[gd_scene load_steps=3 format=3 uid="uid://b6mypp2qa8m4u"]
[gd_scene format=3 uid="uid://b6mypp2qa8m4u"]

[ext_resource type="Script" uid="uid://vflrdq5hi1n4" path="res://addons/talo/samples/authentication/scripts/change_email.gd" id="1_qamph"]
[ext_resource type="Theme" uid="uid://ce2uyi827vc5x" path="res://addons/talo/samples/authentication/assets/theme.tres" id="2_5w58c"]

[node name="ChangeEmail" type="Node2D"]
[node name="ChangeEmail" type="Node2D" unique_id=277492208]
script = ExtResource("1_qamph")

[node name="UI" type="Control" parent="."]
[node name="UI" type="Control" parent="." unique_id=1414145717]
layout_mode = 3
anchors_preset = 0
offset_right = 1080.0
offset_bottom = 720.0

[node name="Background" type="ColorRect" parent="UI"]
[node name="Background" type="ColorRect" parent="UI" unique_id=173163657]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
Expand All @@ -21,7 +21,7 @@ grow_horizontal = 2
grow_vertical = 2
color = Color(0.121569, 0.160784, 0.215686, 1)

[node name="MarginContainer" type="MarginContainer" parent="UI"]
[node name="MarginContainer" type="MarginContainer" parent="UI" unique_id=839615226]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
Expand All @@ -33,49 +33,49 @@ theme_override_constants/margin_top = 40
theme_override_constants/margin_right = 40
theme_override_constants/margin_bottom = 40

[node name="VBoxContainer" type="VBoxContainer" parent="UI/MarginContainer"]
[node name="VBoxContainer" type="VBoxContainer" parent="UI/MarginContainer" unique_id=582372699]
layout_mode = 2
size_flags_vertical = 4
theme_override_constants/separation = 24

[node name="Title" type="Label" parent="UI/MarginContainer/VBoxContainer"]
[node name="Title" type="Label" parent="UI/MarginContainer/VBoxContainer" unique_id=583982607]
layout_mode = 2
size_flags_vertical = 0
theme = ExtResource("2_5w58c")
text = "Change email"
horizontal_alignment = 1

[node name="Password" type="TextEdit" parent="UI/MarginContainer/VBoxContainer"]
[node name="Password" type="TextEdit" parent="UI/MarginContainer/VBoxContainer" unique_id=161400492]
unique_name_in_owner = true
custom_minimum_size = Vector2(400, 40)
layout_mode = 2
size_flags_horizontal = 4
theme = ExtResource("2_5w58c")
placeholder_text = "Current password"

[node name="NewEmail" type="TextEdit" parent="UI/MarginContainer/VBoxContainer"]
[node name="NewEmail" type="TextEdit" parent="UI/MarginContainer/VBoxContainer" unique_id=1874415675]
unique_name_in_owner = true
custom_minimum_size = Vector2(400, 40)
layout_mode = 2
size_flags_horizontal = 4
theme = ExtResource("2_5w58c")
placeholder_text = "New email"

[node name="Submit" type="Button" parent="UI/MarginContainer/VBoxContainer"]
[node name="Submit" type="Button" parent="UI/MarginContainer/VBoxContainer" unique_id=636301946]
custom_minimum_size = Vector2(200, 40)
layout_mode = 2
size_flags_horizontal = 4
theme = ExtResource("2_5w58c")
text = "Submit"

[node name="Cancel" type="Button" parent="UI/MarginContainer/VBoxContainer"]
[node name="Cancel" type="Button" parent="UI/MarginContainer/VBoxContainer" unique_id=1447593585]
custom_minimum_size = Vector2(200, 40)
layout_mode = 2
size_flags_horizontal = 4
theme = ExtResource("2_5w58c")
text = "Cancel"

[node name="ValidationLabel" type="Label" parent="UI/MarginContainer/VBoxContainer"]
[node name="ValidationLabel" type="Label" parent="UI/MarginContainer/VBoxContainer" unique_id=336919666]
unique_name_in_owner = true
custom_minimum_size = Vector2(400, 2.08165e-12)
layout_mode = 2
Expand Down
Loading