└── addons └── talo ├── apis ├── api.gd ├── api.gd.uid ├── channels_api.gd ├── channels_api.gd.uid ├── events_api.gd ├── events_api.gd.uid ├── feedback_api.gd ├── feedback_api.gd.uid ├── game_config_api.gd ├── game_config_api.gd.uid ├── health_check_api.gd ├── health_check_api.gd.uid ├── leaderboards_api.gd ├── leaderboards_api.gd.uid ├── player_auth_api.gd ├── player_auth_api.gd.uid ├── player_groups_api.gd ├── player_groups_api.gd.uid ├── player_presence_api.gd ├── player_presence_api.gd.uid ├── players_api.gd ├── players_api.gd.uid ├── saves_api.gd ├── saves_api.gd.uid ├── socket_tickets_api.gd ├── socket_tickets_api.gd.uid ├── stats_api.gd └── stats_api.gd.uid ├── entities ├── channel.gd ├── channel.gd.uid ├── entity_with_props.gd ├── entity_with_props.gd.uid ├── feedback_category.gd ├── feedback_category.gd.uid ├── game_save.gd ├── game_save.gd.uid ├── leaderboard_entry.gd ├── leaderboard_entry.gd.uid ├── live_config.gd ├── live_config.gd.uid ├── loadable.gd ├── loadable.gd.uid ├── player.gd ├── player.gd.uid ├── player_alias.gd ├── player_alias.gd.uid ├── player_group.gd ├── player_group.gd.uid ├── player_group_stub.gd ├── player_group_stub.gd.uid ├── player_presence.gd ├── player_presence.gd.uid ├── player_stat.gd ├── player_stat.gd.uid ├── player_stat_snapshot.gd ├── player_stat_snapshot.gd.uid ├── prop.gd ├── prop.gd.uid ├── saved_object.gd ├── saved_object.gd.uid ├── stat.gd └── stat.gd.uid ├── plugin.cfg ├── samples ├── authentication │ ├── assets │ │ └── theme.tres │ ├── authentication.tscn │ ├── scripts │ │ ├── authentication.gd │ │ ├── authentication.gd.uid │ │ ├── change_email.gd │ │ ├── change_email.gd.uid │ │ ├── change_password.gd │ │ ├── change_password.gd.uid │ │ ├── delete_account.gd │ │ ├── delete_account.gd.uid │ │ ├── forgot_password.gd │ │ ├── forgot_password.gd.uid │ │ ├── in_game.gd │ │ ├── in_game.gd.uid │ │ ├── login.gd │ │ ├── login.gd.uid │ │ ├── register.gd │ │ ├── register.gd.uid │ │ ├── reset_password.gd │ │ ├── reset_password.gd.uid │ │ ├── verify.gd │ │ └── verify.gd.uid │ └── states │ │ ├── change_email.tscn │ │ ├── change_password.tscn │ │ ├── delete_account.tscn │ │ ├── forgot_password.tscn │ │ ├── in_game.tscn │ │ ├── login.tscn │ │ ├── register.tscn │ │ ├── reset_password.tscn │ │ └── verify.tscn ├── chat │ ├── chat.tscn │ └── scripts │ │ ├── chat.gd │ │ └── chat.gd.uid ├── leaderboards │ ├── entry.tscn │ ├── leaderboard.tscn │ └── scripts │ │ ├── leaderboard.gd │ │ ├── leaderboard.gd.uid │ │ ├── leaderboard_entry.gd │ │ └── leaderboard_entry.gd.uid ├── multiscene_saves │ ├── scenes │ │ ├── blue_zone.tscn │ │ ├── green_zone.tscn │ │ ├── player.tscn │ │ ├── portal.tscn │ │ └── star.tscn │ ├── scripts │ │ ├── loadable_player.gd │ │ ├── loadable_player.gd.uid │ │ ├── loadable_star.gd │ │ ├── loadable_star.gd.uid │ │ ├── player_controller.gd │ │ ├── player_controller.gd.uid │ │ ├── portal.gd │ │ └── portal.gd.uid │ └── starting_zone.tscn ├── playground │ ├── playground.tscn │ └── scripts │ │ ├── add_entry_button.gd │ │ ├── add_entry_button.gd.uid │ │ ├── create_save_button.gd │ │ ├── create_save_button.gd.uid │ │ ├── delete_prop_button.gd │ │ ├── delete_prop_button.gd.uid │ │ ├── delete_save_button.gd │ │ ├── delete_save_button.gd.uid │ │ ├── flush_events_button.gd │ │ ├── flush_events_button.gd.uid │ │ ├── get_all_stats_button.gd │ │ ├── get_all_stats_button.gd.uid │ │ ├── get_categories_button.gd │ │ ├── get_categories_button.gd.uid │ │ ├── get_config_button.gd │ │ ├── get_config_button.gd.uid │ │ ├── get_entries_button.gd │ │ ├── get_entries_button.gd.uid │ │ ├── get_global_history_button.gd │ │ ├── get_global_history_button.gd.uid │ │ ├── get_group_button.gd │ │ ├── get_group_button.gd.uid │ │ ├── get_saves_button.gd │ │ ├── get_saves_button.gd.uid │ │ ├── get_stat_button.gd │ │ ├── get_stat_button.gd.uid │ │ ├── get_stat_history_button.gd │ │ ├── get_stat_history_button.gd.uid │ │ ├── identified_label.gd │ │ ├── identified_label.gd.uid │ │ ├── identified_state.gd │ │ ├── identified_state.gd.uid │ │ ├── identify_button.gd │ │ ├── identify_button.gd.uid │ │ ├── load_save_button.gd │ │ ├── load_save_button.gd.uid │ │ ├── loadable_color_rect.gd │ │ ├── loadable_color_rect.gd.uid │ │ ├── open_docs_button.gd │ │ ├── open_docs_button.gd.uid │ │ ├── randomise_save_button.gd │ │ ├── randomise_save_button.gd.uid │ │ ├── response_label.gd │ │ ├── response_label.gd.uid │ │ ├── send_feedback_button.gd │ │ ├── send_feedback_button.gd.uid │ │ ├── set_prop_button.gd │ │ ├── set_prop_button.gd.uid │ │ ├── toggle_continuity_button.gd │ │ ├── toggle_continuity_button.gd.uid │ │ ├── toggle_network_button.gd │ │ ├── toggle_network_button.gd.uid │ │ ├── track_event_button.gd │ │ ├── track_event_button.gd.uid │ │ ├── track_stat_button.gd │ │ ├── track_stat_button.gd.uid │ │ ├── update_save_button.gd │ │ └── update_save_button.gd.uid └── stateful_buttons │ ├── loadable_button.gd │ ├── loadable_button.gd.uid │ ├── stateful_buttons.tscn │ ├── stateful_buttons_manager.gd │ └── stateful_buttons_manager.gd.uid ├── talo_autoload.gd ├── talo_autoload.gd.uid ├── talo_client.gd ├── talo_client.gd.uid ├── talo_manager.gd ├── talo_manager.gd.uid ├── talo_socket.gd ├── talo_socket.gd.uid └── utils ├── continuity_manager.gd ├── continuity_manager.gd.uid ├── crypto_manager.gd ├── crypto_manager.gd.uid ├── leaderboard_entries_manager.gd ├── leaderboard_entries_manager.gd.uid ├── saves_manager.gd ├── saves_manager.gd.uid ├── session_manager.gd ├── session_manager.gd.uid ├── talo_auth_error.gd ├── talo_auth_error.gd.uid ├── talo_socket_error.gd ├── talo_socket_error.gd.uid ├── time_utils.gd └── time_utils.gd.uid /addons/talo/apis/api.gd: -------------------------------------------------------------------------------- 1 | class_name TaloAPI extends Node 2 | 3 | var client: TaloClient 4 | 5 | func _init(base_path: String): 6 | name = "Talo%s" % base_path 7 | client = TaloClient.new(base_path) 8 | add_child(client) 9 | -------------------------------------------------------------------------------- /addons/talo/apis/api.gd.uid: -------------------------------------------------------------------------------- 1 | uid://jgtpfpnrekpo 2 | -------------------------------------------------------------------------------- /addons/talo/apis/channels_api.gd: -------------------------------------------------------------------------------- 1 | class_name ChannelsAPI extends TaloAPI 2 | ## An interface for communicating with the Talo Channels API. 3 | ## 4 | ## This API is used to send messages between players who are subscribed to channels. 5 | ## 6 | ## @tutorial: https://docs.trytalo.com/docs/godot/channels 7 | 8 | ## Emitted when a message is received from a channel. 9 | signal message_received(channel: TaloChannel, player_alias: TaloPlayerAlias, message: String) 10 | ## Emitted when a player is joined to a channel. 11 | signal player_joined(channel: TaloChannel, player_alias: TaloPlayerAlias) 12 | ## Emitted when a player is left from a channel. 13 | signal player_left(channel: TaloChannel, player_alias: TaloPlayerAlias, reason: ChannelLeavingReason) 14 | ## Emitted when a channel's ownership transferred. 15 | signal channel_ownership_transferred(channel: TaloChannel, new_owner_player_alias: TaloPlayerAlias) 16 | ## Emitted when a channel is deleted. 17 | signal channel_deleted(channel: TaloChannel) 18 | ## Emitted when a channel is updated. 19 | signal channel_updated(channel: TaloChannel, changed_properties: Array[String]) 20 | 21 | func _ready() -> void: 22 | await Talo.init_completed 23 | Talo.socket.message_received.connect(_on_message_received) 24 | 25 | func _on_message_received(res: String, data: Dictionary) -> void: 26 | match res: 27 | "v1.channels.message": 28 | message_received.emit(TaloChannel.new(data.channel), TaloPlayerAlias.new(data.playerAlias), data.message) 29 | "v1.channels.player-joined": 30 | player_joined.emit(TaloChannel.new(data.channel), TaloPlayerAlias.new(data.playerAlias)) 31 | "v1.channels.player-left": 32 | player_left.emit(TaloChannel.new(data.channel), TaloPlayerAlias.new(data.playerAlias), data.meta.reason) 33 | "v1.channels.ownership-transferred": 34 | player_left.emit(TaloChannel.new(data.channel), TaloPlayerAlias.new(data.newOwner)) 35 | "v1.channels.deleted": 36 | channel_deleted.emit(TaloChannel.new(data.channel)) 37 | "v1.channels.updated": 38 | var changed_properties: Array[String] = [] 39 | changed_properties.assign(data.changedProperties) 40 | channel_updated.emit(TaloChannel.new(data.channel), changed_properties) 41 | 42 | ## Get a channel by its ID. 43 | func find(channel_id: int) -> TaloChannel: 44 | var res := await client.make_request(HTTPClient.METHOD_GET, "/%s" % channel_id) 45 | 46 | match res.status: 47 | 200: 48 | return TaloChannel.new(res.body.channel) 49 | _: 50 | return null 51 | 52 | ## Get a list of channels that players can join. 53 | func get_channels(options: GetChannelsOptions = GetChannelsOptions.new()) -> ChannelPage: 54 | var url := "?page=%s" 55 | var url_data := [options.page] 56 | 57 | if options.prop_key != "": 58 | url += "&propKey=%s" 59 | url_data.append(options.prop_key) 60 | 61 | if options.prop_value != "": 62 | url += "&propValue=%s" 63 | url_data.append(options.prop_value) 64 | 65 | var res := await client.make_request(HTTPClient.METHOD_GET, url % url_data) 66 | 67 | match res.status: 68 | 200: 69 | var channels: Array[TaloChannel] = [] 70 | channels.assign(res.body.channels.map(func (channel: Dictionary): return TaloChannel.new(channel))) 71 | return ChannelPage.new(channels, res.body.count, res.body.itemsPerPage, res.body.isLastPage) 72 | _: 73 | return null 74 | 75 | ## Get a list of channels that the current player is subscribed to. 76 | func get_subscribed_channels(options: GetSubscribedChannelsOptions = GetSubscribedChannelsOptions.new()) -> Array[TaloChannel]: 77 | if Talo.identity_check() != OK: 78 | return [] 79 | 80 | var url := "/subscriptions" 81 | var url_data := [] 82 | 83 | if options.prop_key != "": 84 | url += "?propKey=%s" 85 | url_data.append(options.prop_key) 86 | 87 | if options.prop_value != "": 88 | url += "&propValue=%s" 89 | url_data.append(options.prop_value) 90 | 91 | var res := await client.make_request(HTTPClient.METHOD_GET, url % url_data) 92 | 93 | match res.status: 94 | 200: 95 | var channels: Array[TaloChannel] = [] 96 | channels.assign(res.body.channels.map(func (channel: Dictionary): return TaloChannel.new(channel))) 97 | return channels 98 | _: 99 | return [] 100 | 101 | ## Create a new channel. The player who creates this channel will automatically become the owner. If auto cleanup is enabled, the channel will be deleted when the owner or the last member leaves. Private channels can only be joined by players who have been invited to the channel. Channels with temporary membership will remove players at the end of their session. 102 | func create(options: CreateChannelOptions = CreateChannelOptions.new()) -> TaloChannel: 103 | if Talo.identity_check() != OK: 104 | return 105 | 106 | var props_to_send := options.props \ 107 | .keys() \ 108 | .map(func (key: String): return { key = key, value = str(options.props[key]) }) 109 | 110 | var res := await client.make_request(HTTPClient.METHOD_POST, "", { 111 | name = options.name, 112 | autoCleanup = options.auto_cleanup, 113 | props = props_to_send, 114 | private = options.private, 115 | temporaryMembership = options.temporary_membership 116 | }) 117 | 118 | match res.status: 119 | 200: 120 | return TaloChannel.new(res.body.channel) 121 | _: 122 | return null 123 | 124 | ## Join an existing channel. 125 | func join(channel_id: int) -> TaloChannel: 126 | if Talo.identity_check() != OK: 127 | return 128 | 129 | var res := await client.make_request(HTTPClient.METHOD_POST, "/%s/join" % channel_id) 130 | 131 | match res.status: 132 | 200: 133 | return TaloChannel.new(res.body.channel) 134 | _: 135 | return null 136 | 137 | ## Leave a channel. 138 | func leave(channel_id: int) -> void: 139 | if Talo.identity_check() != OK: 140 | return 141 | 142 | await client.make_request(HTTPClient.METHOD_POST, "/%s/leave" % channel_id) 143 | 144 | ## 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: 146 | if Talo.identity_check() != OK: 147 | return 148 | 149 | var data := {} 150 | if not name.is_empty(): 151 | data.name = name 152 | if new_owner_alias_id != -1: 153 | data.ownerAliasId = new_owner_alias_id 154 | if props.size() > 0: 155 | data.props = props 156 | 157 | var res := await client.make_request(HTTPClient.METHOD_PUT, "/%s" % channel_id, data) 158 | 159 | match res.status: 160 | 200: 161 | return TaloChannel.new(res.body.channel) 162 | 403: 163 | push_error("Player does not have permissions to update channel %s." % channel_id) 164 | return null 165 | _: 166 | return null 167 | 168 | ## Delete a channel. This will only work if the current player is the owner of the channel. 169 | func delete(channel_id: int) -> void: 170 | if Talo.identity_check() != OK: 171 | return 172 | 173 | var res := await client.make_request(HTTPClient.METHOD_DELETE, "/%s" % channel_id) 174 | 175 | match res.status: 176 | 403: 177 | push_error("Player does not have permissions to delete channel %s." % channel_id) 178 | 179 | ## Send a message to a channel. 180 | func send_message(channel_id: int, message: String) -> void: 181 | if Talo.identity_check() != OK: 182 | return 183 | 184 | Talo.socket.send("v1.channels.message", { 185 | channel = { 186 | id = channel_id 187 | }, 188 | message = message 189 | }) 190 | 191 | ## Invite a player to a channel. The invitee will automatically join the channel. This will only work if the current player is the owner of the channel. 192 | func invite(channel_id: int, player_alias_id: int) -> void: 193 | if Talo.identity_check() != OK: 194 | return 195 | 196 | var res = await client.make_request(HTTPClient.METHOD_POST, "/%s/invite" % channel_id, { 197 | inviteeAliasId = player_alias_id 198 | }) 199 | 200 | match res.status: 201 | 403: 202 | push_error("Player does not have permissions to invite players to channel %s." % channel_id) 203 | 204 | ## Get the members of a channel. 205 | func get_members(channel_id: int) -> Array[TaloPlayerAlias]: 206 | if Talo.identity_check() != OK: 207 | return [] 208 | 209 | var res := await client.make_request(HTTPClient.METHOD_GET, "/%s/members" % channel_id) 210 | 211 | match res.status: 212 | 200: 213 | var members: Array[TaloPlayerAlias] = [] 214 | members.assign(res.body.members.map(func (member: Dictionary): return TaloPlayerAlias.new(member))) 215 | return members 216 | _: 217 | return [] 218 | 219 | class ChannelPage: 220 | var channels: Array[TaloChannel] 221 | var count: int 222 | var items_per_page: int 223 | var is_last_page: bool 224 | 225 | func _init(channels: Array[TaloChannel], count: int, items_per_page: int, is_last_page: bool) -> void: 226 | self.channels = channels 227 | self.count = count 228 | self.items_per_page = items_per_page 229 | self.is_last_page = is_last_page 230 | 231 | class GetChannelsOptions: 232 | var page: int = 0 233 | var prop_key: String = "" 234 | var prop_value: String = "" 235 | 236 | class GetSubscribedChannelsOptions: 237 | var prop_key: String = "" 238 | var prop_value: String = "" 239 | 240 | class CreateChannelOptions: 241 | var name: String = "" 242 | var auto_cleanup: bool = false 243 | var props: Dictionary = {} 244 | var private: bool = false 245 | var temporary_membership: bool = false 246 | 247 | enum ChannelLeavingReason { 248 | DEFAULT, 249 | TEMPORARY_MEMBERSHIP 250 | } 251 | -------------------------------------------------------------------------------- /addons/talo/apis/channels_api.gd.uid: -------------------------------------------------------------------------------- 1 | uid://din41hkqhyn1f 2 | -------------------------------------------------------------------------------- /addons/talo/apis/events_api.gd: -------------------------------------------------------------------------------- 1 | class_name EventsAPI extends TaloAPI 2 | ## An interface for communicating with the Talo Events API. 3 | ## 4 | ## This API is used to track events in your game. Events are used to measure user interactions such as button clicks, level completions and other kinds of game interactions. 5 | ## 6 | ## @tutorial: https://docs.trytalo.com/docs/godot/events 7 | 8 | var _queue := [] 9 | var _min_queue_size := 10 10 | 11 | func _get_window_mode() -> String: 12 | match DisplayServer.window_get_mode(): 13 | Window.MODE_EXCLUSIVE_FULLSCREEN: return "Exclusive fullscreen" 14 | Window.MODE_FULLSCREEN: return "Fullscreen window" 15 | Window.MODE_MAXIMIZED: return "Maximized window" 16 | Window.MODE_WINDOWED: return "Windowed" 17 | _: return "" 18 | 19 | func _get_game_version() -> String: 20 | return ProjectSettings.get_setting("application/config/version") 21 | 22 | func _build_meta_props() -> Array[TaloProp]: 23 | return [ 24 | TaloProp.new("META_OS", OS.get_name()), 25 | TaloProp.new("META_GAME_VERSION", _get_game_version()), 26 | TaloProp.new("META_WINDOW_MODE", _get_window_mode()), 27 | TaloProp.new("META_SCREEN_WIDTH", str(DisplayServer.window_get_size().x)), 28 | TaloProp.new("META_SCREEN_HEIGHT", str(DisplayServer.window_get_size().y)) 29 | ] 30 | 31 | func _has_errors(errors: Array) -> bool: 32 | return errors.any((func (err: Array): return err.size() > 0)) 33 | 34 | ## 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: 36 | if Talo.identity_check() != OK: 37 | return 38 | 39 | 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 | ) 45 | 46 | _queue.push_back({ 47 | name = name, 48 | props = final_props.map(func (prop: TaloProp): return prop.to_dictionary()), 49 | timestamp = TaloTimeUtils.get_timestamp_msec() 50 | }) 51 | 52 | if _queue.size() >= _min_queue_size: 53 | await flush() 54 | 55 | ## Flush the current queue of events. This is called automatically when the queue reaches the minimum size. 56 | func flush() -> void: 57 | if _queue.size() == 0: 58 | return 59 | 60 | var res := await client.make_request(HTTPClient.METHOD_POST, "/", { events = _queue }) 61 | _queue.clear() 62 | 63 | match res.status: 64 | 200: 65 | if _has_errors(res.body.errors): 66 | push_error("Failed to flush events: %s" % res.body.errors) 67 | -------------------------------------------------------------------------------- /addons/talo/apis/events_api.gd.uid: -------------------------------------------------------------------------------- 1 | uid://d4cho7q661lt 2 | -------------------------------------------------------------------------------- /addons/talo/apis/feedback_api.gd: -------------------------------------------------------------------------------- 1 | class_name FeedbackAPI extends TaloAPI 2 | ## An interface for communicating with the Talo Feedback API. 3 | ## 4 | ## This API is used to submit feedback across different categories from your players. 5 | ## 6 | ## @tutorial: https://docs.trytalo.com/docs/godot/feedback 7 | 8 | ## Get a list of feedback categories that are available for players to submit feedback. 9 | func get_categories() -> Array[TaloFeedbackCategory]: 10 | var res := await client.make_request(HTTPClient.METHOD_GET, "/categories") 11 | 12 | match res.status: 13 | 200: 14 | var categories: Array[TaloFeedbackCategory] = [] 15 | categories.assign(res.body.feedbackCategories.map(func (category: Dictionary): return TaloFeedbackCategory.new(category))) 16 | return categories 17 | _: 18 | return [] 19 | 20 | ## Submit feedback for a specific category. 21 | func send(category_internal_name: String, comment: String) -> void: 22 | if Talo.identity_check() != OK: 23 | return 24 | 25 | await client.make_request(HTTPClient.METHOD_POST, "/categories/%s" % category_internal_name, { 26 | comment = comment 27 | }) 28 | -------------------------------------------------------------------------------- /addons/talo/apis/feedback_api.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cs65yt1dy1n0n 2 | -------------------------------------------------------------------------------- /addons/talo/apis/game_config_api.gd: -------------------------------------------------------------------------------- 1 | class_name GameConfigAPI extends TaloAPI 2 | ## An interface for communicating with the Talo Live Config API. 3 | ## 4 | ## This API is used to fetch the live config for your game. The live config is a set of key-value pairs that can be updated in the Talo dashboard. 5 | ## 6 | ## @tutorial: https://docs.trytalo.com/docs/godot/live-config 7 | 8 | ## Emitted when the live config has been loaded. 9 | signal live_config_loaded(live_config: TaloLiveConfig) 10 | 11 | ## Emitted when the live config has been updated. 12 | signal live_config_updated(live_config: TaloLiveConfig) 13 | 14 | func _ready() -> void: 15 | await Talo.init_completed 16 | Talo.socket.message_received.connect(_on_message_received) 17 | 18 | func _on_message_received(res: String, data: Dictionary) -> void: 19 | if res == "v1.live-config.updated": 20 | live_config_updated.emit(TaloLiveConfig.new(data.config)) 21 | 22 | ## Get the live config for your game. 23 | func get_live_config() -> TaloLiveConfig: 24 | var res := await client.make_request(HTTPClient.METHOD_GET, "/") 25 | match res.status: 26 | 200: 27 | Talo.live_config = TaloLiveConfig.new(res.body.config) 28 | live_config_loaded.emit(Talo.live_config) 29 | return Talo.live_config 30 | _: 31 | return null 32 | -------------------------------------------------------------------------------- /addons/talo/apis/game_config_api.gd.uid: -------------------------------------------------------------------------------- 1 | uid://x7k60yht4k30 2 | -------------------------------------------------------------------------------- /addons/talo/apis/health_check_api.gd: -------------------------------------------------------------------------------- 1 | class_name HealthCheckAPI extends TaloAPI 2 | ## An interface for communicating with the Talo Health Check API. 3 | ## 4 | ## This API is used to check if Talo can be reached by Continuity. You shouldn't need to use this API directly in your game. 5 | ## 6 | ## @tutorial: https://docs.trytalo.com/docs/godot/continuity 7 | 8 | ## Ping the Talo Health Check API to check if Talo can be reached. 9 | func ping() -> bool: 10 | var res := await client.make_request(HTTPClient.METHOD_GET, "") 11 | return res.status == 204 12 | -------------------------------------------------------------------------------- /addons/talo/apis/health_check_api.gd.uid: -------------------------------------------------------------------------------- 1 | uid://qkii7g53k8nt 2 | -------------------------------------------------------------------------------- /addons/talo/apis/leaderboards_api.gd: -------------------------------------------------------------------------------- 1 | class_name LeaderboardsAPI extends TaloAPI 2 | ## An interface for communicating with the Talo Leaderboards API. 3 | ## 4 | ## This API is used to read and update leaderboards in your game. Leaderboards are used to track player scores and rankings. 5 | ## 6 | ## @tutorial: https://docs.trytalo.com/docs/godot/leaderboards 7 | 8 | var _entries_manager := TaloLeaderboardEntriesManager.new() 9 | 10 | ## Get a list of all the entries that have been previously fetched or created for a leaderboard. 11 | func get_cached_entries(internal_name: String) -> Array[TaloLeaderboardEntry]: 12 | return _entries_manager.get_entries(internal_name) 13 | 14 | ## Get a list of all the entries that have been previously fetched or created for a leaderboard for the current player. 15 | func get_cached_entries_for_current_player(internal_name: String) -> Array[TaloLeaderboardEntry]: 16 | if Talo.identity_check() != OK: 17 | return [] 18 | 19 | return _entries_manager.get_entries(internal_name).filter( 20 | func (entry: TaloLeaderboardEntry) -> bool: 21 | return entry.player_alias.id == Talo.current_alias.id 22 | ) 23 | 24 | ## Get a list of entries for a leaderboard. The options include "page", "alias_id", "include_archived", "prop_key" and "prop_value" for additional filtering. 25 | func get_entries(internal_name: String, options = GetEntriesOptions.new()) -> EntriesPage: 26 | var url := "/%s/entries?page=%s" 27 | var url_data := [internal_name, options.page] 28 | 29 | if options.alias_id != -1: 30 | url += "&aliasId=%s" 31 | url_data.append(options.alias_id) 32 | 33 | if options.include_archived: 34 | url += "&withDeleted=1" 35 | 36 | if options.prop_key != "": 37 | url += "&propKey=%s" 38 | url_data.append(options.prop_key) 39 | 40 | if options.prop_value != "": 41 | url += "&propValue=%s" 42 | url_data.append(options.prop_value) 43 | 44 | var res := await client.make_request(HTTPClient.METHOD_GET, url % url_data) 45 | 46 | match res.status: 47 | 200: 48 | var entries: Array[TaloLeaderboardEntry] = Array(res.body.entries.map( 49 | func(data: Dictionary): 50 | var entry := TaloLeaderboardEntry.new(data) 51 | _entries_manager.upsert_entry(internal_name, entry) 52 | 53 | return entry 54 | ), TYPE_OBJECT, (TaloLeaderboardEntry as Script).get_instance_base_type(), TaloLeaderboardEntry) 55 | return EntriesPage.new(entries, res.body.count, res.body.itemsPerPage, res.body.isLastPage) 56 | _: 57 | return null 58 | 59 | func get_entries_for_current_player(internal_name: String, options = GetEntriesOptions.new()) -> EntriesPage: 60 | if Talo.identity_check() != OK: 61 | return null 62 | 63 | options.alias_id = Talo.current_alias.id 64 | 65 | return await get_entries(internal_name, options) 66 | 67 | ## 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: 69 | if Talo.identity_check() != OK: 70 | return null 71 | 72 | var props_to_send := props.keys().map(func(key: String) -> Dictionary: return {key = key, value = str(props[key])}) 73 | 74 | var res := await client.make_request(HTTPClient.METHOD_POST, "/%s/entries" % internal_name, { 75 | score = score, 76 | props = props_to_send 77 | }) 78 | 79 | match res.status: 80 | 200: 81 | var entry = TaloLeaderboardEntry.new(res.body.entry) 82 | _entries_manager.upsert_entry(internal_name, entry) 83 | 84 | return AddEntryResult.new(entry, res.body.updated) 85 | _: 86 | return null 87 | 88 | class EntriesPage: 89 | var entries: Array[TaloLeaderboardEntry] 90 | var count: int 91 | var items_per_page: int 92 | var is_last_page: bool 93 | 94 | func _init(entries: Array[TaloLeaderboardEntry], count: int, items_per_page: int, is_last_page: bool) -> void: 95 | self.entries = entries 96 | self.count = count 97 | self.items_per_page = items_per_page 98 | self.is_last_page = is_last_page 99 | 100 | class AddEntryResult: 101 | var entry: TaloLeaderboardEntry 102 | var updated: bool 103 | 104 | func _init(entry: TaloLeaderboardEntry, updated: bool) -> void: 105 | self.entry = entry 106 | self.updated = updated 107 | 108 | class GetEntriesOptions: 109 | var page: int = 0 110 | var alias_id: int = -1 111 | var include_archived: bool = false 112 | var prop_key: String = "" 113 | var prop_value: String = "" 114 | -------------------------------------------------------------------------------- /addons/talo/apis/leaderboards_api.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bapyhyvy5efx 2 | -------------------------------------------------------------------------------- /addons/talo/apis/player_auth_api.gd: -------------------------------------------------------------------------------- 1 | class_name PlayerAuthAPI extends TaloAPI 2 | ## An interface for communicating with the Talo Player Auth API. 3 | ## 4 | ## This API is used to handle player authentication in your game. It provides methods for registering, logging in and managing player accounts. 5 | ## 6 | ## @tutorial: https://docs.trytalo.com/docs/godot/player-authentication 7 | 8 | enum LoginResult { 9 | OK, 10 | FAILED, 11 | VERIFICATION_REQUIRED, 12 | } 13 | 14 | var session_manager := TaloSessionManager.new() 15 | var last_error: TaloAuthError = null 16 | 17 | func _handle_error(res: Dictionary, ret: Variant = FAILED) -> Variant: 18 | if res.body != null and res.body.has("errorCode"): 19 | last_error = TaloAuthError.new(res.body.errorCode) 20 | else: 21 | last_error = TaloAuthError.new("API_ERROR") 22 | 23 | return ret 24 | 25 | ## Register a new player account. If verification is enabled, a valid email will be required to verify all logins. 26 | func register(identifier: String, password: String, email: String = "", verification_enabled: bool = false) -> Error: 27 | if verification_enabled and email.is_empty(): 28 | push_error("Email is required when verification is enabled") 29 | return FAILED 30 | 31 | var res := await client.make_request(HTTPClient.METHOD_POST, "/register", { 32 | identifier = identifier, 33 | password = password, 34 | email = email, 35 | verificationEnabled = verification_enabled 36 | }) 37 | 38 | match res.status: 39 | 200: 40 | session_manager.handle_session_created(res.body.alias, res.body.sessionToken, res.body.socketToken) 41 | return OK 42 | _: 43 | return _handle_error(res) 44 | 45 | ## Log in to an existing player account. If verification is required, a verification code will be sent to the player's email. 46 | func login(identifier: String, password: String) -> LoginResult: 47 | var res := await client.make_request(HTTPClient.METHOD_POST, "/login", { 48 | identifier = identifier, 49 | password = password 50 | }) 51 | 52 | match res.status: 53 | 200: 54 | if res.body.has("verificationRequired"): 55 | session_manager.save_verification_alias_id(res.body.aliasId) 56 | else: 57 | session_manager.handle_session_created(res.body.alias, res.body.sessionToken, res.body.socketToken) 58 | 59 | if res.body.has("verificationRequired"): 60 | return LoginResult.VERIFICATION_REQUIRED 61 | else: 62 | return LoginResult.OK 63 | _: 64 | return _handle_error(res, LoginResult.FAILED) 65 | 66 | ## Verify a player account using the verification code sent to the player's email. 67 | func verify(verification_code: String) -> Error: 68 | var res := await client.make_request(HTTPClient.METHOD_POST, "/verify", { 69 | aliasId = session_manager.get_verification_alias_id(), 70 | code = verification_code 71 | }) 72 | 73 | match res.status: 74 | 200: 75 | session_manager.handle_session_created(res.body.alias, res.body.sessionToken, res.body.socketToken) 76 | return OK 77 | _: 78 | return _handle_error(res) 79 | 80 | ## Log out of the current player account. 81 | func logout() -> void: 82 | await client.make_request(HTTPClient.METHOD_POST, "/logout") 83 | session_manager.clear_session() 84 | 85 | ## Change the password of the current player account. 86 | func change_password(current_password: String, new_password: String) -> Error: 87 | var res := await client.make_request(HTTPClient.METHOD_POST, "/change_password", { 88 | currentPassword = current_password, 89 | newPassword = new_password 90 | }) 91 | 92 | match res.status: 93 | 204: 94 | return OK 95 | _: 96 | return _handle_error(res) 97 | 98 | ## Change the email of the current player account. 99 | func change_email(current_password: String, new_email: String) -> Error: 100 | var res := await client.make_request(HTTPClient.METHOD_POST, "/change_email", { 101 | currentPassword = current_password, 102 | newEmail = new_email 103 | }) 104 | 105 | match res.status: 106 | 204: 107 | return OK 108 | _: 109 | return _handle_error(res) 110 | 111 | ## Send a password reset email to the player's email. 112 | func forgot_password(email: String) -> Error: 113 | var res := await client.make_request(HTTPClient.METHOD_POST, "/forgot_password", { 114 | email = email 115 | }) 116 | 117 | match res.status: 118 | 204: 119 | return OK 120 | _: 121 | return _handle_error(res) 122 | 123 | ## Reset the password of the player account using the code sent to the player's email. 124 | func reset_password(code: String, password: String) -> Error: 125 | var res := await client.make_request(HTTPClient.METHOD_POST, "/reset_password", { 126 | code = code, 127 | password = password 128 | }) 129 | 130 | match res.status: 131 | 204: 132 | return OK 133 | _: 134 | return _handle_error(res) 135 | 136 | ## Toggle email verification for the current player account. 137 | func toggle_verification(current_password: String, verification_enabled: bool, email: String = "") -> Error: 138 | var res := await client.make_request(HTTPClient.METHOD_PATCH, "/toggle_verification", { 139 | currentPassword = current_password, 140 | verificationEnabled = verification_enabled, 141 | email = email 142 | }) 143 | 144 | match res.status: 145 | 204: 146 | return OK 147 | _: 148 | return _handle_error(res) 149 | 150 | ## Delete the current player account. 151 | func delete_account(current_password: String) -> Error: 152 | var res := await client.make_request(HTTPClient.METHOD_DELETE, "/", { 153 | currentPassword = current_password 154 | }) 155 | 156 | match res.status: 157 | 204: 158 | session_manager.clear_session() 159 | return OK 160 | _: 161 | return _handle_error(res) 162 | -------------------------------------------------------------------------------- /addons/talo/apis/player_auth_api.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c06q0ikswi731 2 | -------------------------------------------------------------------------------- /addons/talo/apis/player_groups_api.gd: -------------------------------------------------------------------------------- 1 | class_name PlayerGroupsAPI extends TaloAPI 2 | ## An interface for communicating with the Talo Player Groups API. 3 | ## 4 | ## This API is used to fetch info about group memberships. Player groups are used to segment players into groups based on filter criteria. 5 | ## 6 | ## @tutorial: https://docs.trytalo.com/docs/godot/groups 7 | 8 | ## Get the members of a group. If the group is public (as set in the dashboard), the members will be returned. If the group is private, only a membership count will be returned. 9 | func get_group(group_id: String) -> TaloPlayerGroup: 10 | var res := await client.make_request(HTTPClient.METHOD_GET, "/%s" % group_id) 11 | 12 | match res.status: 13 | 200: 14 | return TaloPlayerGroup.new(res.body.group) 15 | _: 16 | return null 17 | -------------------------------------------------------------------------------- /addons/talo/apis/player_groups_api.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b8jh7f3s8j6iv 2 | -------------------------------------------------------------------------------- /addons/talo/apis/player_presence_api.gd: -------------------------------------------------------------------------------- 1 | class_name PlayerPresenceAPI extends TaloAPI 2 | ## An interface for communicating with the Talo Player Presence API. 3 | ## 4 | ## This API is used to track and manage player presence in your game. Presence indicates whether players are online 5 | ## and their current status. 6 | ## 7 | ## @tutorial: https://docs.trytalo.com/docs/godot/player-presence 8 | 9 | ## Emitted when a player's presence status changes. 10 | signal presence_changed(presence: TaloPlayerPresence, online_changed: bool, custom_status_changed: bool) 11 | 12 | func _ready(): 13 | await Talo.init_completed 14 | Talo.socket.message_received.connect(_on_message_received) 15 | 16 | func _on_message_received(res: String, data: Dictionary) -> void: 17 | if res == "v1.players.presence.updated": 18 | presence_changed.emit(TaloPlayerPresence.new(data.presence), data.meta.onlineChanged, data.meta.customStatusChanged) 19 | 20 | ## Get the presence status for a specific player. 21 | func get_presence(player_id: String) -> TaloPlayerPresence: 22 | var res := await client.make_request(HTTPClient.METHOD_GET, "/%s" % player_id) 23 | 24 | match res.status: 25 | 200: 26 | return TaloPlayerPresence.new(res.body.presence) 27 | _: 28 | return null 29 | 30 | ## Update the presence status for the current player. 31 | func update_presence(online: bool, custom_status: String = "") -> TaloPlayerPresence: 32 | if Talo.identity_check() != OK: 33 | return null 34 | 35 | var res := await client.make_request(HTTPClient.METHOD_PUT, "", { 36 | online = online, 37 | customStatus = custom_status 38 | }) 39 | 40 | match res.status: 41 | 200: 42 | return TaloPlayerPresence.new(res.body.presence) 43 | _: 44 | return null 45 | -------------------------------------------------------------------------------- /addons/talo/apis/player_presence_api.gd.uid: -------------------------------------------------------------------------------- 1 | uid://d3chop3hiwjk8 2 | -------------------------------------------------------------------------------- /addons/talo/apis/players_api.gd: -------------------------------------------------------------------------------- 1 | class_name PlayersAPI extends TaloAPI 2 | ## An interface for communicating with the Talo Players API. 3 | ## 4 | ## This API is used to identify players and update player data. 5 | ## 6 | ## @tutorial: https://docs.trytalo.com/docs/godot/identifying 7 | 8 | ## Emitted when a player has been identified. 9 | signal identified(player: TaloPlayer) 10 | 11 | ## Identify a player using a service (e.g. "username") and identifier (e.g. "bob"). 12 | func identify(service: String, identifier: String) -> TaloPlayer: 13 | var res := await client.make_request(HTTPClient.METHOD_GET, "/identify?service=%s&identifier=%s" % [service, identifier]) 14 | match res.status: 15 | 200: 16 | Talo.socket.reset_connection() 17 | 18 | Talo.current_alias = TaloPlayerAlias.new(res.body.alias) 19 | Talo.socket.set_socket_token(res.body.socketToken) 20 | identified.emit(Talo.current_player) 21 | return Talo.current_player 22 | _: 23 | if not await Talo.is_offline(): 24 | Talo.player_auth.session_manager.clear_session() 25 | return null 26 | 27 | ## Identify a player using a Steam ticket. 28 | func identify_steam(ticket: String, identity: String = "") -> TaloPlayer: 29 | if identity.is_empty(): 30 | return await identify("steam", ticket) 31 | else: 32 | return await identify("steam", "%s:%s" % [identity, ticket]) 33 | 34 | ## Flush and sync the player's current data with Talo. 35 | func update() -> TaloPlayer: 36 | var res := await client.make_request(HTTPClient.METHOD_PATCH, "/%s" % Talo.current_player.id, { props = Talo.current_player.get_serialized_props() }) 37 | match res.status: 38 | 200: 39 | if is_instance_valid(Talo.current_alias.player): 40 | Talo.current_alias.player.update_from_raw_data(res.body.player) 41 | else: 42 | Talo.current_alias.player = TaloPlayer.new(res.body.player) 43 | return Talo.current_player 44 | _: 45 | return null 46 | 47 | ## Merge all of the data from player_id2 into player_id1 and delete player_id2. 48 | func merge(player_id1: String, player_id2: String) -> TaloPlayer: 49 | var res := await client.make_request(HTTPClient.METHOD_POST, "/merge", { 50 | playerId1 = player_id1, 51 | playerId2 = player_id2 52 | }) 53 | 54 | match res.status: 55 | 200: 56 | return TaloPlayer.new(res.body.player) 57 | _: 58 | return null 59 | 60 | ## Get a player by their ID. 61 | func find(player_id: String) -> TaloPlayer: 62 | var res := await client.make_request(HTTPClient.METHOD_GET, "/%s" % player_id) 63 | match res.status: 64 | 200: 65 | return TaloPlayer.new(res.body.player) 66 | _: 67 | return null 68 | 69 | ## Generate a mostly-unique identifier. 70 | func generate_identifier() -> String: 71 | var time_hash := str(TaloTimeUtils.get_timestamp_msec()).sha256_text() 72 | var size := 12 73 | var split_start := RandomNumberGenerator.new().randi_range(0, time_hash.length() - size) 74 | return time_hash.substr(split_start, size) 75 | -------------------------------------------------------------------------------- /addons/talo/apis/players_api.gd.uid: -------------------------------------------------------------------------------- 1 | uid://04367l1m5t6q 2 | -------------------------------------------------------------------------------- /addons/talo/apis/saves_api.gd: -------------------------------------------------------------------------------- 1 | class_name SavesAPI extends TaloAPI 2 | ## An interface for communicating with the Talo Saves API. 3 | ## 4 | ## This API allows you to save and load game data for your players. You can create, update, and delete saves, as well as load and unload them. 5 | ## 6 | ## @tutorial: https://docs.trytalo.com/docs/godot/saves 7 | 8 | ## Emitted when the player's saves have been fetched and received. 9 | signal saves_loaded 10 | ## Emitted when a save has been chosen. 11 | signal save_chosen(save: TaloGameSave) 12 | ## Emitted when the chosen save has finished loading. 13 | signal save_loading_completed 14 | 15 | var _saves_manager := TaloSavesManager.new() 16 | 17 | ## All of the player's fetched saves. 18 | var all: Array[TaloGameSave]: 19 | get: return _saves_manager.all_saves 20 | 21 | ## The latest save that was updated. 22 | var latest: TaloGameSave: 23 | get: return _saves_manager.get_latest_save() 24 | 25 | ## The current save that has been chosen. 26 | var current: TaloGameSave: 27 | get: return _saves_manager.current_save 28 | 29 | ## Sync an offline save with an online save using the offline save data. 30 | func replace_save_with_offline_save(offline_save: TaloGameSave) -> TaloGameSave: 31 | var res := await client.make_request(HTTPClient.METHOD_PATCH, "/%s" % offline_save.id, { 32 | name = offline_save.name, 33 | content = offline_save.content 34 | }) 35 | 36 | match res.status: 37 | 200: 38 | return TaloGameSave.new(res.body.save) 39 | _: 40 | return null 41 | 42 | ## Get all of the player's saves. 43 | func get_saves() -> Array[TaloGameSave]: 44 | var saves: Array[TaloGameSave] = [] 45 | var offline_saves: Array[TaloGameSave] = _saves_manager.read_offline_saves() 46 | var online_saves: Array[TaloGameSave] = [] 47 | 48 | if await Talo.is_offline(): 49 | saves.append_array(offline_saves) 50 | else: 51 | if Talo.identity_check() != OK: 52 | return [] 53 | 54 | var res := await client.make_request(HTTPClient.METHOD_GET, "/") 55 | match res.status: 56 | 200: 57 | online_saves.append_array(res.body.saves.map(func (data: Dictionary): return TaloGameSave.new(data))) 58 | saves.append_array(await _saves_manager.get_synced_saves(online_saves)) 59 | 60 | _saves_manager.all_saves = saves 61 | saves_loaded.emit() 62 | 63 | for save in _saves_manager.all_saves: 64 | _saves_manager.update_offline_saves(save) 65 | 66 | return _saves_manager.all_saves 67 | 68 | ## Set the chosen save and optionally (default true) load it. 69 | func choose_save(save: TaloGameSave, load_save = true) -> void: 70 | _saves_manager.set_chosen_save(save, load_save) 71 | 72 | ## Unload the current save. 73 | func unload_current_save() -> void: 74 | _saves_manager.set_chosen_save(null, false) 75 | 76 | ## Create a new save with the given name and content. 77 | func create_save(save_name: String, content: Dictionary = {}) -> TaloGameSave: 78 | var save: TaloGameSave 79 | var save_content := content if not content.is_empty() else _saves_manager.get_save_content() 80 | 81 | if await Talo.is_offline(): 82 | save = TaloGameSave.new({ 83 | name = save_name, 84 | content = save_content, 85 | updatedAt = TaloTimeUtils.get_current_datetime_string() 86 | }) 87 | else: 88 | var res := await client.make_request(HTTPClient.METHOD_POST, "/", { 89 | name=save_name, 90 | content=save_content 91 | }) 92 | 93 | match res.status: 94 | 200: 95 | save = TaloGameSave.new(res.body.save) 96 | 97 | _saves_manager.all_saves.push_back(save) 98 | _saves_manager.update_offline_saves(save) 99 | choose_save(save) 100 | 101 | return save 102 | 103 | ## Register a loadable object to be saved and loaded. 104 | func register(loadable: TaloLoadable) -> void: 105 | _saves_manager.register(loadable) 106 | 107 | ## Update the currently loaded save using the current state of the game and with the given name. 108 | func update_current_save(new_name: String = "") -> TaloGameSave: 109 | return await update_save(_saves_manager.current_save, new_name) 110 | 111 | ## Update the given save using the current state of the game and with the given name. 112 | func update_save(save: TaloGameSave, new_name: String = "") -> TaloGameSave: 113 | var content := _saves_manager.get_save_content() 114 | 115 | if await Talo.is_offline(): 116 | if not new_name.is_empty(): 117 | save.name = new_name 118 | 119 | save.content = content 120 | save.updated_at = TaloTimeUtils.get_current_datetime_string() 121 | else: 122 | if Talo.identity_check() != OK: 123 | return 124 | 125 | var res := await client.make_request(HTTPClient.METHOD_PATCH, "/%s" % save.id, { 126 | name=save.name if new_name.is_empty() else new_name, 127 | content=content 128 | }) 129 | 130 | match res.status: 131 | 200: 132 | save = TaloGameSave.new(res.body.save) 133 | 134 | _saves_manager.replace_save(save) 135 | return save 136 | 137 | ## Delete the given save. 138 | func delete_save(save: TaloGameSave) -> void: 139 | if not await Talo.is_offline(): 140 | if Talo.identity_check() != OK: 141 | return 142 | 143 | var res := await client.make_request(HTTPClient.METHOD_DELETE, "/%s" % save.id) 144 | 145 | match res.status: 146 | _: 147 | return 148 | 149 | _saves_manager.all_saves = _saves_manager.all_saves.filter(func (s: TaloGameSave): s.id != save.id) 150 | _saves_manager.delete_offline_save(save) 151 | 152 | if _saves_manager.current_save and _saves_manager.current_save.id == save.id: 153 | unload_current_save() 154 | 155 | ## Get the format version for the current save. 156 | func get_format_version() -> String: 157 | return _saves_manager.get_format_version() 158 | -------------------------------------------------------------------------------- /addons/talo/apis/saves_api.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dynh0qu46xw0u 2 | -------------------------------------------------------------------------------- /addons/talo/apis/socket_tickets_api.gd: -------------------------------------------------------------------------------- 1 | class_name SocketTicketsAPI extends TaloAPI 2 | ## An interface for communicating with the Talo Socket Tickets API. 3 | ## 4 | ## This API is used to create tickets for connecting to the Talo Socket. 5 | ## 6 | ## @tutorial: https://docs.trytalo.com/docs/godot/socket 7 | 8 | ## Create a new socket ticket. 9 | func create_ticket() -> String: 10 | var res := await client.make_request(HTTPClient.METHOD_POST, "") 11 | 12 | match res.status: 13 | 200: 14 | return res.body.ticket 15 | _: 16 | push_error("Failed to get socket ticket") 17 | return "" 18 | -------------------------------------------------------------------------------- /addons/talo/apis/socket_tickets_api.gd.uid: -------------------------------------------------------------------------------- 1 | uid://6dikgb3cyfhr 2 | -------------------------------------------------------------------------------- /addons/talo/apis/stats_api.gd: -------------------------------------------------------------------------------- 1 | class_name StatsAPI extends TaloAPI 2 | ## An interface for communicating with the Talo Stats API. 3 | ## 4 | ## This API is used to track player stats in your game. Stats are used to track player metrics both individually and globally. 5 | ## 6 | ## @tutorial: https://docs.trytalo.com/docs/godot/stats 7 | 8 | ## List all stats and their values. 9 | func get_stats() -> Array[TaloStat]: 10 | var res := await client.make_request(HTTPClient.METHOD_GET, "") 11 | 12 | match res.status: 13 | 200: 14 | var stats: Array[TaloStat] = [] 15 | stats.assign(res.body.stats.map(func (stat: Dictionary): return TaloStat.new(stat))) 16 | return stats 17 | _: 18 | return [] 19 | 20 | ## Get a stat by its internal name. 21 | func find(internal_name: String) -> TaloStat: 22 | var res := await client.make_request(HTTPClient.METHOD_GET, "/%s" % internal_name) 23 | 24 | match res.status: 25 | 200: 26 | return TaloStat.new(res.body.stat) 27 | _: 28 | return null 29 | 30 | ## Track a stat for the current player. The stat will be updated by the change amount (default 1.0). Returns the updated player stat and global stat values. 31 | func track(internal_name: String, change: float = 1.0) -> TaloPlayerStat: 32 | if Talo.identity_check() != OK: 33 | return 34 | 35 | var res := await client.make_request(HTTPClient.METHOD_PUT, "/%s" % internal_name, { change = change }) 36 | return TaloPlayerStat.new(res.body.playerStat) 37 | 38 | ## Get a paginated array of changes to a player stat value (and its global value) over time. History items can be filtered by when they were tracked. 39 | func get_history(internal_name: String, page: int = 0, start_date: String = "", end_date: String = "") -> StatHistoryPage: 40 | if Talo.identity_check() != OK: 41 | return null 42 | 43 | var query_params := PackedStringArray(["page=%s" % page]) 44 | if start_date != "": 45 | query_params.append("startDate=%s" % start_date) 46 | if end_date != "": 47 | query_params.append("endDate=%s" % end_date) 48 | 49 | var query_string := "&".join(query_params) 50 | var url := "/%s/history?%s" % [internal_name, query_string] 51 | 52 | var res := await client.make_request(HTTPClient.METHOD_GET, url) 53 | 54 | match res.status: 55 | 200: 56 | var history: Array[TaloPlayerStatSnapshot] = [] 57 | history.assign(res.body.history.map(func (snapshot: Dictionary): return TaloPlayerStatSnapshot.new(snapshot))) 58 | return StatHistoryPage.new(history, res.body.count, res.body.itemsPerPage, res.body.isLastPage) 59 | _: 60 | return null 61 | 62 | ## Get a paginated array of changes to a global stat over time. History items can be filtered by when they were tracked and by player. 63 | func get_global_history(internal_name: String, page: int = 0, player_id = "", start_date: String = "", end_date: String = "") -> GlobalStatHistoryPage: 64 | var query_params := PackedStringArray(["page=%s" % page]) 65 | if player_id != "": 66 | query_params.append("playerId=%s" % player_id) 67 | if start_date != "": 68 | query_params.append("startDate=%s" % start_date) 69 | if end_date != "": 70 | query_params.append("endDate=%s" % end_date) 71 | 72 | var query_string := "&".join(query_params) 73 | var url := "/%s/global-history?%s" % [internal_name, query_string] 74 | 75 | var res := await client.make_request(HTTPClient.METHOD_GET, url) 76 | 77 | match res.status: 78 | 200: 79 | var history: Array[TaloPlayerStatSnapshot] = [] 80 | history.assign(res.body.history.map(func (snapshot: Dictionary): return TaloPlayerStatSnapshot.new(snapshot))) 81 | return GlobalStatHistoryPage.new( 82 | history, 83 | res.body.globalValue, 84 | res.body.playerValue, 85 | res.body.count, 86 | res.body.itemsPerPage, 87 | res.body.isLastPage 88 | ) 89 | _: 90 | return null 91 | 92 | class StatHistoryPage: 93 | var history: Array[TaloPlayerStatSnapshot] 94 | var count: int 95 | var items_per_page: int 96 | var is_last_page: bool 97 | 98 | func _init(history: Array[TaloPlayerStatSnapshot], count: int, items_per_page: int, is_last_page: bool) -> void: 99 | self.history = history 100 | self.count = count 101 | self.items_per_page = items_per_page 102 | self.is_last_page = is_last_page 103 | 104 | class GlobalValueMetrics: 105 | var min_value: float 106 | var max_value: float 107 | var median_value: float 108 | var average_value: float 109 | var average_change: float 110 | 111 | func _init(data: Dictionary): 112 | min_value = data.minValue 113 | max_value = data.maxValue 114 | median_value = data.medianValue 115 | average_value = data.averageValue 116 | average_change = data.averageChange 117 | 118 | class PlayerValueMetrics: 119 | var min_value: float 120 | var max_value: float 121 | var median_value: float 122 | var average_value: float 123 | 124 | func _init(data: Dictionary): 125 | min_value = data.minValue 126 | max_value = data.maxValue 127 | median_value = data.medianValue 128 | average_value = data.averageValue 129 | 130 | class GlobalStatHistoryPage: 131 | var history: Array[TaloPlayerStatSnapshot] 132 | var global_value: GlobalValueMetrics 133 | var player_value: PlayerValueMetrics 134 | var count: int 135 | var items_per_page: int 136 | var is_last_page: bool 137 | 138 | func _init(history: Array[TaloPlayerStatSnapshot], global_value: Dictionary, player_value: Dictionary, count: int, items_per_page: int, is_last_page: bool) -> void: 139 | self.history = history 140 | self.global_value = GlobalValueMetrics.new(global_value) 141 | self.player_value = PlayerValueMetrics.new(player_value) 142 | self.count = count 143 | self.items_per_page = items_per_page 144 | self.is_last_page = is_last_page 145 | -------------------------------------------------------------------------------- /addons/talo/apis/stats_api.gd.uid: -------------------------------------------------------------------------------- 1 | uid://xeuokfxfaoek 2 | -------------------------------------------------------------------------------- /addons/talo/entities/channel.gd: -------------------------------------------------------------------------------- 1 | class_name TaloChannel extends TaloEntityWithProps 2 | 3 | var id: int 4 | var name: String 5 | var owner_alias: TaloPlayerAlias 6 | var total_messages: int 7 | var member_count: int 8 | var private: bool 9 | var created_at: String 10 | var updated_at: String 11 | 12 | func _init(data: Dictionary): 13 | super._init(data.props.map(func (prop): return TaloProp.new(prop.key, prop.value))) 14 | 15 | id = data.id 16 | name = data.name 17 | if data.owner: 18 | owner_alias = TaloPlayerAlias.new(data.owner) 19 | total_messages = data.totalMessages 20 | member_count = data.get('memberCount', 0) # TODO: socket messages don't currently send the memberCount 21 | private = data.private 22 | created_at = data.createdAt 23 | updated_at = data.updatedAt 24 | -------------------------------------------------------------------------------- /addons/talo/entities/channel.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bescifuwm8ich 2 | -------------------------------------------------------------------------------- /addons/talo/entities/entity_with_props.gd: -------------------------------------------------------------------------------- 1 | class_name TaloEntityWithProps extends RefCounted 2 | 3 | var props: Array[TaloProp] = [] 4 | 5 | func _init(props: Array) -> void: 6 | self.props.assign(props) 7 | 8 | ## Get a property value by key. Returns the fallback value if the key is not found. 9 | func get_prop(key: String, fallback: String = "") -> String: 10 | var filtered := props.filter(func (prop: TaloProp): return prop.key == key && prop.value != null) 11 | return fallback if filtered.is_empty() else filtered.front().value 12 | 13 | ## Set a property by key and value. 14 | func set_prop(key: String, value: String) -> void: 15 | var filtered := props.filter(func (prop: TaloProp): return prop.key == key) 16 | if filtered.is_empty(): 17 | props.push_back(TaloProp.new(key, value)) 18 | else: 19 | filtered.front().value = value 20 | 21 | ## Delete a property by key. 22 | func delete_prop(key: String) -> void: 23 | props.assign(props.map( 24 | func (prop: TaloProp): 25 | if prop.key == key: 26 | prop.value = null 27 | return prop 28 | )) 29 | 30 | func get_serialized_props() -> Array: 31 | return props.map(func (prop: TaloProp): return prop.to_dictionary()) 32 | -------------------------------------------------------------------------------- /addons/talo/entities/entity_with_props.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cngw8geo25xlq 2 | -------------------------------------------------------------------------------- /addons/talo/entities/feedback_category.gd: -------------------------------------------------------------------------------- 1 | class_name TaloFeedbackCategory extends RefCounted 2 | 3 | var id: int 4 | var internal_name: String 5 | var name: String 6 | var description: String 7 | var anonymised: bool 8 | var created_at: String 9 | var updated_at: String 10 | 11 | func _init(data: Dictionary): 12 | id = data.id 13 | internal_name = data.internalName 14 | name = data.name 15 | description = data.description 16 | anonymised = data.anonymised 17 | created_at = data.createdAt 18 | updated_at = data.updatedAt 19 | -------------------------------------------------------------------------------- /addons/talo/entities/feedback_category.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dmk6ntacy4kg 2 | -------------------------------------------------------------------------------- /addons/talo/entities/game_save.gd: -------------------------------------------------------------------------------- 1 | class_name TaloGameSave extends RefCounted 2 | 3 | var id: int 4 | var name: String 5 | var content: Dictionary 6 | var updated_at: String 7 | 8 | func _init(data: Dictionary) -> void: 9 | id = data.id if data.has("id") else 0 10 | name = data.name 11 | content = data.content 12 | updated_at = data.updatedAt 13 | 14 | func to_dictionary() -> Dictionary: 15 | return { 16 | id = id, 17 | name = name, 18 | content = content, 19 | updatedAt = updated_at 20 | } 21 | -------------------------------------------------------------------------------- /addons/talo/entities/game_save.gd.uid: -------------------------------------------------------------------------------- 1 | uid://hjhsc3t83e3n 2 | -------------------------------------------------------------------------------- /addons/talo/entities/leaderboard_entry.gd: -------------------------------------------------------------------------------- 1 | class_name TaloLeaderboardEntry extends TaloEntityWithProps 2 | 3 | var id: int 4 | var position: int 5 | var score: float 6 | var player_alias: TaloPlayerAlias 7 | var created_at: String 8 | var updated_at: String 9 | var deleted_at: String 10 | 11 | func _init(data: Dictionary): 12 | super._init(data.props.map(func (prop): return TaloProp.new(prop.key, prop.value))) 13 | 14 | id = data.id 15 | position = data.position 16 | score = data.score 17 | player_alias = TaloPlayerAlias.new(data.playerAlias) 18 | created_at = data.createdAt 19 | updated_at = data.updatedAt 20 | if data.deletedAt: 21 | deleted_at = data.deletedAt 22 | -------------------------------------------------------------------------------- /addons/talo/entities/leaderboard_entry.gd.uid: -------------------------------------------------------------------------------- 1 | uid://h6xfa64fx4lm 2 | -------------------------------------------------------------------------------- /addons/talo/entities/live_config.gd: -------------------------------------------------------------------------------- 1 | class_name TaloLiveConfig extends RefCounted 2 | ## The live config is a set of key-value pairs that can be updated in the Talo dashboard. 3 | ## 4 | ## @tutorial: https://docs.trytalo.com/docs/godot/live-config 5 | 6 | var props: Array[TaloProp] = [] 7 | 8 | func _init(props: Array): 9 | self.props.assign(props.map(func (prop): return TaloProp.new(prop.key, prop.value))) 10 | 11 | ## Get a property value by key. Returns the fallback value if the key is not found. 12 | func get_prop(key: String, fallback: String) -> String: 13 | var filtered := props.filter(func (prop: TaloProp): return prop.key == key) 14 | return fallback if filtered.is_empty() else filtered.front().value 15 | -------------------------------------------------------------------------------- /addons/talo/entities/live_config.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c718qqv4o5f0t 2 | -------------------------------------------------------------------------------- /addons/talo/entities/loadable.gd: -------------------------------------------------------------------------------- 1 | class_name TaloLoadable extends Node 2 | ## An object that can be saved and loaded. 3 | ## 4 | ## This class is used to save and load objects in your game. It should be inherited by a child class that implements register_fields() and on_loaded(). The saving and loading logic is managed by the SavesAPI and SavesManager. 5 | ## 6 | ## @tutorial: https://docs.trytalo.com/docs/godot/saves 7 | 8 | ## The unique identifier for this loadable. 9 | @export var id: String 10 | 11 | var _saved_fields: Dictionary[String, Variant] 12 | 13 | func _ready() -> void: 14 | Talo.saves.register(self) 15 | 16 | func _convert_serialised_data(item: Dictionary) -> Variant: 17 | match Talo.saves.get_format_version(): 18 | "godot.v1": return type_convert(item.value, int(item.type)) 19 | _: return str_to_var(item.value) 20 | 21 | ## Update this loadable with the latest data. 22 | func hydrate(data: Array[Dictionary]) -> void: 23 | var fields := {} 24 | for item in data: 25 | fields[item.key] = _convert_serialised_data(item) 26 | 27 | on_loaded(fields) 28 | 29 | ## Register all the fields that should be saved and loaded. 30 | func register_fields() -> void: 31 | pass 32 | 33 | ## Register the given key with a value. When this object is saved, the value will be saved and loaded. 34 | func register_field(key: String, value: Variant) -> void: 35 | _saved_fields.set(key, value) 36 | 37 | ## Handle the loaded data. This must be implemented by the child class. 38 | func on_loaded(data: Dictionary) -> void: 39 | assert(false, "on_loaded() must be implemented") 40 | 41 | ## Handle if this object was previously destroyed. If it was, remove it from the scene. 42 | func handle_destroyed(data: Dictionary) -> bool: 43 | var destroyed = data.has("meta.destroyed") 44 | if destroyed: 45 | queue_free() 46 | 47 | return destroyed 48 | 49 | ## Ensure the data is up-to-date and return the serialised saved fields. 50 | func get_latest_data() -> Array[Dictionary]: 51 | register_fields() 52 | 53 | var data: Array[Dictionary] = [] 54 | data.assign(_saved_fields.keys().map( 55 | func (key: String): 56 | var value = _saved_fields[key] 57 | return { 58 | key = key, 59 | value = var_to_str(value), 60 | type = str(typeof(value)) 61 | } 62 | )) 63 | return data 64 | -------------------------------------------------------------------------------- /addons/talo/entities/loadable.gd.uid: -------------------------------------------------------------------------------- 1 | uid://r3fisihyip7e 2 | -------------------------------------------------------------------------------- /addons/talo/entities/player.gd: -------------------------------------------------------------------------------- 1 | class_name TaloPlayer extends TaloEntityWithProps 2 | ## @tutorial: https://docs.trytalo.com/docs/godot/player-props 3 | 4 | var id: String 5 | var groups: Array[TaloPlayerGroupStub] = [] 6 | 7 | func _init(data: Dictionary): 8 | super._init([]) 9 | update_from_raw_data(data) 10 | 11 | ## Update the player from raw JSON data. 12 | func update_from_raw_data(data: Dictionary) -> void: 13 | props.assign(data.props.map(func (prop): return TaloProp.new(prop.key, prop.value))) 14 | 15 | id = data.id 16 | groups.assign(data.groups.map(func (group): return TaloPlayerGroupStub.new(group.id, group.name))) 17 | 18 | ## Set a property by key and value. Optionally sync the player (default true) with Talo. 19 | func set_prop(key: String, value: String, update: bool = true) -> void: 20 | super.set_prop(key, value) 21 | if update: 22 | await Talo.players.update() 23 | 24 | ## Delete a property by key. Optionally sync the player (default true) with Talo. 25 | func delete_prop(key: String, update: bool = true) -> void: 26 | super.delete_prop(key) 27 | if update: 28 | await Talo.players.update() 29 | 30 | ## Check if the player is in a group with the given ID. 31 | func is_in_talo_group_id(group_id: String) -> bool: 32 | return not groups.filter(func (group: TaloPlayerGroupStub): return group.id == group_id).is_empty() 33 | 34 | ## Check if the player is in a group with the given name. 35 | func is_in_talo_group_name(group_name: String) -> bool: 36 | return not groups.filter(func (group: TaloPlayerGroupStub): return group.name == group_name).is_empty() 37 | -------------------------------------------------------------------------------- /addons/talo/entities/player.gd.uid: -------------------------------------------------------------------------------- 1 | uid://blodvwgq6t73t 2 | -------------------------------------------------------------------------------- /addons/talo/entities/player_alias.gd: -------------------------------------------------------------------------------- 1 | class_name TaloPlayerAlias extends RefCounted 2 | 3 | var id: int 4 | var service: String 5 | var identifier: String 6 | var player: TaloPlayer 7 | var last_seen_at: String 8 | var created_at: String 9 | var updated_at: String 10 | 11 | func _init(data: Dictionary): 12 | id = data.id 13 | service = data.service 14 | identifier = data.identifier 15 | player = TaloPlayer.new(data.player) 16 | last_seen_at = data.lastSeenAt 17 | created_at = data.createdAt 18 | updated_at = data.updatedAt 19 | -------------------------------------------------------------------------------- /addons/talo/entities/player_alias.gd.uid: -------------------------------------------------------------------------------- 1 | uid://ctpkgruxdpdcl 2 | -------------------------------------------------------------------------------- /addons/talo/entities/player_group.gd: -------------------------------------------------------------------------------- 1 | class_name TaloPlayerGroup extends RefCounted 2 | 3 | var id: String 4 | var name: String 5 | var description: String 6 | var rules: Array 7 | var rule_mode: String 8 | var members_visible: bool 9 | var count: int 10 | var members: Array[TaloPlayer] 11 | var updated_at: String 12 | 13 | func _init(data: Dictionary): 14 | id = data.id 15 | name = data.name 16 | description = data.description 17 | rules = data.rules 18 | rule_mode = data.ruleMode 19 | members_visible = data.membersVisible 20 | count = data.count 21 | if data.has("members"): 22 | members.assign(data.members.map(func (member): return TaloPlayer.new(member))) 23 | else: 24 | members = [] 25 | updated_at = data.updatedAt 26 | -------------------------------------------------------------------------------- /addons/talo/entities/player_group.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bv8xiyy2pc1ks 2 | -------------------------------------------------------------------------------- /addons/talo/entities/player_group_stub.gd: -------------------------------------------------------------------------------- 1 | class_name TaloPlayerGroupStub extends RefCounted 2 | 3 | var id: String 4 | var name: String 5 | 6 | func _init(id: String, name: String): 7 | self.id = id 8 | self.name = name 9 | -------------------------------------------------------------------------------- /addons/talo/entities/player_group_stub.gd.uid: -------------------------------------------------------------------------------- 1 | uid://djh8n4mmuxlyk 2 | -------------------------------------------------------------------------------- /addons/talo/entities/player_presence.gd: -------------------------------------------------------------------------------- 1 | class_name TaloPlayerPresence extends RefCounted 2 | 3 | var online: bool 4 | var custom_status: String 5 | var player_alias: TaloPlayerAlias 6 | var updated_at: String 7 | 8 | func _init(data: Dictionary): 9 | online = data.online 10 | custom_status = data.customStatus 11 | player_alias = null if data.playerAlias == null else TaloPlayerAlias.new(data.playerAlias) 12 | updated_at = data.updatedAt 13 | -------------------------------------------------------------------------------- /addons/talo/entities/player_presence.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dooadsgmdy8vl 2 | -------------------------------------------------------------------------------- /addons/talo/entities/player_stat.gd: -------------------------------------------------------------------------------- 1 | class_name TaloPlayerStat extends RefCounted 2 | 3 | var id: int 4 | var stat: TaloStat 5 | var value: float 6 | var created_at: String 7 | var updated_at: String 8 | 9 | func _init(data: Dictionary): 10 | id = data.id 11 | stat = TaloStat.new(data.stat) 12 | value = data.value 13 | created_at = data.createdAt 14 | updated_at = data.updatedAt 15 | -------------------------------------------------------------------------------- /addons/talo/entities/player_stat.gd.uid: -------------------------------------------------------------------------------- 1 | uid://ciyxprukacbi8 2 | -------------------------------------------------------------------------------- /addons/talo/entities/player_stat_snapshot.gd: -------------------------------------------------------------------------------- 1 | class_name TaloPlayerStatSnapshot extends RefCounted 2 | 3 | var player_alias: TaloPlayerAlias 4 | var change: float 5 | var value: float 6 | var global_value: float 7 | var created_at: String 8 | 9 | func _init(data: Dictionary): 10 | player_alias = TaloPlayerAlias.new(data.playerAlias) 11 | change = data.change 12 | value = data.value 13 | global_value = data.get("globalValue", 0.0) 14 | created_at = data.createdAt 15 | -------------------------------------------------------------------------------- /addons/talo/entities/player_stat_snapshot.gd.uid: -------------------------------------------------------------------------------- 1 | uid://csh28li6i0cd7 2 | -------------------------------------------------------------------------------- /addons/talo/entities/prop.gd: -------------------------------------------------------------------------------- 1 | class_name TaloProp extends RefCounted 2 | 3 | var key: String 4 | var value: Variant 5 | 6 | func _init(key: String, value: Variant): 7 | self.key = key 8 | self.value = str(value) if value != null else value 9 | 10 | func to_dictionary() -> Dictionary: 11 | return { key = key, value = value } 12 | -------------------------------------------------------------------------------- /addons/talo/entities/prop.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c0c6sct2gc0s6 2 | -------------------------------------------------------------------------------- /addons/talo/entities/saved_object.gd: -------------------------------------------------------------------------------- 1 | class_name TaloSavedObject extends RefCounted 2 | 3 | var loadable: TaloLoadable 4 | var id: String 5 | var name: String 6 | var _cached_data: Array[Dictionary] = [] 7 | var _root_path: String 8 | 9 | func _init(saved_object: Dictionary) -> void: 10 | id = saved_object.id 11 | name = saved_object.get("name", "unknown") 12 | _cached_data.assign(saved_object.data) 13 | 14 | # Match a loadable with a saved object to hydrate the loadable with the latest data. 15 | func register_loadable(loadable: TaloLoadable, hydrate = true) -> void: 16 | self.loadable = loadable 17 | _root_path = loadable.get_tree().current_scene.get_path() 18 | if hydrate: 19 | loadable.hydrate(_cached_data) 20 | 21 | func _get_latest_data() -> Array[Dictionary]: 22 | _cached_data.assign(loadable.get_latest_data()) 23 | return _cached_data 24 | 25 | func _is_loadable_valid() -> bool: 26 | return is_instance_valid(loadable) and not loadable.is_queued_for_deletion() 27 | 28 | func _current_scene_matches_name() -> bool: 29 | # if the root path hasn't been set, it may not have been registered yet 30 | # in that case, we should only return cached data 31 | return _root_path and name.begins_with(_root_path) 32 | 33 | func _serialise_data() -> Array[Dictionary]: 34 | var valid := _is_loadable_valid() 35 | 36 | # doesn't exist but isn't part of the scene, don't modify it 37 | if not valid and not _current_scene_matches_name(): 38 | return _cached_data 39 | 40 | # exists and is part of the scene, serialise it 41 | if valid: 42 | return _get_latest_data() 43 | 44 | # doesn't exist and is part of the scene, mark it as destroyed 45 | _cached_data.push_back({ 46 | key = "meta.destroyed", 47 | value = str(true), 48 | type = str(TYPE_BOOL) 49 | }) 50 | 51 | return _cached_data 52 | 53 | # Serialise the saved object so that it can be saved. 54 | func to_dictionary() -> Dictionary: 55 | return { 56 | id = id, 57 | name = name, 58 | data = _serialise_data() 59 | } 60 | -------------------------------------------------------------------------------- /addons/talo/entities/saved_object.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cvlt0740uikx3 2 | -------------------------------------------------------------------------------- /addons/talo/entities/stat.gd: -------------------------------------------------------------------------------- 1 | class_name TaloStat extends RefCounted 2 | 3 | var id: int 4 | var internal_name: String 5 | var name: String 6 | var global: bool 7 | var global_value: float 8 | var default_value: float 9 | var max_change: float 10 | var min_value: float 11 | var max_value: float 12 | var min_time_between_updates: int 13 | var created_at: String 14 | var updated_at: String 15 | 16 | func _init(data: Dictionary): 17 | id = data.id 18 | internal_name = data.internalName 19 | name = data.name 20 | global = data.global 21 | global_value = data.globalValue 22 | default_value = data.defaultValue 23 | max_change = data.maxChange if data.maxChange else INF 24 | min_value = data.minValue if data.minValue else -INF 25 | max_value = data.maxValue if data.maxValue else INF 26 | min_time_between_updates = data.minTimeBetweenUpdates 27 | created_at = data.createdAt 28 | updated_at = data.updatedAt 29 | -------------------------------------------------------------------------------- /addons/talo/entities/stat.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cxca7w1c87p3d 2 | -------------------------------------------------------------------------------- /addons/talo/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Talo Game Services" 4 | 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" 5 | author="trytalo" 6 | version="0.28.0" 7 | script="talo_autoload.gd" 8 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/assets/theme.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Theme" load_steps=12 format=3 uid="uid://ce2uyi827vc5x"] 2 | 3 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_m0swq"] 4 | bg_color = Color(0.215686, 0.188235, 0.639216, 1) 5 | corner_radius_top_left = 4 6 | corner_radius_top_right = 4 7 | corner_radius_bottom_right = 4 8 | corner_radius_bottom_left = 4 9 | 10 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_eq32b"] 11 | bg_color = Color(0.309804, 0.27451, 0.898039, 1) 12 | corner_radius_top_left = 4 13 | corner_radius_top_right = 4 14 | corner_radius_bottom_right = 4 15 | corner_radius_bottom_left = 4 16 | 17 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ujvrd"] 18 | bg_color = Color(0.388235, 0.4, 0.945098, 1) 19 | corner_radius_top_left = 4 20 | corner_radius_top_right = 4 21 | corner_radius_bottom_right = 4 22 | corner_radius_bottom_left = 4 23 | 24 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dveld"] 25 | bg_color = Color(0.215686, 0.188235, 0.639216, 1) 26 | 27 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_4qdym"] 28 | bg_color = Color(0.392157, 0.4, 0.913725, 1) 29 | 30 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_56lla"] 31 | content_margin_left = 8.0 32 | bg_color = Color(0.392157, 0.4, 0.913725, 1) 33 | corner_radius_top_left = 4 34 | corner_radius_top_right = 4 35 | corner_radius_bottom_right = 4 36 | corner_radius_bottom_left = 4 37 | expand_margin_top = 4.0 38 | expand_margin_bottom = 4.0 39 | 40 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5jvg3"] 41 | content_margin_left = 8.0 42 | bg_color = Color(0.392157, 0.4, 0.913725, 1) 43 | corner_radius_top_left = 4 44 | corner_radius_top_right = 4 45 | corner_radius_bottom_right = 4 46 | corner_radius_bottom_left = 4 47 | expand_margin_top = 4.0 48 | expand_margin_bottom = 4.0 49 | 50 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_lfli8"] 51 | content_margin_left = 8.0 52 | bg_color = Color(0.392157, 0.4, 0.913725, 1) 53 | corner_radius_top_left = 4 54 | corner_radius_top_right = 4 55 | corner_radius_bottom_right = 4 56 | corner_radius_bottom_left = 4 57 | expand_margin_top = 4.0 58 | expand_margin_bottom = 4.0 59 | 60 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_4pstf"] 61 | content_margin_left = 8.0 62 | bg_color = Color(0.392157, 0.4, 0.913725, 1) 63 | corner_radius_top_left = 4 64 | corner_radius_top_right = 4 65 | corner_radius_bottom_right = 4 66 | corner_radius_bottom_left = 4 67 | expand_margin_top = 4.0 68 | expand_margin_bottom = 4.0 69 | 70 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hh5ds"] 71 | content_margin_left = 8.0 72 | content_margin_top = 8.0 73 | corner_radius_top_left = 4 74 | corner_radius_top_right = 4 75 | corner_radius_bottom_right = 4 76 | corner_radius_bottom_left = 4 77 | 78 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bfd4v"] 79 | content_margin_left = 8.0 80 | content_margin_top = 8.0 81 | bg_color = Color(0.392157, 0.4, 0.913725, 1) 82 | corner_radius_top_left = 4 83 | corner_radius_top_right = 4 84 | corner_radius_bottom_right = 4 85 | corner_radius_bottom_left = 4 86 | 87 | [resource] 88 | Button/styles/focus = SubResource("StyleBoxFlat_m0swq") 89 | Button/styles/hover = SubResource("StyleBoxFlat_eq32b") 90 | Button/styles/normal = SubResource("StyleBoxFlat_ujvrd") 91 | Button/styles/pressed = SubResource("StyleBoxFlat_dveld") 92 | CheckBox/constants/check_v_offset = 1 93 | CheckBox/constants/h_separation = 6 94 | CheckBox/styles/focus = SubResource("StyleBoxFlat_4qdym") 95 | CheckBox/styles/hover = SubResource("StyleBoxFlat_56lla") 96 | CheckBox/styles/hover_pressed = SubResource("StyleBoxFlat_5jvg3") 97 | CheckBox/styles/normal = SubResource("StyleBoxFlat_lfli8") 98 | CheckBox/styles/pressed = SubResource("StyleBoxFlat_4pstf") 99 | Label/colors/font_color = Color(1, 1, 1, 1) 100 | Label/font_sizes/font_size = 32 101 | TextEdit/colors/background_color = Color(0.294118, 0.333333, 0.388235, 1) 102 | TextEdit/styles/focus = SubResource("StyleBoxFlat_hh5ds") 103 | TextEdit/styles/normal = SubResource("StyleBoxFlat_bfd4v") 104 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/authentication.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=11 format=3 uid="uid://bwh4ytfs4g7js"] 2 | 3 | [ext_resource type="PackedScene" uid="uid://bjopxp3rhpinb" path="res://addons/talo/samples/authentication/states/register.tscn" id="1_5qw25"] 4 | [ext_resource type="Script" uid="uid://dn2sal6firbmh" path="res://addons/talo/samples/authentication/scripts/authentication.gd" id="1_aqcrf"] 5 | [ext_resource type="PackedScene" uid="uid://co0uou3idf65q" path="res://addons/talo/samples/authentication/states/login.tscn" id="2_fsclp"] 6 | [ext_resource type="PackedScene" uid="uid://dv6o0q01wov2o" path="res://addons/talo/samples/authentication/states/verify.tscn" id="3_njdp7"] 7 | [ext_resource type="PackedScene" uid="uid://b6mypp2qa8m4u" path="res://addons/talo/samples/authentication/states/change_email.tscn" id="4_vngav"] 8 | [ext_resource type="PackedScene" uid="uid://bdfuoxfyblqtw" path="res://addons/talo/samples/authentication/states/change_password.tscn" id="5_q15qy"] 9 | [ext_resource type="PackedScene" uid="uid://df6qjcnw1atpn" path="res://addons/talo/samples/authentication/states/in_game.tscn" id="7_sxmru"] 10 | [ext_resource type="PackedScene" uid="uid://b0nvmmnpff27a" path="res://addons/talo/samples/authentication/states/forgot_password.tscn" id="8_tqrpb"] 11 | [ext_resource type="PackedScene" uid="uid://8aqpg6o0a1xx" path="res://addons/talo/samples/authentication/states/reset_password.tscn" id="9_6wobc"] 12 | [ext_resource type="PackedScene" uid="uid://d17bvaxw1qew4" path="res://addons/talo/samples/authentication/states/delete_account.tscn" id="10_gndcp"] 13 | 14 | [node name="Authentication" type="Node2D"] 15 | script = ExtResource("1_aqcrf") 16 | 17 | [node name="UI" type="Control" parent="."] 18 | layout_mode = 3 19 | anchors_preset = 0 20 | offset_right = 1080.0 21 | offset_bottom = 720.0 22 | 23 | [node name="States" type="Control" parent="UI"] 24 | unique_name_in_owner = true 25 | anchors_preset = 0 26 | offset_right = 40.0 27 | offset_bottom = 40.0 28 | 29 | [node name="Login" parent="UI/States" instance=ExtResource("2_fsclp")] 30 | unique_name_in_owner = true 31 | 32 | [node name="Register" parent="UI/States" instance=ExtResource("1_5qw25")] 33 | unique_name_in_owner = true 34 | visible = false 35 | 36 | [node name="Verify" parent="UI/States" instance=ExtResource("3_njdp7")] 37 | unique_name_in_owner = true 38 | visible = false 39 | 40 | [node name="InGame" parent="UI/States" instance=ExtResource("7_sxmru")] 41 | unique_name_in_owner = true 42 | visible = false 43 | 44 | [node name="ChangeEmail" parent="UI/States" instance=ExtResource("4_vngav")] 45 | unique_name_in_owner = true 46 | visible = false 47 | 48 | [node name="ChangePassword" parent="UI/States" instance=ExtResource("5_q15qy")] 49 | unique_name_in_owner = true 50 | visible = false 51 | 52 | [node name="ForgotPassword" parent="UI/States" instance=ExtResource("8_tqrpb")] 53 | unique_name_in_owner = true 54 | visible = false 55 | 56 | [node name="ResetPassword" parent="UI/States" instance=ExtResource("9_6wobc")] 57 | unique_name_in_owner = true 58 | visible = false 59 | 60 | [node name="DeleteAccount" parent="UI/States" instance=ExtResource("10_gndcp")] 61 | unique_name_in_owner = true 62 | visible = false 63 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/scripts/authentication.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @onready var login = %Login 4 | @onready var register = %Register 5 | @onready var verify = %Verify 6 | @onready var in_game = %InGame 7 | @onready var change_password = %ChangePassword 8 | @onready var change_email = %ChangeEmail 9 | @onready var forgot_password = %ForgotPassword 10 | @onready var reset_password = %ResetPassword 11 | @onready var delete_account = %DeleteAccount 12 | @onready var all_states = %States 13 | 14 | func _ready() -> void: 15 | _configure_signals() 16 | _make_state_visible(login) 17 | 18 | func _make_state_visible(state: Node2D): 19 | for child in all_states.get_children(): 20 | child.visible = child == state 21 | 22 | func _configure_signals(): 23 | login.verification_required.connect(func (): _make_state_visible(verify)) 24 | login.go_to_forgot_password.connect(func (): _make_state_visible(forgot_password)) 25 | login.go_to_register.connect(func (): _make_state_visible(register)) 26 | 27 | register.go_to_login.connect(func (): _make_state_visible(login)) 28 | 29 | in_game.go_to_change_password.connect(func (): _make_state_visible(change_password)) 30 | in_game.go_to_change_email.connect(func (): _make_state_visible(change_email)) 31 | in_game.go_to_delete.connect(func (): _make_state_visible(delete_account)) 32 | in_game.logout_success.connect(func (): _make_state_visible(login)) 33 | 34 | change_password.password_change_success.connect(func (): _make_state_visible(in_game)) 35 | change_password.go_to_game.connect(func (): _make_state_visible(in_game)) 36 | 37 | change_email.email_change_success.connect(func (): _make_state_visible(in_game)) 38 | change_email.go_to_game.connect(func (): _make_state_visible(in_game)) 39 | 40 | forgot_password.forgot_password_success.connect(func (): _make_state_visible(reset_password)) 41 | forgot_password.go_to_login.connect(func (): _make_state_visible(login)) 42 | 43 | reset_password.password_reset_success.connect(func (): _make_state_visible(login)) 44 | reset_password.go_to_forgot_password.connect(func (): _make_state_visible(forgot_password)) 45 | 46 | delete_account.delete_account_success.connect(func (): _make_state_visible(login)) 47 | delete_account.go_to_game.connect(func (): _make_state_visible(in_game)) 48 | 49 | Talo.players.identified.connect(func (player): _make_state_visible(in_game)) 50 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/scripts/authentication.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dn2sal6firbmh 2 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/scripts/change_email.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | signal email_change_success 4 | signal go_to_game 5 | 6 | @onready var password: TextEdit = %Password 7 | @onready var new_email: TextEdit = %NewEmail 8 | @onready var validation_label: Label = %ValidationLabel 9 | 10 | func _on_submit_pressed() -> void: 11 | validation_label.text = "" 12 | 13 | if not password.text: 14 | validation_label.text = "Current password is required" 15 | return 16 | 17 | if not new_email.text: 18 | validation_label.text = "New email is required" 19 | return 20 | 21 | var res := await Talo.player_auth.change_email(password.text, new_email.text) 22 | if res != OK: 23 | match Talo.player_auth.last_error.get_code(): 24 | TaloAuthError.ErrorCode.INVALID_CREDENTIALS: 25 | validation_label.text = "Current password is incorrect" 26 | TaloAuthError.ErrorCode.NEW_EMAIL_MATCHES_CURRENT_EMAIL: 27 | validation_label.text = "New email must be different from the current email" 28 | TaloAuthError.ErrorCode.INVALID_EMAIL: 29 | validation_label.text = "Invalid email address" 30 | _: 31 | validation_label.text = Talo.player_auth.last_error.get_string() 32 | else: 33 | email_change_success.emit() 34 | 35 | func _on_cancel_pressed() -> void: 36 | go_to_game.emit() 37 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/scripts/change_email.gd.uid: -------------------------------------------------------------------------------- 1 | uid://vflrdq5hi1n4 2 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/scripts/change_password.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | signal password_change_success 4 | signal go_to_game 5 | 6 | @onready var current_password: TextEdit = %CurrentPassword 7 | @onready var new_password: TextEdit = %NewPassword 8 | @onready var validation_label: Label = %ValidationLabel 9 | 10 | func _on_submit_pressed() -> void: 11 | validation_label.text = "" 12 | 13 | if not current_password.text: 14 | validation_label.text = "Current password is required" 15 | return 16 | 17 | if not new_password.text: 18 | validation_label.text = "New password is required" 19 | return 20 | 21 | var res := await Talo.player_auth.change_password(current_password.text, new_password.text) 22 | if res != OK: 23 | match Talo.player_auth.last_error.get_code(): 24 | TaloAuthError.ErrorCode.INVALID_CREDENTIALS: 25 | validation_label.text = "Current password is incorrect" 26 | TaloAuthError.ErrorCode.NEW_PASSWORD_MATCHES_CURRENT_PASSWORD: 27 | validation_label.text = "New password must be different from the current password" 28 | _: 29 | validation_label.text = Talo.player_auth.last_error.get_string() 30 | else: 31 | password_change_success.emit() 32 | 33 | func _on_cancel_pressed() -> void: 34 | go_to_game.emit() 35 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/scripts/change_password.gd.uid: -------------------------------------------------------------------------------- 1 | uid://0pi5cxko0b07 2 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/scripts/delete_account.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | signal delete_account_success 4 | signal go_to_game 5 | 6 | @onready var current_password: TextEdit = %CurrentPassword 7 | @onready var validation_label: Label = %ValidationLabel 8 | 9 | func _on_delete_pressed() -> void: 10 | validation_label.text = "" 11 | 12 | if not current_password.text: 13 | validation_label.text = "Current password is required" 14 | return 15 | 16 | var res := await Talo.player_auth.delete_account(current_password.text) 17 | if res != OK: 18 | match Talo.player_auth.last_error.get_code(): 19 | TaloAuthError.ErrorCode.INVALID_CREDENTIALS: 20 | validation_label.text = "Current password is incorrect" 21 | _: 22 | validation_label.text = Talo.player_auth.last_error.get_string() 23 | else: 24 | delete_account_success.emit() 25 | 26 | func _on_cancel_pressed() -> void: 27 | go_to_game.emit() 28 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/scripts/delete_account.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bnojd38smydqq 2 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/scripts/forgot_password.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | signal forgot_password_success 4 | signal go_to_login 5 | 6 | @onready var email: TextEdit = %Email 7 | @onready var validation_label: Label = %ValidationLabel 8 | 9 | func _on_submit_pressed() -> void: 10 | validation_label.text = "" 11 | 12 | if not email.text: 13 | validation_label.text = "Email is required" 14 | return 15 | 16 | if await Talo.player_auth.forgot_password(email.text) == OK: 17 | forgot_password_success.emit() 18 | 19 | func _on_cancel_pressed() -> void: 20 | go_to_login.emit() 21 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/scripts/forgot_password.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b5ulc7214nljl 2 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/scripts/in_game.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | signal go_to_change_password 4 | signal go_to_change_email 5 | signal go_to_delete 6 | signal logout_success 7 | 8 | @onready var username: Label = %Username 9 | 10 | func _ready() -> void: 11 | Talo.players.identified.connect(_on_player_identified) 12 | 13 | func _on_player_identified(player: TaloPlayer) -> void: 14 | username.text = "What would you like to do,\n%s?" % Talo.current_alias.identifier 15 | 16 | func _on_change_password_pressed() -> void: 17 | go_to_change_password.emit() 18 | 19 | func _on_change_email_pressed() -> void: 20 | go_to_change_email.emit() 21 | 22 | func _on_logout_pressed() -> void: 23 | await Talo.player_auth.logout() 24 | logout_success.emit() 25 | 26 | func _on_delete_pressed() -> void: 27 | go_to_delete.emit() 28 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/scripts/in_game.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bqe3klqichice 2 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/scripts/login.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | signal verification_required 4 | signal go_to_forgot_password 5 | signal go_to_register 6 | 7 | @onready var username: TextEdit = %Username 8 | @onready var password: TextEdit = %Password 9 | @onready var validation_label: Label = %ValidationLabel 10 | 11 | func _on_submit_pressed() -> void: 12 | validation_label.text = "" 13 | 14 | if not username.text: 15 | validation_label.text = "Username is required" 16 | return 17 | 18 | if not password.text: 19 | validation_label.text = "Password is required" 20 | return 21 | 22 | var res := await Talo.player_auth.login(username.text, password.text) 23 | match res: 24 | Talo.player_auth.LoginResult.FAILED: 25 | match Talo.player_auth.last_error.get_code(): 26 | TaloAuthError.ErrorCode.INVALID_CREDENTIALS: 27 | validation_label.text = "Username or password is incorrect" 28 | _: 29 | validation_label.text = Talo.player_auth.last_error.get_string() 30 | Talo.player_auth.LoginResult.VERIFICATION_REQUIRED: 31 | verification_required.emit() 32 | Talo.player_auth.LoginResult.OK: 33 | pass 34 | 35 | func _on_forgot_password_pressed() -> void: 36 | go_to_forgot_password.emit() 37 | 38 | func _on_register_pressed() -> void: 39 | go_to_register.emit() 40 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/scripts/login.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bkvrg5x0p3hn4 2 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/scripts/register.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | signal go_to_login 4 | 5 | @onready var username: TextEdit = %Username 6 | @onready var password: TextEdit = %Password 7 | @onready var enable_verification: CheckBox = %EnableVerification 8 | @onready var email: TextEdit = %Email 9 | @onready var validation_label: Label = %ValidationLabel 10 | 11 | func _on_submit_button_pressed() -> void: 12 | validation_label.text = "" 13 | 14 | if not username.text: 15 | validation_label.text = "Username is required" 16 | return 17 | 18 | if not password.text: 19 | validation_label.text = "Password is required" 20 | return 21 | 22 | if enable_verification.button_pressed and not email.text: 23 | validation_label.text = "Email is required when verification is enabled" 24 | return 25 | 26 | var res := await Talo.player_auth.register(username.text, password.text, email.text, enable_verification.button_pressed) 27 | if res != OK: 28 | match Talo.player_auth.last_error.get_code(): 29 | TaloAuthError.ErrorCode.IDENTIFIER_TAKEN: 30 | validation_label.text = "Username is already taken" 31 | TaloAuthError.ErrorCode.INVALID_EMAIL: 32 | validation_label.text = "Invalid email address" 33 | _: 34 | validation_label.text = Talo.player_auth.last_error.get_string() 35 | 36 | func _on_login_pressed() -> void: 37 | go_to_login.emit() 38 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/scripts/register.gd.uid: -------------------------------------------------------------------------------- 1 | uid://vymday6fb02a 2 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/scripts/reset_password.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | signal password_reset_success 4 | signal go_to_forgot_password 5 | 6 | @onready var code: TextEdit = %Code 7 | @onready var new_password: TextEdit = %NewPassword 8 | @onready var validation_label: Label = %ValidationLabel 9 | 10 | func _on_submit_pressed() -> void: 11 | validation_label.text = "" 12 | 13 | if not code.text: 14 | validation_label.text = "Code is required" 15 | return 16 | 17 | if not new_password.text: 18 | validation_label.text = "New password is required" 19 | return 20 | 21 | var res := await Talo.player_auth.reset_password(code.text, new_password.text) 22 | if res != OK: 23 | match Talo.player_auth.last_error.get_code(): 24 | TaloAuthError.ErrorCode.PASSWORD_RESET_CODE_INVALID: 25 | validation_label.text = "Reset code is invalid" 26 | _: 27 | validation_label.text = Talo.player_auth.last_error.get_string() 28 | else: 29 | password_reset_success.emit() 30 | 31 | func _on_cancel_pressed() -> void: 32 | go_to_forgot_password.emit() 33 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/scripts/reset_password.gd.uid: -------------------------------------------------------------------------------- 1 | uid://d0hkbj6wjepg3 2 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/scripts/verify.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @onready var code: TextEdit = %Code 4 | @onready var validation_label: Label = %ValidationLabel 5 | 6 | func _on_submit_pressed() -> void: 7 | validation_label.text = "" 8 | 9 | if not code.text: 10 | validation_label.text = "Verification code is required" 11 | return 12 | 13 | var res := await Talo.player_auth.verify(code.text) 14 | if res != OK: 15 | match Talo.player_auth.last_error.get_code(): 16 | TaloAuthError.ErrorCode.VERIFICATION_CODE_INVALID: 17 | validation_label.text = "Verification code is incorrect" 18 | TaloAuthError.ErrorCode.VERIFICATION_ALIAS_NOT_FOUND: 19 | validation_label.text = "Verification session is invalid" 20 | _: 21 | validation_label.text = Talo.player_auth.last_error.get_string() 22 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/scripts/verify.gd.uid: -------------------------------------------------------------------------------- 1 | uid://djje4dcxkijj3 2 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/states/change_email.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://b6mypp2qa8m4u"] 2 | 3 | [ext_resource type="Script" uid="uid://vflrdq5hi1n4" path="res://addons/talo/samples/authentication/scripts/change_email.gd" id="1_qamph"] 4 | [ext_resource type="Theme" uid="uid://ce2uyi827vc5x" path="res://addons/talo/samples/authentication/assets/theme.tres" id="2_5w58c"] 5 | 6 | [node name="ChangeEmail" type="Node2D"] 7 | script = ExtResource("1_qamph") 8 | 9 | [node name="UI" type="Control" parent="."] 10 | layout_mode = 3 11 | anchors_preset = 0 12 | offset_right = 1080.0 13 | offset_bottom = 720.0 14 | 15 | [node name="Background" type="ColorRect" parent="UI"] 16 | layout_mode = 1 17 | anchors_preset = 15 18 | anchor_right = 1.0 19 | anchor_bottom = 1.0 20 | grow_horizontal = 2 21 | grow_vertical = 2 22 | color = Color(0.121569, 0.160784, 0.215686, 1) 23 | 24 | [node name="MarginContainer" type="MarginContainer" parent="UI"] 25 | layout_mode = 1 26 | anchors_preset = 15 27 | anchor_right = 1.0 28 | anchor_bottom = 1.0 29 | grow_horizontal = 2 30 | grow_vertical = 2 31 | theme_override_constants/margin_left = 40 32 | theme_override_constants/margin_top = 40 33 | theme_override_constants/margin_right = 40 34 | theme_override_constants/margin_bottom = 40 35 | 36 | [node name="VBoxContainer" type="VBoxContainer" parent="UI/MarginContainer"] 37 | layout_mode = 2 38 | size_flags_vertical = 4 39 | theme_override_constants/separation = 24 40 | 41 | [node name="Title" type="Label" parent="UI/MarginContainer/VBoxContainer"] 42 | layout_mode = 2 43 | size_flags_vertical = 0 44 | theme = ExtResource("2_5w58c") 45 | text = "Change email" 46 | horizontal_alignment = 1 47 | 48 | [node name="Password" type="TextEdit" parent="UI/MarginContainer/VBoxContainer"] 49 | unique_name_in_owner = true 50 | custom_minimum_size = Vector2(400, 40) 51 | layout_mode = 2 52 | size_flags_horizontal = 4 53 | theme = ExtResource("2_5w58c") 54 | placeholder_text = "Current password" 55 | 56 | [node name="NewEmail" type="TextEdit" parent="UI/MarginContainer/VBoxContainer"] 57 | unique_name_in_owner = true 58 | custom_minimum_size = Vector2(400, 40) 59 | layout_mode = 2 60 | size_flags_horizontal = 4 61 | theme = ExtResource("2_5w58c") 62 | placeholder_text = "New email" 63 | 64 | [node name="Submit" type="Button" parent="UI/MarginContainer/VBoxContainer"] 65 | custom_minimum_size = Vector2(200, 40) 66 | layout_mode = 2 67 | size_flags_horizontal = 4 68 | theme = ExtResource("2_5w58c") 69 | text = "Submit" 70 | 71 | [node name="Cancel" type="Button" parent="UI/MarginContainer/VBoxContainer"] 72 | custom_minimum_size = Vector2(200, 40) 73 | layout_mode = 2 74 | size_flags_horizontal = 4 75 | theme = ExtResource("2_5w58c") 76 | text = "Cancel" 77 | 78 | [node name="ValidationLabel" type="Label" parent="UI/MarginContainer/VBoxContainer"] 79 | unique_name_in_owner = true 80 | custom_minimum_size = Vector2(400, 2.08165e-12) 81 | layout_mode = 2 82 | size_flags_horizontal = 4 83 | 84 | [connection signal="pressed" from="UI/MarginContainer/VBoxContainer/Submit" to="." method="_on_submit_pressed"] 85 | [connection signal="pressed" from="UI/MarginContainer/VBoxContainer/Cancel" to="." method="_on_cancel_pressed"] 86 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/states/change_password.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://bdfuoxfyblqtw"] 2 | 3 | [ext_resource type="Script" uid="uid://0pi5cxko0b07" path="res://addons/talo/samples/authentication/scripts/change_password.gd" id="1_36xs1"] 4 | [ext_resource type="Theme" uid="uid://ce2uyi827vc5x" path="res://addons/talo/samples/authentication/assets/theme.tres" id="2_hwmu0"] 5 | 6 | [node name="ChangePassword" type="Node2D"] 7 | script = ExtResource("1_36xs1") 8 | 9 | [node name="UI" type="Control" parent="."] 10 | layout_mode = 3 11 | anchors_preset = 0 12 | offset_right = 1080.0 13 | offset_bottom = 720.0 14 | 15 | [node name="Background" type="ColorRect" parent="UI"] 16 | layout_mode = 1 17 | anchors_preset = 15 18 | anchor_right = 1.0 19 | anchor_bottom = 1.0 20 | grow_horizontal = 2 21 | grow_vertical = 2 22 | color = Color(0.121569, 0.160784, 0.215686, 1) 23 | 24 | [node name="MarginContainer" type="MarginContainer" parent="UI"] 25 | layout_mode = 1 26 | anchors_preset = 15 27 | anchor_right = 1.0 28 | anchor_bottom = 1.0 29 | grow_horizontal = 2 30 | grow_vertical = 2 31 | theme_override_constants/margin_left = 40 32 | theme_override_constants/margin_top = 40 33 | theme_override_constants/margin_right = 40 34 | theme_override_constants/margin_bottom = 40 35 | 36 | [node name="VBoxContainer" type="VBoxContainer" parent="UI/MarginContainer"] 37 | layout_mode = 2 38 | size_flags_vertical = 4 39 | theme_override_constants/separation = 24 40 | 41 | [node name="Title" type="Label" parent="UI/MarginContainer/VBoxContainer"] 42 | layout_mode = 2 43 | size_flags_vertical = 0 44 | theme = ExtResource("2_hwmu0") 45 | text = "Change password" 46 | horizontal_alignment = 1 47 | 48 | [node name="CurrentPassword" type="TextEdit" parent="UI/MarginContainer/VBoxContainer"] 49 | unique_name_in_owner = true 50 | custom_minimum_size = Vector2(400, 40) 51 | layout_mode = 2 52 | size_flags_horizontal = 4 53 | theme = ExtResource("2_hwmu0") 54 | placeholder_text = "Current password" 55 | 56 | [node name="NewPassword" type="TextEdit" parent="UI/MarginContainer/VBoxContainer"] 57 | unique_name_in_owner = true 58 | custom_minimum_size = Vector2(400, 40) 59 | layout_mode = 2 60 | size_flags_horizontal = 4 61 | theme = ExtResource("2_hwmu0") 62 | placeholder_text = "New password" 63 | 64 | [node name="Submit" type="Button" parent="UI/MarginContainer/VBoxContainer"] 65 | custom_minimum_size = Vector2(200, 40) 66 | layout_mode = 2 67 | size_flags_horizontal = 4 68 | theme = ExtResource("2_hwmu0") 69 | text = "Submit" 70 | 71 | [node name="Cancel" type="Button" parent="UI/MarginContainer/VBoxContainer"] 72 | custom_minimum_size = Vector2(200, 40) 73 | layout_mode = 2 74 | size_flags_horizontal = 4 75 | theme = ExtResource("2_hwmu0") 76 | text = "Cancel" 77 | 78 | [node name="ValidationLabel" type="Label" parent="UI/MarginContainer/VBoxContainer"] 79 | unique_name_in_owner = true 80 | custom_minimum_size = Vector2(400, 2.08165e-12) 81 | layout_mode = 2 82 | size_flags_horizontal = 4 83 | 84 | [connection signal="pressed" from="UI/MarginContainer/VBoxContainer/Submit" to="." method="_on_submit_pressed"] 85 | [connection signal="pressed" from="UI/MarginContainer/VBoxContainer/Cancel" to="." method="_on_cancel_pressed"] 86 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/states/delete_account.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=3 uid="uid://d17bvaxw1qew4"] 2 | 3 | [ext_resource type="Script" uid="uid://bnojd38smydqq" path="res://addons/talo/samples/authentication/scripts/delete_account.gd" id="1_ggdf5"] 4 | [ext_resource type="Theme" uid="uid://ce2uyi827vc5x" path="res://addons/talo/samples/authentication/assets/theme.tres" id="2_n2cbx"] 5 | 6 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xxxlc"] 7 | bg_color = Color(0.784314, 0.156863, 0.156863, 1) 8 | 9 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fa3fr"] 10 | bg_color = Color(0.690196, 0.129412, 0.129412, 1) 11 | 12 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hijpl"] 13 | bg_color = Color(0.603922, 0.105882, 0.105882, 1) 14 | 15 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wems6"] 16 | bg_color = Color(0.603922, 0.105882, 0.105882, 1) 17 | 18 | [node name="DeleteAccount" type="Node2D"] 19 | script = ExtResource("1_ggdf5") 20 | 21 | [node name="UI" type="Control" parent="."] 22 | layout_mode = 3 23 | anchors_preset = 0 24 | offset_right = 1080.0 25 | offset_bottom = 720.0 26 | 27 | [node name="Background" type="ColorRect" parent="UI"] 28 | layout_mode = 1 29 | anchors_preset = 15 30 | anchor_right = 1.0 31 | anchor_bottom = 1.0 32 | grow_horizontal = 2 33 | grow_vertical = 2 34 | color = Color(0.121569, 0.160784, 0.215686, 1) 35 | 36 | [node name="MarginContainer" type="MarginContainer" parent="UI"] 37 | layout_mode = 1 38 | anchors_preset = 15 39 | anchor_right = 1.0 40 | anchor_bottom = 1.0 41 | grow_horizontal = 2 42 | grow_vertical = 2 43 | theme_override_constants/margin_left = 40 44 | theme_override_constants/margin_top = 40 45 | theme_override_constants/margin_right = 40 46 | theme_override_constants/margin_bottom = 40 47 | 48 | [node name="VBoxContainer" type="VBoxContainer" parent="UI/MarginContainer"] 49 | layout_mode = 2 50 | size_flags_vertical = 4 51 | theme_override_constants/separation = 24 52 | 53 | [node name="Title" type="Label" parent="UI/MarginContainer/VBoxContainer"] 54 | layout_mode = 2 55 | size_flags_vertical = 0 56 | theme = ExtResource("2_n2cbx") 57 | text = "Delete account" 58 | horizontal_alignment = 1 59 | 60 | [node name="CurrentPassword" type="TextEdit" parent="UI/MarginContainer/VBoxContainer"] 61 | unique_name_in_owner = true 62 | custom_minimum_size = Vector2(400, 40) 63 | layout_mode = 2 64 | size_flags_horizontal = 4 65 | theme = ExtResource("2_n2cbx") 66 | placeholder_text = "Current password" 67 | 68 | [node name="Delete" type="Button" parent="UI/MarginContainer/VBoxContainer"] 69 | custom_minimum_size = Vector2(200, 40) 70 | layout_mode = 2 71 | size_flags_horizontal = 4 72 | theme = ExtResource("2_n2cbx") 73 | theme_override_styles/normal = SubResource("StyleBoxFlat_xxxlc") 74 | theme_override_styles/hover = SubResource("StyleBoxFlat_fa3fr") 75 | theme_override_styles/pressed = SubResource("StyleBoxFlat_hijpl") 76 | theme_override_styles/focus = SubResource("StyleBoxFlat_wems6") 77 | text = "Delete account" 78 | 79 | [node name="Cancel" type="Button" parent="UI/MarginContainer/VBoxContainer"] 80 | custom_minimum_size = Vector2(200, 40) 81 | layout_mode = 2 82 | size_flags_horizontal = 4 83 | theme = ExtResource("2_n2cbx") 84 | text = "Cancel" 85 | 86 | [node name="ValidationLabel" type="Label" parent="UI/MarginContainer/VBoxContainer"] 87 | unique_name_in_owner = true 88 | custom_minimum_size = Vector2(400, 2.08165e-12) 89 | layout_mode = 2 90 | size_flags_horizontal = 4 91 | 92 | [connection signal="pressed" from="UI/MarginContainer/VBoxContainer/Delete" to="." method="_on_delete_pressed"] 93 | [connection signal="pressed" from="UI/MarginContainer/VBoxContainer/Cancel" to="." method="_on_cancel_pressed"] 94 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/states/forgot_password.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://b0nvmmnpff27a"] 2 | 3 | [ext_resource type="Script" uid="uid://b5ulc7214nljl" path="res://addons/talo/samples/authentication/scripts/forgot_password.gd" id="1_o203d"] 4 | [ext_resource type="Theme" uid="uid://ce2uyi827vc5x" path="res://addons/talo/samples/authentication/assets/theme.tres" id="2_e8hfu"] 5 | 6 | [node name="ForgotPassword" type="Node2D"] 7 | script = ExtResource("1_o203d") 8 | 9 | [node name="UI" type="Control" parent="."] 10 | layout_mode = 3 11 | anchors_preset = 0 12 | offset_right = 1080.0 13 | offset_bottom = 720.0 14 | 15 | [node name="Background" type="ColorRect" parent="UI"] 16 | layout_mode = 1 17 | anchors_preset = 15 18 | anchor_right = 1.0 19 | anchor_bottom = 1.0 20 | grow_horizontal = 2 21 | grow_vertical = 2 22 | color = Color(0.121569, 0.160784, 0.215686, 1) 23 | 24 | [node name="MarginContainer" type="MarginContainer" parent="UI"] 25 | layout_mode = 1 26 | anchors_preset = 15 27 | anchor_right = 1.0 28 | anchor_bottom = 1.0 29 | grow_horizontal = 2 30 | grow_vertical = 2 31 | theme_override_constants/margin_left = 40 32 | theme_override_constants/margin_top = 40 33 | theme_override_constants/margin_right = 40 34 | theme_override_constants/margin_bottom = 40 35 | 36 | [node name="VBoxContainer" type="VBoxContainer" parent="UI/MarginContainer"] 37 | layout_mode = 2 38 | size_flags_vertical = 4 39 | theme_override_constants/separation = 24 40 | 41 | [node name="Title" type="Label" parent="UI/MarginContainer/VBoxContainer"] 42 | layout_mode = 2 43 | size_flags_vertical = 0 44 | theme = ExtResource("2_e8hfu") 45 | text = "Forgot password" 46 | horizontal_alignment = 1 47 | 48 | [node name="Email" type="TextEdit" parent="UI/MarginContainer/VBoxContainer"] 49 | unique_name_in_owner = true 50 | custom_minimum_size = Vector2(400, 40) 51 | layout_mode = 2 52 | size_flags_horizontal = 4 53 | theme = ExtResource("2_e8hfu") 54 | placeholder_text = "Email" 55 | 56 | [node name="Submit" type="Button" parent="UI/MarginContainer/VBoxContainer"] 57 | custom_minimum_size = Vector2(200, 40) 58 | layout_mode = 2 59 | size_flags_horizontal = 4 60 | theme = ExtResource("2_e8hfu") 61 | text = "Submit" 62 | 63 | [node name="Cancel" type="Button" parent="UI/MarginContainer/VBoxContainer"] 64 | custom_minimum_size = Vector2(200, 40) 65 | layout_mode = 2 66 | size_flags_horizontal = 4 67 | theme = ExtResource("2_e8hfu") 68 | text = "Cancel" 69 | 70 | [node name="ValidationLabel" type="Label" parent="UI/MarginContainer/VBoxContainer"] 71 | unique_name_in_owner = true 72 | custom_minimum_size = Vector2(400, 2.08165e-12) 73 | layout_mode = 2 74 | size_flags_horizontal = 4 75 | 76 | [connection signal="pressed" from="UI/MarginContainer/VBoxContainer/Submit" to="." method="_on_submit_pressed"] 77 | [connection signal="pressed" from="UI/MarginContainer/VBoxContainer/Cancel" to="." method="_on_cancel_pressed"] 78 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/states/in_game.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://df6qjcnw1atpn"] 2 | 3 | [ext_resource type="Script" uid="uid://bqe3klqichice" path="res://addons/talo/samples/authentication/scripts/in_game.gd" id="1_is44q"] 4 | [ext_resource type="Theme" uid="uid://ce2uyi827vc5x" path="res://addons/talo/samples/authentication/assets/theme.tres" id="2_7u3gt"] 5 | 6 | [node name="InGame" type="Node2D"] 7 | script = ExtResource("1_is44q") 8 | 9 | [node name="UI" type="Control" parent="."] 10 | layout_mode = 3 11 | anchors_preset = 0 12 | offset_right = 1080.0 13 | offset_bottom = 720.0 14 | 15 | [node name="Background" type="ColorRect" parent="UI"] 16 | layout_mode = 1 17 | anchors_preset = 15 18 | anchor_right = 1.0 19 | anchor_bottom = 1.0 20 | grow_horizontal = 2 21 | grow_vertical = 2 22 | color = Color(0.121569, 0.160784, 0.215686, 1) 23 | 24 | [node name="MarginContainer" type="MarginContainer" parent="UI"] 25 | layout_mode = 1 26 | anchors_preset = 15 27 | anchor_right = 1.0 28 | anchor_bottom = 1.0 29 | grow_horizontal = 2 30 | grow_vertical = 2 31 | theme_override_constants/margin_left = 40 32 | theme_override_constants/margin_top = 40 33 | theme_override_constants/margin_right = 40 34 | theme_override_constants/margin_bottom = 40 35 | 36 | [node name="VBoxContainer" type="VBoxContainer" parent="UI/MarginContainer"] 37 | layout_mode = 2 38 | size_flags_vertical = 4 39 | theme_override_constants/separation = 24 40 | 41 | [node name="Username" type="Label" parent="UI/MarginContainer/VBoxContainer"] 42 | unique_name_in_owner = true 43 | layout_mode = 2 44 | size_flags_vertical = 0 45 | theme = ExtResource("2_7u3gt") 46 | text = "What would you like to do, 47 | {username}?" 48 | horizontal_alignment = 1 49 | 50 | [node name="ChangePassword" type="Button" parent="UI/MarginContainer/VBoxContainer"] 51 | custom_minimum_size = Vector2(200, 40) 52 | layout_mode = 2 53 | size_flags_horizontal = 4 54 | theme = ExtResource("2_7u3gt") 55 | text = "Change password" 56 | 57 | [node name="ChangeEmail" type="Button" parent="UI/MarginContainer/VBoxContainer"] 58 | custom_minimum_size = Vector2(200, 40) 59 | layout_mode = 2 60 | size_flags_horizontal = 4 61 | theme = ExtResource("2_7u3gt") 62 | text = "Change email" 63 | 64 | [node name="Logout" type="Button" parent="UI/MarginContainer/VBoxContainer"] 65 | custom_minimum_size = Vector2(200, 40) 66 | layout_mode = 2 67 | size_flags_horizontal = 4 68 | theme = ExtResource("2_7u3gt") 69 | text = "Log out" 70 | 71 | [node name="Delete" type="Button" parent="UI/MarginContainer/VBoxContainer"] 72 | custom_minimum_size = Vector2(200, 40) 73 | layout_mode = 2 74 | size_flags_horizontal = 4 75 | theme = ExtResource("2_7u3gt") 76 | text = "Delete account" 77 | 78 | [connection signal="pressed" from="UI/MarginContainer/VBoxContainer/ChangePassword" to="." method="_on_change_password_pressed"] 79 | [connection signal="pressed" from="UI/MarginContainer/VBoxContainer/ChangeEmail" to="." method="_on_change_email_pressed"] 80 | [connection signal="pressed" from="UI/MarginContainer/VBoxContainer/Logout" to="." method="_on_logout_pressed"] 81 | [connection signal="pressed" from="UI/MarginContainer/VBoxContainer/Delete" to="." method="_on_delete_pressed"] 82 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/states/login.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://co0uou3idf65q"] 2 | 3 | [ext_resource type="Script" uid="uid://bkvrg5x0p3hn4" path="res://addons/talo/samples/authentication/scripts/login.gd" id="1_enqms"] 4 | [ext_resource type="Theme" uid="uid://ce2uyi827vc5x" path="res://addons/talo/samples/authentication/assets/theme.tres" id="2_77km8"] 5 | 6 | [node name="Login" type="Node2D"] 7 | script = ExtResource("1_enqms") 8 | 9 | [node name="UI" type="Control" parent="."] 10 | layout_mode = 3 11 | anchors_preset = 0 12 | offset_right = 1080.0 13 | offset_bottom = 720.0 14 | 15 | [node name="Background" type="ColorRect" parent="UI"] 16 | layout_mode = 1 17 | anchors_preset = 15 18 | anchor_right = 1.0 19 | anchor_bottom = 1.0 20 | grow_horizontal = 2 21 | grow_vertical = 2 22 | color = Color(0.121569, 0.160784, 0.215686, 1) 23 | 24 | [node name="MarginContainer" type="MarginContainer" parent="UI"] 25 | layout_mode = 1 26 | anchors_preset = 15 27 | anchor_right = 1.0 28 | anchor_bottom = 1.0 29 | grow_horizontal = 2 30 | grow_vertical = 2 31 | theme_override_constants/margin_left = 40 32 | theme_override_constants/margin_top = 40 33 | theme_override_constants/margin_right = 40 34 | theme_override_constants/margin_bottom = 40 35 | 36 | [node name="VBoxContainer" type="VBoxContainer" parent="UI/MarginContainer"] 37 | layout_mode = 2 38 | size_flags_vertical = 4 39 | theme_override_constants/separation = 24 40 | 41 | [node name="Title" type="Label" parent="UI/MarginContainer/VBoxContainer"] 42 | layout_mode = 2 43 | size_flags_vertical = 0 44 | theme = ExtResource("2_77km8") 45 | text = "Login" 46 | horizontal_alignment = 1 47 | 48 | [node name="Username" type="TextEdit" parent="UI/MarginContainer/VBoxContainer"] 49 | unique_name_in_owner = true 50 | custom_minimum_size = Vector2(400, 40) 51 | layout_mode = 2 52 | size_flags_horizontal = 4 53 | theme = ExtResource("2_77km8") 54 | placeholder_text = "Username" 55 | 56 | [node name="Password" type="TextEdit" parent="UI/MarginContainer/VBoxContainer"] 57 | unique_name_in_owner = true 58 | custom_minimum_size = Vector2(400, 40) 59 | layout_mode = 2 60 | size_flags_horizontal = 4 61 | theme = ExtResource("2_77km8") 62 | placeholder_text = "Password" 63 | 64 | [node name="Submit" type="Button" parent="UI/MarginContainer/VBoxContainer"] 65 | custom_minimum_size = Vector2(200, 40) 66 | layout_mode = 2 67 | size_flags_horizontal = 4 68 | theme = ExtResource("2_77km8") 69 | text = "Submit" 70 | 71 | [node name="ForgotPassword" type="Button" parent="UI/MarginContainer/VBoxContainer"] 72 | custom_minimum_size = Vector2(200, 40) 73 | layout_mode = 2 74 | size_flags_horizontal = 4 75 | theme = ExtResource("2_77km8") 76 | text = "Forgot password" 77 | 78 | [node name="Register" type="Button" parent="UI/MarginContainer/VBoxContainer"] 79 | custom_minimum_size = Vector2(200, 40) 80 | layout_mode = 2 81 | size_flags_horizontal = 4 82 | theme = ExtResource("2_77km8") 83 | text = "Go to Register" 84 | 85 | [node name="ValidationLabel" type="Label" parent="UI/MarginContainer/VBoxContainer"] 86 | unique_name_in_owner = true 87 | custom_minimum_size = Vector2(400, 2.08165e-12) 88 | layout_mode = 2 89 | size_flags_horizontal = 4 90 | 91 | [connection signal="pressed" from="UI/MarginContainer/VBoxContainer/Submit" to="." method="_on_submit_pressed"] 92 | [connection signal="pressed" from="UI/MarginContainer/VBoxContainer/ForgotPassword" to="." method="_on_forgot_password_pressed"] 93 | [connection signal="pressed" from="UI/MarginContainer/VBoxContainer/Register" to="." method="_on_register_pressed"] 94 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/states/register.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://bjopxp3rhpinb"] 2 | 3 | [ext_resource type="Script" uid="uid://vymday6fb02a" path="res://addons/talo/samples/authentication/scripts/register.gd" id="1_n3mxk"] 4 | [ext_resource type="Theme" uid="uid://ce2uyi827vc5x" path="res://addons/talo/samples/authentication/assets/theme.tres" id="2_rmohq"] 5 | 6 | [node name="Register" type="Node2D"] 7 | script = ExtResource("1_n3mxk") 8 | 9 | [node name="UI" type="Control" parent="."] 10 | layout_mode = 3 11 | anchors_preset = 0 12 | offset_right = 1080.0 13 | offset_bottom = 720.0 14 | 15 | [node name="Background" type="ColorRect" parent="UI"] 16 | layout_mode = 1 17 | anchors_preset = 15 18 | anchor_right = 1.0 19 | anchor_bottom = 1.0 20 | grow_horizontal = 2 21 | grow_vertical = 2 22 | color = Color(0.121569, 0.160784, 0.215686, 1) 23 | 24 | [node name="MarginContainer" type="MarginContainer" parent="UI"] 25 | layout_mode = 1 26 | anchors_preset = 15 27 | anchor_right = 1.0 28 | anchor_bottom = 1.0 29 | grow_horizontal = 2 30 | grow_vertical = 2 31 | theme_override_constants/margin_left = 40 32 | theme_override_constants/margin_top = 40 33 | theme_override_constants/margin_right = 40 34 | theme_override_constants/margin_bottom = 40 35 | 36 | [node name="VBoxContainer" type="VBoxContainer" parent="UI/MarginContainer"] 37 | layout_mode = 2 38 | size_flags_vertical = 4 39 | theme_override_constants/separation = 24 40 | 41 | [node name="Title" type="Label" parent="UI/MarginContainer/VBoxContainer"] 42 | layout_mode = 2 43 | size_flags_vertical = 0 44 | theme = ExtResource("2_rmohq") 45 | text = "Register" 46 | horizontal_alignment = 1 47 | 48 | [node name="Username" type="TextEdit" parent="UI/MarginContainer/VBoxContainer"] 49 | unique_name_in_owner = true 50 | custom_minimum_size = Vector2(400, 40) 51 | layout_mode = 2 52 | size_flags_horizontal = 4 53 | theme = ExtResource("2_rmohq") 54 | placeholder_text = "Username" 55 | 56 | [node name="Password" type="TextEdit" parent="UI/MarginContainer/VBoxContainer"] 57 | unique_name_in_owner = true 58 | custom_minimum_size = Vector2(400, 40) 59 | layout_mode = 2 60 | size_flags_horizontal = 4 61 | theme = ExtResource("2_rmohq") 62 | placeholder_text = "Password" 63 | 64 | [node name="EnableVerification" type="CheckBox" parent="UI/MarginContainer/VBoxContainer"] 65 | unique_name_in_owner = true 66 | custom_minimum_size = Vector2(400, 2.08165e-12) 67 | layout_mode = 2 68 | size_flags_horizontal = 4 69 | theme = ExtResource("2_rmohq") 70 | text = "Enable email verification" 71 | 72 | [node name="Email" type="TextEdit" parent="UI/MarginContainer/VBoxContainer"] 73 | unique_name_in_owner = true 74 | custom_minimum_size = Vector2(400, 40) 75 | layout_mode = 2 76 | size_flags_horizontal = 4 77 | theme = ExtResource("2_rmohq") 78 | placeholder_text = "Email address" 79 | 80 | [node name="Submit" type="Button" parent="UI/MarginContainer/VBoxContainer"] 81 | custom_minimum_size = Vector2(200, 40) 82 | layout_mode = 2 83 | size_flags_horizontal = 4 84 | theme = ExtResource("2_rmohq") 85 | text = "Submit" 86 | 87 | [node name="Login" type="Button" parent="UI/MarginContainer/VBoxContainer"] 88 | custom_minimum_size = Vector2(200, 40) 89 | layout_mode = 2 90 | size_flags_horizontal = 4 91 | theme = ExtResource("2_rmohq") 92 | text = "Go to Login" 93 | 94 | [node name="ValidationLabel" type="Label" parent="UI/MarginContainer/VBoxContainer"] 95 | unique_name_in_owner = true 96 | custom_minimum_size = Vector2(400, 2.08165e-12) 97 | layout_mode = 2 98 | size_flags_horizontal = 4 99 | 100 | [connection signal="button_down" from="UI/MarginContainer/VBoxContainer/Submit" to="." method="_on_submit_button_pressed"] 101 | [connection signal="pressed" from="UI/MarginContainer/VBoxContainer/Login" to="." method="_on_login_pressed"] 102 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/states/reset_password.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://8aqpg6o0a1xx"] 2 | 3 | [ext_resource type="Script" uid="uid://d0hkbj6wjepg3" path="res://addons/talo/samples/authentication/scripts/reset_password.gd" id="1_7fv48"] 4 | [ext_resource type="Theme" uid="uid://ce2uyi827vc5x" path="res://addons/talo/samples/authentication/assets/theme.tres" id="2_4roab"] 5 | 6 | [node name="ResetPassword" type="Node2D"] 7 | script = ExtResource("1_7fv48") 8 | 9 | [node name="UI" type="Control" parent="."] 10 | layout_mode = 3 11 | anchors_preset = 0 12 | offset_right = 1080.0 13 | offset_bottom = 720.0 14 | 15 | [node name="Background" type="ColorRect" parent="UI"] 16 | layout_mode = 1 17 | anchors_preset = 15 18 | anchor_right = 1.0 19 | anchor_bottom = 1.0 20 | grow_horizontal = 2 21 | grow_vertical = 2 22 | color = Color(0.121569, 0.160784, 0.215686, 1) 23 | 24 | [node name="MarginContainer" type="MarginContainer" parent="UI"] 25 | layout_mode = 1 26 | anchors_preset = 15 27 | anchor_right = 1.0 28 | anchor_bottom = 1.0 29 | grow_horizontal = 2 30 | grow_vertical = 2 31 | theme_override_constants/margin_left = 40 32 | theme_override_constants/margin_top = 40 33 | theme_override_constants/margin_right = 40 34 | theme_override_constants/margin_bottom = 40 35 | 36 | [node name="VBoxContainer" type="VBoxContainer" parent="UI/MarginContainer"] 37 | layout_mode = 2 38 | size_flags_vertical = 4 39 | theme_override_constants/separation = 24 40 | 41 | [node name="Title" type="Label" parent="UI/MarginContainer/VBoxContainer"] 42 | layout_mode = 2 43 | size_flags_vertical = 0 44 | theme = ExtResource("2_4roab") 45 | text = "Reset password" 46 | horizontal_alignment = 1 47 | 48 | [node name="Code" type="TextEdit" parent="UI/MarginContainer/VBoxContainer"] 49 | unique_name_in_owner = true 50 | custom_minimum_size = Vector2(400, 40) 51 | layout_mode = 2 52 | size_flags_horizontal = 4 53 | theme = ExtResource("2_4roab") 54 | placeholder_text = "Code" 55 | 56 | [node name="NewPassword" type="TextEdit" parent="UI/MarginContainer/VBoxContainer"] 57 | unique_name_in_owner = true 58 | custom_minimum_size = Vector2(400, 40) 59 | layout_mode = 2 60 | size_flags_horizontal = 4 61 | theme = ExtResource("2_4roab") 62 | placeholder_text = "New password" 63 | 64 | [node name="Submit" type="Button" parent="UI/MarginContainer/VBoxContainer"] 65 | custom_minimum_size = Vector2(200, 40) 66 | layout_mode = 2 67 | size_flags_horizontal = 4 68 | theme = ExtResource("2_4roab") 69 | text = "Submit" 70 | 71 | [node name="Cancel" type="Button" parent="UI/MarginContainer/VBoxContainer"] 72 | custom_minimum_size = Vector2(200, 40) 73 | layout_mode = 2 74 | size_flags_horizontal = 4 75 | theme = ExtResource("2_4roab") 76 | text = "Cancel" 77 | 78 | [node name="ValidationLabel" type="Label" parent="UI/MarginContainer/VBoxContainer"] 79 | unique_name_in_owner = true 80 | custom_minimum_size = Vector2(400, 2.08165e-12) 81 | layout_mode = 2 82 | size_flags_horizontal = 4 83 | 84 | [connection signal="pressed" from="UI/MarginContainer/VBoxContainer/Submit" to="." method="_on_submit_pressed"] 85 | [connection signal="pressed" from="UI/MarginContainer/VBoxContainer/Cancel" to="." method="_on_cancel_pressed"] 86 | -------------------------------------------------------------------------------- /addons/talo/samples/authentication/states/verify.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://dv6o0q01wov2o"] 2 | 3 | [ext_resource type="Script" uid="uid://djje4dcxkijj3" path="res://addons/talo/samples/authentication/scripts/verify.gd" id="1_idjpk"] 4 | [ext_resource type="Theme" uid="uid://ce2uyi827vc5x" path="res://addons/talo/samples/authentication/assets/theme.tres" id="2_8uapl"] 5 | 6 | [node name="Verify" type="Node2D"] 7 | script = ExtResource("1_idjpk") 8 | 9 | [node name="UI" type="Control" parent="."] 10 | layout_mode = 3 11 | anchors_preset = 0 12 | offset_right = 1080.0 13 | offset_bottom = 720.0 14 | 15 | [node name="Background" type="ColorRect" parent="UI"] 16 | layout_mode = 1 17 | anchors_preset = 15 18 | anchor_right = 1.0 19 | anchor_bottom = 1.0 20 | grow_horizontal = 2 21 | grow_vertical = 2 22 | color = Color(0.121569, 0.160784, 0.215686, 1) 23 | 24 | [node name="MarginContainer" type="MarginContainer" parent="UI"] 25 | layout_mode = 1 26 | anchors_preset = 15 27 | anchor_right = 1.0 28 | anchor_bottom = 1.0 29 | grow_horizontal = 2 30 | grow_vertical = 2 31 | theme_override_constants/margin_left = 40 32 | theme_override_constants/margin_top = 40 33 | theme_override_constants/margin_right = 40 34 | theme_override_constants/margin_bottom = 40 35 | 36 | [node name="VBoxContainer" type="VBoxContainer" parent="UI/MarginContainer"] 37 | layout_mode = 2 38 | size_flags_vertical = 4 39 | theme_override_constants/separation = 24 40 | 41 | [node name="Title" type="Label" parent="UI/MarginContainer/VBoxContainer"] 42 | layout_mode = 2 43 | size_flags_vertical = 0 44 | theme = ExtResource("2_8uapl") 45 | text = "Verify" 46 | horizontal_alignment = 1 47 | 48 | [node name="Code" type="TextEdit" parent="UI/MarginContainer/VBoxContainer"] 49 | unique_name_in_owner = true 50 | custom_minimum_size = Vector2(400, 40) 51 | layout_mode = 2 52 | size_flags_horizontal = 4 53 | theme = ExtResource("2_8uapl") 54 | placeholder_text = "Verification code" 55 | 56 | [node name="Submit" type="Button" parent="UI/MarginContainer/VBoxContainer"] 57 | custom_minimum_size = Vector2(200, 40) 58 | layout_mode = 2 59 | size_flags_horizontal = 4 60 | theme = ExtResource("2_8uapl") 61 | text = "Submit" 62 | 63 | [node name="ValidationLabel" type="Label" parent="UI/MarginContainer/VBoxContainer"] 64 | unique_name_in_owner = true 65 | custom_minimum_size = Vector2(400, 2.08165e-12) 66 | layout_mode = 2 67 | size_flags_horizontal = 4 68 | 69 | [connection signal="pressed" from="UI/MarginContainer/VBoxContainer/Submit" to="." method="_on_submit_pressed"] 70 | -------------------------------------------------------------------------------- /addons/talo/samples/chat/chat.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://b6am7sdy8k7gu"] 2 | 3 | [ext_resource type="Script" uid="uid://cj4v4kri7ymn" path="res://addons/talo/samples/chat/scripts/chat.gd" id="1_pqhos"] 4 | 5 | [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_gim24"] 6 | content_margin_left = 5.0 7 | content_margin_top = 5.0 8 | content_margin_right = 5.0 9 | content_margin_bottom = 5.0 10 | 11 | [node name="Chat" type="Node2D"] 12 | script = ExtResource("1_pqhos") 13 | 14 | [node name="Control" type="Control" parent="."] 15 | layout_mode = 3 16 | anchors_preset = 0 17 | offset_right = 1080.0 18 | offset_bottom = 720.0 19 | 20 | [node name="ChannelListLabel" type="Label" parent="Control"] 21 | layout_mode = 0 22 | offset_left = 20.0 23 | offset_top = 20.0 24 | offset_right = 164.0 25 | offset_bottom = 43.0 26 | text = "Available channels" 27 | 28 | [node name="ChannelList" type="Panel" parent="Control"] 29 | layout_mode = 1 30 | anchors_preset = 9 31 | anchor_bottom = 1.0 32 | offset_left = 20.0 33 | offset_top = 60.0 34 | offset_right = 220.0 35 | offset_bottom = -80.0 36 | grow_vertical = 2 37 | 38 | [node name="ScrollContainer" type="ScrollContainer" parent="Control/ChannelList"] 39 | layout_mode = 1 40 | anchors_preset = 15 41 | anchor_right = 1.0 42 | anchor_bottom = 1.0 43 | grow_horizontal = 2 44 | grow_vertical = 2 45 | theme_override_styles/panel = SubResource("StyleBoxEmpty_gim24") 46 | horizontal_scroll_mode = 0 47 | 48 | [node name="Channels" type="VBoxContainer" parent="Control/ChannelList/ScrollContainer"] 49 | unique_name_in_owner = true 50 | layout_mode = 2 51 | size_flags_horizontal = 3 52 | 53 | [node name="ChatLabel" type="Label" parent="Control"] 54 | layout_mode = 0 55 | offset_left = 240.0 56 | offset_top = 20.0 57 | offset_right = 384.0 58 | offset_bottom = 43.0 59 | text = "Chat messages" 60 | 61 | [node name="Chat" type="Panel" parent="Control"] 62 | layout_mode = 1 63 | anchors_preset = 15 64 | anchor_right = 1.0 65 | anchor_bottom = 1.0 66 | offset_left = 240.0 67 | offset_top = 60.0 68 | offset_right = -20.0 69 | offset_bottom = -80.0 70 | grow_horizontal = 2 71 | grow_vertical = 2 72 | 73 | [node name="ScrollContainer" type="ScrollContainer" parent="Control/Chat"] 74 | layout_mode = 1 75 | anchors_preset = 15 76 | anchor_right = 1.0 77 | anchor_bottom = 1.0 78 | grow_horizontal = 2 79 | grow_vertical = 2 80 | theme_override_styles/panel = SubResource("StyleBoxEmpty_gim24") 81 | 82 | [node name="Messages" type="VBoxContainer" parent="Control/Chat/ScrollContainer"] 83 | unique_name_in_owner = true 84 | layout_mode = 2 85 | size_flags_horizontal = 3 86 | 87 | [node name="ChatMessage" type="LineEdit" parent="Control"] 88 | unique_name_in_owner = true 89 | layout_mode = 1 90 | anchors_preset = 3 91 | anchor_left = 1.0 92 | anchor_top = 1.0 93 | anchor_right = 1.0 94 | anchor_bottom = 1.0 95 | offset_left = -840.0 96 | offset_top = -60.0 97 | offset_right = -20.0 98 | offset_bottom = -20.0 99 | grow_horizontal = 0 100 | grow_vertical = 0 101 | placeholder_text = "Send a message" 102 | 103 | [node name="ChannelName" type="LineEdit" parent="Control"] 104 | unique_name_in_owner = true 105 | layout_mode = 1 106 | anchors_preset = 3 107 | anchor_left = 1.0 108 | anchor_top = 1.0 109 | anchor_right = 1.0 110 | anchor_bottom = 1.0 111 | offset_left = -1060.0 112 | offset_top = -60.0 113 | offset_right = -930.0 114 | offset_bottom = -20.0 115 | grow_horizontal = 0 116 | grow_vertical = 0 117 | placeholder_text = "Channel name" 118 | 119 | [node name="AddChannelButton" type="Button" parent="Control"] 120 | layout_mode = 1 121 | anchors_preset = 2 122 | anchor_top = 1.0 123 | anchor_bottom = 1.0 124 | offset_left = 160.0 125 | offset_top = -60.0 126 | offset_right = 220.0 127 | offset_bottom = -20.0 128 | grow_vertical = 0 129 | text = "Add" 130 | 131 | [connection signal="text_submitted" from="Control/ChatMessage" to="." method="_on_chat_message_text_submitted"] 132 | [connection signal="pressed" from="Control/AddChannelButton" to="." method="_on_add_channel_button_pressed"] 133 | -------------------------------------------------------------------------------- /addons/talo/samples/chat/scripts/chat.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @export var player_username := "" 4 | 5 | var _active_channel_id := -1 6 | var _subscriptions: Array[TaloChannel] = [] 7 | 8 | func _ready(): 9 | Talo.players.identified.connect(_on_identified) 10 | Talo.channels.message_received.connect(_on_message_received) 11 | Talo.player_presence.presence_changed.connect(_on_presence_changed) 12 | 13 | if player_username.is_empty(): 14 | _add_chat_message("[SYSTEM] No player_username set, please set one in the Chat root node") 15 | return 16 | 17 | Talo.players.identify("username", player_username) 18 | 19 | func _on_presence_changed(presence: TaloPlayerPresence, online_changed: bool, custom_status_changed: bool) -> void: 20 | if online_changed: 21 | _add_chat_message("[SYSTEM] %s is now %s" % [presence.player_alias.identifier, "online" if presence.online else "offline"]) 22 | 23 | func _on_identified(player: TaloPlayer) -> void: 24 | _subscriptions = await Talo.channels.get_subscribed_channels() 25 | 26 | var options := Talo.channels.GetChannelsOptions.new() 27 | options.page = 0 28 | var res := await Talo.channels.get_channels(options) 29 | 30 | assert(is_instance_valid(res)) 31 | var channels := res.channels 32 | 33 | _add_chat_message("[SYSTEM] Found %s channel%s" % [channels.size(), "" if channels.size() == 1 else "s"]) 34 | for channel in channels: 35 | _add_channel_label(channel.id, channel.name) 36 | 37 | func _on_message_received(channel: TaloChannel, player_alias: TaloPlayerAlias, message: String) -> void: 38 | if channel.id == _active_channel_id: 39 | _add_chat_message("[%s] %s: %s" % [channel.name, player_alias.identifier, message]) 40 | 41 | func _on_add_channel_button_pressed() -> void: 42 | if %ChannelName.text.is_empty(): 43 | return 44 | 45 | var options := Talo.channels.CreateChannelOptions.new() 46 | options.name = %ChannelName.text 47 | options.auto_cleanup = true 48 | 49 | var channel := await Talo.channels.create(options) 50 | if channel: 51 | _subscriptions.append(channel) 52 | _add_channel_label(channel.id, channel.name) 53 | %ChannelName.text = "" 54 | 55 | func _add_chat_message(message: String) -> void: 56 | var chat_message := Label.new() 57 | chat_message.text = message 58 | %Messages.add_child(chat_message) 59 | 60 | func _is_subscribed_to_channel(id: int) -> bool: 61 | return _subscriptions.map(func (channel): return channel.id).find(id) != -1 62 | 63 | func _add_channel_label(id: int, name: String) -> void: 64 | var button := Button.new() 65 | button.text = name 66 | button.pressed.connect(func (): _set_active_channel(id, name)) 67 | %Channels.add_child(button) 68 | 69 | func _set_active_channel(id: int, name: String) -> void: 70 | if _active_channel_id == id: 71 | return 72 | 73 | if !_is_subscribed_to_channel(id): 74 | var channel := await Talo.channels.join(id) 75 | _subscriptions.append(channel) 76 | _add_chat_message("[SYSTEM] Subscribed to channel %s" % name) 77 | 78 | _active_channel_id = id 79 | _add_chat_message("[SYSTEM] Switched to channel %s" % name) 80 | 81 | func _on_chat_message_text_submitted(new_text: String) -> void: 82 | if new_text.is_empty(): 83 | return 84 | 85 | if _active_channel_id == -1: 86 | _add_chat_message("[SYSTEM] No active channel, create one first") 87 | return 88 | 89 | await Talo.channels.send_message(_active_channel_id, new_text) 90 | %ChatMessage.text = "" 91 | -------------------------------------------------------------------------------- /addons/talo/samples/chat/scripts/chat.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cj4v4kri7ymn 2 | -------------------------------------------------------------------------------- /addons/talo/samples/leaderboards/entry.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://rrbrrh7ka5tq"] 2 | 3 | [ext_resource type="Theme" uid="uid://ce2uyi827vc5x" path="res://addons/talo/samples/authentication/assets/theme.tres" id="1_hjjod"] 4 | [ext_resource type="Script" uid="uid://c72ppt5dawi36" path="res://addons/talo/samples/leaderboards/scripts/leaderboard_entry.gd" id="2_hlvf3"] 5 | 6 | [node name="Entry" type="Label"] 7 | custom_minimum_size = Vector2(2.08165e-12, 32) 8 | theme = ExtResource("1_hjjod") 9 | theme_override_font_sizes/font_size = 20 10 | text = "{pos}. {username} scored: {score} points" 11 | script = ExtResource("2_hlvf3") 12 | -------------------------------------------------------------------------------- /addons/talo/samples/leaderboards/leaderboard.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://10aipp4jmgtd"] 2 | 3 | [ext_resource type="Script" uid="uid://b130i3slrurbc" path="res://addons/talo/samples/leaderboards/scripts/leaderboard.gd" id="1_ul1ks"] 4 | [ext_resource type="Theme" uid="uid://ce2uyi827vc5x" path="res://addons/talo/samples/authentication/assets/theme.tres" id="2_aw1ey"] 5 | 6 | [node name="Leaderboard" type="Node2D"] 7 | script = ExtResource("1_ul1ks") 8 | leaderboard_internal_name = "team-scores" 9 | 10 | [node name="UI" type="Control" parent="."] 11 | layout_mode = 3 12 | anchors_preset = 0 13 | offset_right = 1080.0 14 | offset_bottom = 720.0 15 | 16 | [node name="Background" type="ColorRect" parent="UI"] 17 | layout_mode = 1 18 | anchors_preset = 15 19 | anchor_right = 1.0 20 | anchor_bottom = 1.0 21 | grow_horizontal = 2 22 | grow_vertical = 2 23 | color = Color(0.121569, 0.160784, 0.215686, 1) 24 | 25 | [node name="MarginContainer" type="MarginContainer" parent="UI"] 26 | layout_mode = 1 27 | anchors_preset = 15 28 | anchor_right = 1.0 29 | anchor_bottom = 1.0 30 | grow_horizontal = 2 31 | grow_vertical = 2 32 | theme_override_constants/margin_left = 40 33 | theme_override_constants/margin_top = 40 34 | theme_override_constants/margin_right = 40 35 | theme_override_constants/margin_bottom = 40 36 | 37 | [node name="VBoxContainer" type="VBoxContainer" parent="UI/MarginContainer"] 38 | layout_mode = 2 39 | size_flags_vertical = 4 40 | theme_override_constants/separation = 24 41 | 42 | [node name="LeaderboardName" type="Label" parent="UI/MarginContainer/VBoxContainer"] 43 | unique_name_in_owner = true 44 | layout_mode = 2 45 | size_flags_vertical = 0 46 | theme = ExtResource("2_aw1ey") 47 | text = "{leaderboard} entries" 48 | horizontal_alignment = 1 49 | 50 | [node name="InfoLabel" type="Label" parent="UI/MarginContainer/VBoxContainer"] 51 | unique_name_in_owner = true 52 | custom_minimum_size = Vector2(400, 2.08165e-12) 53 | layout_mode = 2 54 | size_flags_horizontal = 4 55 | text = "Loading..." 56 | horizontal_alignment = 1 57 | 58 | [node name="ScrollContainer" type="ScrollContainer" parent="UI/MarginContainer/VBoxContainer"] 59 | custom_minimum_size = Vector2(2.08165e-12, 300) 60 | layout_mode = 2 61 | size_flags_vertical = 3 62 | horizontal_scroll_mode = 0 63 | 64 | [node name="Entries" type="VBoxContainer" parent="UI/MarginContainer/VBoxContainer/ScrollContainer"] 65 | unique_name_in_owner = true 66 | custom_minimum_size = Vector2(2.08165e-12, 32) 67 | layout_mode = 2 68 | size_flags_horizontal = 3 69 | alignment = 1 70 | 71 | [node name="Username" type="TextEdit" parent="UI/MarginContainer/VBoxContainer"] 72 | unique_name_in_owner = true 73 | custom_minimum_size = Vector2(400, 40) 74 | layout_mode = 2 75 | size_flags_horizontal = 4 76 | theme = ExtResource("2_aw1ey") 77 | placeholder_text = "Username" 78 | 79 | [node name="Submit" type="Button" parent="UI/MarginContainer/VBoxContainer"] 80 | custom_minimum_size = Vector2(200, 40) 81 | layout_mode = 2 82 | size_flags_horizontal = 4 83 | theme = ExtResource("2_aw1ey") 84 | text = "Add entry" 85 | 86 | [node name="Filter" type="Button" parent="UI/MarginContainer/VBoxContainer"] 87 | unique_name_in_owner = true 88 | custom_minimum_size = Vector2(200, 40) 89 | layout_mode = 2 90 | size_flags_horizontal = 4 91 | theme = ExtResource("2_aw1ey") 92 | text = "Blue team scores" 93 | 94 | [connection signal="pressed" from="UI/MarginContainer/VBoxContainer/Submit" to="." method="_on_submit_pressed"] 95 | [connection signal="pressed" from="UI/MarginContainer/VBoxContainer/Filter" to="." method="_on_filter_pressed"] 96 | -------------------------------------------------------------------------------- /addons/talo/samples/leaderboards/scripts/leaderboard.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | var entry_scene = preload("res://addons/talo/samples/leaderboards/entry.tscn") 4 | 5 | @export var leaderboard_internal_name: String 6 | @export var include_archived: bool 7 | 8 | @onready var leaderboard_name: Label = %LeaderboardName 9 | @onready var entries_container: VBoxContainer = %Entries 10 | @onready var info_label: Label = %InfoLabel 11 | @onready var username: TextEdit = %Username 12 | @onready var filter_button: Button = %Filter 13 | 14 | var _entries_error: bool 15 | var _filter: String = "All" 16 | var _filter_idx: int 17 | 18 | func _ready() -> void: 19 | leaderboard_name.text = leaderboard_name.text.replace("{leaderboard}", leaderboard_internal_name) 20 | await _load_entries() 21 | _set_entry_count() 22 | 23 | func _set_entry_count(): 24 | if entries_container.get_child_count() == 0: 25 | info_label.text = "No entries yet!" if not _entries_error else "Failed loading leaderboard %s. Does it exist?" % leaderboard_internal_name 26 | else: 27 | info_label.text = "%s entries" % entries_container.get_child_count() 28 | if _filter != "All": 29 | info_label.text += " (%s team)" % _filter 30 | 31 | func _create_entry(entry: TaloLeaderboardEntry) -> void: 32 | var entry_instance = entry_scene.instantiate() 33 | entry_instance.set_data(entry) 34 | entries_container.add_child(entry_instance) 35 | 36 | func _build_entries() -> void: 37 | for child in entries_container.get_children(): 38 | child.queue_free() 39 | 40 | var entries = Talo.leaderboards.get_cached_entries(leaderboard_internal_name) 41 | if _filter != "All": 42 | entries = entries.filter(func(entry: TaloLeaderboardEntry): return entry.get_prop("team", "") == _filter) 43 | 44 | for entry in entries: 45 | entry.position = entries.find(entry) 46 | _create_entry(entry) 47 | 48 | func _load_entries() -> void: 49 | var page := 0 50 | var done := false 51 | 52 | while !done: 53 | var options := Talo.leaderboards.GetEntriesOptions.new() 54 | options.page = page 55 | options.include_archived = include_archived 56 | 57 | var res := await Talo.leaderboards.get_entries(leaderboard_internal_name, options) 58 | 59 | if not is_instance_valid(res): 60 | _entries_error = true 61 | return 62 | 63 | var entries := res.entries 64 | var is_last_page := res.is_last_page 65 | 66 | if is_last_page: 67 | done = true 68 | else: 69 | page += 1 70 | 71 | _build_entries() 72 | 73 | func _on_submit_pressed() -> void: 74 | await Talo.players.identify("username", username.text) 75 | var score := RandomNumberGenerator.new().randi_range(0, 100) 76 | var team := "Blue" if RandomNumberGenerator.new().randi_range(0, 1) == 0 else "Red" 77 | 78 | var res := await Talo.leaderboards.add_entry(leaderboard_internal_name, score, {team = team}) 79 | assert(is_instance_valid(res)) 80 | info_label.text = "You scored %s points for the %s team!%s" % [score, team, " Your highscore was updated!" if res.updated else ""] 81 | 82 | _build_entries() 83 | 84 | func _get_next_filter(idx: int) -> String: 85 | return ["All", "Blue", "Red"][idx % 3] 86 | 87 | func _on_filter_pressed() -> void: 88 | _filter_idx += 1 89 | _filter = _get_next_filter(_filter_idx) 90 | 91 | info_label.text = "Filtering on %s" % filter_button.text.to_lower() 92 | filter_button.text = "%s team scores" % _get_next_filter(_filter_idx + 1) 93 | 94 | _build_entries() 95 | -------------------------------------------------------------------------------- /addons/talo/samples/leaderboards/scripts/leaderboard.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b130i3slrurbc 2 | -------------------------------------------------------------------------------- /addons/talo/samples/leaderboards/scripts/leaderboard_entry.gd: -------------------------------------------------------------------------------- 1 | extends Label 2 | 3 | func _set_pos(pos: int) -> void: 4 | text = text.replace("{pos}", str(pos + 1)) 5 | 6 | func _set_username(username: String) -> void: 7 | text = text.replace("{username}", username) 8 | 9 | func _set_score(score: int) -> void: 10 | text = text.replace("{score}", str(int(score))) 11 | 12 | func _set_team(team: String) -> void: 13 | if not team.is_empty(): 14 | text += " (%s team)" % team 15 | 16 | func set_data(entry: TaloLeaderboardEntry) -> void: 17 | _set_pos(entry.position) 18 | _set_username(entry.player_alias.identifier) 19 | _set_score(entry.score) 20 | _set_team(entry.get_prop("team", "")) 21 | 22 | if not entry.deleted_at.is_empty(): 23 | text += " (archived)" 24 | -------------------------------------------------------------------------------- /addons/talo/samples/leaderboards/scripts/leaderboard_entry.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c72ppt5dawi36 2 | -------------------------------------------------------------------------------- /addons/talo/samples/multiscene_saves/scenes/blue_zone.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://c4doqk5ectldu"] 2 | 3 | [ext_resource type="PackedScene" uid="uid://ci52f1pjsppv3" path="res://addons/talo/samples/multiscene_saves/scenes/star.tscn" id="1_h5eft"] 4 | [ext_resource type="PackedScene" uid="uid://b0jdoni6crl0s" path="res://addons/talo/samples/multiscene_saves/scenes/portal.tscn" id="2_yfx2i"] 5 | [ext_resource type="PackedScene" uid="uid://b3b4ek8meeflf" path="res://addons/talo/samples/multiscene_saves/scenes/player.tscn" id="3_v3fgw"] 6 | 7 | [node name="BlueZone" type="Node2D"] 8 | 9 | [node name="Map" type="Polygon2D" parent="."] 10 | color = Color(0.277352, 0.505327, 0.795826, 1) 11 | polygon = PackedVector2Array(86, -140, 240, -128, 430, -135, 488, 81, 227, 319, 8.73462, 225.248, -107, 285, -378, 217, -315, -2, -359, -186, -175, -303, 2, -263) 12 | 13 | [node name="Star1" parent="." instance=ExtResource("1_h5eft")] 14 | position = Vector2(350, -80) 15 | id = "blue_zone_star1" 16 | 17 | [node name="Star2" parent="." instance=ExtResource("1_h5eft")] 18 | position = Vector2(-250, -51) 19 | id = "blue_zone_star2" 20 | 21 | [node name="Star3" parent="." instance=ExtResource("1_h5eft")] 22 | position = Vector2(-222, 165) 23 | id = "blue_zone_star3" 24 | 25 | [node name="Star4" parent="." instance=ExtResource("1_h5eft")] 26 | position = Vector2(216, 245) 27 | id = "blue_zone_star4" 28 | 29 | [node name="Star5" parent="." instance=ExtResource("1_h5eft")] 30 | position = Vector2(221, 49) 31 | id = "blue_zone_star5" 32 | 33 | [node name="Star6" parent="." instance=ExtResource("1_h5eft")] 34 | position = Vector2(-99, -178) 35 | id = "blue_zone_star6" 36 | 37 | [node name="Portal" parent="." instance=ExtResource("2_yfx2i")] 38 | position = Vector2(-233, -237) 39 | rotation = 1.00356 40 | to_level = "green_zone" 41 | spawn_point = Vector2(225, 166) 42 | 43 | [node name="Player" parent="." instance=ExtResource("3_v3fgw")] 44 | position = Vector2(-192, -175) 45 | -------------------------------------------------------------------------------- /addons/talo/samples/multiscene_saves/scenes/green_zone.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://bucpvtjej40ju"] 2 | 3 | [ext_resource type="PackedScene" uid="uid://b3b4ek8meeflf" path="res://addons/talo/samples/multiscene_saves/scenes/player.tscn" id="1_mdugq"] 4 | [ext_resource type="PackedScene" uid="uid://ci52f1pjsppv3" path="res://addons/talo/samples/multiscene_saves/scenes/star.tscn" id="2_4im17"] 5 | [ext_resource type="PackedScene" uid="uid://b0jdoni6crl0s" path="res://addons/talo/samples/multiscene_saves/scenes/portal.tscn" id="3_vv7xr"] 6 | 7 | [node name="GreenZone" type="Node2D"] 8 | 9 | [node name="Map" type="Polygon2D" parent="."] 10 | color = Color(0.160784, 0.588235, 0.360784, 1) 11 | polygon = PackedVector2Array(100, -186, 347, -261, 416, -21, 364, 185, 227, 319, -235, 344, -395, 38, -314, -337, 0, -287) 12 | 13 | [node name="Star1" parent="." instance=ExtResource("2_4im17")] 14 | position = Vector2(76, 63) 15 | id = "green_zone_star1" 16 | 17 | [node name="Star2" parent="." instance=ExtResource("2_4im17")] 18 | position = Vector2(-147, 227) 19 | id = "green_zone_star2" 20 | 21 | [node name="Star3" parent="." instance=ExtResource("2_4im17")] 22 | position = Vector2(-130, -141) 23 | id = "green_zone_star3" 24 | 25 | [node name="Star4" parent="." instance=ExtResource("2_4im17")] 26 | position = Vector2(281, 61) 27 | id = "green_zone_star4" 28 | 29 | [node name="PortalBlueZone" parent="." instance=ExtResource("3_vv7xr")] 30 | position = Vector2(288, 225) 31 | rotation = 0.794125 32 | to_level = "blue_zone" 33 | spawn_point = Vector2(-192, -175) 34 | 35 | [node name="PortalStartingZone" parent="." instance=ExtResource("3_vv7xr")] 36 | position = Vector2(-329, -151) 37 | rotation = 0.214676 38 | to_level = "starting_zone" 39 | spawn_point = Vector2(68, -106) 40 | 41 | [node name="Player" parent="." instance=ExtResource("1_mdugq")] 42 | position = Vector2(-262, -136) 43 | -------------------------------------------------------------------------------- /addons/talo/samples/multiscene_saves/scenes/player.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=3 uid="uid://b3b4ek8meeflf"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://b3naludt1ank2" path="res://icon.png" id="1_rswej"] 4 | [ext_resource type="Script" uid="uid://pjwribovgraj" path="res://addons/talo/samples/multiscene_saves/scripts/loadable_player.gd" id="1_t5o6r"] 5 | [ext_resource type="Script" uid="uid://cbm1re05xae6k" path="res://addons/talo/samples/multiscene_saves/scripts/player_controller.gd" id="2_bg0gd"] 6 | 7 | [sub_resource type="CircleShape2D" id="CircleShape2D_mdugq"] 8 | radius = 450.0 9 | 10 | [node name="Player" type="CharacterBody2D"] 11 | scale = Vector2(0.05, 0.05) 12 | script = ExtResource("2_bg0gd") 13 | 14 | [node name="Loadable" type="Node2D" parent="."] 15 | script = ExtResource("1_t5o6r") 16 | username = "saves_demo_player" 17 | 18 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 19 | shape = SubResource("CircleShape2D_mdugq") 20 | 21 | [node name="Sprite2D" type="Sprite2D" parent="."] 22 | texture = ExtResource("1_rswej") 23 | 24 | [node name="Camera" type="Camera2D" parent="."] 25 | unique_name_in_owner = true 26 | zoom = Vector2(1.5, 1.5) 27 | 28 | [node name="Stars" type="Label" parent="."] 29 | unique_name_in_owner = true 30 | anchors_preset = 7 31 | anchor_left = 0.5 32 | anchor_top = 1.0 33 | anchor_right = 0.5 34 | anchor_bottom = 1.0 35 | offset_left = -1000.0 36 | offset_top = 464.0 37 | offset_right = 1000.0 38 | offset_bottom = 1010.0 39 | grow_horizontal = 2 40 | grow_vertical = 0 41 | theme_override_font_sizes/font_size = 350 42 | text = "0 stars" 43 | horizontal_alignment = 1 44 | -------------------------------------------------------------------------------- /addons/talo/samples/multiscene_saves/scenes/portal.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://b0jdoni6crl0s"] 2 | 3 | [ext_resource type="Script" uid="uid://bnkfuweqaugsr" path="res://addons/talo/samples/multiscene_saves/scripts/portal.gd" id="1_dmas5"] 4 | 5 | [sub_resource type="CanvasTexture" id="CanvasTexture_mdugq"] 6 | 7 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_4im17"] 8 | size = Vector2(45, 45) 9 | 10 | [node name="Portal" type="Area2D"] 11 | script = ExtResource("1_dmas5") 12 | 13 | [node name="Sprite2D" type="Sprite2D" parent="."] 14 | modulate = Color(0.299775, 0.228743, 0.432167, 1) 15 | scale = Vector2(50, 50) 16 | texture = SubResource("CanvasTexture_mdugq") 17 | 18 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 19 | shape = SubResource("RectangleShape2D_4im17") 20 | 21 | [connection signal="body_entered" from="." to="." method="_on_body_entered"] 22 | -------------------------------------------------------------------------------- /addons/talo/samples/multiscene_saves/scenes/star.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://ci52f1pjsppv3"] 2 | 3 | [ext_resource type="Script" uid="uid://bwicim7dlamat" path="res://addons/talo/samples/multiscene_saves/scripts/loadable_star.gd" id="1_vjn8d"] 4 | 5 | [sub_resource type="CircleShape2D" id="CircleShape2D_c1tps"] 6 | radius = 12.0 7 | 8 | [sub_resource type="CanvasTexture" id="CanvasTexture_blg0n"] 9 | 10 | [node name="Star" type="Node2D"] 11 | script = ExtResource("1_vjn8d") 12 | 13 | [node name="Area2D" type="Area2D" parent="."] 14 | 15 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"] 16 | shape = SubResource("CircleShape2D_c1tps") 17 | 18 | [node name="Sprite2D" type="Sprite2D" parent="Area2D"] 19 | modulate = Color(1, 0.797, 0.42, 1) 20 | scale = Vector2(20, 20) 21 | texture = SubResource("CanvasTexture_blg0n") 22 | 23 | [node name="Inner" type="Sprite2D" parent="Area2D/Sprite2D"] 24 | rotation = 0.785398 25 | texture = SubResource("CanvasTexture_blg0n") 26 | 27 | [connection signal="body_entered" from="Area2D" to="." method="_on_area_2d_body_entered"] 28 | -------------------------------------------------------------------------------- /addons/talo/samples/multiscene_saves/scripts/loadable_player.gd: -------------------------------------------------------------------------------- 1 | extends TaloLoadable 2 | 3 | @export var username = "generate_identifier" 4 | 5 | @onready var character_body: CharacterBody2D = get_parent() 6 | 7 | var stars := 0 8 | var spawn_level := "starting_zone" 9 | var spawn_point := Vector2.ZERO 10 | 11 | @onready var levels: Dictionary[String, Resource] = { 12 | "starting_zone" = load("res://addons/talo/samples/multiscene_saves/starting_zone.tscn"), 13 | "green_zone" = load("res://addons/talo/samples/multiscene_saves/scenes/green_zone.tscn"), 14 | "blue_zone" = load("res://addons/talo/samples/multiscene_saves/scenes/blue_zone.tscn") 15 | } 16 | 17 | func _toggle_scene_visibility(visible: bool): 18 | character_body.get_parent().visible = visible 19 | 20 | func _ready(): 21 | _toggle_scene_visibility(false) 22 | %Stars.visible = false 23 | 24 | Talo.players.identified.connect(_on_identified) 25 | Talo.players.identify("username", username) 26 | 27 | id = username 28 | # register the loadable 29 | super() 30 | 31 | func _on_identified(_player: TaloPlayer): 32 | var saves := await Talo.saves.get_saves() 33 | if saves.is_empty(): 34 | # the save is automatically chosen when its created 35 | await Talo.saves.create_save("save") 36 | else: 37 | await Talo.saves.choose_save(Talo.saves.all.front()) 38 | 39 | func register_fields(): 40 | register_field("stars", stars) 41 | register_field("spawn_point", spawn_point) 42 | register_field("spawn_level", spawn_level) 43 | 44 | func on_loaded(data: Dictionary): 45 | update_stars(data.get("stars", 0)) 46 | 47 | spawn_point = data.get("spawn_point", Vector2.ZERO) 48 | character_body.position = spawn_point 49 | character_body.destination = spawn_point 50 | 51 | spawn_level = data.get("spawn_level", "starting_zone") 52 | if get_tree().current_scene.scene_file_path.ends_with("%s.tscn" % spawn_level): 53 | # the correct level is loaded so we won't be changing scenes 54 | _toggle_scene_visibility(true) 55 | else: 56 | # change to the correct level scene 57 | call_deferred("change_scene", spawn_level) 58 | 59 | func on_star_pickup(): 60 | update_stars(stars + 1) 61 | spawn_point = character_body.position 62 | await Talo.saves.update_current_save() 63 | 64 | func update_stars(new_stars: int): 65 | stars = new_stars 66 | if stars > 0: 67 | %Stars.visible = true 68 | %Stars.text = "%s star%s" % [stars, "s" if stars > 1 else ""] 69 | 70 | func on_portal_entered(new_level: String, new_spawn_point: Vector2): 71 | spawn_level = new_level 72 | spawn_point = new_spawn_point 73 | await Talo.saves.update_current_save() 74 | call_deferred("change_scene", new_level) 75 | 76 | func change_scene(to_level: String): 77 | get_tree().change_scene_to_packed(levels[to_level]) 78 | -------------------------------------------------------------------------------- /addons/talo/samples/multiscene_saves/scripts/loadable_player.gd.uid: -------------------------------------------------------------------------------- 1 | uid://pjwribovgraj 2 | -------------------------------------------------------------------------------- /addons/talo/samples/multiscene_saves/scripts/loadable_star.gd: -------------------------------------------------------------------------------- 1 | extends TaloLoadable 2 | 3 | func on_loaded(data: Dictionary): 4 | handle_destroyed(data) 5 | 6 | func _on_area_2d_body_entered(body :Node2D) -> void: 7 | queue_free() 8 | body.find_child("Loadable").on_star_pickup() 9 | -------------------------------------------------------------------------------- /addons/talo/samples/multiscene_saves/scripts/loadable_star.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bwicim7dlamat 2 | -------------------------------------------------------------------------------- /addons/talo/samples/multiscene_saves/scripts/player_controller.gd: -------------------------------------------------------------------------------- 1 | extends CharacterBody2D 2 | 3 | var destination := position 4 | 5 | func _unhandled_input(event: InputEvent) -> void: 6 | if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT: 7 | destination = get_global_mouse_position() 8 | 9 | func _physics_process(delta: float) -> void: 10 | velocity = position.direction_to(destination) * 200.0 11 | move_and_slide() 12 | 13 | if position.distance_to(destination) <= 2: 14 | destination = position 15 | -------------------------------------------------------------------------------- /addons/talo/samples/multiscene_saves/scripts/player_controller.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cbm1re05xae6k 2 | -------------------------------------------------------------------------------- /addons/talo/samples/multiscene_saves/scripts/portal.gd: -------------------------------------------------------------------------------- 1 | extends Area2D 2 | 3 | @export var to_level := "" 4 | @export var spawn_point := Vector2.ZERO 5 | 6 | func _on_body_entered(body: Node2D) -> void: 7 | body.find_child("Loadable").on_portal_entered(to_level, spawn_point) 8 | -------------------------------------------------------------------------------- /addons/talo/samples/multiscene_saves/scripts/portal.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bnkfuweqaugsr 2 | -------------------------------------------------------------------------------- /addons/talo/samples/multiscene_saves/starting_zone.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://4ffkl87wxptv"] 2 | 3 | [ext_resource type="PackedScene" uid="uid://b0jdoni6crl0s" path="res://addons/talo/samples/multiscene_saves/scenes/portal.tscn" id="1_olj8c"] 4 | [ext_resource type="PackedScene" uid="uid://b3b4ek8meeflf" path="res://addons/talo/samples/multiscene_saves/scenes/player.tscn" id="2_r5twb"] 5 | 6 | [node name="StartingZone" type="Node2D"] 7 | 8 | [node name="Map" type="Polygon2D" parent="."] 9 | color = Color(0.937255, 0.666667, 0.466667, 1) 10 | polygon = PackedVector2Array(269, -108, 284, 25, 218, 162, 38, 236, -173, 212, -300, 14, -196, -165, 7, -244) 11 | 12 | [node name="Portal" parent="." instance=ExtResource("1_olj8c")] 13 | position = Vector2(103, -166) 14 | rotation = 0.47822 15 | to_level = "green_zone" 16 | spawn_point = Vector2(-262, -136) 17 | 18 | [node name="ClickToMove" type="Label" parent="."] 19 | offset_left = -65.0 20 | offset_top = 50.0 21 | offset_right = 65.0 22 | offset_bottom = 78.0 23 | theme_override_font_sizes/font_size = 20 24 | text = "Click to move" 25 | 26 | [node name="VisitThePortal" type="Label" parent="."] 27 | offset_left = 66.0 28 | offset_top = -252.0 29 | offset_right = 209.0 30 | offset_bottom = -224.0 31 | rotation = 0.47822 32 | theme_override_font_sizes/font_size = 20 33 | text = "Visit the portal" 34 | 35 | [node name="Player" parent="." instance=ExtResource("2_r5twb")] 36 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/add_entry_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | @export var leaderboard_name: String 4 | 5 | func _on_pressed() -> void: 6 | if Talo.identity_check() != OK: 7 | %ResponseLabel.text = "You need to identify a player first!" 8 | return 9 | 10 | if leaderboard_name.is_empty(): 11 | %ResponseLabel.text = "leaderboard_name not set on AddEntryButton" 12 | return 13 | 14 | var score := RandomNumberGenerator.new().randi_range(1, 50) 15 | var res := await Talo.leaderboards.add_entry(leaderboard_name, score) 16 | 17 | if is_instance_valid(res): 18 | %ResponseLabel.text = "Added score: %s, new high score: %s" % [score, res.updated] 19 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/add_entry_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://ckl7tmtxca4d7 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/create_save_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | func _on_pressed() -> void: 4 | var name = "Save %s version 1" % [TaloTimeUtils.get_current_datetime_string()] 5 | await Talo.saves.create_save(name) 6 | %ResponseLabel.text = "Created save: %s" % name 7 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/create_save_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dopfk835jjbls 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/delete_prop_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | @export var prop_name: String 4 | 5 | func _on_pressed() -> void: 6 | if Talo.identity_check() != OK: 7 | %ResponseLabel.text = "You need to identify a player first!" 8 | return 9 | 10 | if prop_name.is_empty(): 11 | %ResponseLabel.text = "prop_name not set on DeletePropButton" 12 | return 13 | 14 | Talo.current_player.delete_prop(prop_name) 15 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/delete_prop_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bxtgq6w8bk1g2 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/delete_save_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | func _on_pressed() -> void: 4 | if not Talo.saves.current: 5 | %ResponseLabel.text = "No save currently loaded" 6 | return 7 | 8 | var name = Talo.saves.current.name 9 | 10 | await Talo.saves.delete_save(Talo.saves.current) 11 | %ResponseLabel.text = "Deleted save: %s" % name 12 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/delete_save_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://qrvgsfbol2pi 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/flush_events_button.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | func _on_pressed() -> void: 4 | if Talo.identity_check() != OK: 5 | %ResponseLabel.text = "You need to identify a player first!" 6 | return 7 | 8 | await Talo.events.flush() 9 | %ResponseLabel.text = "Events flushed" 10 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/flush_events_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://0aituqdch37e 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/get_all_stats_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | func _on_pressed() -> void: 4 | var res := await Talo.stats.get_stats() 5 | var all_stats := PackedStringArray(res.map(func(item: TaloStat): return item.internal_name)) 6 | var internal_names := ", ".join(all_stats) if all_stats.size() > 0 else "no stats" 7 | %ResponseLabel.text = "Stats: %s" % [internal_names] 8 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/get_all_stats_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://wefkgexhdmqa 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/get_categories_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | func _on_pressed() -> void: 4 | var categories := await Talo.feedback.get_categories() 5 | 6 | if categories.size() == 0: 7 | %ResponseLabel.text = "No categories found. Create some in the Talo dashboard!" 8 | else: 9 | var mapped := categories.map(func (c): return "%s (%s)" % [c.name, c.internal_name]) 10 | %ResponseLabel.text = "Categories: " + ", ".join(mapped) 11 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/get_categories_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dv0oalue8qe6 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/get_config_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | func _ready() -> void: 4 | Talo.game_config.live_config_loaded.connect(_on_live_config_loaded) 5 | Talo.game_config.live_config_updated.connect(_on_live_config_updated) 6 | 7 | func _on_pressed() -> void: 8 | Talo.game_config.get_live_config() 9 | 10 | func _stringify_props(props: Array[TaloProp]) -> String: 11 | return JSON.stringify(props.map(func (prop: TaloProp): return prop.to_dictionary())) 12 | 13 | func _on_live_config_loaded(config: TaloLiveConfig): 14 | %ResponseLabel.text = "Live config loaded: %s" % _stringify_props(config.props) 15 | 16 | func _on_live_config_updated(live_config: TaloLiveConfig) -> void: 17 | %ResponseLabel.text = "Live config updated: %s" % _stringify_props(live_config.props) 18 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/get_config_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dlo0wibtjxpyb 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/get_entries_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | @export var leaderboard_name: String 4 | 5 | func _on_pressed() -> void: 6 | if leaderboard_name.is_empty(): 7 | %ResponseLabel.text = "leaderboard_name not set on GetEntriesButton" 8 | return 9 | 10 | var options := Talo.leaderboards.GetEntriesOptions.new() 11 | options.page = 0 12 | var res := await Talo.leaderboards.get_entries(leaderboard_name, options) 13 | 14 | if is_instance_valid(res): 15 | var entries := res.entries 16 | %ResponseLabel.text = "Received %s entries" % entries.size() 17 | else: 18 | %ResponseLabel.text = "No entries found for %s" % leaderboard_name 19 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/get_entries_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://frjbck0h2sou 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/get_global_history_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | @export var stat_name: String 4 | @export var player_id: String 5 | 6 | func _on_pressed() -> void: 7 | if stat_name.is_empty(): 8 | %ResponseLabel.text = "stat_name not set on GetGlobalHistoryButton" 9 | return 10 | 11 | var res := await Talo.stats.get_global_history(stat_name, 0, player_id) 12 | if res: 13 | var global_metrics := res.global_value 14 | var player_metrics := res.player_value 15 | 16 | %ResponseLabel.text = "Min: %s, max: %s, median: %s, average: %s, average change: %s, average player value: %s" % [ 17 | global_metrics.min_value, 18 | global_metrics.max_value, 19 | global_metrics.median_value, 20 | global_metrics.average_value, 21 | global_metrics.average_change, 22 | player_metrics.average_value 23 | ] 24 | else: 25 | %ResponseLabel.text = "Could not fetch global history, is your stat global?" 26 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/get_global_history_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://d2xemrkvvbehq 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/get_group_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | @export var group_id: String 4 | 5 | func _on_pressed() -> void: 6 | if group_id.is_empty(): 7 | %ResponseLabel.text = "group_id not set on GetGroupButton" 8 | return 9 | 10 | var group := await Talo.player_groups.get_group(group_id) 11 | if group != null: 12 | %ResponseLabel.text = "%s has %s player(s)" % [group.name, group.count] 13 | else: 14 | %ResponseLabel.text = "Group %s not found" % group_id 15 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/get_group_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cb1clc2ajdd08 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/get_saves_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | func _on_pressed() -> void: 4 | var saves = await Talo.saves.get_saves() 5 | %ResponseLabel.text = "Found %s saves" % saves.size() 6 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/get_saves_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://djmqsknngd77i 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/get_stat_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | @export var stat_name: String 4 | 5 | func _on_pressed() -> void: 6 | if stat_name.is_empty(): 7 | %ResponseLabel.text = "stat_name not set on GetStatHistoryButton" 8 | return 9 | 10 | var res := await Talo.stats.find(stat_name) 11 | 12 | %ResponseLabel.text = "%s is%s a global stat, with a default value of %s" % [res.name, "" if res.global else " not", res.default_value] 13 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/get_stat_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b8h2k0eilha8q 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/get_stat_history_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | @export var stat_name: String 4 | 5 | func _on_pressed() -> void: 6 | if Talo.identity_check() != OK: 7 | %ResponseLabel.text = "You need to identify a player first!" 8 | return 9 | 10 | if stat_name.is_empty(): 11 | %ResponseLabel.text = "stat_name not set on GetStatHistoryButton" 12 | return 13 | 14 | var res := await Talo.stats.get_history(stat_name) 15 | var changes := PackedStringArray(res.history.map(func(item): return str(item.change))) 16 | var change_string := ", ".join(changes) if changes.size() > 0 else "no changes" 17 | 18 | %ResponseLabel.text = "%s changed by: %s" % [stat_name, change_string] 19 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/get_stat_history_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://0ii4skasvnun 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/identified_label.gd: -------------------------------------------------------------------------------- 1 | extends Label 2 | 3 | func _ready() -> void: 4 | Talo.players.identified.connect(_on_identified) 5 | 6 | func _on_identified(_player: TaloPlayer) -> void: 7 | text = "Played identified" 8 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/identified_label.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b4wo31voksqh3 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/identified_state.gd: -------------------------------------------------------------------------------- 1 | extends ColorRect 2 | 3 | func _ready() -> void: 4 | Talo.players.identified.connect(_on_identified) 5 | 6 | func _on_identified(_player: TaloPlayer) -> void: 7 | color = Color(120.0 / 255.0, 230.0 / 255.0, 160.0 / 255.0) 8 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/identified_state.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b8sqvkjr862hc 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/identify_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | @export var service: String = "username" 4 | @export var identifier: String 5 | 6 | func _on_pressed(): 7 | if service.is_empty() or identifier.is_empty(): 8 | %ResponseLabel.text = "service or identifier not set on IdentifyButton" 9 | return 10 | 11 | Talo.players.identify(service, identifier) 12 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/identify_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://caukqabpein8y 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/load_save_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | func _on_pressed() -> void: 4 | var saves := Talo.saves.all 5 | 6 | if saves.is_empty(): 7 | %ResponseLabel.text = "No saves found, fetching..." 8 | saves = await Talo.saves.get_saves() 9 | 10 | if saves.is_empty(): 11 | push_warning("No saves to load") 12 | %ResponseLabel.text = "No saves to load" 13 | return 14 | 15 | await Talo.saves.choose_save(Talo.saves.latest) 16 | %ResponseLabel.text = "Loaded %s" % Talo.saves.latest.name 17 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/load_save_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://e7o24oda43k5 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/loadable_color_rect.gd: -------------------------------------------------------------------------------- 1 | class_name LoadableColorRect extends TaloLoadable 2 | 3 | var color_rect: ColorRect 4 | 5 | func _ready() -> void: 6 | color_rect = get_child(1) 7 | super() 8 | 9 | func register_fields() -> void: 10 | register_field("r", color_rect.color.r) 11 | register_field("g", color_rect.color.g) 12 | register_field("b", color_rect.color.b) 13 | 14 | func on_loaded(data: Dictionary) -> void: 15 | color_rect.color = Color(data["r"], data["g"], data["b"]) 16 | 17 | func randomise() -> void: 18 | color_rect.color = Color(randf(), randf(), randf()) 19 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/loadable_color_rect.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dwt5hlptjs8i3 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/open_docs_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | func _on_pressed() -> void: 4 | OS.shell_open("https://docs.trytalo.com/docs/godot/install?utm_source=godot-playground") 5 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/open_docs_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://kr81pchfe6rk 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/randomise_save_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | @export var grid: GridContainer 4 | 5 | func _on_pressed() -> void: 6 | for child: LoadableColorRect in grid.get_children(): 7 | child.randomise() 8 | 9 | %ResponseLabel.text = "Colours randomised!" 10 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/randomise_save_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://jlqywlqta7x2 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/response_label.gd: -------------------------------------------------------------------------------- 1 | extends Label 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/response_label.gd.uid: -------------------------------------------------------------------------------- 1 | uid://di22ttcji0mvs 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/send_feedback_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | @export var category_name: String 4 | @export var feedback_comment: String = "There is a bug in the game somewhere, go find it" 5 | 6 | func _on_pressed() -> void: 7 | if Talo.identity_check() != OK: 8 | %ResponseLabel.text = "You need to identify a player first!" 9 | return 10 | 11 | if category_name.is_empty() or feedback_comment.is_empty(): 12 | %ResponseLabel.text = "category_name or feedback_comment not set on SendFeedbackButton" 13 | return 14 | 15 | await Talo.feedback.send(category_name, feedback_comment) 16 | %ResponseLabel.text = "Feedback sent for %s: %s" % [category_name, feedback_comment] 17 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/send_feedback_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cm1bgc6hi7ah5 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/set_prop_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | @export var prop_name: String 4 | @export var prop_value: String 5 | 6 | func _on_pressed() -> void: 7 | if Talo.identity_check() != OK: 8 | %ResponseLabel.text = "You need to identify a player first!" 9 | return 10 | 11 | if prop_name.is_empty() or prop_value.is_empty(): 12 | %ResponseLabel.text = "prop_name or prop_value not set on SetPropButton" 13 | return 14 | 15 | Talo.current_player.set_prop(prop_name, prop_value) 16 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/set_prop_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://w0ed6vrr44bi 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/toggle_continuity_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | func _ready() -> void: 4 | _set_text(_get_value()) 5 | 6 | func _get_value(): 7 | return Talo.settings.get_value("continuity", "enabled", true) 8 | 9 | func _set_text(enabled: bool): 10 | text = "Toggle off" if enabled else "Toggle on" 11 | 12 | func _on_pressed() -> void: 13 | var enabled = _get_value() 14 | 15 | Talo.settings.set_value( 16 | "continuity", 17 | "enabled", 18 | not enabled 19 | ) 20 | _set_text(not enabled) 21 | %ResponseLabel.text = "Continuity is now " + ("enabled" if not enabled else "disabled") 22 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/toggle_continuity_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b4f4adbsitp4 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/toggle_network_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | func _ready() -> void: 4 | _set_text(Talo.offline_mode_enabled()) 5 | 6 | func _set_text(offline: bool): 7 | text = "Go online" if offline else "Go offline" 8 | 9 | func _on_pressed() -> void: 10 | var offline := Talo.offline_mode_enabled() 11 | 12 | Talo.settings.set_value( 13 | "debug", 14 | "offline_mode", 15 | not offline 16 | ) 17 | _set_text(not offline) 18 | %ResponseLabel.text = "You are now " + ("offline" if not offline else "online") 19 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/toggle_network_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://jvg0tbcowo57 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/track_event_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | @export var event_name: String 4 | @export var event_props: Dictionary = { prop1 = "value1" } 5 | @export var flush_immediately: bool 6 | 7 | func _on_pressed() -> void: 8 | if Talo.identity_check() != OK: 9 | %ResponseLabel.text = "You need to identify a player first!" 10 | return 11 | 12 | if event_name.is_empty(): 13 | %ResponseLabel.text = "event_name not set on TrackEventButton" 14 | return 15 | 16 | Talo.events.track(event_name, event_props) 17 | if flush_immediately: 18 | await Talo.events.flush() 19 | %ResponseLabel.text = "Event flushed: %s %s" % [event_name, event_props] 20 | else: 21 | %ResponseLabel.text = "Event queued: %s %s" % [event_name, event_props] 22 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/track_event_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://14qix2gkp2a8 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/track_stat_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | @export var stat_name: String 4 | 5 | func _on_pressed() -> void: 6 | if Talo.identity_check() != OK: 7 | %ResponseLabel.text = "You need to identify a player first!" 8 | return 9 | 10 | if stat_name.is_empty(): 11 | %ResponseLabel.text = "stat_name not set on TrackStatButton" 12 | return 13 | 14 | var res := await Talo.stats.track(stat_name) 15 | %ResponseLabel.text = "Stat incremented: %s, new value is %s" % [stat_name, res.value] 16 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/track_stat_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://drtclyykhc1m4 2 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/update_save_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | func _on_pressed() -> void: 4 | if not Talo.saves.current: 5 | %ResponseLabel.text = "No save currently loaded" 6 | return 7 | 8 | var version := 0 9 | 10 | var regex := RegEx.new() 11 | regex.compile("version\\s+(\\d+)") 12 | 13 | var result := regex.search(Talo.saves.current.name) 14 | if result: 15 | version = int(result.get_string(1)) 16 | 17 | var new_name := Talo.saves.current.name.replace("version %s" % version, "version %s" % (version + 1)) 18 | 19 | await Talo.saves.update_current_save(new_name) 20 | %ResponseLabel.text = "Updated save, new name is: %s" % new_name 21 | -------------------------------------------------------------------------------- /addons/talo/samples/playground/scripts/update_save_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dujbepgthemyv 2 | -------------------------------------------------------------------------------- /addons/talo/samples/stateful_buttons/loadable_button.gd: -------------------------------------------------------------------------------- 1 | class_name LoadableButton extends TaloLoadable 2 | 3 | var button: Button 4 | var clicks: int 5 | 6 | func _ready() -> void: 7 | super() 8 | button = get_child(0) 9 | 10 | func register_fields() -> void: 11 | register_field("clicks", clicks) 12 | 13 | func _set_button_text() -> void: 14 | button.text = "%s clicks" % clicks 15 | 16 | func on_loaded(data: Dictionary) -> void: 17 | clicks = data["clicks"] 18 | _set_button_text() 19 | 20 | func _on_button_pressed() -> void: 21 | clicks += 1 22 | _set_button_text() 23 | await Talo.saves.update_current_save() 24 | -------------------------------------------------------------------------------- /addons/talo/samples/stateful_buttons/loadable_button.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c1gm0arpfk0eb 2 | -------------------------------------------------------------------------------- /addons/talo/samples/stateful_buttons/stateful_buttons.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://cbmprpim3xigq"] 2 | 3 | [ext_resource type="Script" uid="uid://ceb1i0adxqrb8" path="res://addons/talo/samples/stateful_buttons/stateful_buttons_manager.gd" id="1_0jiiv"] 4 | [ext_resource type="Script" uid="uid://c1gm0arpfk0eb" path="res://addons/talo/samples/stateful_buttons/loadable_button.gd" id="2_ge7ge"] 5 | 6 | [node name="StatefulButtons" type="Node2D"] 7 | script = ExtResource("1_0jiiv") 8 | 9 | [node name="Control" type="Control" parent="."] 10 | layout_mode = 3 11 | anchors_preset = 8 12 | anchor_left = 0.5 13 | anchor_top = 0.5 14 | anchor_right = 0.5 15 | anchor_bottom = 0.5 16 | offset_right = 1080.0 17 | offset_bottom = 720.0 18 | grow_horizontal = 2 19 | grow_vertical = 2 20 | 21 | [node name="GridContainer" type="GridContainer" parent="Control"] 22 | layout_mode = 1 23 | anchors_preset = 8 24 | anchor_left = 0.5 25 | anchor_top = 0.5 26 | anchor_right = 0.5 27 | anchor_bottom = 0.5 28 | offset_left = -116.0 29 | offset_top = -66.5 30 | offset_right = 116.0 31 | offset_bottom = 66.5 32 | grow_horizontal = 2 33 | grow_vertical = 2 34 | theme_override_constants/h_separation = 20 35 | theme_override_constants/v_separation = 20 36 | columns = 3 37 | 38 | [node name="LoadableButton" type="Control" parent="Control/GridContainer"] 39 | custom_minimum_size = Vector2(65, 32) 40 | layout_mode = 2 41 | script = ExtResource("2_ge7ge") 42 | id = "Button1" 43 | 44 | [node name="Button" type="Button" parent="Control/GridContainer/LoadableButton"] 45 | layout_mode = 0 46 | offset_right = 8.0 47 | offset_bottom = 8.0 48 | text = "0 clicks" 49 | 50 | [node name="LoadableButton2" type="Control" parent="Control/GridContainer"] 51 | custom_minimum_size = Vector2(65, 32) 52 | layout_mode = 2 53 | script = ExtResource("2_ge7ge") 54 | id = "Button2" 55 | 56 | [node name="Button" type="Button" parent="Control/GridContainer/LoadableButton2"] 57 | layout_mode = 0 58 | offset_right = 8.0 59 | offset_bottom = 8.0 60 | text = "0 clicks" 61 | 62 | [node name="LoadableButton3" type="Control" parent="Control/GridContainer"] 63 | custom_minimum_size = Vector2(65, 32) 64 | layout_mode = 2 65 | script = ExtResource("2_ge7ge") 66 | id = "Button3" 67 | 68 | [node name="Button" type="Button" parent="Control/GridContainer/LoadableButton3"] 69 | layout_mode = 0 70 | offset_right = 8.0 71 | offset_bottom = 8.0 72 | text = "0 clicks" 73 | 74 | [node name="LoadableButton4" type="Control" parent="Control/GridContainer"] 75 | custom_minimum_size = Vector2(65, 32) 76 | layout_mode = 2 77 | script = ExtResource("2_ge7ge") 78 | id = "Button4" 79 | 80 | [node name="Button" type="Button" parent="Control/GridContainer/LoadableButton4"] 81 | layout_mode = 0 82 | offset_right = 8.0 83 | offset_bottom = 8.0 84 | text = "0 clicks" 85 | 86 | [node name="LoadableButton5" type="Control" parent="Control/GridContainer"] 87 | custom_minimum_size = Vector2(65, 32) 88 | layout_mode = 2 89 | script = ExtResource("2_ge7ge") 90 | id = "Button5" 91 | 92 | [node name="Button" type="Button" parent="Control/GridContainer/LoadableButton5"] 93 | layout_mode = 0 94 | offset_right = 8.0 95 | offset_bottom = 8.0 96 | text = "0 clicks" 97 | 98 | [node name="LoadableButton6" type="Control" parent="Control/GridContainer"] 99 | custom_minimum_size = Vector2(65, 32) 100 | layout_mode = 2 101 | script = ExtResource("2_ge7ge") 102 | id = "Button6" 103 | 104 | [node name="Button" type="Button" parent="Control/GridContainer/LoadableButton6"] 105 | layout_mode = 0 106 | offset_right = 8.0 107 | offset_bottom = 8.0 108 | text = "0 clicks" 109 | 110 | [connection signal="pressed" from="Control/GridContainer/LoadableButton/Button" to="Control/GridContainer/LoadableButton" method="_on_button_pressed"] 111 | [connection signal="pressed" from="Control/GridContainer/LoadableButton2/Button" to="Control/GridContainer/LoadableButton2" method="_on_button_pressed"] 112 | [connection signal="pressed" from="Control/GridContainer/LoadableButton3/Button" to="Control/GridContainer/LoadableButton3" method="_on_button_pressed"] 113 | [connection signal="pressed" from="Control/GridContainer/LoadableButton4/Button" to="Control/GridContainer/LoadableButton4" method="_on_button_pressed"] 114 | [connection signal="pressed" from="Control/GridContainer/LoadableButton5/Button" to="Control/GridContainer/LoadableButton5" method="_on_button_pressed"] 115 | [connection signal="pressed" from="Control/GridContainer/LoadableButton6/Button" to="Control/GridContainer/LoadableButton6" method="_on_button_pressed"] 116 | -------------------------------------------------------------------------------- /addons/talo/samples/stateful_buttons/stateful_buttons_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @export var username: String = "username" 4 | 5 | func _ready() -> void: 6 | Talo.players.identified.connect(_on_identified) 7 | Talo.players.identify("username", username) 8 | 9 | func _on_identified(_player: TaloPlayer) -> void: 10 | var saves := await Talo.saves.get_saves() 11 | if saves.is_empty(): 12 | await Talo.saves.create_save("save") 13 | 14 | await Talo.saves.choose_save(Talo.saves.all.front()) 15 | -------------------------------------------------------------------------------- /addons/talo/samples/stateful_buttons/stateful_buttons_manager.gd.uid: -------------------------------------------------------------------------------- 1 | uid://ceb1i0adxqrb8 2 | -------------------------------------------------------------------------------- /addons/talo/talo_autoload.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | func _enter_tree(): 5 | add_autoload_singleton("Talo", "res://addons/talo/talo_manager.gd") 6 | 7 | func _exit_tree(): 8 | remove_autoload_singleton("Talo") 9 | -------------------------------------------------------------------------------- /addons/talo/talo_autoload.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c2tgpg0h31acq 2 | -------------------------------------------------------------------------------- /addons/talo/talo_client.gd: -------------------------------------------------------------------------------- 1 | class_name TaloClient extends Node 2 | 3 | var _base_url: String 4 | 5 | func _init(base_url: String): 6 | _base_url = base_url 7 | name = "Client" 8 | 9 | func _get_method_name(method: HTTPClient.Method): 10 | match method: 11 | HTTPClient.METHOD_GET: return "GET" 12 | HTTPClient.METHOD_POST: return "POST" 13 | HTTPClient.METHOD_PUT: return "PUT" 14 | HTTPClient.METHOD_PATCH: return "PATCH" 15 | HTTPClient.METHOD_DELETE: return "DELETE" 16 | 17 | func _simulate_offline_request() -> TaloClientResponse: 18 | return TaloClientResponse.new( 19 | HTTPRequest.RESULT_CANT_CONNECT, 20 | 0, 21 | PackedStringArray(), 22 | PackedByteArray() 23 | ) 24 | 25 | func _build_response(http_request: HTTPRequest) -> TaloClientResponse: 26 | var res = await http_request.request_completed 27 | return TaloClientResponse.new(res[0], res[1], res[2], res[3]) 28 | 29 | func make_request(method: HTTPClient.Method, url: String, body: Dictionary = {}, headers: Array[String] = [], continuity: bool = false) -> Dictionary: 30 | var continuity_timestamp := TaloTimeUtils.get_timestamp_msec() 31 | 32 | var full_url := url if continuity else _build_full_url(url) 33 | var all_headers := headers if continuity else _build_headers(headers) 34 | var request_body := "" if body.keys().is_empty() else JSON.stringify(body) 35 | 36 | var http_request := HTTPRequest.new() 37 | add_child(http_request) 38 | http_request.name = "%s %s" % [_get_method_name(method), url] 39 | 40 | http_request.request(full_url, all_headers, method, request_body) 41 | var res := _simulate_offline_request() if Talo.offline_mode_enabled() else await _build_response(http_request) 42 | var status := res.response_code 43 | 44 | var response_body := res.body 45 | var json := JSON.new() 46 | json.parse(response_body.get_string_from_utf8()) 47 | 48 | if res.result != HTTPRequest.RESULT_SUCCESS: 49 | json.set_data({ 50 | message = 51 | "Request failed: result %s, details: https://docs.godotengine.org/en/stable/classes/class_httprequest.html#enum-httprequest-result" % res.result 52 | }) 53 | 54 | if Talo.settings.get_value("logging", "requests", false): 55 | print_rich("[color=%s]--> %s %s %s %s[/color]" % [ 56 | "yellow" if continuity else "orange", 57 | "[CONTINUITY]" if continuity else "", 58 | _get_method_name(method), 59 | full_url, 60 | request_body 61 | ]) 62 | 63 | if Talo.settings.get_value("logging", "responses", false): 64 | print_rich("[color=green]<-- %s %s[/color]" % [status, json.data]) 65 | 66 | var ret = { 67 | status = status, 68 | body = json.data 69 | } 70 | 71 | if ret.status >= 400: 72 | handle_error(ret) 73 | 74 | if res.result != HTTPRequest.RESULT_SUCCESS or ret.status > 500: 75 | Talo.continuity_manager.push_request(method, full_url, body, all_headers, continuity_timestamp) 76 | 77 | http_request.queue_free() 78 | 79 | return ret 80 | 81 | func _build_headers(extra_headers: Array[String] = []) -> Array[String]: 82 | var headers: Array[String] = [ 83 | "Authorization: Bearer %s" % Talo.settings.get_value("", "access_key"), 84 | "Content-Type: application/json", 85 | "Accept: application/json", 86 | "X-Talo-Dev-Build: %s" % ("1" if OS.is_debug_build() else "0"), 87 | "X-Talo-Include-Dev-Data: %s" % ("1" if OS.is_debug_build() else "0") 88 | ] 89 | 90 | if Talo.current_alias: 91 | headers.append_array([ 92 | "X-Talo-Player: %s" % Talo.current_player.id, 93 | "X-Talo-Alias: %s" % Talo.current_alias.id 94 | ]) 95 | 96 | var session_token := Talo.player_auth.session_manager.get_token() 97 | if session_token: 98 | headers.append("X-Talo-Session: %s" % session_token) 99 | 100 | headers.append_array(extra_headers) 101 | 102 | return headers 103 | 104 | func _build_full_url(url: String) -> String: 105 | return "%s%s%s" % [ 106 | Talo.settings.get_value("", "api_url"), 107 | _base_url, 108 | url.replace(" ", "%20") 109 | ] 110 | 111 | func handle_error(res: Dictionary) -> void: 112 | if res.body != null: 113 | if res.body.has("message"): 114 | push_error("%s: %s" % [res.status, res.body.message]) 115 | return 116 | 117 | if res.body.has("errors"): 118 | push_error("%s: %s" % [res.status, res.body.errors]) 119 | return 120 | 121 | push_error("%s: Unknown error" % res.status) 122 | 123 | class TaloClientResponse: 124 | var result: int 125 | var response_code: int 126 | var headers: PackedStringArray 127 | var body: PackedByteArray 128 | 129 | func _init(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void: 130 | self.result = result 131 | self.response_code = response_code 132 | self.headers = headers 133 | self.body = body 134 | -------------------------------------------------------------------------------- /addons/talo/talo_client.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c1pdtthh0yeya 2 | -------------------------------------------------------------------------------- /addons/talo/talo_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | signal init_completed 4 | 5 | var current_alias: TaloPlayerAlias 6 | var current_player: TaloPlayer: 7 | get: 8 | return null if not current_alias else current_alias.player 9 | 10 | var settings: ConfigFile 11 | 12 | var players: PlayersAPI 13 | var events: EventsAPI 14 | var game_config: GameConfigAPI 15 | var stats: StatsAPI 16 | var leaderboards: LeaderboardsAPI 17 | var saves: SavesAPI 18 | var feedback: FeedbackAPI 19 | var player_auth: PlayerAuthAPI 20 | var health_check: HealthCheckAPI 21 | var player_groups: PlayerGroupsAPI 22 | var channels: ChannelsAPI 23 | var socket_tickets: SocketTicketsAPI 24 | var player_presence: PlayerPresenceAPI 25 | 26 | var live_config: TaloLiveConfig 27 | 28 | var crypto_manager: TaloCryptoManager 29 | var continuity_manager: TaloContinuityManager 30 | 31 | var socket: TaloSocket 32 | 33 | func _ready() -> void: 34 | _load_config() 35 | _load_apis() 36 | _init_crypto_manager() 37 | _init_continuity() 38 | _init_socket() 39 | _check_session() 40 | 41 | get_tree().set_auto_accept_quit(false) 42 | process_mode = ProcessMode.PROCESS_MODE_ALWAYS 43 | init_completed.emit() 44 | 45 | func _init_crypto_manager() -> void: 46 | crypto_manager = TaloCryptoManager.new() 47 | 48 | func _init_continuity() -> void: 49 | continuity_manager = TaloContinuityManager.new() 50 | add_child(continuity_manager) 51 | 52 | func _init_socket() -> void: 53 | socket = TaloSocket.new() 54 | add_child(socket) 55 | 56 | if Talo.settings.get_value("", "auto_connect_socket", true): 57 | socket.open_connection() 58 | 59 | func _notification(what: int): 60 | match what: 61 | NOTIFICATION_WM_CLOSE_REQUEST: 62 | _do_flush() 63 | if Talo.settings.get_value("", "handle_tree_quit", true): 64 | get_tree().quit() 65 | NOTIFICATION_APPLICATION_FOCUS_OUT, NOTIFICATION_APPLICATION_PAUSED: 66 | _do_flush() 67 | 68 | func _load_config() -> void: 69 | var settings_path = "res://addons/talo/settings.cfg" 70 | settings = ConfigFile.new() 71 | 72 | if not FileAccess.file_exists(settings_path): 73 | settings.set_value("", "access_key", "") 74 | settings.set_value("", "api_url", "https://api.trytalo.com") 75 | settings.set_value("", "socket_url", TaloSocket.DEFAULT_SOCKET_URL) 76 | settings.set_value("", "auto_connect_socket", true) 77 | settings.set_value("", "handle_tree_quit", true) 78 | settings.set_value("continuity", "enabled", true) 79 | settings.save(settings_path) 80 | 81 | print_rich("[color=green]Talo settings.cfg created! Please close the game and fill in your access_key.[/color]") 82 | else: 83 | settings.load(settings_path) 84 | 85 | if (settings.get_value("", "access_key", "").is_empty()) && OS.is_debug_build(): 86 | print_rich("[color=yellow]Warning: Talo access_key in settings.cfg is empty[/color]") 87 | 88 | func _load_apis() -> void: 89 | players = preload("res://addons/talo/apis/players_api.gd").new("/v1/players") 90 | events = preload("res://addons/talo/apis/events_api.gd").new("/v1/events") 91 | game_config = preload("res://addons/talo/apis/game_config_api.gd").new("/v1/game-config") 92 | stats = preload("res://addons/talo/apis/stats_api.gd").new("/v1/game-stats") 93 | leaderboards = preload("res://addons/talo/apis/leaderboards_api.gd").new("/v1/leaderboards") 94 | saves = preload("res://addons/talo/apis/saves_api.gd").new("/v1/game-saves") 95 | feedback = preload("res://addons/talo/apis/feedback_api.gd").new("/v1/game-feedback") 96 | player_auth = preload("res://addons/talo/apis/player_auth_api.gd").new("/v1/players/auth") 97 | health_check = preload("res://addons/talo/apis/health_check_api.gd").new("/v1/health-check") 98 | player_groups = preload("res://addons/talo/apis/player_groups_api.gd").new("/v1/player-groups") 99 | channels = preload("res://addons/talo/apis/channels_api.gd").new("/v1/game-channels") 100 | socket_tickets = preload("res://addons/talo/apis/socket_tickets_api.gd").new("/v1/socket-tickets") 101 | player_presence = preload("res://addons/talo/apis/player_presence_api.gd").new("/v1/players/presence") 102 | 103 | for api in [ 104 | players, 105 | events, 106 | game_config, 107 | stats, 108 | leaderboards, 109 | saves, 110 | feedback, 111 | player_auth, 112 | health_check, 113 | player_groups, 114 | channels, 115 | socket_tickets, 116 | player_presence 117 | ]: 118 | add_child(api) 119 | 120 | func has_identity() -> bool: 121 | return current_alias != null 122 | 123 | func identity_check(should_error = true) -> Error: 124 | if not has_identity(): 125 | if should_error: 126 | push_error("You need to identify a player using Talo.players.identify() before doing this") 127 | return ERR_UNAUTHORIZED 128 | 129 | return OK 130 | 131 | func offline_mode_enabled() -> bool: 132 | return settings.get_value("debug", "offline_mode", false) 133 | 134 | func is_offline() -> bool: 135 | return offline_mode_enabled() or not await health_check.ping() 136 | 137 | func _do_flush() -> void: 138 | if identity_check(false) == OK: 139 | events.flush() 140 | 141 | func _check_session() -> void: 142 | var session_token := player_auth.session_manager.get_token() 143 | if not session_token.is_empty(): 144 | players.identify("talo", player_auth.session_manager.get_identifier()) 145 | -------------------------------------------------------------------------------- /addons/talo/talo_manager.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cf5txmco4tia4 2 | -------------------------------------------------------------------------------- /addons/talo/talo_socket.gd: -------------------------------------------------------------------------------- 1 | class_name TaloSocket extends Node 2 | ## An interface for communicating with the Talo Socket server. 3 | ## 4 | ## Sockets are used to provide interactivity to your game. It is highly recommended to use the Talo APIs instead of the socket directly. 5 | ## 6 | ## @tutorial: https://docs.trytalo.com/docs/godot/socket 7 | 8 | const DEFAULT_SOCKET_URL = "wss://api.trytalo.com" 9 | 10 | var _socket := WebSocketPeer.new() 11 | var _temp_socket_token: String 12 | var _socket_authenticated: bool 13 | var _identified: bool 14 | 15 | func _init() -> void: 16 | name = "TaloSocket" 17 | 18 | ## Emitted when a message is received from the Talo Socket server. Not recommended for direct use. See the Talo docs for a list of responses and message structures. 19 | signal message_received(res: String, message: Dictionary) 20 | 21 | ## Emitted when the connection to the Talo Socket server is closed. The code and reason are provided. 22 | signal connection_closed(code: int, reason: String) 23 | 24 | ## Emitted when an error is received from the Talo Socket server. 25 | signal error_received(err: TaloSocketError) 26 | 27 | func _ready() -> void: 28 | message_received.connect(_on_message_received) 29 | 30 | func _identify_player() -> void: 31 | if not _socket_authenticated: 32 | return 33 | 34 | var payload = { 35 | playerAliasId = Talo.current_alias.id, 36 | socketToken = _temp_socket_token 37 | } 38 | 39 | var session_token = Talo.player_auth.session_manager.get_token() 40 | if not session_token.is_empty(): 41 | payload.sessionToken = session_token 42 | 43 | send("v1.players.identify", payload) 44 | 45 | func _get_socket_url(ticket: String) -> String: 46 | var url := Talo.settings.get_value("", "socket_url", DEFAULT_SOCKET_URL) 47 | return "%s/?ticket=%s" % [url, ticket] 48 | 49 | ## Open the connection to the Talo Socket server. A new ticket is created to authenticate the connection. 50 | func open_connection(): 51 | var ticket := await Talo.socket_tickets.create_ticket() 52 | 53 | var err := _socket.connect_to_url(_get_socket_url(ticket)) 54 | if err != OK: 55 | print_rich("[color=yellow]Warning: Failed connecting to the Talo Socket: %s[/color]" % err) 56 | 57 | func _on_message_received(res: String, data: Dictionary) -> void: 58 | if Talo.settings.get_value("logging", "responses", false): 59 | print_rich("[color=aqua]<-- %s %s[/color]" % [res, data]) 60 | 61 | match res: 62 | "v1.connected": 63 | _socket_authenticated = true 64 | if not _identified and not _temp_socket_token.is_empty(): 65 | _identify_player() 66 | "v1.players.identify.success": 67 | _identified = true 68 | _temp_socket_token = "" 69 | "v1.error": 70 | error_received.emit(TaloSocketError.new(data)) 71 | 72 | ## A socket token is created for a player alias each time they are identified. This must be sent in order to validate the current socket session. 73 | func set_socket_token(token: String) -> void: 74 | _temp_socket_token = token 75 | if not _identified and _socket_authenticated: 76 | _identify_player() 77 | 78 | ## Send a message to the Talo Socket server. Not recommended for direct use. See the Talo docs for available requests and message structures. 79 | func send(req: String, data: Dictionary = {}) -> int: 80 | if Talo.settings.get_value("logging", "requests", false): 81 | print_rich("[color=orange]--> %s %s[/color]" % [req, data]) 82 | 83 | return _socket.send_text(JSON.stringify({ 84 | req = req, 85 | data = data 86 | })) 87 | 88 | func _get_json() -> String: 89 | var pkt := _socket.get_packet() 90 | return pkt.get_string_from_utf8() 91 | 92 | func _emit_message(message: String) -> void: 93 | var json := JSON.new() 94 | json.parse(message) 95 | 96 | var res = json.data.res 97 | var data = json.data.data 98 | message_received.emit(res, data) 99 | 100 | ## Close the connection to the Talo Socket server. 101 | func close_connection(code: int = 1000, reason: String = "") -> void: 102 | _socket.close(code, reason) 103 | 104 | ## Close the current connection and create a new connection to the Talo Socket server. 105 | func reset_connection() -> void: 106 | if not _identified: 107 | return 108 | 109 | close_connection() 110 | _reset_socket() 111 | open_connection() 112 | 113 | func _reset_socket() -> void: 114 | connection_closed.emit(_socket.get_close_code(), _socket.get_close_reason()) 115 | _socket = WebSocketPeer.new() 116 | _socket_authenticated = false 117 | _identified = false 118 | 119 | func _poll() -> void: 120 | if _socket.get_ready_state() != _socket.STATE_CLOSED: 121 | _socket.poll() 122 | elif _socket.get_ready_state() == _socket.STATE_CLOSED and _socket_authenticated: 123 | _reset_socket() 124 | 125 | while _socket.get_ready_state() == _socket.STATE_OPEN and _socket.get_available_packet_count() > 0: 126 | var message := _get_json() 127 | _emit_message(message) 128 | 129 | func _process(_delta: float) -> void: 130 | _poll() 131 | -------------------------------------------------------------------------------- /addons/talo/talo_socket.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b0ro5trotb1tb 2 | -------------------------------------------------------------------------------- /addons/talo/utils/continuity_manager.gd: -------------------------------------------------------------------------------- 1 | class_name TaloContinuityManager extends Timer 2 | 3 | var _client: TaloClient 4 | var _requests: Array = [] 5 | 6 | const _CONTINUITY_PATH = "user://tc.bin" 7 | const _CONTINUITY_TIMESTAMP_HEADER = "X-Talo-Continuity-Timestamp" 8 | 9 | const _EXCLUDED_ENDPOINTS: Array[String] = [ 10 | "/v1/health-check", 11 | "/v1/players/auth", 12 | "/v1/players/identify", 13 | "/v1/socket-tickets" 14 | ] 15 | 16 | func _ready() -> void: 17 | name = "TaloContinuityManager" 18 | _client = TaloClient.new("") 19 | add_child(_client) 20 | 21 | _requests = _read_requests() 22 | 23 | wait_time = 10 24 | connect("timeout", _on_timeout) 25 | start() 26 | 27 | func push_request(method: HTTPClient.Method, url: String, body: Dictionary, headers: Array[String], timestamp: int): 28 | if not Talo.settings.get_value("continuity", "enabled", true): 29 | return 30 | 31 | if _EXCLUDED_ENDPOINTS.any(func (endpoint: String): return url.find(endpoint) != -1): 32 | return 33 | 34 | _requests.push_back({ 35 | method = method, 36 | url = url, 37 | body = body, 38 | headers = headers.filter(func (h: String): return h.find("Authorization") == -1), 39 | timestamp = timestamp 40 | }) 41 | 42 | _write_requests() 43 | 44 | func _read_requests() -> Array: 45 | if not FileAccess.file_exists(_CONTINUITY_PATH): 46 | return [] 47 | 48 | var content := FileAccess.open_encrypted_with_pass(_CONTINUITY_PATH, FileAccess.READ, Talo.crypto_manager.get_key()) 49 | if content == null: 50 | TaloCryptoManager.handle_undecryptable_file(_CONTINUITY_PATH, "continuity file") 51 | return [] 52 | 53 | var json := JSON.new() 54 | json.parse(content.get_as_text()) 55 | 56 | return json.data 57 | 58 | func _write_requests(): 59 | var file := FileAccess.open_encrypted_with_pass(_CONTINUITY_PATH, FileAccess.WRITE, Talo.crypto_manager.get_key()) 60 | file.store_line(JSON.stringify(_requests)) 61 | 62 | func _on_timeout(): 63 | if _requests.is_empty() or not (await Talo.health_check.ping()): 64 | return 65 | 66 | for i in range(10): 67 | if _requests.is_empty(): 68 | break 69 | 70 | var req := _requests.pop_front() 71 | _write_requests() 72 | 73 | var headers: Array[String] = ["Authorization: Bearer %s" % Talo.settings.get_value("", "access_key")] 74 | headers.append_array(req.headers) 75 | 76 | if not req.headers.any(func (h: String): return h.find(_CONTINUITY_TIMESTAMP_HEADER) != -1): 77 | headers.append("%s: %s" % [_CONTINUITY_TIMESTAMP_HEADER, req.timestamp]) 78 | 79 | await _client.make_request(req.method, req.url, req.body, headers, true) 80 | -------------------------------------------------------------------------------- /addons/talo/utils/continuity_manager.gd.uid: -------------------------------------------------------------------------------- 1 | uid://5vuisrp4h4u4 2 | -------------------------------------------------------------------------------- /addons/talo/utils/crypto_manager.gd: -------------------------------------------------------------------------------- 1 | class_name TaloCryptoManager extends RefCounted 2 | 3 | const _KEY_FILE_PATH = "user://ti.bin" 4 | 5 | static func handle_undecryptable_file(path: String, what: String) -> void: 6 | push_error("Failed to decrypt %s" % what) 7 | var split_path := path.split(".") 8 | var timestamp := TaloTimeUtils.get_timestamp_msec() 9 | DirAccess.rename_absolute(path, "%s-invalid-%s.%s" % [split_path[0], timestamp, split_path[1]]) 10 | 11 | func _get_pass() -> String: 12 | if OS.has_feature("web"): 13 | return Talo.settings.get_value("", "access_key") 14 | 15 | return OS.get_unique_id() 16 | 17 | func _init() -> void: 18 | if not FileAccess.file_exists(_KEY_FILE_PATH): 19 | if _get_pass().is_empty(): 20 | push_error("Unable to create key file: cannot generate a suitable password") 21 | return 22 | 23 | var crypto := Crypto.new() 24 | var key := crypto.generate_random_bytes(32).hex_encode() 25 | 26 | var file := FileAccess.open_encrypted_with_pass(_KEY_FILE_PATH, FileAccess.WRITE, _get_pass()) 27 | file.store_line(key) 28 | file.close() 29 | 30 | func get_key() -> String: 31 | if not FileAccess.file_exists(_KEY_FILE_PATH): 32 | _init() 33 | 34 | var file := FileAccess.open_encrypted_with_pass(_KEY_FILE_PATH, FileAccess.READ, _get_pass()) 35 | if file == null: 36 | handle_undecryptable_file(_KEY_FILE_PATH, "crypto init file") 37 | return "" 38 | 39 | var key := file.get_as_text() 40 | file.close() 41 | return key 42 | -------------------------------------------------------------------------------- /addons/talo/utils/crypto_manager.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cu7h77b2k0yvo 2 | -------------------------------------------------------------------------------- /addons/talo/utils/leaderboard_entries_manager.gd: -------------------------------------------------------------------------------- 1 | class_name TaloLeaderboardEntriesManager extends RefCounted 2 | 3 | # String -> Array[TaloLeaderboardEntry] 4 | # TODO: update when nested typed collections are supported 5 | var _current_entries: Dictionary = {} 6 | 7 | func get_entries(internal_name: String) -> Array[TaloLeaderboardEntry]: 8 | return _current_entries.get(internal_name, Array([], TYPE_OBJECT, "RefCounted", TaloLeaderboardEntry)) 9 | 10 | func upsert_entry(internal_name: String, entry: TaloLeaderboardEntry) -> void: 11 | var named_entries: Array[TaloLeaderboardEntry] = [] 12 | named_entries = _current_entries.get_or_add(internal_name, named_entries).filter( 13 | func (e: TaloLeaderboardEntry) -> bool: return e.id != entry.id 14 | ) 15 | 16 | if entry.position >= named_entries.size(): 17 | named_entries.append(entry) 18 | else: 19 | named_entries.insert(entry.position, entry) 20 | 21 | for idx in range(entry.position, named_entries.size()): 22 | named_entries[idx].position = idx 23 | 24 | _current_entries[internal_name] = named_entries 25 | -------------------------------------------------------------------------------- /addons/talo/utils/leaderboard_entries_manager.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c8r04586okv2u 2 | -------------------------------------------------------------------------------- /addons/talo/utils/saves_manager.gd: -------------------------------------------------------------------------------- 1 | class_name TaloSavesManager extends RefCounted 2 | 3 | var all_saves: Array[TaloGameSave] = [] 4 | var current_save: TaloGameSave 5 | 6 | var _saved_objects: Dictionary[String, TaloSavedObject] = {} 7 | var _loadables: Dictionary[String, TaloLoadable] = {} 8 | 9 | var _format_version := "godot.v2" 10 | 11 | const _OFFLINE_SAVES_PATH = "user://ts.bin" 12 | 13 | func read_offline_saves() -> Array[TaloGameSave]: 14 | if not FileAccess.file_exists(_OFFLINE_SAVES_PATH): 15 | return [] 16 | 17 | var content := FileAccess.open_encrypted_with_pass(_OFFLINE_SAVES_PATH, FileAccess.READ, Talo.crypto_manager.get_key()) 18 | if content == null: 19 | TaloCryptoManager.handle_undecryptable_file(_OFFLINE_SAVES_PATH, "offline saves file") 20 | return [] 21 | 22 | var json := JSON.new() 23 | json.parse(content.get_as_text()) 24 | 25 | var res: Array[TaloGameSave] = [] 26 | res.assign(json.data.map(func (data: Dictionary): return TaloGameSave.new(data))) 27 | 28 | return res 29 | 30 | func write_offline_saves(offline_saves: Array[TaloGameSave]): 31 | var saves := FileAccess.open_encrypted_with_pass(_OFFLINE_SAVES_PATH, FileAccess.WRITE, Talo.crypto_manager.get_key()) 32 | saves.store_line(JSON.stringify(offline_saves.map(func (save: TaloGameSave): return save.to_dictionary()))) 33 | 34 | func sync_save(online_save: TaloGameSave, offline_save: TaloGameSave) -> TaloGameSave: 35 | var online_updated_at := Time.get_unix_time_from_datetime_string(online_save.updated_at) 36 | var offline_updated_at := Time.get_unix_time_from_datetime_string(offline_save.updated_at) 37 | 38 | if offline_updated_at > online_updated_at: 39 | var save := await Talo.saves.replace_save_with_offline_save(offline_save) 40 | delete_offline_save(offline_save) 41 | return save 42 | 43 | return online_save 44 | 45 | func delete_offline_save(save: TaloGameSave): 46 | var offline_saves: Array[TaloGameSave] = read_offline_saves() 47 | offline_saves = offline_saves.filter(func (s: TaloGameSave): return s.id != save.id) 48 | write_offline_saves(offline_saves) 49 | 50 | func sync_offline_saves(offline_saves: Array[TaloGameSave]) -> Array[TaloGameSave]: 51 | var new_saves: Array[TaloGameSave] = [] 52 | 53 | for offline_save in offline_saves: 54 | if offline_save.id < 0: 55 | var save := await Talo.saves.create_save(offline_save.name, offline_save.content) 56 | delete_offline_save(offline_save) 57 | new_saves.push_back(save) 58 | 59 | return new_saves 60 | 61 | func get_synced_saves(online_saves: Array[TaloGameSave]) -> Array[TaloGameSave]: 62 | var saves: Array[TaloGameSave] = [] 63 | var offline_saves: Array[TaloGameSave] = read_offline_saves() 64 | 65 | if not offline_saves.is_empty(): 66 | for online_save in online_saves: 67 | var filtered := offline_saves.filter(func (save: TaloGameSave): return save.id == online_save.id) 68 | if not filtered.is_empty(): 69 | var save := await sync_save(online_save, filtered.front()) 70 | saves.push_back(save) 71 | else: 72 | saves.push_back(online_save) 73 | 74 | var synced_saves := await sync_offline_saves(offline_saves) 75 | saves.append_array(synced_saves) 76 | 77 | return saves 78 | 79 | func update_offline_saves(incoming_save: TaloGameSave) -> void: 80 | var offline_saves := read_offline_saves() 81 | var updated := false 82 | 83 | for i in range(offline_saves.size()): 84 | if offline_saves[i].id == incoming_save.id: 85 | offline_saves[i] = incoming_save 86 | updated = true 87 | break 88 | 89 | if not updated: 90 | if incoming_save.id == 0: 91 | incoming_save.id = -offline_saves.size() - 1 92 | 93 | offline_saves.append(incoming_save) 94 | 95 | write_offline_saves(offline_saves) 96 | 97 | func set_chosen_save(save: TaloGameSave, load_save: bool) -> void: 98 | current_save = save 99 | if not load_save: 100 | return 101 | 102 | Talo.saves.save_chosen.emit(save) 103 | 104 | for object in save.content.get("objects", []): 105 | var saved_object := TaloSavedObject.new(object) 106 | _saved_objects.set(object.id, saved_object) 107 | 108 | var matching_loadable = _loadables.get(saved_object.id) 109 | if matching_loadable: 110 | saved_object.register_loadable(matching_loadable) 111 | 112 | Talo.saves.save_loading_completed.emit() 113 | 114 | func register(loadable: TaloLoadable) -> void: 115 | _loadables.set(loadable.id, loadable) 116 | 117 | # create a new saved object in case it isn't in the save file yet 118 | var saved_object := TaloSavedObject.new({ 119 | id = loadable.id, 120 | name = loadable.get_path(), 121 | data = loadable.get_latest_data() 122 | }) 123 | saved_object.register_loadable(loadable, false) 124 | _saved_objects.set(loadable.id, saved_object) 125 | 126 | func get_save_content() -> Dictionary: 127 | return { 128 | version = _format_version, 129 | objects = _saved_objects.values().map( 130 | func (saved_object: TaloSavedObject): return saved_object.to_dictionary() 131 | ) 132 | } 133 | 134 | func replace_save(new_save: TaloGameSave) -> void: 135 | var existing_saves := all_saves.filter(func (save): return save.id == new_save.id) 136 | if existing_saves.is_empty(): 137 | push_error("Save %s cannot be replaced as it does not exist" % new_save.id) 138 | all_saves.push_back(new_save) 139 | else: 140 | all_saves[all_saves.find(existing_saves.front())] = new_save 141 | 142 | if current_save.id == new_save.id: 143 | current_save = new_save 144 | 145 | update_offline_saves(new_save) 146 | 147 | func get_latest_save() -> TaloGameSave: 148 | var dupe := all_saves.duplicate() 149 | if dupe.is_empty(): 150 | return null 151 | 152 | dupe.sort_custom( 153 | func (a, b): 154 | var time_a := Time.get_unix_time_from_datetime_string(a.updated_at) 155 | var time_b := Time.get_unix_time_from_datetime_string(b.updated_at) 156 | return time_a > time_b 157 | ) 158 | 159 | return dupe.front() 160 | 161 | func get_format_version() -> String: 162 | var default := "godot.v1" # version 1 didn't have this key, so fallback to it 163 | if not current_save: 164 | return default 165 | 166 | return current_save.content.get("version", default) 167 | -------------------------------------------------------------------------------- /addons/talo/utils/saves_manager.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dwb6lai8jafc7 2 | -------------------------------------------------------------------------------- /addons/talo/utils/session_manager.gd: -------------------------------------------------------------------------------- 1 | class_name TaloSessionManager extends RefCounted 2 | 3 | var _verification_alias_id: int 4 | 5 | const _SESSION_CONFIG_PATH = "user://talo_session.cfg" 6 | 7 | func _load_config(path: String) -> ConfigFile: 8 | var config := ConfigFile.new() 9 | config.load(path) 10 | return config 11 | 12 | func _save_session(session_token: String) -> void: 13 | var config := ConfigFile.new() 14 | config.set_value("session", "token", session_token) 15 | config.set_value("session", "identifier", Talo.current_alias.identifier) 16 | config.save(_SESSION_CONFIG_PATH) 17 | 18 | func clear_session() -> void: 19 | Talo.current_alias = null 20 | Talo.socket.reset_connection() 21 | 22 | var config := _load_config(_SESSION_CONFIG_PATH) 23 | 24 | if config.has_section("session"): 25 | config.erase_section("session") 26 | config.save(_SESSION_CONFIG_PATH) 27 | 28 | func get_token() -> String: 29 | var config := _load_config(_SESSION_CONFIG_PATH) 30 | return config.get_value("session", "token", "") 31 | 32 | func get_identifier() -> String: 33 | var config := _load_config(_SESSION_CONFIG_PATH) 34 | return config.get_value("session", "identifier", "") 35 | 36 | func save_verification_alias_id(alias_id: int) -> void: 37 | _verification_alias_id = alias_id 38 | 39 | func get_verification_alias_id() -> int: 40 | return _verification_alias_id 41 | 42 | func handle_session_created(alias: Dictionary, session_token: String, socket_token: String) -> void: 43 | Talo.current_alias = TaloPlayerAlias.new(alias) 44 | Talo.players.identified.emit(Talo.current_player) 45 | _save_session(session_token) 46 | Talo.socket.set_socket_token(socket_token) 47 | -------------------------------------------------------------------------------- /addons/talo/utils/session_manager.gd.uid: -------------------------------------------------------------------------------- 1 | uid://drigrjbgs2oud 2 | -------------------------------------------------------------------------------- /addons/talo/utils/talo_auth_error.gd: -------------------------------------------------------------------------------- 1 | class_name TaloAuthError extends RefCounted 2 | 3 | enum ErrorCode { 4 | API_ERROR, 5 | INVALID_CREDENTIALS, 6 | VERIFICATION_ALIAS_NOT_FOUND, 7 | VERIFICATION_CODE_INVALID, 8 | IDENTIFIER_TAKEN, 9 | MISSING_SESSION, 10 | INVALID_SESSION, 11 | NEW_PASSWORD_MATCHES_CURRENT_PASSWORD, 12 | NEW_EMAIL_MATCHES_CURRENT_EMAIL, 13 | PASSWORD_RESET_CODE_INVALID, 14 | VERIFICATION_EMAIL_REQUIRED, 15 | INVALID_EMAIL 16 | } 17 | 18 | var _error_string := "" 19 | 20 | func _init(error_string: String) -> void: 21 | _error_string = error_string 22 | 23 | ## Get the human-readable player auth error message. 24 | func get_string() -> String: 25 | if _error_string == "API_ERROR": 26 | return "API error - see the Errors Output for more details" 27 | 28 | return _error_string 29 | 30 | ## Get the player auth error code using the ErrorCode enum. 31 | func get_code() -> ErrorCode: 32 | return ErrorCode.get(_error_string) 33 | -------------------------------------------------------------------------------- /addons/talo/utils/talo_auth_error.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dcvbv3strwuc2 2 | -------------------------------------------------------------------------------- /addons/talo/utils/talo_socket_error.gd: -------------------------------------------------------------------------------- 1 | class_name TaloSocketError extends RefCounted 2 | 3 | enum ErrorCode { 4 | API_ERROR, 5 | INVALID_MESSAGE, 6 | INVALID_MESSAGE_DATA, 7 | NO_PLAYER_FOUND, 8 | UNHANDLED_REQUEST, 9 | ROUTING_ERROR, 10 | LISTENER_ERROR, 11 | INVALID_SOCKET_TOKEN, 12 | INVALID_SESSION_TOKEN, 13 | MISSING_ACCESS_KEY_SCOPES, 14 | RATE_LIMIT_EXCEEDED 15 | } 16 | 17 | ## The original req that triggered the error. 18 | var req: String 19 | 20 | ## The socket error code using the ErrorCode enum. 21 | var code: ErrorCode 22 | 23 | ## The human-readable socket error message. 24 | var message: String 25 | 26 | ## The cause of the error. Only some error types will provide this. 27 | var cause: String 28 | 29 | func _init(error_data: Dictionary) -> void: 30 | req = error_data.get("req", "unknown") 31 | code = ErrorCode.get(error_data.get("errorCode"), "API_ERROR") 32 | message = error_data.get("message", "") 33 | cause = error_data.get("cause", "") 34 | -------------------------------------------------------------------------------- /addons/talo/utils/talo_socket_error.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cmr1csabplkt2 2 | -------------------------------------------------------------------------------- /addons/talo/utils/time_utils.gd: -------------------------------------------------------------------------------- 1 | class_name TaloTimeUtils extends RefCounted 2 | 3 | ## Get an ISO 8601 datetime string (YYYY-MM-DDTHH:MM:SS) from the current time. 4 | static func get_current_datetime_string() -> String: 5 | return Time.get_datetime_string_from_unix_time(int(Time.get_unix_time_from_system())) 6 | 7 | ## Get the current time in milliseconds. 8 | static func get_timestamp_msec() -> int: 9 | return ceil(Time.get_unix_time_from_system()) * 1000 10 | -------------------------------------------------------------------------------- /addons/talo/utils/time_utils.gd.uid: -------------------------------------------------------------------------------- 1 | uid://svao2heuexdv 2 | --------------------------------------------------------------------------------