generated from krampus/template-godot4
153 lines
4.5 KiB
GDScript3
153 lines
4.5 KiB
GDScript3
|
|
@icon("./icons/tube_context.svg")
|
|||
|
|
@tool
|
|||
|
|
class_name TubeContext extends Resource
|
|||
|
|
## A resource that holds configuration and helper methods for managing simple multiplayer session.
|
|||
|
|
|
|||
|
|
## Character set to generate app IDs. Contains most printable ASCII characters.
|
|||
|
|
const _APP_ID_CHARACTER_SET := "!#$%&()*+,-./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890:;<=>?@[]^_{|}~"
|
|||
|
|
|
|||
|
|
@export_tool_button("Generate app id", "RandomNumberGenerator") var _generate_app_id_tool_button = (func():
|
|||
|
|
app_id = _get_random_string(15, _APP_ID_CHARACTER_SET)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
## Application identifier for this multiplayer context.
|
|||
|
|
## Must be exactly 15 ASCII characters long.
|
|||
|
|
@export var app_id: String
|
|||
|
|
|
|||
|
|
## Character set used to generate session IDs.
|
|||
|
|
## Must not be empty and should only contain ASCII characters.
|
|||
|
|
## A larger set reduces the probability of collision. With 62 characters
|
|||
|
|
## (A–Z, a–z, 0–9), the chance of two random 5-character IDs matching is approximately 1 in 916 million.
|
|||
|
|
## For readability by players, consider removing ambiguous characters (e.g., oO0, ilj1I, z2).
|
|||
|
|
@export_multiline var session_id_characters_set: String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"
|
|||
|
|
|
|||
|
|
## List of tracker server URLs used for session signaling.
|
|||
|
|
@export var trackers_urls: Array[String] = []
|
|||
|
|
|
|||
|
|
## List of STUN server URLs used for WebRTC ICE candidate resolution.
|
|||
|
|
@export var stun_servers_urls: Array[String] = []
|
|||
|
|
|
|||
|
|
## List of TURN servers (optional). Turn server are dictionnary in the form:
|
|||
|
|
## [codeblock]
|
|||
|
|
## {
|
|||
|
|
## "urls": "turn:turn.example.com:3478",
|
|||
|
|
## "username: "my-username",
|
|||
|
|
## "credential": "my-credential",
|
|||
|
|
## }
|
|||
|
|
@export var turn_servers: Array[Dictionary] = []
|
|||
|
|
|
|||
|
|
|
|||
|
|
func _to_string() -> String:
|
|||
|
|
return "AppID: %s | Trackers: %s | STUN: %s" % [app_id, str(trackers_urls), str(stun_servers_urls)]
|
|||
|
|
|
|||
|
|
|
|||
|
|
func _is_ascii(string: String) -> bool:
|
|||
|
|
for char_index in range(string.length()):
|
|||
|
|
if string.unicode_at(char_index) >= 128:
|
|||
|
|
return false
|
|||
|
|
return true
|
|||
|
|
|
|||
|
|
## Checks if the context configuration is valid.
|
|||
|
|
func is_valid() -> bool:
|
|||
|
|
if 0 == session_id_characters_set.length():
|
|||
|
|
printerr("Session ID Character Set is empty")
|
|||
|
|
return false
|
|||
|
|
|
|||
|
|
if not _is_ascii(session_id_characters_set):
|
|||
|
|
printerr("Session ID Character Set can only contain ASCII characters")
|
|||
|
|
return false
|
|||
|
|
|
|||
|
|
if null == app_id or 15 != app_id.length() or not _is_ascii(app_id):
|
|||
|
|
printerr("App id is invalid")
|
|||
|
|
return false
|
|||
|
|
|
|||
|
|
return true
|
|||
|
|
|
|||
|
|
## Returns ICE server configuration dictionary for WebRTC peer connection.
|
|||
|
|
##
|
|||
|
|
## Example:
|
|||
|
|
## [codeblock]
|
|||
|
|
## {
|
|||
|
|
## "iceServers": [
|
|||
|
|
## {
|
|||
|
|
## "urls": [ "stun:stun.example.com:3478" ], # One or more STUN servers.
|
|||
|
|
## },
|
|||
|
|
## {
|
|||
|
|
## "urls": [ "turn:turn.example.com:3478" ], # One or more TURN servers.
|
|||
|
|
## "username": "a_username", # Optional username for the TURN server.
|
|||
|
|
## "credential": "a_password", # Optional password for the TURN server.
|
|||
|
|
## }
|
|||
|
|
## ]
|
|||
|
|
## }
|
|||
|
|
|
|||
|
|
## [/codeblock]
|
|||
|
|
func get_ice_servers() -> Dictionary:
|
|||
|
|
var ice_servers := []
|
|||
|
|
|
|||
|
|
if null != stun_servers_urls:
|
|||
|
|
for url in stun_servers_urls:
|
|||
|
|
ice_servers.append({
|
|||
|
|
"urls": url
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if null != turn_servers:
|
|||
|
|
for turn_server in turn_servers:
|
|||
|
|
ice_servers.append(turn_server)
|
|||
|
|
|
|||
|
|
if ice_servers.is_empty():
|
|||
|
|
return {}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"iceServers": ice_servers
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
func _get_random_string(p_size: int, character_set: String) -> String:
|
|||
|
|
var rng := RandomNumberGenerator.new()
|
|||
|
|
rng.randomize()
|
|||
|
|
|
|||
|
|
var character_set_length := character_set.length()
|
|||
|
|
|
|||
|
|
var out := ""
|
|||
|
|
for i in range(p_size):
|
|||
|
|
var index := rng.randi()%character_set_length
|
|||
|
|
out += character_set[index]
|
|||
|
|
|
|||
|
|
return out
|
|||
|
|
|
|||
|
|
|
|||
|
|
## Generates a random 5-character session ID.
|
|||
|
|
func generate_session_id() -> String:
|
|||
|
|
return _get_random_string(5, session_id_characters_set)
|
|||
|
|
|
|||
|
|
|
|||
|
|
## Validates if a session ID is correct
|
|||
|
|
func is_session_id_valid(p_session_id: String) -> bool:
|
|||
|
|
return 5 == p_session_id.length()
|
|||
|
|
|
|||
|
|
|
|||
|
|
## Validates if a peer ID hash is numeric and valid.
|
|||
|
|
func is_peer_id_hash_valid(p_peer_id_hash: String) -> bool:
|
|||
|
|
return p_peer_id_hash.is_valid_int()
|
|||
|
|
|
|||
|
|
## Returns the combined "info hash" (app ID and session ID) for tracker usage.
|
|||
|
|
func get_info_hash(p_session_id: String) -> String:
|
|||
|
|
if not is_session_id_valid(p_session_id):
|
|||
|
|
printerr("Invalid session id")
|
|||
|
|
return ""
|
|||
|
|
|
|||
|
|
return app_id + p_session_id
|
|||
|
|
|
|||
|
|
|
|||
|
|
## Converts a integer peer ID hash into an peer ID hash for tracker usage.
|
|||
|
|
func get_peer_id_hash(p_peer_id: int) -> String:
|
|||
|
|
return str(p_peer_id).pad_zeros(20)
|
|||
|
|
|
|||
|
|
|
|||
|
|
## Converts a peer ID hash into an integer peer ID.
|
|||
|
|
func get_peer_id(p_peer_id_hash: String) -> int:
|
|||
|
|
if not is_peer_id_hash_valid(p_peer_id_hash):
|
|||
|
|
return 0
|
|||
|
|
|
|||
|
|
return int(p_peer_id_hash)
|