Skip to content

Commit 0156dc1

Browse files
authored
Merge pull request #118 from TaloDev/develop
Release 0.29.0
2 parents fca6daf + 1f29aa1 commit 0156dc1

27 files changed

+367
-33
lines changed

.github/hooks/pre-commit

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/bin/bash
2+
3+
cd addons/talo
4+
5+
PLUGIN_CFG="plugin.cfg"
6+
TALO_CLIENT_GD="talo_client.gd"
7+
8+
if [ ! -f "$PLUGIN_CFG" ]; then
9+
echo "Error: $PLUGIN_CFG not found."
10+
exit 1
11+
fi
12+
13+
if [ ! -f "$TALO_CLIENT_GD" ]; then
14+
echo "Error: $TALO_CLIENT_GD not found."
15+
exit 1
16+
fi
17+
18+
# `grep -E '^version='` finds the line starting with 'version='.
19+
# `sed -E 's/version="([^"]+)"/\1/'` extracts the version string between quotes.
20+
NEW_VERSION=$(grep -E '^version=' "$PLUGIN_CFG" | sed -E 's/version="([^"]+)"/\1/')
21+
22+
if [ -z "$NEW_VERSION" ]; then
23+
echo "Error: could not extract version from $PLUGIN_CFG."
24+
exit 1
25+
fi
26+
27+
echo "Detected new version: $NEW_VERSION"
28+
29+
# `sed -i.bak` edits the file in place and creates a backup with .bak extension.
30+
# the regex `(const TALO_CLIENT_VERSION = \")[^\"]+(\")` captures the parts before and after the version.
31+
# `\1$NEW_VERSION\2` reconstructs the line with the new version inserted.
32+
sed -i.bak -E "s/(const TALO_CLIENT_VERSION = \")[^\"]+(\")/\1$NEW_VERSION\2/" "$TALO_CLIENT_GD"
33+
34+
if [ $? -ne 0 ]; then
35+
echo "Error: failed to update version in $TALO_CLIENT_GD."
36+
rm -f "$TALO_CLIENT_GD.bak" # Clean up the backup file if an error occurred
37+
exit 1
38+
fi
39+
40+
rm -f "$TALO_CLIENT_GD.bak"
41+
42+
git add "$TALO_CLIENT_GD"
43+
44+
echo "Successfully updated $TALO_CLIENT_GD with version $NEW_VERSION."
45+
echo "Pre-commit hook finished."
46+
47+
exit 0

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ channels](https://trytalo.com/channels): Send real-time messages between players
3333
- 🎮 Playground: a text-based playground allowing you to test identifying, events, stats and leaderboards.
3434
- 🕹️ Leaderboards: a basic leaderboard UI, allowing you to add and update entries.
3535
- 💾 Multi-scene saves: a small game showing how to persist save data across multiple scenes.
36-
- 💾 Stateful buttons: a simple demo showing how to save and load game state.
36+
- 💾 Persistent buttons: a simple demo showing how to save and load game state.
3737
- 💬 Chat: showing how to send messages between channels in a chat room.
38+
- 🤝 Channel storage: showing how to store data that can be accessed by other players using channels.
3839

3940
## Documentation
4041

addons/talo/apis/channels_api.gd

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,17 @@ signal channel_ownership_transferred(channel: TaloChannel, new_owner_player_alia
1717
signal channel_deleted(channel: TaloChannel)
1818
## Emitted when a channel is updated.
1919
signal channel_updated(channel: TaloChannel, changed_properties: Array[String])
20+
## Emitted when channel storage props are updated or deleted.
21+
signal channel_storage_props_updated(channel: TaloChannel, upserted_props: Array[TaloChannelStorageProp], deleted_props: Array[TaloChannelStorageProp])
22+
## Emitted when one or more storage props were not successfully set.
23+
signal channel_storage_props_failed_to_set(channel: TaloChannel, failed_props: Array[TaloChannelStoragePropError])
24+
25+
var _storage_manager := TaloChannelStorageManager.new()
2026

2127
func _ready() -> void:
2228
await Talo.init_completed
2329
Talo.socket.message_received.connect(_on_message_received)
30+
channel_storage_props_updated.connect(_storage_manager.on_props_updated)
2431

2532
func _on_message_received(res: String, data: Dictionary) -> void:
2633
match res:
@@ -31,13 +38,19 @@ func _on_message_received(res: String, data: Dictionary) -> void:
3138
"v1.channels.player-left":
3239
player_left.emit(TaloChannel.new(data.channel), TaloPlayerAlias.new(data.playerAlias), data.meta.reason)
3340
"v1.channels.ownership-transferred":
34-
player_left.emit(TaloChannel.new(data.channel), TaloPlayerAlias.new(data.newOwner))
41+
channel_ownership_transferred.emit(TaloChannel.new(data.channel), TaloPlayerAlias.new(data.newOwner))
3542
"v1.channels.deleted":
3643
channel_deleted.emit(TaloChannel.new(data.channel))
3744
"v1.channels.updated":
3845
var changed_properties: Array[String] = []
3946
changed_properties.assign(data.changedProperties)
4047
channel_updated.emit(TaloChannel.new(data.channel), changed_properties)
48+
"v1.channels.storage.updated":
49+
var upserted_props: Array[TaloChannelStorageProp] = []
50+
upserted_props.assign(data.upsertedProps.map(func (prop: Dictionary): return TaloChannelStorageProp.new(prop)))
51+
var deleted_props: Array[TaloChannelStorageProp] = []
52+
deleted_props.assign(data.deletedProps.map(func (prop: Dictionary): return TaloChannelStorageProp.new(prop)))
53+
channel_storage_props_updated.emit(TaloChannel.new(data.channel), upserted_props, deleted_props)
4154

4255
## Get a channel by its ID.
4356
func find(channel_id: int) -> TaloChannel:
@@ -142,7 +155,7 @@ func leave(channel_id: int) -> void:
142155
await client.make_request(HTTPClient.METHOD_POST, "/%s/leave" % channel_id)
143156

144157
## Update a channel. This will only work if the current player is the owner of the channel.
145-
func update(channel_id: int, name: String = "", new_owner_alias_id: int = -1, props: Dictionary = {}) -> TaloChannel:
158+
func update(channel_id: int, name: String = "", new_owner_alias_id: int = -1, props: Dictionary[String, Variant] = {}) -> TaloChannel:
146159
if Talo.identity_check() != OK:
147160
return
148161

@@ -152,7 +165,7 @@ func update(channel_id: int, name: String = "", new_owner_alias_id: int = -1, pr
152165
if new_owner_alias_id != -1:
153166
data.ownerAliasId = new_owner_alias_id
154167
if props.size() > 0:
155-
data.props = props
168+
data.props = TaloPropUtils.dictionary_to_array(props)
156169

157170
var res := await client.make_request(HTTPClient.METHOD_PUT, "/%s" % channel_id, data)
158171

@@ -216,6 +229,43 @@ func get_members(channel_id: int) -> Array[TaloPlayerAlias]:
216229
_:
217230
return []
218231

232+
func get_storage_prop(channel_id: int, prop_key: String, bust_cache: bool = false) -> TaloChannelStorageProp:
233+
if Talo.identity_check() != OK:
234+
return null
235+
236+
if not bust_cache:
237+
return await _storage_manager.get_prop(channel_id, prop_key)
238+
239+
var res := await client.make_request(HTTPClient.METHOD_GET, "/%s/storage?propKey=%s" % [channel_id, prop_key])
240+
241+
match res.status:
242+
200:
243+
if not res.body.prop:
244+
return null
245+
246+
var prop := TaloChannelStorageProp.new(res.body.prop)
247+
_storage_manager.upsert_prop(channel_id, prop)
248+
return prop
249+
_:
250+
return null
251+
252+
func set_storage_props(channel_id: int, props: Dictionary[String, Variant]) -> void:
253+
if Talo.identity_check() != OK:
254+
return
255+
256+
var res := await client.make_request(HTTPClient.METHOD_PUT, "/%s/storage" % channel_id, {
257+
props = TaloPropUtils.dictionary_to_array(props)
258+
})
259+
260+
match res.status:
261+
200:
262+
if res.body.failedProps.size() > 0:
263+
var failed_props: Array[TaloChannelStoragePropError] = []
264+
failed_props.assign(res.body.failedProps.map(
265+
func (prop: Dictionary): return TaloChannelStoragePropError.new(prop.key, prop.error))
266+
)
267+
channel_storage_props_failed_to_set.emit(TaloChannel.new(res.body.channel), failed_props)
268+
219269
class ChannelPage:
220270
var channels: Array[TaloChannel]
221271
var count: int
@@ -240,7 +290,7 @@ class GetSubscribedChannelsOptions:
240290
class CreateChannelOptions:
241291
var name: String = ""
242292
var auto_cleanup: bool = false
243-
var props: Dictionary = {}
293+
var props: Dictionary[String, String] = {}
244294
var private: bool = false
245295
var temporary_membership: bool = false
246296

addons/talo/apis/events_api.gd

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,16 @@ func _has_errors(errors: Array) -> bool:
3232
return errors.any((func (err: Array): return err.size() > 0))
3333

3434
## Track an event with optional props (key-value pairs) and add it to the queue of events ready to be sent to the backend. If the queue reaches the minimum size, it will be flushed.
35-
func track(name: String, props: Dictionary = {}) -> void:
35+
func track(name: String, props: Dictionary[String, Variant] = {}) -> void:
3636
if Talo.identity_check() != OK:
3737
return
3838

3939
var final_props := _build_meta_props()
40-
final_props.append_array(
41-
props
42-
.keys()
43-
.map(func (key: String): return TaloProp.new(key, str(props[key])))
44-
)
40+
final_props.append_array(TaloPropUtils.dictionary_to_prop_array(props))
4541

4642
_queue.push_back({
4743
name = name,
48-
props = final_props.map(func (prop: TaloProp): return prop.to_dictionary()),
44+
props = TaloPropUtils.serialise_prop_array(final_props),
4945
timestamp = TaloTimeUtils.get_timestamp_msec()
5046
})
5147

addons/talo/apis/leaderboards_api.gd

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func get_entries(internal_name: String, options = GetEntriesOptions.new()) -> En
4646
match res.status:
4747
200:
4848
var entries: Array[TaloLeaderboardEntry] = Array(res.body.entries.map(
49-
func(data: Dictionary):
49+
func (data: Dictionary):
5050
var entry := TaloLeaderboardEntry.new(data)
5151
_entries_manager.upsert_entry(internal_name, entry)
5252

@@ -65,15 +65,13 @@ func get_entries_for_current_player(internal_name: String, options = GetEntriesO
6565
return await get_entries(internal_name, options)
6666

6767
## Add an entry to a leaderboard. The props (key-value pairs) parameter is used to store additional data with the entry.
68-
func add_entry(internal_name: String, score: float, props: Dictionary = {}) -> AddEntryResult:
68+
func add_entry(internal_name: String, score: float, props: Dictionary[String, Variant] = {}) -> AddEntryResult:
6969
if Talo.identity_check() != OK:
7070
return null
7171

72-
var props_to_send := props.keys().map(func(key: String) -> Dictionary: return {key = key, value = str(props[key])})
73-
7472
var res := await client.make_request(HTTPClient.METHOD_POST, "/%s/entries" % internal_name, {
7573
score = score,
76-
props = props_to_send
74+
props = TaloPropUtils.dictionary_to_array(props)
7775
})
7876

7977
match res.status:
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
class_name TaloChannelStorageProp extends RefCounted
2+
3+
var key: String
4+
var value: String
5+
var created_by_alias: TaloPlayerAlias
6+
var last_updated_by_alias: TaloPlayerAlias
7+
var created_at: String
8+
var updated_at: String
9+
10+
func _init(data: Dictionary):
11+
key = data.key
12+
value = data.value
13+
created_by_alias = TaloPlayerAlias.new(data.createdBy)
14+
last_updated_by_alias = TaloPlayerAlias.new(data.lastUpdatedBy)
15+
created_at = data.createdAt
16+
updated_at = data.updatedAt
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://cwk21i34hfe62

addons/talo/plugin.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
name="Talo Game Services"
44
description="Talo (https://trytalo.com) is an open-source game backend with services designed to help you build games faster. You can currently:\n\n- Identify and authenticate players\n- Store persistent data across players\n- Track events (levelling up, finding loot, etc)\n- Display high scores with leaderboards\n- Store and load player saves\n- Load game config options and flags from the cloud\n- Get feedback directly from your players\n- Keep your data in-sync even when players are offline\n- Send channel messages between players\n- See if players are online and set custom statuses"
55
author="trytalo"
6-
version="0.28.0"
6+
version="0.29.0"
77
script="talo_autoload.gd"
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
[gd_scene load_steps=2 format=3 uid="uid://cw6g72wpjl0g8"]
2+
3+
[ext_resource type="Script" uid="uid://u7gy82vw7fn6" path="res://addons/talo/samples/channel_storage/scripts/channel_storage_demo.gd" id="1_1wlas"]
4+
5+
[node name="ChannelStorage" type="Node2D"]
6+
script = ExtResource("1_1wlas")
7+
8+
[node name="Control" type="Control" parent="."]
9+
layout_mode = 3
10+
anchors_preset = 0
11+
offset_right = 1080.0
12+
offset_bottom = 720.0
13+
14+
[node name="VBoxContainer" type="VBoxContainer" parent="Control"]
15+
layout_mode = 1
16+
anchors_preset = 8
17+
anchor_left = 0.5
18+
anchor_top = 0.5
19+
anchor_right = 0.5
20+
anchor_bottom = 0.5
21+
offset_left = -450.0
22+
offset_top = -208.5
23+
offset_right = 450.0
24+
offset_bottom = 208.5
25+
grow_horizontal = 2
26+
grow_vertical = 2
27+
theme_override_constants/separation = 20
28+
29+
[node name="PropKey" type="LineEdit" parent="Control/VBoxContainer"]
30+
unique_name_in_owner = true
31+
layout_mode = 2
32+
placeholder_text = "Prop key"
33+
34+
[node name="PropValue" type="LineEdit" parent="Control/VBoxContainer"]
35+
unique_name_in_owner = true
36+
layout_mode = 2
37+
placeholder_text = "Prop value"
38+
39+
[node name="PropLiveValueLabel" type="Label" parent="Control/VBoxContainer"]
40+
unique_name_in_owner = true
41+
layout_mode = 2
42+
text = "%s live value is: %s"
43+
44+
[node name="PropUpdatedLabel" type="Label" parent="Control/VBoxContainer"]
45+
unique_name_in_owner = true
46+
layout_mode = 2
47+
text = "%s was last updated %s by %s"
48+
49+
[node name="UpsertPropButton" type="Button" parent="Control/VBoxContainer"]
50+
layout_mode = 2
51+
text = "Upsert"
52+
53+
[node name="DeletePropButton" type="Button" parent="Control/VBoxContainer"]
54+
layout_mode = 2
55+
text = "Delete"
56+
57+
[node name="InfoLabel" type="Label" parent="Control/VBoxContainer"]
58+
custom_minimum_size = Vector2(900, 0)
59+
layout_mode = 2
60+
text = "
61+
How to use this demo: go to \"Debug > Customize Run Instances...\" and select \"Enable Multiple Instances\".
62+
63+
Enter a prop key and value, then upsert it. You can also prefill the prop by setting the \"Prop Key\" exported variable on the root \"ChannelStorage\" node. You will see changes to your selected prop key in real time."
64+
autowrap_mode = 2
65+
66+
[connection signal="pressed" from="Control/VBoxContainer/UpsertPropButton" to="." method="_on_upsert_prop_button_pressed"]
67+
[connection signal="pressed" from="Control/VBoxContainer/DeletePropButton" to="." method="_on_delete_prop_button_pressed"]

0 commit comments

Comments
 (0)