Adds SilentWolf plugin in preparation for leaderboard support
							parent
							
								
									c35e3cbb20
								
							
						
					
					
						commit
						adcc9c937d
					
				| 
						 | 
				
			
			@ -17,3 +17,6 @@ mono_crash.*.json
 | 
			
		|||
 | 
			
		||||
# Export
 | 
			
		||||
/Export/
 | 
			
		||||
 | 
			
		||||
# No Secrets
 | 
			
		||||
secrets.json
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,19 @@
 | 
			
		|||
extends Node
 | 
			
		||||
 | 
			
		||||
var score : int = 0
 | 
			
		||||
 | 
			
		||||
const secrets_file_path : String = "res://secrets.json"
 | 
			
		||||
var initialised_silent_wolf : bool = false
 | 
			
		||||
 | 
			
		||||
func _ready():
 | 
			
		||||
	var json : JSON = JSON.new()
 | 
			
		||||
	var file : FileAccess = FileAccess.open(secrets_file_path, FileAccess.READ)
 | 
			
		||||
	var contents : String = file.get_as_text()
 | 
			
		||||
	
 | 
			
		||||
	if json.parse(contents) == OK:
 | 
			
		||||
		if json.data is Dictionary:
 | 
			
		||||
			SilentWolf.configure(json.data)
 | 
			
		||||
	
 | 
			
		||||
	SilentWolf.configure_scores({
 | 
			
		||||
		"open_scene_on_close": "res://Scenes/main_menu.tscn"
 | 
			
		||||
		})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											Binary file not shown.
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,459 @@
 | 
			
		|||
extends Node
 | 
			
		||||
 | 
			
		||||
const SWLocalFileStorage = preload("res://addons/silent_wolf/utils/SWLocalFileStorage.gd")
 | 
			
		||||
const SWUtils = preload("res://addons/silent_wolf/utils/SWUtils.gd")
 | 
			
		||||
const SWLogger = preload("res://addons/silent_wolf/utils/SWLogger.gd")
 | 
			
		||||
const UUID = preload("res://addons/silent_wolf/utils/UUID.gd")
 | 
			
		||||
 | 
			
		||||
signal sw_login_complete
 | 
			
		||||
signal sw_logout_complete
 | 
			
		||||
signal sw_registration_complete
 | 
			
		||||
signal sw_registration_user_pwd_complete
 | 
			
		||||
signal sw_email_verif_complete
 | 
			
		||||
signal sw_resend_conf_code_complete
 | 
			
		||||
signal sw_session_check_complete
 | 
			
		||||
signal sw_request_password_reset_complete
 | 
			
		||||
signal sw_reset_password_complete
 | 
			
		||||
signal sw_get_player_details_complete
 | 
			
		||||
 | 
			
		||||
var tmp_username = null
 | 
			
		||||
var logged_in_player = null
 | 
			
		||||
var logged_in_player_email = null
 | 
			
		||||
var logged_in_anon = false
 | 
			
		||||
var sw_access_token = null
 | 
			
		||||
var sw_id_token = null
 | 
			
		||||
 | 
			
		||||
var RegisterPlayer = null
 | 
			
		||||
var VerifyEmail = null
 | 
			
		||||
var ResendConfCode = null
 | 
			
		||||
var LoginPlayer = null
 | 
			
		||||
var ValidateSession = null
 | 
			
		||||
var RequestPasswordReset = null
 | 
			
		||||
var ResetPassword = null
 | 
			
		||||
var GetPlayerDetails = null
 | 
			
		||||
 | 
			
		||||
# wekrefs
 | 
			
		||||
var wrRegisterPlayer = null
 | 
			
		||||
var wrVerifyEmail = null
 | 
			
		||||
var wrResendConfCode = null
 | 
			
		||||
var wrLoginPlayer = null
 | 
			
		||||
var wrValidateSession = null
 | 
			
		||||
var wrRequestPasswordReset = null
 | 
			
		||||
var wrResetPassword = null
 | 
			
		||||
var wrGetPlayerDetails = null
 | 
			
		||||
 | 
			
		||||
var login_timeout = 0
 | 
			
		||||
var login_timer = null
 | 
			
		||||
 | 
			
		||||
var complete_session_check_wait_timer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func register_player_anon(player_name = null) -> Node:
 | 
			
		||||
	var user_local_id: String = get_anon_user_id()
 | 
			
		||||
	var prepared_http_req = SilentWolf.prepare_http_request()
 | 
			
		||||
	RegisterPlayer = prepared_http_req.request
 | 
			
		||||
	wrRegisterPlayer = prepared_http_req.weakref
 | 
			
		||||
	RegisterPlayer.request_completed.connect(_on_RegisterPlayer_request_completed)
 | 
			
		||||
	SWLogger.info("Calling SilentWolf to register an anonymous player")
 | 
			
		||||
	var payload = { "game_id": SilentWolf.config.game_id, "anon": true, "player_name": player_name, "user_local_id": user_local_id }
 | 
			
		||||
	var request_url = "https://api.silentwolf.com/create_new_player"
 | 
			
		||||
	SilentWolf.send_post_request(RegisterPlayer, request_url, payload)
 | 
			
		||||
	return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func register_player(player_name: String, email: String, password: String, confirm_password: String) -> Node:
 | 
			
		||||
	tmp_username = player_name
 | 
			
		||||
	var prepared_http_req = SilentWolf.prepare_http_request()
 | 
			
		||||
	RegisterPlayer = prepared_http_req.request
 | 
			
		||||
	wrRegisterPlayer = prepared_http_req.weakref
 | 
			
		||||
	RegisterPlayer.request_completed.connect(_on_RegisterPlayer_request_completed)
 | 
			
		||||
	SWLogger.info("Calling SilentWolf to register a player")
 | 
			
		||||
	var payload = { "game_id": SilentWolf.config.game_id, "anon": false, "player_name": player_name, "email":  email, "password": password, "confirm_password": confirm_password }
 | 
			
		||||
	var request_url = "https://api.silentwolf.com/create_new_player"
 | 
			
		||||
	SilentWolf.send_post_request(RegisterPlayer, request_url, payload)
 | 
			
		||||
	return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_RegisterPlayer_request_completed(result, response_code, headers, body) -> void:
 | 
			
		||||
	var status_check = SWUtils.check_http_response(response_code, headers, body)
 | 
			
		||||
	SilentWolf.free_request(wrRegisterPlayer, RegisterPlayer)
 | 
			
		||||
	
 | 
			
		||||
	if status_check:
 | 
			
		||||
		var json_body = JSON.parse_string(body.get_string_from_utf8())
 | 
			
		||||
		# also get a JWT token here, when available in backend
 | 
			
		||||
		# send a different signal depending on registration success or failure
 | 
			
		||||
		var sw_result: Dictionary = SilentWolf.build_result(json_body)
 | 
			
		||||
		if json_body.success:
 | 
			
		||||
			SWLogger.info("SilentWolf register player success, player_name: " + str(json_body.player_name))
 | 
			
		||||
			#sw_token = json_body.swtoken
 | 
			
		||||
			var anon = json_body.anon
 | 
			
		||||
			if anon:
 | 
			
		||||
				SWLogger.info("Anonymous Player registration succeeded")
 | 
			
		||||
				logged_in_anon = true
 | 
			
		||||
				if 'player_name' in json_body:
 | 
			
		||||
					logged_in_player = json_body.player_name
 | 
			
		||||
				elif 'player_local_id' in json_body: 
 | 
			
		||||
					logged_in_player = str("anon##" + json_body.player_local_id)
 | 
			
		||||
				else:
 | 
			
		||||
					logged_in_player = "anon##unknown"
 | 
			
		||||
				SWLogger.info("Anonymous registration, logged in player: " + str(logged_in_player))
 | 
			
		||||
			else: 
 | 
			
		||||
				# if email confirmation is enabled for the game, we can't log in the player just yet
 | 
			
		||||
				var email_conf_enabled = json_body.email_conf_enabled
 | 
			
		||||
				if email_conf_enabled:
 | 
			
		||||
					SWLogger.info("Player registration succeeded, but player still needs to verify email address")
 | 
			
		||||
				else:
 | 
			
		||||
					SWLogger.info("Player registration succeeded, email verification is disabled")
 | 
			
		||||
					logged_in_player = tmp_username
 | 
			
		||||
		else:
 | 
			
		||||
			SWLogger.error("SilentWolf player registration failure: " + str(json_body.error))
 | 
			
		||||
		sw_registration_complete.emit(sw_result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func register_player_user_password(player_name: String, password: String, confirm_password: String) -> Node:
 | 
			
		||||
	var prepared_http_req = SilentWolf.prepare_http_request()
 | 
			
		||||
	RegisterPlayer = prepared_http_req.request
 | 
			
		||||
	wrRegisterPlayer = prepared_http_req.weakref
 | 
			
		||||
	RegisterPlayer.request_completed.connect(_on_RegisterPlayerUserPassword_request_completed)
 | 
			
		||||
	SWLogger.info("Calling SilentWolf to register a player")
 | 
			
		||||
	var payload = { "game_id": SilentWolf.config.game_id, "player_name": player_name, "password": password, "confirm_password": confirm_password }
 | 
			
		||||
	var request_url = "https://api.silentwolf.com/create_new_player"
 | 
			
		||||
	SilentWolf.send_post_request(RegisterPlayer, request_url, payload)
 | 
			
		||||
	return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_RegisterPlayerUserPassword_request_completed(result, response_code, headers, body) -> void:
 | 
			
		||||
	var status_check = SWUtils.check_http_response(response_code, headers, body)
 | 
			
		||||
	#RegisterPlayer.queue_free()
 | 
			
		||||
	SilentWolf.free_request(wrRegisterPlayer, RegisterPlayer)
 | 
			
		||||
	
 | 
			
		||||
	if status_check:
 | 
			
		||||
		var json_body = JSON.parse_string(body.get_string_from_utf8())
 | 
			
		||||
		# also get a JWT token here
 | 
			
		||||
		# send a different signal depending on registration success or failure
 | 
			
		||||
		var sw_result: Dictionary = SilentWolf.build_result(json_body)
 | 
			
		||||
		if json_body.success:
 | 
			
		||||
			# if email confirmation is enabled for the game, we can't log in the player just yet
 | 
			
		||||
			var email_conf_enabled = json_body.email_conf_enabled
 | 
			
		||||
			SWLogger.info("Player registration with username/password succeeded, player account autoconfirmed.")
 | 
			
		||||
			logged_in_player = tmp_username
 | 
			
		||||
		else:
 | 
			
		||||
			SWLogger.error("SilentWolf username/password player registration failure: " + str(json_body.error))
 | 
			
		||||
		sw_registration_user_pwd_complete.emit(sw_result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func verify_email(player_name: String, code: String) -> Node:
 | 
			
		||||
	var prepared_http_req = SilentWolf.prepare_http_request()
 | 
			
		||||
	VerifyEmail = prepared_http_req.request
 | 
			
		||||
	wrVerifyEmail = prepared_http_req.weakref
 | 
			
		||||
	VerifyEmail.request_completed.connect(_on_VerifyEmail_request_completed)
 | 
			
		||||
	SWLogger.info("Calling SilentWolf to verify email address for: " + str(player_name))
 | 
			
		||||
	var payload = { "game_id": SilentWolf.config.game_id, "username":  player_name, "code": code }
 | 
			
		||||
	var request_url = "https://api.silentwolf.com/confirm_verif_code"
 | 
			
		||||
	SilentWolf.send_post_request(VerifyEmail, request_url, payload)
 | 
			
		||||
	return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_VerifyEmail_request_completed(result, response_code, headers, body) -> void:
 | 
			
		||||
	var status_check = SWUtils.check_http_response(response_code, headers, body)
 | 
			
		||||
	SilentWolf.free_request(wrVerifyEmail, VerifyEmail)
 | 
			
		||||
	
 | 
			
		||||
	if status_check:
 | 
			
		||||
		var json_body = JSON.parse_string(body.get_string_from_utf8())
 | 
			
		||||
		SWLogger.info("SilentWolf verify email success? : " + str(json_body.success))
 | 
			
		||||
		# also get a JWT token here
 | 
			
		||||
		# send a different signal depending on registration success or failure
 | 
			
		||||
		var sw_result: Dictionary = SilentWolf.build_result(json_body)
 | 
			
		||||
		if json_body.success:
 | 
			
		||||
			SWLogger.info("SilentWolf email verification success.")
 | 
			
		||||
			logged_in_player  = tmp_username
 | 
			
		||||
		else:
 | 
			
		||||
			SWLogger.error("SilentWolf email verification failure: " + str(json_body.error))
 | 
			
		||||
		sw_email_verif_complete.emit(sw_result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func resend_conf_code(player_name: String) -> Node:
 | 
			
		||||
	var prepared_http_req = SilentWolf.prepare_http_request()
 | 
			
		||||
	ResendConfCode = prepared_http_req.request
 | 
			
		||||
	wrResendConfCode = prepared_http_req.weakref
 | 
			
		||||
	ResendConfCode.request_completed.connect(_on_ResendConfCode_request_completed)
 | 
			
		||||
	SWLogger.info("Calling SilentWolf to resend confirmation code for: " + str(player_name))
 | 
			
		||||
	var payload = { "game_id": SilentWolf.config.game_id, "username": player_name }
 | 
			
		||||
	var request_url = "https://api.silentwolf.com/resend_conf_code"
 | 
			
		||||
	SilentWolf.send_post_request(ResendConfCode, request_url, payload)
 | 
			
		||||
	return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_ResendConfCode_request_completed(result, response_code, headers, body) -> void:
 | 
			
		||||
	SWLogger.info("ResendConfCode request completed")
 | 
			
		||||
	var status_check = SWUtils.check_http_response(response_code, headers, body)
 | 
			
		||||
	SilentWolf.free_request(wrResendConfCode, ResendConfCode)
 | 
			
		||||
	
 | 
			
		||||
	if status_check:
 | 
			
		||||
		var json_body = JSON.parse_string(body.get_string_from_utf8())
 | 
			
		||||
		# also get a JWT token here
 | 
			
		||||
		# send a different signal depending on registration success or failure
 | 
			
		||||
		var sw_result: Dictionary = SilentWolf.build_result(json_body)
 | 
			
		||||
		if json_body.success:
 | 
			
		||||
			SWLogger.info("SilentWolf resend conf code success.")
 | 
			
		||||
		else:
 | 
			
		||||
			SWLogger.error("SilentWolf resend conf code failure: " + str(json_body.error))
 | 
			
		||||
		sw_resend_conf_code_complete.emit(sw_result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func login_player(username: String, password: String, remember_me:bool=false) -> Node:
 | 
			
		||||
	tmp_username = username
 | 
			
		||||
	var prepared_http_req = SilentWolf.prepare_http_request()
 | 
			
		||||
	LoginPlayer = prepared_http_req.request
 | 
			
		||||
	wrLoginPlayer = prepared_http_req.weakref
 | 
			
		||||
	LoginPlayer.request_completed.connect(_on_LoginPlayer_request_completed)
 | 
			
		||||
	SWLogger.info("Calling SilentWolf to log in a player")
 | 
			
		||||
	var payload = { "game_id": SilentWolf.config.game_id, "username": username, "password": password, "remember_me": str(remember_me) }
 | 
			
		||||
	if SilentWolf.auth_config.has("saved_session_expiration_days") and typeof(SilentWolf.auth_config.saved_session_expiration_days) == 2:
 | 
			
		||||
		payload["remember_me_expires_in"] = str(SilentWolf.auth_config.saved_session_expiration_days)
 | 
			
		||||
	var payload_for_logging = payload
 | 
			
		||||
	var obfuscated_password = SWUtils.obfuscate_string(payload["password"])
 | 
			
		||||
	print("obfuscated password: " + str(obfuscated_password))
 | 
			
		||||
	payload_for_logging["password"] = obfuscated_password
 | 
			
		||||
	SWLogger.debug("SilentWolf login player payload: " + str(payload_for_logging))
 | 
			
		||||
	var request_url = "https://api.silentwolf.com/login_player"
 | 
			
		||||
	SilentWolf.send_post_request(LoginPlayer, request_url, payload)
 | 
			
		||||
	return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_LoginPlayer_request_completed(result, response_code, headers, body) -> void:
 | 
			
		||||
	var status_check = SWUtils.check_http_response(response_code, headers, body)
 | 
			
		||||
	SilentWolf.free_request(wrLoginPlayer, LoginPlayer)
 | 
			
		||||
	
 | 
			
		||||
	if status_check:
 | 
			
		||||
		var json_body = JSON.parse_string(body.get_string_from_utf8())
 | 
			
		||||
		if "lookup" in json_body.keys():
 | 
			
		||||
			SWLogger.debug("remember me lookup: " + str(json_body.lookup))
 | 
			
		||||
			save_session(json_body.lookup, json_body.validator)
 | 
			
		||||
		if "validator" in json_body.keys():
 | 
			
		||||
			SWLogger.debug("remember me validator: " + str(json_body.validator))
 | 
			
		||||
		# send a different signal depending on login success or failure
 | 
			
		||||
		var sw_result: Dictionary = SilentWolf.build_result(json_body)
 | 
			
		||||
		if json_body.success:
 | 
			
		||||
			SWLogger.info("SilentWolf resend conf code success.")
 | 
			
		||||
			sw_access_token = json_body.swtoken
 | 
			
		||||
			sw_id_token = json_body.swidtoken
 | 
			
		||||
			set_player_logged_in(tmp_username)
 | 
			
		||||
		else:
 | 
			
		||||
			SWLogger.error("SilentWolf login player failure: " + str(json_body.error))
 | 
			
		||||
		sw_login_complete.emit(sw_result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func logout_player() -> void:
 | 
			
		||||
	logged_in_player = null
 | 
			
		||||
	# remove any player data if present
 | 
			
		||||
	SilentWolf.Players.clear_player_data()
 | 
			
		||||
	# remove stored session if any
 | 
			
		||||
	var delete_success = remove_stored_session()
 | 
			
		||||
	print("delete_success: " + str(delete_success))
 | 
			
		||||
	sw_access_token = null
 | 
			
		||||
	sw_id_token = null
 | 
			
		||||
	sw_logout_complete.emit(true, "")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func request_player_password_reset(player_name: String) -> Node:
 | 
			
		||||
	var prepared_http_req = SilentWolf.prepare_http_request()
 | 
			
		||||
	RequestPasswordReset = prepared_http_req.request
 | 
			
		||||
	wrRequestPasswordReset = prepared_http_req.weakref
 | 
			
		||||
	RequestPasswordReset.request_completed.connect(_on_RequestPasswordReset_request_completed)
 | 
			
		||||
	SWLogger.info("Calling SilentWolf to request a password reset for: " + str(player_name))
 | 
			
		||||
	var payload = { "game_id": SilentWolf.config.game_id, "player_name": player_name }
 | 
			
		||||
	SWLogger.debug("SilentWolf request player password reset payload: " + str(payload))
 | 
			
		||||
	var request_url = "https://api.silentwolf.com/request_player_password_reset"
 | 
			
		||||
	SilentWolf.send_post_request(RequestPasswordReset, request_url, payload)
 | 
			
		||||
	return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_RequestPasswordReset_request_completed(result, response_code, headers, body) -> void:
 | 
			
		||||
	SWLogger.info("RequestPasswordReset request completed")
 | 
			
		||||
	var status_check = SWUtils.check_http_response(response_code, headers, body)
 | 
			
		||||
	SilentWolf.free_request(wrRequestPasswordReset, RequestPasswordReset)
 | 
			
		||||
	
 | 
			
		||||
	if status_check:
 | 
			
		||||
		var json_body = JSON.parse_string(body.get_string_from_utf8())
 | 
			
		||||
		var sw_result: Dictionary = SilentWolf.build_result(json_body)
 | 
			
		||||
		if json_body.success:
 | 
			
		||||
			SWLogger.info("SilentWolf request player password reset success.")
 | 
			
		||||
		else:
 | 
			
		||||
			SWLogger.error("SilentWolf request password reset failure: " + str(json_body.error))
 | 
			
		||||
		sw_request_password_reset_complete.emit(sw_result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func reset_player_password(player_name: String, conf_code: String, new_password: String, confirm_password: String) -> Node:
 | 
			
		||||
	var prepared_http_req = SilentWolf.prepare_http_request()
 | 
			
		||||
	ResetPassword = prepared_http_req.request
 | 
			
		||||
	wrResetPassword = prepared_http_req.weakref
 | 
			
		||||
	ResetPassword.request_completed.connect(_on_ResetPassword_completed)
 | 
			
		||||
	SWLogger.info("Calling SilentWolf to reset password for: " + str(player_name))
 | 
			
		||||
	var payload = { "game_id": SilentWolf.config.game_id, "player_name": player_name, "conf_code": conf_code, "password": new_password, "confirm_password": confirm_password }
 | 
			
		||||
	SWLogger.debug("SilentWolf request player password reset payload: " + str(payload))
 | 
			
		||||
	var request_url = "https://api.silentwolf.com/reset_player_password"
 | 
			
		||||
	SilentWolf.send_post_request(ResetPassword, request_url, payload)
 | 
			
		||||
	return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_ResetPassword_completed(result, response_code, headers, body) -> void:
 | 
			
		||||
	var status_check = SWUtils.check_http_response(response_code, headers, body)
 | 
			
		||||
	SilentWolf.free_request(wrResetPassword, ResetPassword)
 | 
			
		||||
 | 
			
		||||
	if status_check:
 | 
			
		||||
		var json_body = JSON.parse_string(body.get_string_from_utf8())
 | 
			
		||||
		var sw_result: Dictionary = SilentWolf.build_result(json_body)
 | 
			
		||||
		if json_body.success:
 | 
			
		||||
			SWLogger.info("SilentWolf reset player password success.")
 | 
			
		||||
		else:
 | 
			
		||||
			SWLogger.error("SilentWolf reset password failure: " + str(json_body.error))
 | 
			
		||||
		sw_reset_password_complete.emit(sw_result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func get_player_details(player_name: String) -> Node:
 | 
			
		||||
	var prepared_http_req = SilentWolf.prepare_http_request()
 | 
			
		||||
	GetPlayerDetails = prepared_http_req.request
 | 
			
		||||
	wrGetPlayerDetails = prepared_http_req.weakref
 | 
			
		||||
	GetPlayerDetails.request_completed.connect(_on_GetPlayerDetails_request_completed)
 | 
			
		||||
	SWLogger.info("Calling SilentWolf to get player details")
 | 
			
		||||
	var payload = { "game_id": SilentWolf.config.game_id, "player_name": player_name }
 | 
			
		||||
	var request_url = "https://api.silentwolf.com/get_player_details"
 | 
			
		||||
	SilentWolf.send_post_request(GetPlayerDetails, request_url, payload)
 | 
			
		||||
	return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_GetPlayerDetails_request_completed(result, response_code, headers, body) -> void:
 | 
			
		||||
	var status_check = SWUtils.check_http_response(response_code, headers, body)
 | 
			
		||||
	SilentWolf.free_request(wrGetPlayerDetails, GetPlayerDetails)
 | 
			
		||||
	
 | 
			
		||||
	if status_check:
 | 
			
		||||
		var json_body = JSON.parse_string(body.get_string_from_utf8())
 | 
			
		||||
		var sw_result: Dictionary = SilentWolf.build_result(json_body)
 | 
			
		||||
		if json_body.success:
 | 
			
		||||
			SWLogger.info("SilentWolf get player details success: " + str(json_body.player_details))
 | 
			
		||||
			sw_result["player_details"] = json_body.player_details
 | 
			
		||||
		else:
 | 
			
		||||
			SWLogger.error("SilentWolf get player details failure: " + str(json_body.error))
 | 
			
		||||
		sw_get_player_details_complete.emit(sw_result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func validate_player_session(lookup: String, validator: String, scene: Node=get_tree().get_current_scene()) -> Node:
 | 
			
		||||
	var prepared_http_req = SilentWolf.prepare_http_request()
 | 
			
		||||
	ValidateSession = prepared_http_req.request
 | 
			
		||||
	wrValidateSession = prepared_http_req.weakref
 | 
			
		||||
	ValidateSession.request_completed.connect(_on_ValidateSession_request_completed)
 | 
			
		||||
	SWLogger.info("Calling SilentWolf to validate an existing player session")
 | 
			
		||||
	var payload = { "game_id": SilentWolf.config.game_id, "lookup": lookup, "validator": validator }
 | 
			
		||||
	SWLogger.debug("Validate session payload: " + str(payload))
 | 
			
		||||
	var request_url = "https://api.silentwolf.com/validate_remember_me"
 | 
			
		||||
	SilentWolf.send_post_request(ValidateSession, request_url, payload)
 | 
			
		||||
	return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_ValidateSession_request_completed(result, response_code, headers, body) -> void:
 | 
			
		||||
	var status_check = SWUtils.check_http_response(response_code, headers, body)
 | 
			
		||||
	SilentWolf.free_request(wrValidateSession, ValidateSession)
 | 
			
		||||
	
 | 
			
		||||
	if status_check:
 | 
			
		||||
		var json_body = JSON.parse_string(body.get_string_from_utf8())
 | 
			
		||||
		var sw_result: Dictionary = SilentWolf.build_result(json_body)
 | 
			
		||||
		if json_body.success:
 | 
			
		||||
			SWLogger.info("SilentWolf validate session success.")	
 | 
			
		||||
			set_player_logged_in(json_body.player_name)
 | 
			
		||||
			sw_result["logged_in_player"] = logged_in_player
 | 
			
		||||
		else:
 | 
			
		||||
			SWLogger.error("SilentWolf validate session failure: " + str(json_body.error))
 | 
			
		||||
		complete_session_check(sw_result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func auto_login_player() -> Node:
 | 
			
		||||
	var sw_session_data = load_session()
 | 
			
		||||
	SWLogger.debug("SW session data " + str(sw_session_data))
 | 
			
		||||
	if sw_session_data:
 | 
			
		||||
		SWLogger.debug("Found saved SilentWolf session data, attempting autologin...")
 | 
			
		||||
		var lookup = sw_session_data.lookup
 | 
			
		||||
		var validator = sw_session_data.validator
 | 
			
		||||
		# whether successful or not, in the end the "sw_session_check_complete" signal will be emitted
 | 
			
		||||
		validate_player_session(lookup, validator)
 | 
			
		||||
	else:
 | 
			
		||||
		SWLogger.debug("No saved SilentWolf session data, so no autologin will be performed")
 | 
			
		||||
		# the following is needed to delay the emission of the signal just a little bit, otherwise the signal is never received!
 | 
			
		||||
		setup_complete_session_check_wait_timer()
 | 
			
		||||
		complete_session_check_wait_timer.start()
 | 
			
		||||
	return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func set_player_logged_in(player_name: String) -> void:
 | 
			
		||||
	logged_in_player  = player_name
 | 
			
		||||
	SWLogger.info("SilentWolf - player logged in as " + str(player_name))
 | 
			
		||||
	if SilentWolf.auth_config.has("session_duration_seconds") and typeof(SilentWolf.auth_config.session_duration_seconds) == 2:
 | 
			
		||||
		login_timeout = SilentWolf.auth_config.session_duration_seconds
 | 
			
		||||
	else:
 | 
			
		||||
		login_timeout = 0
 | 
			
		||||
	SWLogger.info("SilentWolf login timeout: " + str(login_timeout))
 | 
			
		||||
	if login_timeout != 0:
 | 
			
		||||
		setup_login_timer()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func get_anon_user_id() -> String:
 | 
			
		||||
	var anon_user_id = OS.get_unique_id()
 | 
			
		||||
	if anon_user_id == '':
 | 
			
		||||
		anon_user_id = UUID.generate_uuid_v4()
 | 
			
		||||
	print("anon_user_id: " + str(anon_user_id))
 | 
			
		||||
	return anon_user_id
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func remove_stored_session() -> bool:
 | 
			
		||||
	var path = "user://swsession.save"
 | 
			
		||||
	var delete_success = SWLocalFileStorage.remove_data(path, "Removing SilentWolf session if any: " )
 | 
			
		||||
	return delete_success
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Signal can't be emitted directly from auto_login_player() function
 | 
			
		||||
# otherwise it won't connect back to calling script
 | 
			
		||||
func complete_session_check(sw_result=null) -> void:
 | 
			
		||||
	SWLogger.debug("SilentWolf: completing session check")
 | 
			
		||||
	sw_session_check_complete.emit(sw_result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func setup_complete_session_check_wait_timer() -> void:
 | 
			
		||||
	complete_session_check_wait_timer = Timer.new()
 | 
			
		||||
	complete_session_check_wait_timer.set_one_shot(true)
 | 
			
		||||
	complete_session_check_wait_timer.set_wait_time(0.01)
 | 
			
		||||
	complete_session_check_wait_timer.timeout.connect(complete_session_check)
 | 
			
		||||
	add_child(complete_session_check_wait_timer)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func setup_login_timer() -> void:
 | 
			
		||||
	login_timer = Timer.new()
 | 
			
		||||
	login_timer.set_one_shot(true)
 | 
			
		||||
	login_timer.set_wait_time(login_timeout)
 | 
			
		||||
	login_timer.timeout.connect(on_login_timeout_complete)
 | 
			
		||||
	add_child(login_timer)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func on_login_timeout_complete() -> void:
 | 
			
		||||
	logout_player()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# store lookup (not logged in player name) and validator in local file
 | 
			
		||||
func save_session(lookup: String, validator: String) -> void:
 | 
			
		||||
	SWLogger.debug("Saving session, lookup: " + str(lookup) + ", validator: " + str(validator))
 | 
			
		||||
	var path = "user://swsession.save"
 | 
			
		||||
	var session_data: Dictionary = {
 | 
			
		||||
		"lookup": lookup,
 | 
			
		||||
		"validator": validator
 | 
			
		||||
	}
 | 
			
		||||
	SWLocalFileStorage.save_data("user://swsession.save", session_data, "Saving SilentWolf session: ")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# reload lookup and validator and send them back to the server to auto-login user
 | 
			
		||||
func load_session() -> Dictionary:
 | 
			
		||||
	var sw_session_data = null
 | 
			
		||||
	var path = "user://swsession.save"
 | 
			
		||||
	sw_session_data = SWLocalFileStorage.get_data(path)
 | 
			
		||||
	if sw_session_data == null:
 | 
			
		||||
		SWLogger.debug("No local SilentWolf session stored, or session data stored in incorrect format")
 | 
			
		||||
	SWLogger.info("Found session data: " + str(sw_session_data))
 | 
			
		||||
	return sw_session_data
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
uid://cpvig1whdvguk
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,71 @@
 | 
			
		|||
extends TextureRect
 | 
			
		||||
 | 
			
		||||
const SWLogger = preload("res://addons/silent_wolf/utils/SWLogger.gd")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _ready():
 | 
			
		||||
	SilentWolf.Auth.sw_email_verif_complete.connect(_on_confirmation_complete)
 | 
			
		||||
	SilentWolf.Auth.sw_resend_conf_code_complete.connect(_on_resend_code_complete)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_confirmation_complete(sw_result: Dictionary) -> void:
 | 
			
		||||
	if sw_result.success:
 | 
			
		||||
		confirmation_success()
 | 
			
		||||
	else:
 | 
			
		||||
		confirmation_failure(sw_result.error)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func confirmation_success() -> void:
 | 
			
		||||
	SWLogger.info("email verification succeeded: " + str(SilentWolf.Auth.logged_in_player))
 | 
			
		||||
	# redirect to configured scene (user is logged in after registration)
 | 
			
		||||
	var scene_name = SilentWolf.auth_config.redirect_to_scene
 | 
			
		||||
	get_tree().change_scene_to_file(scene_name)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func confirmation_failure(error: String) -> void:
 | 
			
		||||
	hide_processing_label()
 | 
			
		||||
	SWLogger.info("email verification failed: " + str(error))
 | 
			
		||||
	$"FormContainer/ErrorMessage".text = error
 | 
			
		||||
	$"FormContainer/ErrorMssage".show()
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
func _on_resend_code_complete(sw_result: Dictionary) -> void:
 | 
			
		||||
	if sw_result.success:
 | 
			
		||||
		resend_code_success()
 | 
			
		||||
	else:
 | 
			
		||||
		resend_code_failure()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func resend_code_success() -> void:
 | 
			
		||||
	SWLogger.info("Code resend succeeded for player: " + str(SilentWolf.Auth.tmp_username))
 | 
			
		||||
	$"FormContainer/ErrorMessage".text = "Confirmation code was resent to your email address. Please check your inbox (and your spam)."
 | 
			
		||||
	$"FormContainer/ErrorMessage".show()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func resend_code_failure() -> void:
 | 
			
		||||
	SWLogger.info("Code resend failed for player: " + str(SilentWolf.Auth.tmp_username))
 | 
			
		||||
	$"FormContainer/ErrorMessage".text = "Confirmation code could not be resent"
 | 
			
		||||
	$"FormContainer/ErrorMessage".show()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func show_processing_label() -> void:
 | 
			
		||||
	$"FormContainer/ProcessingLabel".show()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func hide_processing_label() -> void:
 | 
			
		||||
	$"FormContainer/ProcessingLabel".hide()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_ConfirmButton_pressed() -> void:
 | 
			
		||||
	var username = SilentWolf.Auth.tmp_username
 | 
			
		||||
	var code = $"FormContainer/CodeContainer/VerifCode".text
 | 
			
		||||
	SWLogger.debug("Email verification form submitted, code: " + str(code))
 | 
			
		||||
	SilentWolf.Auth.verify_email(username, code)
 | 
			
		||||
	show_processing_label()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_ResendConfCodeButton_pressed() -> void:
 | 
			
		||||
	var username = SilentWolf.Auth.tmp_username
 | 
			
		||||
	SWLogger.debug("Requesting confirmation code resend")
 | 
			
		||||
	SilentWolf.Auth.resend_conf_code(username)
 | 
			
		||||
	show_processing_label()
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
uid://dlaymvnl1b3ur
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,71 @@
 | 
			
		|||
[gd_scene load_steps=4 format=3 uid="uid://bsh33b4p03ayd"]
 | 
			
		||||
 | 
			
		||||
[ext_resource type="Script" uid="uid://dlaymvnl1b3ur" path="res://addons/silent_wolf/Auth/ConfirmEmail.gd" id="1"]
 | 
			
		||||
[ext_resource type="Theme" uid="uid://d2eakbmaefnt6" path="res://addons/silent_wolf/assets/themes/sw_theme.tres" id="1_wyfy3"]
 | 
			
		||||
[ext_resource type="PackedScene" uid="uid://clllbf6am8qdf" path="res://addons/silent_wolf/common/SWButton.tscn" id="2"]
 | 
			
		||||
 | 
			
		||||
[node name="ConfirmEmail" type="TextureRect"]
 | 
			
		||||
anchors_preset = 15
 | 
			
		||||
anchor_right = 1.0
 | 
			
		||||
anchor_bottom = 1.0
 | 
			
		||||
offset_right = -1920.0
 | 
			
		||||
offset_bottom = -1080.0
 | 
			
		||||
theme = ExtResource("1_wyfy3")
 | 
			
		||||
script = ExtResource("1")
 | 
			
		||||
 | 
			
		||||
[node name="FormContainer" type="VBoxContainer" parent="."]
 | 
			
		||||
layout_mode = 0
 | 
			
		||||
offset_left = 562.0
 | 
			
		||||
offset_top = 221.0
 | 
			
		||||
offset_right = 1096.0
 | 
			
		||||
offset_bottom = 804.0
 | 
			
		||||
theme_override_constants/separation = 24
 | 
			
		||||
 | 
			
		||||
[node name="Label" type="Label" parent="FormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_font_sizes/font_size = 72
 | 
			
		||||
text = "Confirm your email address"
 | 
			
		||||
horizontal_alignment = 1
 | 
			
		||||
 | 
			
		||||
[node name="CodeContainer" type="HBoxContainer" parent="FormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_horizontal = 4
 | 
			
		||||
size_flags_vertical = 6
 | 
			
		||||
 | 
			
		||||
[node name="Label" type="Label" parent="FormContainer/CodeContainer"]
 | 
			
		||||
custom_minimum_size = Vector2(160, 0)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
text = "Code"
 | 
			
		||||
 | 
			
		||||
[node name="VerifCode" type="LineEdit" parent="FormContainer/CodeContainer"]
 | 
			
		||||
custom_minimum_size = Vector2(220, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
max_length = 30
 | 
			
		||||
 | 
			
		||||
[node name="ErrorMessage" type="Label" parent="FormContainer"]
 | 
			
		||||
visible = false
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_colors/font_color = Color(0.92549, 0, 0, 1)
 | 
			
		||||
autowrap_mode = 1
 | 
			
		||||
 | 
			
		||||
[node name="ConfirmButton" parent="FormContainer" instance=ExtResource("2")]
 | 
			
		||||
custom_minimum_size = Vector2(200, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_horizontal = 4
 | 
			
		||||
text = "Submit"
 | 
			
		||||
 | 
			
		||||
[node name="ResendConfCodeButton" parent="FormContainer" instance=ExtResource("2")]
 | 
			
		||||
custom_minimum_size = Vector2(320, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_horizontal = 4
 | 
			
		||||
text = "Resend code"
 | 
			
		||||
 | 
			
		||||
[node name="ProcessingLabel" type="Label" parent="FormContainer"]
 | 
			
		||||
visible = false
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_font_sizes/font_size = 36
 | 
			
		||||
text = "Processing..."
 | 
			
		||||
horizontal_alignment = 1
 | 
			
		||||
 | 
			
		||||
[connection signal="pressed" from="FormContainer/ConfirmButton" to="." method="_on_ConfirmButton_pressed"]
 | 
			
		||||
[connection signal="pressed" from="FormContainer/ResendConfCodeButton" to="." method="_on_ResendConfCodeButton_pressed"]
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,54 @@
 | 
			
		|||
extends TextureRect
 | 
			
		||||
 | 
			
		||||
const SWLogger = preload("res://addons/silent_wolf/utils/SWLogger.gd")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _ready():
 | 
			
		||||
	SilentWolf.Auth.sw_login_complete.connect(_on_login_complete)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_LoginButton_pressed() -> void:
 | 
			
		||||
	var username = $"FormContainer/UsernameContainer/Username".text
 | 
			
		||||
	var password = $"FormContainer/PasswordContainer/Password".text
 | 
			
		||||
	var remember_me = $"FormContainer/RememberMeCheckBox".is_pressed()
 | 
			
		||||
	SWLogger.debug("Login form submitted, remember_me: " + str(remember_me))
 | 
			
		||||
	SilentWolf.Auth.login_player(username, password, remember_me)
 | 
			
		||||
	show_processing_label()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_login_complete(sw_result: Dictionary) -> void:
 | 
			
		||||
	if sw_result.success:
 | 
			
		||||
		login_success()
 | 
			
		||||
	else:
 | 
			
		||||
		login_failure(sw_result.error)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func login_success() -> void:
 | 
			
		||||
	var scene_name = SilentWolf.auth_config.redirect_to_scene
 | 
			
		||||
	SWLogger.info("logged in as: " + str(SilentWolf.Auth.logged_in_player))
 | 
			
		||||
	get_tree().change_scene_to_file(scene_name)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func login_failure(error: String) -> void:
 | 
			
		||||
	hide_processing_label()
 | 
			
		||||
	SWLogger.info("log in failed: " + str(error))
 | 
			
		||||
	$"FormContainer/ErrorMessage".text = error
 | 
			
		||||
	$"FormContainer/ErrorMessage".show()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func show_processing_label() -> void:
 | 
			
		||||
	$"FormContainer/ProcessingLabel".show()
 | 
			
		||||
	$"FormContainer/ProcessingLabel".show()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func hide_processing_label() -> void:
 | 
			
		||||
	$"FormContainer/ProcessingLabel".hide()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_LinkButton_pressed() -> void:
 | 
			
		||||
	get_tree().change_scene_to_file(SilentWolf.auth_config.reset_password_scene)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_back_button_pressed():
 | 
			
		||||
	print("Back button pressed")
 | 
			
		||||
	get_tree().change_scene_to_file(SilentWolf.auth_config.redirect_to_scene)
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
uid://srgdira4bqf2
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,124 @@
 | 
			
		|||
[gd_scene load_steps=9 format=3 uid="uid://bi7lxcglf2tnc"]
 | 
			
		||||
 | 
			
		||||
[ext_resource type="Script" uid="uid://srgdira4bqf2" path="res://addons/silent_wolf/Auth/Login.gd" id="1"]
 | 
			
		||||
[ext_resource type="Theme" uid="uid://d2eakbmaefnt6" path="res://addons/silent_wolf/assets/themes/sw_theme.tres" id="1_bbh8k"]
 | 
			
		||||
[ext_resource type="PackedScene" uid="uid://clllbf6am8qdf" path="res://addons/silent_wolf/common/SWButton.tscn" id="2"]
 | 
			
		||||
[ext_resource type="Texture2D" uid="uid://bpn62aabunxva" path="res://addons/silent_wolf/assets/gfx/checkbox_checked.png" id="4_ak74a"]
 | 
			
		||||
[ext_resource type="Texture2D" uid="uid://bgs8a50ilk5cj" path="res://addons/silent_wolf/assets/gfx/checkbox_unchecked.png" id="5_3asm1"]
 | 
			
		||||
 | 
			
		||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_oxxi6"]
 | 
			
		||||
bg_color = Color(0.301961, 0.301961, 0.301961, 1)
 | 
			
		||||
 | 
			
		||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qt8ss"]
 | 
			
		||||
bg_color = Color(0.301961, 0.301961, 0.301961, 1)
 | 
			
		||||
 | 
			
		||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_o52ik"]
 | 
			
		||||
bg_color = Color(0.301961, 0.301961, 0.301961, 1)
 | 
			
		||||
 | 
			
		||||
[node name="Login" type="TextureRect"]
 | 
			
		||||
anchors_preset = -1
 | 
			
		||||
anchor_right = 0.989
 | 
			
		||||
anchor_bottom = 0.984
 | 
			
		||||
offset_right = -1898.88
 | 
			
		||||
offset_bottom = -1062.72
 | 
			
		||||
mouse_filter = 2
 | 
			
		||||
theme = ExtResource("1_bbh8k")
 | 
			
		||||
script = ExtResource("1")
 | 
			
		||||
 | 
			
		||||
[node name="BackButton" parent="." instance=ExtResource("2")]
 | 
			
		||||
process_priority = 1
 | 
			
		||||
layout_mode = 0
 | 
			
		||||
offset_left = 595.0
 | 
			
		||||
offset_top = 148.0
 | 
			
		||||
offset_right = 740.0
 | 
			
		||||
offset_bottom = 197.0
 | 
			
		||||
focus_mode = 1
 | 
			
		||||
text = "← Back"
 | 
			
		||||
 | 
			
		||||
[node name="FormContainer" type="VBoxContainer" parent="."]
 | 
			
		||||
layout_mode = 0
 | 
			
		||||
offset_left = 435.0
 | 
			
		||||
offset_top = 226.0
 | 
			
		||||
offset_right = 1562.0
 | 
			
		||||
offset_bottom = 990.0
 | 
			
		||||
theme_override_constants/separation = 24
 | 
			
		||||
alignment = 1
 | 
			
		||||
 | 
			
		||||
[node name="Label" type="Label" parent="FormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_constants/line_spacing = 16
 | 
			
		||||
theme_override_font_sizes/font_size = 72
 | 
			
		||||
text = "Log in"
 | 
			
		||||
horizontal_alignment = 1
 | 
			
		||||
 | 
			
		||||
[node name="UsernameContainer" type="HBoxContainer" parent="FormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
alignment = 1
 | 
			
		||||
 | 
			
		||||
[node name="Label" type="Label" parent="FormContainer/UsernameContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
text = "Username:     "
 | 
			
		||||
horizontal_alignment = 1
 | 
			
		||||
 | 
			
		||||
[node name="Username" type="LineEdit" parent="FormContainer/UsernameContainer"]
 | 
			
		||||
custom_minimum_size = Vector2(250, 0)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
max_length = 30
 | 
			
		||||
 | 
			
		||||
[node name="PasswordContainer" type="HBoxContainer" parent="FormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
alignment = 1
 | 
			
		||||
 | 
			
		||||
[node name="Label" type="Label" parent="FormContainer/PasswordContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
text = "Password:      "
 | 
			
		||||
 | 
			
		||||
[node name="Password" type="LineEdit" parent="FormContainer/PasswordContainer"]
 | 
			
		||||
custom_minimum_size = Vector2(250, 0)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
max_length = 30
 | 
			
		||||
secret = true
 | 
			
		||||
 | 
			
		||||
[node name="LinkButton" type="LinkButton" parent="FormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_horizontal = 4
 | 
			
		||||
theme_override_constants/outline_size = 0
 | 
			
		||||
text = "Forgot Password?"
 | 
			
		||||
 | 
			
		||||
[node name="RememberMeCheckBox" type="CheckBox" parent="FormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_horizontal = 4
 | 
			
		||||
focus_mode = 0
 | 
			
		||||
theme_override_colors/font_color = Color(1, 1, 1, 1)
 | 
			
		||||
theme_override_colors/font_pressed_color = Color(1, 1, 1, 1)
 | 
			
		||||
theme_override_colors/font_hover_color = Color(1, 1, 1, 1)
 | 
			
		||||
theme_override_icons/checked = ExtResource("4_ak74a")
 | 
			
		||||
theme_override_icons/unchecked = ExtResource("5_3asm1")
 | 
			
		||||
theme_override_styles/normal = SubResource("StyleBoxFlat_oxxi6")
 | 
			
		||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_qt8ss")
 | 
			
		||||
theme_override_styles/hover = SubResource("StyleBoxFlat_o52ik")
 | 
			
		||||
text = "Stay signed in for 30 days"
 | 
			
		||||
expand_icon = true
 | 
			
		||||
 | 
			
		||||
[node name="ErrorMessage" type="Label" parent="FormContainer"]
 | 
			
		||||
custom_minimum_size = Vector2(0, 48)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_colors/font_color = Color(0.92549, 0, 0, 1)
 | 
			
		||||
autowrap_mode = 1
 | 
			
		||||
 | 
			
		||||
[node name="LoginButton" parent="FormContainer" instance=ExtResource("2")]
 | 
			
		||||
custom_minimum_size = Vector2(280, 120)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_horizontal = 4
 | 
			
		||||
theme_override_font_sizes/font_size = 48
 | 
			
		||||
text = "Submit"
 | 
			
		||||
 | 
			
		||||
[node name="ProcessingLabel" type="Label" parent="FormContainer"]
 | 
			
		||||
visible = false
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
text = "Processing..."
 | 
			
		||||
horizontal_alignment = 1
 | 
			
		||||
 | 
			
		||||
[connection signal="pressed" from="BackButton" to="." method="_on_back_button_pressed"]
 | 
			
		||||
[connection signal="pressed" from="FormContainer/LinkButton" to="." method="_on_LinkButton_pressed"]
 | 
			
		||||
[connection signal="pressed" from="FormContainer/LoginButton" to="." method="_on_LoginButton_pressed"]
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,93 @@
 | 
			
		|||
extends TextureRect
 | 
			
		||||
 | 
			
		||||
const SWLogger = preload("res://addons/silent_wolf/utils/SWLogger.gd")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _ready():
 | 
			
		||||
	SilentWolf.check_auth_ready()
 | 
			
		||||
	SilentWolf.Auth.sw_registration_complete.connect(_on_registration_complete)
 | 
			
		||||
	SilentWolf.Auth.sw_registration_user_pwd_complete.connect(_on_registration_user_pwd_complete)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_RegisterButton_pressed() -> void:
 | 
			
		||||
	var player_name = $"FormContainer/MainFormContainer/FormInputFields/PlayerName".text
 | 
			
		||||
	var email = $"FormContainer/MainFormContainer/FormInputFields/Email".text
 | 
			
		||||
	var password = $"FormContainer/MainFormContainer/FormInputFields/Password".text
 | 
			
		||||
	var confirm_password = $"FormContainer/MainFormContainer/FormInputFields/ConfirmPassword".text
 | 
			
		||||
	SilentWolf.Auth.register_player(player_name, email, password, confirm_password)
 | 
			
		||||
	show_processing_label()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_RegisterUPButton_pressed() -> void:
 | 
			
		||||
	var player_name = $"FormContainer/MainFormContainer/FormInputFields/PlayerName".text
 | 
			
		||||
	var password = $"FormContainer/MainFormContainer/FormInputFields/Password".text
 | 
			
		||||
	var confirm_password = $"FormContainer/MainFormContainer/FormInputFields/ConfirmPassword".text
 | 
			
		||||
	SilentWolf.Auth.register_player_user_password(player_name, password, confirm_password)
 | 
			
		||||
	show_processing_label()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_registration_complete(sw_result: Dictionary) -> void:
 | 
			
		||||
	if sw_result.success:
 | 
			
		||||
		registration_success()
 | 
			
		||||
	else:
 | 
			
		||||
		registration_failure(sw_result.error)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_registration_user_pwd_complete(sw_result: Dictionary) -> void:
 | 
			
		||||
	if sw_result.success:
 | 
			
		||||
		registration_user_pwd_success()
 | 
			
		||||
	else:
 | 
			
		||||
		registration_failure(sw_result.error)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func registration_success() -> void:
 | 
			
		||||
	# redirect to configured scene (user is logged in after registration)
 | 
			
		||||
	var scene_name = SilentWolf.auth_config.redirect_to_scene
 | 
			
		||||
	# if doing email verification, open scene to confirm email address
 | 
			
		||||
	if ("email_confirmation_scene" in SilentWolf.auth_config) and (SilentWolf.auth_config.email_confirmation_scene) != "":
 | 
			
		||||
		SWLogger.info("registration succeeded, waiting for email verification...")
 | 
			
		||||
		scene_name = SilentWolf.auth_config.email_confirmation_scene
 | 
			
		||||
	else:
 | 
			
		||||
		SWLogger.info("registration succeeded, logged in player: " + str(SilentWolf.Auth.logged_in_player))
 | 
			
		||||
	get_tree().change_scene_to_file(scene_name)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func registration_user_pwd_success() -> void:
 | 
			
		||||
	var scene_name = SilentWolf.auth_config.redirect_to_scene
 | 
			
		||||
	get_tree().change_scene_to_file(scene_name)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func registration_failure(error: String) -> void:
 | 
			
		||||
	hide_processing_label()
 | 
			
		||||
	$"FormContainer/ErrorMessage".text = error
 | 
			
		||||
	$"FormContainer/ErrorMessage".show()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_BackButton_pressed() -> void:
 | 
			
		||||
	get_tree().change_scene_to_file(SilentWolf.auth_config.redirect_to_scene)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func show_processing_label() -> void:
 | 
			
		||||
	$"FormContainer/ProcessingLabel".show()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func hide_processing_label() -> void:
 | 
			
		||||
	$"FormContainer/ProcessingLabel".hide()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_UsernameToolButton_mouse_entered() -> void:
 | 
			
		||||
	$"FormContainer/InfoBox".text = "Username should contain at least 6 characters (letters or numbers) and no spaces."
 | 
			
		||||
	$"FormContainer/InfoBox".show()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_UsernameToolButton_mouse_exited() -> void:
 | 
			
		||||
	$"FormContainer/InfoBox".hide()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_PasswordToolButton_mouse_entered() -> void:
 | 
			
		||||
	$"FormContainer/InfoBox".text = "Password should contain at least 8 characters including uppercase and lowercase letters, numbers and (optionally) special characters."
 | 
			
		||||
	$"FormContainer/InfoBox".show()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_PasswordToolButton_mouse_exited() -> void:
 | 
			
		||||
	$"FormContainer/InfoBox".hide()
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
uid://dvf4mw8y68sa
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,157 @@
 | 
			
		|||
[gd_scene load_steps=6 format=3 uid="uid://chxe78yjqftkl"]
 | 
			
		||||
 | 
			
		||||
[ext_resource type="Script" uid="uid://dvf4mw8y68sa" path="res://addons/silent_wolf/Auth/Register.gd" id="1"]
 | 
			
		||||
[ext_resource type="Theme" uid="uid://d2eakbmaefnt6" path="res://addons/silent_wolf/assets/themes/sw_theme.tres" id="1_dsws8"]
 | 
			
		||||
[ext_resource type="PackedScene" uid="uid://clllbf6am8qdf" path="res://addons/silent_wolf/common/SWButton.tscn" id="2"]
 | 
			
		||||
[ext_resource type="Texture2D" uid="uid://dh8fem1lgom04" path="res://addons/silent_wolf/assets/gfx/info_icon_small.png" id="5"]
 | 
			
		||||
[ext_resource type="Texture2D" uid="uid://gdw18po2h7hb" path="res://addons/silent_wolf/assets/gfx/dummy_info_icon_small.png" id="6"]
 | 
			
		||||
 | 
			
		||||
[node name="Register" type="TextureRect"]
 | 
			
		||||
anchors_preset = 15
 | 
			
		||||
anchor_right = 1.0
 | 
			
		||||
anchor_bottom = 1.0
 | 
			
		||||
offset_right = -1920.0
 | 
			
		||||
offset_bottom = -1080.0
 | 
			
		||||
grow_horizontal = 2
 | 
			
		||||
grow_vertical = 2
 | 
			
		||||
theme = ExtResource("1_dsws8")
 | 
			
		||||
script = ExtResource("1")
 | 
			
		||||
 | 
			
		||||
[node name="BackButton" parent="." instance=ExtResource("2")]
 | 
			
		||||
layout_mode = 0
 | 
			
		||||
offset_left = 215.0
 | 
			
		||||
offset_top = 51.0
 | 
			
		||||
offset_right = 281.0
 | 
			
		||||
offset_bottom = 82.0
 | 
			
		||||
theme_override_font_sizes/font_size = 36
 | 
			
		||||
text = "← Back"
 | 
			
		||||
 | 
			
		||||
[node name="FormContainer" type="VBoxContainer" parent="."]
 | 
			
		||||
layout_mode = 0
 | 
			
		||||
offset_left = 449.0
 | 
			
		||||
offset_top = 194.0
 | 
			
		||||
offset_right = 1436.0
 | 
			
		||||
offset_bottom = 998.0
 | 
			
		||||
theme_override_constants/separation = 36
 | 
			
		||||
 | 
			
		||||
[node name="Label" type="Label" parent="FormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_font_sizes/font_size = 72
 | 
			
		||||
text = "Sign up"
 | 
			
		||||
horizontal_alignment = 1
 | 
			
		||||
 | 
			
		||||
[node name="MainFormContainer" type="HBoxContainer" parent="FormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_constants/separation = 36
 | 
			
		||||
 | 
			
		||||
[node name="FormLabels" type="VBoxContainer" parent="FormContainer/MainFormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_vertical = 4
 | 
			
		||||
theme_override_constants/separation = 32
 | 
			
		||||
 | 
			
		||||
[node name="PlayerNameLabel" type="Label" parent="FormContainer/MainFormContainer/FormLabels"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_font_sizes/font_size = 36
 | 
			
		||||
text = "Username:"
 | 
			
		||||
 | 
			
		||||
[node name="EmailLabel" type="Label" parent="FormContainer/MainFormContainer/FormLabels"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_font_sizes/font_size = 36
 | 
			
		||||
text = "Email:"
 | 
			
		||||
 | 
			
		||||
[node name="PasswordLabel" type="Label" parent="FormContainer/MainFormContainer/FormLabels"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_font_sizes/font_size = 36
 | 
			
		||||
text = "Password:"
 | 
			
		||||
 | 
			
		||||
[node name="ConfirmPasswordLabel" type="Label" parent="FormContainer/MainFormContainer/FormLabels"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_font_sizes/font_size = 36
 | 
			
		||||
text = "Confirm password:"
 | 
			
		||||
 | 
			
		||||
[node name="FormInputFields" type="VBoxContainer" parent="FormContainer/MainFormContainer"]
 | 
			
		||||
custom_minimum_size = Vector2(460, 0)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="PlayerName" type="LineEdit" parent="FormContainer/MainFormContainer/FormInputFields"]
 | 
			
		||||
custom_minimum_size = Vector2(0, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
max_length = 30
 | 
			
		||||
 | 
			
		||||
[node name="Email" type="LineEdit" parent="FormContainer/MainFormContainer/FormInputFields"]
 | 
			
		||||
custom_minimum_size = Vector2(0, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
max_length = 50
 | 
			
		||||
 | 
			
		||||
[node name="Password" type="LineEdit" parent="FormContainer/MainFormContainer/FormInputFields"]
 | 
			
		||||
custom_minimum_size = Vector2(0, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
max_length = 30
 | 
			
		||||
secret = true
 | 
			
		||||
 | 
			
		||||
[node name="ConfirmPassword" type="LineEdit" parent="FormContainer/MainFormContainer/FormInputFields"]
 | 
			
		||||
custom_minimum_size = Vector2(0, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
max_length = 30
 | 
			
		||||
secret = true
 | 
			
		||||
 | 
			
		||||
[node name="InfoLabels" type="VBoxContainer" parent="FormContainer/MainFormContainer"]
 | 
			
		||||
layout_direction = 3
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme = ExtResource("1_dsws8")
 | 
			
		||||
 | 
			
		||||
[node name="UsernameToolButton" type="Button" parent="FormContainer/MainFormContainer/InfoLabels"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
icon = ExtResource("5")
 | 
			
		||||
flat = true
 | 
			
		||||
 | 
			
		||||
[node name="DummyToolButton1" type="Button" parent="FormContainer/MainFormContainer/InfoLabels"]
 | 
			
		||||
modulate = Color(1, 1, 1, 0)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
icon = ExtResource("6")
 | 
			
		||||
 | 
			
		||||
[node name="PasswordToolButton" type="Button" parent="FormContainer/MainFormContainer/InfoLabels"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
icon = ExtResource("5")
 | 
			
		||||
flat = true
 | 
			
		||||
 | 
			
		||||
[node name="DummyToolButton2" type="Button" parent="FormContainer/MainFormContainer/InfoLabels"]
 | 
			
		||||
modulate = Color(1, 1, 1, 0)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
icon = ExtResource("6")
 | 
			
		||||
 | 
			
		||||
[node name="InfoBox" type="Label" parent="FormContainer"]
 | 
			
		||||
visible = false
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
text = "Password should contain at least 8 characters including uppercase and lowercase letters, numbers and (optionally) special characters."
 | 
			
		||||
autowrap_mode = 1
 | 
			
		||||
 | 
			
		||||
[node name="ErrorMessage" type="Label" parent="FormContainer"]
 | 
			
		||||
visible = false
 | 
			
		||||
custom_minimum_size = Vector2(36, 0)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme = ExtResource("1_dsws8")
 | 
			
		||||
theme_override_colors/font_color = Color(0.92549, 0, 0, 1)
 | 
			
		||||
horizontal_alignment = 1
 | 
			
		||||
autowrap_mode = 1
 | 
			
		||||
 | 
			
		||||
[node name="RegisterButton" parent="FormContainer" instance=ExtResource("2")]
 | 
			
		||||
custom_minimum_size = Vector2(280, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_horizontal = 4
 | 
			
		||||
theme_override_font_sizes/font_size = 48
 | 
			
		||||
text = "Submit"
 | 
			
		||||
 | 
			
		||||
[node name="ProcessingLabel" type="Label" parent="FormContainer"]
 | 
			
		||||
visible = false
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_horizontal = 4
 | 
			
		||||
theme_override_font_sizes/font_size = 36
 | 
			
		||||
text = "Processing..."
 | 
			
		||||
 | 
			
		||||
[connection signal="pressed" from="BackButton" to="." method="_on_BackButton_pressed"]
 | 
			
		||||
[connection signal="mouse_entered" from="FormContainer/MainFormContainer/InfoLabels/UsernameToolButton" to="." method="_on_UsernameToolButton_mouse_entered"]
 | 
			
		||||
[connection signal="mouse_exited" from="FormContainer/MainFormContainer/InfoLabels/UsernameToolButton" to="." method="_on_UsernameToolButton_mouse_exited"]
 | 
			
		||||
[connection signal="mouse_entered" from="FormContainer/MainFormContainer/InfoLabels/PasswordToolButton" to="." method="_on_PasswordToolButton_mouse_entered"]
 | 
			
		||||
[connection signal="mouse_exited" from="FormContainer/MainFormContainer/InfoLabels/PasswordToolButton" to="." method="_on_PasswordToolButton_mouse_exited"]
 | 
			
		||||
[connection signal="pressed" from="FormContainer/RegisterButton" to="." method="_on_RegisterButton_pressed"]
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,133 @@
 | 
			
		|||
[gd_scene load_steps=6 format=3 uid="uid://smtpyjhmn308"]
 | 
			
		||||
 | 
			
		||||
[ext_resource type="Script" uid="uid://dvf4mw8y68sa" path="res://addons/silent_wolf/Auth/Register.gd" id="1"]
 | 
			
		||||
[ext_resource type="Theme" uid="uid://d2eakbmaefnt6" path="res://addons/silent_wolf/assets/themes/sw_theme.tres" id="1_ksggb"]
 | 
			
		||||
[ext_resource type="PackedScene" uid="uid://clllbf6am8qdf" path="res://addons/silent_wolf/common/SWButton.tscn" id="2"]
 | 
			
		||||
[ext_resource type="Texture2D" uid="uid://dh8fem1lgom04" path="res://addons/silent_wolf/assets/gfx/info_icon_small.png" id="5"]
 | 
			
		||||
[ext_resource type="Texture2D" uid="uid://gdw18po2h7hb" path="res://addons/silent_wolf/assets/gfx/dummy_info_icon_small.png" id="6"]
 | 
			
		||||
 | 
			
		||||
[node name="Register" type="TextureRect"]
 | 
			
		||||
anchors_preset = -1
 | 
			
		||||
anchor_right = 1.0
 | 
			
		||||
anchor_bottom = 0.949
 | 
			
		||||
offset_right = -1920.0
 | 
			
		||||
offset_bottom = -1024.92
 | 
			
		||||
theme = ExtResource("1_ksggb")
 | 
			
		||||
script = ExtResource("1")
 | 
			
		||||
 | 
			
		||||
[node name="BackButton" parent="." instance=ExtResource("2")]
 | 
			
		||||
layout_mode = 0
 | 
			
		||||
offset_left = 225.0
 | 
			
		||||
offset_top = 92.0
 | 
			
		||||
offset_right = 370.0
 | 
			
		||||
offset_bottom = 141.0
 | 
			
		||||
text = "← Back"
 | 
			
		||||
 | 
			
		||||
[node name="FormContainer" type="VBoxContainer" parent="."]
 | 
			
		||||
layout_mode = 0
 | 
			
		||||
offset_left = 408.0
 | 
			
		||||
offset_top = 190.0
 | 
			
		||||
offset_right = 1486.0
 | 
			
		||||
offset_bottom = 859.0
 | 
			
		||||
theme_override_constants/separation = 24
 | 
			
		||||
 | 
			
		||||
[node name="Label" type="Label" parent="FormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_font_sizes/font_size = 72
 | 
			
		||||
text = "Sign up"
 | 
			
		||||
horizontal_alignment = 1
 | 
			
		||||
 | 
			
		||||
[node name="MainFormContainer" type="HBoxContainer" parent="FormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_constants/separation = 36
 | 
			
		||||
 | 
			
		||||
[node name="FormLabels" type="VBoxContainer" parent="FormContainer/MainFormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_vertical = 5
 | 
			
		||||
theme_override_constants/separation = 73
 | 
			
		||||
 | 
			
		||||
[node name="PlayerNameLabel" type="Label" parent="FormContainer/MainFormContainer/FormLabels"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_font_sizes/font_size = 36
 | 
			
		||||
text = "Username:"
 | 
			
		||||
 | 
			
		||||
[node name="PasswordLabel" type="Label" parent="FormContainer/MainFormContainer/FormLabels"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
text = "Password:"
 | 
			
		||||
 | 
			
		||||
[node name="ConfirmPasswordLabel" type="Label" parent="FormContainer/MainFormContainer/FormLabels"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
text = "Confirm password:"
 | 
			
		||||
 | 
			
		||||
[node name="FormInputFields" type="VBoxContainer" parent="FormContainer/MainFormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_constants/separation = 28
 | 
			
		||||
 | 
			
		||||
[node name="PlayerName" type="LineEdit" parent="FormContainer/MainFormContainer/FormInputFields"]
 | 
			
		||||
custom_minimum_size = Vector2(400, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
max_length = 30
 | 
			
		||||
 | 
			
		||||
[node name="Password" type="LineEdit" parent="FormContainer/MainFormContainer/FormInputFields"]
 | 
			
		||||
custom_minimum_size = Vector2(400, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
max_length = 30
 | 
			
		||||
secret = true
 | 
			
		||||
 | 
			
		||||
[node name="ConfirmPassword" type="LineEdit" parent="FormContainer/MainFormContainer/FormInputFields"]
 | 
			
		||||
custom_minimum_size = Vector2(400, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
max_length = 30
 | 
			
		||||
secret = true
 | 
			
		||||
 | 
			
		||||
[node name="InfoLabels" type="VBoxContainer" parent="FormContainer/MainFormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_constants/separation = 32
 | 
			
		||||
 | 
			
		||||
[node name="UsernameToolButton" type="Button" parent="FormContainer/MainFormContainer/InfoLabels"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
icon = ExtResource("5")
 | 
			
		||||
flat = true
 | 
			
		||||
 | 
			
		||||
[node name="PasswordToolButton" type="Button" parent="FormContainer/MainFormContainer/InfoLabels"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
icon = ExtResource("5")
 | 
			
		||||
flat = true
 | 
			
		||||
 | 
			
		||||
[node name="DummyToolButton1" type="Button" parent="FormContainer/MainFormContainer/InfoLabels"]
 | 
			
		||||
modulate = Color(1, 1, 1, 0)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
icon = ExtResource("6")
 | 
			
		||||
 | 
			
		||||
[node name="InfoBox" type="Label" parent="FormContainer"]
 | 
			
		||||
visible = false
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
text = "Password should contain at least 8 characters including uppercase and lowercase letters, numbers and (optionally) special characters."
 | 
			
		||||
autowrap_mode = 1
 | 
			
		||||
 | 
			
		||||
[node name="ErrorMessage" type="Label" parent="FormContainer"]
 | 
			
		||||
visible = false
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_colors/font_color = Color(0.92549, 0, 0, 1)
 | 
			
		||||
autowrap_mode = 1
 | 
			
		||||
 | 
			
		||||
[node name="RegisterUPButton" parent="FormContainer" instance=ExtResource("2")]
 | 
			
		||||
custom_minimum_size = Vector2(220, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_horizontal = 4
 | 
			
		||||
theme_override_font_sizes/font_size = 48
 | 
			
		||||
text = "Submit"
 | 
			
		||||
 | 
			
		||||
[node name="ProcessingLabel" type="Label" parent="FormContainer"]
 | 
			
		||||
visible = false
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_font_sizes/font_size = 36
 | 
			
		||||
text = "Processing..."
 | 
			
		||||
horizontal_alignment = 1
 | 
			
		||||
 | 
			
		||||
[connection signal="pressed" from="BackButton" to="." method="_on_BackButton_pressed"]
 | 
			
		||||
[connection signal="mouse_entered" from="FormContainer/MainFormContainer/InfoLabels/UsernameToolButton" to="." method="_on_UsernameToolButton_mouse_entered"]
 | 
			
		||||
[connection signal="mouse_exited" from="FormContainer/MainFormContainer/InfoLabels/UsernameToolButton" to="." method="_on_UsernameToolButton_mouse_exited"]
 | 
			
		||||
[connection signal="mouse_entered" from="FormContainer/MainFormContainer/InfoLabels/PasswordToolButton" to="." method="_on_PasswordToolButton_mouse_entered"]
 | 
			
		||||
[connection signal="mouse_exited" from="FormContainer/MainFormContainer/InfoLabels/PasswordToolButton" to="." method="_on_PasswordToolButton_mouse_exited"]
 | 
			
		||||
[connection signal="pressed" from="FormContainer/RegisterUPButton" to="." method="_on_RegisterUPButton_pressed"]
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,77 @@
 | 
			
		|||
extends TextureRect
 | 
			
		||||
 | 
			
		||||
var player_name = null
 | 
			
		||||
var login_scene = "res://addons/silent_wolf/Auth/Login.tscn"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Called when the node enters the scene tree for the first time.
 | 
			
		||||
func _ready():
 | 
			
		||||
	$"RequestFormContainer/ProcessingLabel".hide()
 | 
			
		||||
	$"PwdResetFormContainer/ProcessingLabel".hide()
 | 
			
		||||
	$"PasswordChangedContainer".hide()
 | 
			
		||||
	$"PwdResetFormContainer".hide()
 | 
			
		||||
	$"RequestFormContainer".show()
 | 
			
		||||
	SilentWolf.Auth.sw_request_password_reset_complete.connect(_on_send_code_complete)
 | 
			
		||||
	SilentWolf.Auth.sw_reset_password_complete.connect(_on_reset_complete)
 | 
			
		||||
	if "login_scene" in SilentWolf.Auth:
 | 
			
		||||
		login_scene = SilentWolf.Auth.login_scene
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_BackButton_pressed() -> void:
 | 
			
		||||
	get_tree().change_scene_to_file(login_scene)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_PlayerNameSubmitButton_pressed() -> void:
 | 
			
		||||
	player_name = $"RequestFormContainer/FormContainer/FormInputFields/PlayerName".text
 | 
			
		||||
	SilentWolf.Auth.request_player_password_reset(player_name)
 | 
			
		||||
	$"RequestFormContainer/ProcessingLabel".show()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_send_code_complete(sw_result: Dictionary) -> void:
 | 
			
		||||
	if sw_result.success:
 | 
			
		||||
		send_code_success()
 | 
			
		||||
	else:
 | 
			
		||||
		send_code_failure(sw_result.error)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func send_code_success() -> void:
 | 
			
		||||
	$"RequestFormContainer/ProcessingLabel".hide()
 | 
			
		||||
	$"RequestFormContainer".hide()
 | 
			
		||||
	$"PwdResetFormContainer".show()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func send_code_failure(error: String) -> void:
 | 
			
		||||
	$"RequestFormContainer/ProcessingLabel".hide()
 | 
			
		||||
	$"RequestFormContainer/ErrorMessage".text = "Could not send confirmation code. " + str(error)
 | 
			
		||||
	$"RequestFormContainer/ErrorMessage".show()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_NewPasswordSubmitButton_pressed() -> void:
 | 
			
		||||
	var code = $"PwdResetFormContainer/FormContainer/FormInputFields/Code".text
 | 
			
		||||
	var password = $"PwdResetFormContainer/FormContainer/FormInputFields/Password".text
 | 
			
		||||
	var confirm_password = $"PwdResetFormContainer/FormContainer/FormInputFields/ConfirmPassword".text
 | 
			
		||||
	SilentWolf.Auth.reset_player_password(player_name, code, password, confirm_password)
 | 
			
		||||
	$"PwdResetFormContainer/ProcessingLabel".show()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_reset_complete(sw_result: Dictionary) -> void:
 | 
			
		||||
	if sw_result.success:
 | 
			
		||||
		reset_success()
 | 
			
		||||
	else:
 | 
			
		||||
		reset_failure(sw_result.error)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func reset_success() -> void:
 | 
			
		||||
	$"PwdResetFormContainer/ProcessingLabel".hide()
 | 
			
		||||
	$"PwdResetFormContainer".hide()
 | 
			
		||||
	$"PasswordChangedContainer".show()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func reset_failure(error: String) -> void:
 | 
			
		||||
	$"PwdResetFormContainer/ProcessingLabel".hide()
 | 
			
		||||
	$"PwdResetFormContainer/ErrorMessage".text = "Could not reset password. " + str(error)
 | 
			
		||||
	$"PwdResetFormContainer/ErrorMessage".show()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_CloseButton_pressed() -> void:
 | 
			
		||||
	get_tree().change_scene_to_file(login_scene)
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
uid://cr0afxttlphm7
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,200 @@
 | 
			
		|||
[gd_scene load_steps=4 format=3 uid="uid://dlfgu138q4bat"]
 | 
			
		||||
 | 
			
		||||
[ext_resource type="Script" uid="uid://cr0afxttlphm7" path="res://addons/silent_wolf/Auth/ResetPassword.gd" id="1"]
 | 
			
		||||
[ext_resource type="Theme" uid="uid://d2eakbmaefnt6" path="res://addons/silent_wolf/assets/themes/sw_theme.tres" id="1_qg4ch"]
 | 
			
		||||
[ext_resource type="PackedScene" uid="uid://clllbf6am8qdf" path="res://addons/silent_wolf/common/SWButton.tscn" id="2"]
 | 
			
		||||
 | 
			
		||||
[node name="ResetPassword" type="TextureRect"]
 | 
			
		||||
anchors_preset = 15
 | 
			
		||||
anchor_right = 1.0
 | 
			
		||||
anchor_bottom = 1.0
 | 
			
		||||
offset_right = -1920.0
 | 
			
		||||
offset_bottom = -1080.0
 | 
			
		||||
grow_horizontal = 2
 | 
			
		||||
grow_vertical = 2
 | 
			
		||||
theme = ExtResource("1_qg4ch")
 | 
			
		||||
script = ExtResource("1")
 | 
			
		||||
 | 
			
		||||
[node name="BackButton" parent="." instance=ExtResource("2")]
 | 
			
		||||
layout_mode = 0
 | 
			
		||||
offset_left = 137.0
 | 
			
		||||
offset_top = 95.0
 | 
			
		||||
offset_right = 282.0
 | 
			
		||||
offset_bottom = 144.0
 | 
			
		||||
text = "← Back"
 | 
			
		||||
 | 
			
		||||
[node name="RequestFormContainer" type="VBoxContainer" parent="."]
 | 
			
		||||
visible = false
 | 
			
		||||
layout_mode = 0
 | 
			
		||||
offset_left = 650.0
 | 
			
		||||
offset_top = 212.0
 | 
			
		||||
offset_right = 1346.0
 | 
			
		||||
offset_bottom = 812.0
 | 
			
		||||
theme_override_constants/separation = 48
 | 
			
		||||
 | 
			
		||||
[node name="Label" type="Label" parent="RequestFormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_font_sizes/font_size = 72
 | 
			
		||||
text = "Reset password"
 | 
			
		||||
horizontal_alignment = 1
 | 
			
		||||
 | 
			
		||||
[node name="LabelExplainer" type="Label" parent="RequestFormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
text = "Please enter your player name below."
 | 
			
		||||
 | 
			
		||||
[node name="FormContainer" type="HBoxContainer" parent="RequestFormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_constants/separation = 24
 | 
			
		||||
 | 
			
		||||
[node name="FormLabels" type="VBoxContainer" parent="RequestFormContainer/FormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="PlayerNameLabel" type="Label" parent="RequestFormContainer/FormContainer/FormLabels"]
 | 
			
		||||
custom_minimum_size = Vector2(0, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
text = "Player name"
 | 
			
		||||
vertical_alignment = 1
 | 
			
		||||
 | 
			
		||||
[node name="FormInputFields" type="VBoxContainer" parent="RequestFormContainer/FormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="PlayerName" type="LineEdit" parent="RequestFormContainer/FormContainer/FormInputFields"]
 | 
			
		||||
custom_minimum_size = Vector2(440, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
max_length = 50
 | 
			
		||||
 | 
			
		||||
[node name="ErrorMessage" type="Label" parent="RequestFormContainer"]
 | 
			
		||||
visible = false
 | 
			
		||||
modulate = Color(0.92549, 0, 0, 1)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="PlayerNameSubmitButton" parent="RequestFormContainer" instance=ExtResource("2")]
 | 
			
		||||
custom_minimum_size = Vector2(220, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_horizontal = 4
 | 
			
		||||
text = "Submit"
 | 
			
		||||
 | 
			
		||||
[node name="ProcessingLabel" type="Label" parent="RequestFormContainer"]
 | 
			
		||||
visible = false
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_font_sizes/font_size = 36
 | 
			
		||||
text = "Processing..."
 | 
			
		||||
horizontal_alignment = 1
 | 
			
		||||
 | 
			
		||||
[node name="PwdResetFormContainer" type="VBoxContainer" parent="."]
 | 
			
		||||
layout_mode = 0
 | 
			
		||||
offset_left = 277.0
 | 
			
		||||
offset_top = 208.0
 | 
			
		||||
offset_right = 1769.0
 | 
			
		||||
offset_bottom = 870.0
 | 
			
		||||
theme_override_constants/separation = 48
 | 
			
		||||
 | 
			
		||||
[node name="Label" type="Label" parent="PwdResetFormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_font_sizes/font_size = 72
 | 
			
		||||
text = "Reset password"
 | 
			
		||||
horizontal_alignment = 1
 | 
			
		||||
 | 
			
		||||
[node name="LabelExplainer" type="Label" parent="PwdResetFormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
text = "Please enter below the code we sent you by email and your new password twice."
 | 
			
		||||
 | 
			
		||||
[node name="FormContainer" type="HBoxContainer" parent="PwdResetFormContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_horizontal = 4
 | 
			
		||||
theme_override_constants/separation = 24
 | 
			
		||||
 | 
			
		||||
[node name="FormLabels" type="VBoxContainer" parent="PwdResetFormContainer/FormContainer"]
 | 
			
		||||
custom_minimum_size = Vector2(0, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_constants/separation = 18
 | 
			
		||||
 | 
			
		||||
[node name="CodeLabel" type="Label" parent="PwdResetFormContainer/FormContainer/FormLabels"]
 | 
			
		||||
custom_minimum_size = Vector2(0, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
text = "Code"
 | 
			
		||||
vertical_alignment = 1
 | 
			
		||||
 | 
			
		||||
[node name="PasswordLabel" type="Label" parent="PwdResetFormContainer/FormContainer/FormLabels"]
 | 
			
		||||
custom_minimum_size = Vector2(0, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
text = "Password"
 | 
			
		||||
vertical_alignment = 1
 | 
			
		||||
 | 
			
		||||
[node name="ConfirmPasswordLabel" type="Label" parent="PwdResetFormContainer/FormContainer/FormLabels"]
 | 
			
		||||
custom_minimum_size = Vector2(0, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
text = "Confirm password"
 | 
			
		||||
vertical_alignment = 1
 | 
			
		||||
 | 
			
		||||
[node name="FormInputFields" type="VBoxContainer" parent="PwdResetFormContainer/FormContainer"]
 | 
			
		||||
custom_minimum_size = Vector2(440, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_constants/separation = 18
 | 
			
		||||
 | 
			
		||||
[node name="Code" type="LineEdit" parent="PwdResetFormContainer/FormContainer/FormInputFields"]
 | 
			
		||||
custom_minimum_size = Vector2(0, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
max_length = 50
 | 
			
		||||
 | 
			
		||||
[node name="Password" type="LineEdit" parent="PwdResetFormContainer/FormContainer/FormInputFields"]
 | 
			
		||||
custom_minimum_size = Vector2(0, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
max_length = 30
 | 
			
		||||
secret = true
 | 
			
		||||
 | 
			
		||||
[node name="ConfirmPassword" type="LineEdit" parent="PwdResetFormContainer/FormContainer/FormInputFields"]
 | 
			
		||||
custom_minimum_size = Vector2(0, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
max_length = 30
 | 
			
		||||
secret = true
 | 
			
		||||
 | 
			
		||||
[node name="ErrorMessage" type="Label" parent="PwdResetFormContainer"]
 | 
			
		||||
visible = false
 | 
			
		||||
modulate = Color(0.92549, 0, 0, 1)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
autowrap_mode = 1
 | 
			
		||||
 | 
			
		||||
[node name="NewPasswordSubmitButton" parent="PwdResetFormContainer" instance=ExtResource("2")]
 | 
			
		||||
custom_minimum_size = Vector2(220, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_horizontal = 4
 | 
			
		||||
text = "Submit"
 | 
			
		||||
 | 
			
		||||
[node name="ProcessingLabel" type="Label" parent="PwdResetFormContainer"]
 | 
			
		||||
visible = false
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
text = "Processing..."
 | 
			
		||||
horizontal_alignment = 1
 | 
			
		||||
 | 
			
		||||
[node name="PasswordChangedContainer" type="VBoxContainer" parent="."]
 | 
			
		||||
visible = false
 | 
			
		||||
layout_mode = 0
 | 
			
		||||
offset_left = 525.0
 | 
			
		||||
offset_top = 269.0
 | 
			
		||||
offset_right = 1469.0
 | 
			
		||||
offset_bottom = 793.0
 | 
			
		||||
theme_override_constants/separation = 60
 | 
			
		||||
 | 
			
		||||
[node name="PwdChanedLabel" type="Label" parent="PasswordChangedContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_horizontal = 4
 | 
			
		||||
size_flags_vertical = 1
 | 
			
		||||
theme_override_font_sizes/font_size = 72
 | 
			
		||||
text = "Password reset"
 | 
			
		||||
 | 
			
		||||
[node name="PasswordChangedLabelExplainer" type="Label" parent="PasswordChangedContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
text = "Your password was changed successfully."
 | 
			
		||||
horizontal_alignment = 1
 | 
			
		||||
 | 
			
		||||
[node name="CloseButton" parent="PasswordChangedContainer" instance=ExtResource("2")]
 | 
			
		||||
custom_minimum_size = Vector2(320, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_horizontal = 4
 | 
			
		||||
text = "Close"
 | 
			
		||||
 | 
			
		||||
[connection signal="pressed" from="BackButton" to="." method="_on_BackButton_pressed"]
 | 
			
		||||
[connection signal="pressed" from="RequestFormContainer/PlayerNameSubmitButton" to="." method="_on_PlayerNameSubmitButton_pressed"]
 | 
			
		||||
[connection signal="pressed" from="PwdResetFormContainer/NewPasswordSubmitButton" to="." method="_on_NewPasswordSubmitButton_pressed"]
 | 
			
		||||
[connection signal="pressed" from="PasswordChangedContainer/CloseButton" to="." method="_on_CloseButton_pressed"]
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
extends Node
 | 
			
		||||
 | 
			
		||||
@onready var WSClient = Node.new()
 | 
			
		||||
 | 
			
		||||
var mp_ws_ready = false
 | 
			
		||||
var mp_session_started = false
 | 
			
		||||
 | 
			
		||||
var mp_player_name = ""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _ready():
 | 
			
		||||
	mp_ws_ready = false
 | 
			
		||||
	mp_session_started = false
 | 
			
		||||
	var ws_client_script = load("res://addons/silent_wolf/Multiplayer/ws/WSClient.gd")
 | 
			
		||||
	WSClient.set_script(ws_client_script)
 | 
			
		||||
	add_child(WSClient)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func init_mp_session(player_name):
 | 
			
		||||
	#mp_player_name = player_name
 | 
			
		||||
	WSClient.init_mp_session(player_name)
 | 
			
		||||
	# TODO: instead of waiting an arbitrary amount of time, yield on 
 | 
			
		||||
	# a function that guarantees that child ready() function has run
 | 
			
		||||
	#yield(get_tree().create_timer(0.3), "timeout")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _send_init_message():
 | 
			
		||||
	WSClient.init_mp_session(mp_player_name)
 | 
			
		||||
	mp_ws_ready = true
 | 
			
		||||
	mp_session_started = true
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func send(data: Dictionary):
 | 
			
		||||
	# First check that WSClient is in tree
 | 
			
		||||
	print("Attempting to send data to web socket server")
 | 
			
		||||
	if WSClient.is_inside_tree():
 | 
			
		||||
		# TODO: check if data is properly formatted (should be dictionary?)
 | 
			
		||||
		print("Sending data to web socket server...")
 | 
			
		||||
		WSClient.send_to_server("update", data)
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
uid://ci4ycv1aesr8m
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,82 @@
 | 
			
		|||
extends Node
 | 
			
		||||
 | 
			
		||||
const SWLogger = preload("res://addons/silent_wolf/utils/SWLogger.gd")
 | 
			
		||||
 | 
			
		||||
# The URL we will connect to
 | 
			
		||||
@export var websocket_url = "wss://ws.silentwolfmp.com/server"
 | 
			
		||||
@export var ws_room_init_url = "wss://ws.silentwolfmp.com/init"
 | 
			
		||||
 | 
			
		||||
signal ws_client_ready
 | 
			
		||||
 | 
			
		||||
# Our WebSocketClient instance
 | 
			
		||||
#var _client = WebSocketClient.new()
 | 
			
		||||
 | 
			
		||||
func _ready():
 | 
			
		||||
	SWLogger.debug("Entering MPClient _ready function")
 | 
			
		||||
	# Connect base signals to get notified of connection open, close, and errors.
 | 
			
		||||
	#_client.connect("connection_closed", self, "_closed")
 | 
			
		||||
	#_client.connect("connection_error", self, "_closed")
 | 
			
		||||
	#_client.connect("connection_established", self, "_connected")
 | 
			
		||||
	# This signal is emitted when not using the Multiplayer API every time
 | 
			
		||||
	# a full packet is received.
 | 
			
		||||
	# Alternatively, you could check get_peer(1).get_available_packets() in a loop.
 | 
			
		||||
	#_client.connect("data_received", self, "_on_data")
 | 
			
		||||
 | 
			
		||||
	# Initiate connection to the given URL.
 | 
			
		||||
	#var err = _client.connect_to_url(websocket_url)
 | 
			
		||||
	#if err != OK:
 | 
			
		||||
		#SWLogger.debug("Unable to connect to WS server")
 | 
			
		||||
	#	print("Unable to connect to WS server")
 | 
			
		||||
	#	set_process(false)
 | 
			
		||||
	#emit_signal("ws_client_ready")
 | 
			
		||||
 | 
			
		||||
func _closed(was_clean = false):
 | 
			
		||||
	# was_clean will tell you if the disconnection was correctly notified
 | 
			
		||||
	# by the remote peer before closing the socket.
 | 
			
		||||
	SWLogger.debug("WS connection closed, clean: " + str(was_clean))
 | 
			
		||||
	set_process(false)
 | 
			
		||||
 | 
			
		||||
func _connected(proto = ""):
 | 
			
		||||
	# This is called on connection, "proto" will be the selected WebSocket
 | 
			
		||||
	# sub-protocol (which is optional)
 | 
			
		||||
	#SWLogger.debug("Connected with protocol: " + str(proto))
 | 
			
		||||
	print("Connected with protocol: ", proto)
 | 
			
		||||
	# You MUST always use get_peer(1).put_packet to send data to server,
 | 
			
		||||
	# and not put_packet directly when not using the MultiplayerAPI.
 | 
			
		||||
	#var test_packet = { "data": "Test packet" }
 | 
			
		||||
	#send_to_server(test_packet)
 | 
			
		||||
	#_client.get_peer(1).put_packet("Test packet".to_utf8())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_data():
 | 
			
		||||
	# Print the received packet, you MUST always use get_peer(1).get_packet
 | 
			
		||||
	# to receive data from server, and not get_packet directly when not
 | 
			
		||||
	# using the MultiplayerAPI.
 | 
			
		||||
	#SWLogger.debug("Got data from WS server: " + str(_client.get_peer(1).get_packet().get_string_from_utf8()))
 | 
			
		||||
	#print("Got data from WS server: ", _client.get_peer(1).get_packet().get_string_from_utf8())
 | 
			
		||||
	pass
 | 
			
		||||
 | 
			
		||||
func _process(delta):
 | 
			
		||||
	# Call this in _process or _physics_process. Data transfer, and signals
 | 
			
		||||
	# emission will only happen when calling this function.
 | 
			
		||||
	#_client.poll()
 | 
			
		||||
	pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# send arbitrary data to backend
 | 
			
		||||
func send_to_server(message_type, data):
 | 
			
		||||
	data["message_type"] = message_type
 | 
			
		||||
	print("Sending data to server: " + str(data))
 | 
			
		||||
	#_client.get_peer(1).put_packet(str(JSON.stringify(data)).to_utf8())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func init_mp_session(player_name):
 | 
			
		||||
	print("WSClient init_mp_session, sending initialisation packet to server")
 | 
			
		||||
	var init_packet = { 
 | 
			
		||||
		"player_name": player_name 
 | 
			
		||||
	}
 | 
			
		||||
	return send_to_server("init", init_packet)
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
func create_room():
 | 
			
		||||
	pass
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
uid://bwth2adqsbpey
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
extends Node
 | 
			
		||||
 | 
			
		||||
@onready var WSClient = Node.new()
 | 
			
		||||
 | 
			
		||||
var mp_ws_ready = false
 | 
			
		||||
var mp_session_started = false
 | 
			
		||||
 | 
			
		||||
var mp_player_name = ""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _ready():
 | 
			
		||||
	mp_ws_ready = false
 | 
			
		||||
	mp_session_started = false
 | 
			
		||||
	var ws_client_script = load("res://addons/silent_wolf/Multiplayer/ws/WSClient.gd")
 | 
			
		||||
	WSClient.set_script(ws_client_script)
 | 
			
		||||
	add_child(WSClient)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func init_mp_session(player_name):
 | 
			
		||||
	#mp_player_name = player_name
 | 
			
		||||
	WSClient.init_mp_session(player_name)
 | 
			
		||||
	# TODO: instead of waiting an arbitrary amount of time, yield on 
 | 
			
		||||
	# a function that guarantees that child ready() function has run
 | 
			
		||||
	#yield(get_tree().create_timer(0.3), "timeout")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _send_init_message():
 | 
			
		||||
	WSClient.init_mp_session(mp_player_name)
 | 
			
		||||
	mp_ws_ready = true
 | 
			
		||||
	mp_session_started = true
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func send(data: Dictionary):
 | 
			
		||||
	# First check that WSClient is in tree
 | 
			
		||||
	print("Attempting to send data to web socket server")
 | 
			
		||||
	if WSClient.is_inside_tree():
 | 
			
		||||
		# TODO: check if data is properly formatted (should be dictionary?)
 | 
			
		||||
		print("Sending data to web socket server...")
 | 
			
		||||
		WSClient.send_to_server("update", data)
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
uid://dl2dueymuik76
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,82 @@
 | 
			
		|||
extends Node
 | 
			
		||||
 | 
			
		||||
const SWLogger = preload("res://addons/silent_wolf/utils/SWLogger.gd")
 | 
			
		||||
 | 
			
		||||
# The URL we will connect to
 | 
			
		||||
@export var websocket_url = "wss://ws.silentwolfmp.com/server"
 | 
			
		||||
@export var ws_room_init_url = "wss://ws.silentwolfmp.com/init"
 | 
			
		||||
 | 
			
		||||
signal ws_client_ready
 | 
			
		||||
 | 
			
		||||
# Our WebSocketClient instance
 | 
			
		||||
#var _client = WebSocketClient.new()
 | 
			
		||||
 | 
			
		||||
func _ready():
 | 
			
		||||
	SWLogger.debug("Entering MPClient _ready function")
 | 
			
		||||
	# Connect base signals to get notified of connection open, close, and errors.
 | 
			
		||||
	#_client.connect("connection_closed", self, "_closed")
 | 
			
		||||
	#_client.connect("connection_error", self, "_closed")
 | 
			
		||||
	#_client.connect("connection_established", self, "_connected")
 | 
			
		||||
	# This signal is emitted when not using the Multiplayer API every time
 | 
			
		||||
	# a full packet is received.
 | 
			
		||||
	# Alternatively, you could check get_peer(1).get_available_packets() in a loop.
 | 
			
		||||
	#_client.connect("data_received", self, "_on_data")
 | 
			
		||||
 | 
			
		||||
	# Initiate connection to the given URL.
 | 
			
		||||
	#var err = _client.connect_to_url(websocket_url)
 | 
			
		||||
	#if err != OK:
 | 
			
		||||
		#SWLogger.debug("Unable to connect to WS server")
 | 
			
		||||
	#	print("Unable to connect to WS server")
 | 
			
		||||
	#	set_process(false)
 | 
			
		||||
	#emit_signal("ws_client_ready")
 | 
			
		||||
 | 
			
		||||
func _closed(was_clean = false):
 | 
			
		||||
	# was_clean will tell you if the disconnection was correctly notified
 | 
			
		||||
	# by the remote peer before closing the socket.
 | 
			
		||||
	SWLogger.debug("WS connection closed, clean: " + str(was_clean))
 | 
			
		||||
	set_process(false)
 | 
			
		||||
 | 
			
		||||
func _connected(proto = ""):
 | 
			
		||||
	# This is called on connection, "proto" will be the selected WebSocket
 | 
			
		||||
	# sub-protocol (which is optional)
 | 
			
		||||
	#SWLogger.debug("Connected with protocol: " + str(proto))
 | 
			
		||||
	print("Connected with protocol: ", proto)
 | 
			
		||||
	# You MUST always use get_peer(1).put_packet to send data to server,
 | 
			
		||||
	# and not put_packet directly when not using the MultiplayerAPI.
 | 
			
		||||
	#var test_packet = { "data": "Test packet" }
 | 
			
		||||
	#send_to_server(test_packet)
 | 
			
		||||
	#_client.get_peer(1).put_packet("Test packet".to_utf8())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_data():
 | 
			
		||||
	# Print the received packet, you MUST always use get_peer(1).get_packet
 | 
			
		||||
	# to receive data from server, and not get_packet directly when not
 | 
			
		||||
	# using the MultiplayerAPI.
 | 
			
		||||
	#SWLogger.debug("Got data from WS server: " + str(_client.get_peer(1).get_packet().get_string_from_utf8()))
 | 
			
		||||
	#print("Got data from WS server: ", _client.get_peer(1).get_packet().get_string_from_utf8())
 | 
			
		||||
	pass
 | 
			
		||||
 | 
			
		||||
func _process(delta):
 | 
			
		||||
	# Call this in _process or _physics_process. Data transfer, and signals
 | 
			
		||||
	# emission will only happen when calling this function.
 | 
			
		||||
	#_client.poll()
 | 
			
		||||
	pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# send arbitrary data to backend
 | 
			
		||||
func send_to_server(message_type, data):
 | 
			
		||||
	data["message_type"] = message_type
 | 
			
		||||
	print("Sending data to server: " + str(data))
 | 
			
		||||
	#_client.get_peer(1).put_packet(str(JSON.stringify(data)).to_utf8())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func init_mp_session(player_name):
 | 
			
		||||
	print("WSClient init_mp_session, sending initialisation packet to server")
 | 
			
		||||
	var init_packet = { 
 | 
			
		||||
		"player_name": player_name 
 | 
			
		||||
	}
 | 
			
		||||
	return send_to_server("init", init_packet)
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
func create_room():
 | 
			
		||||
	pass
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
uid://berbygfcg03k
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,171 @@
 | 
			
		|||
extends Node
 | 
			
		||||
 | 
			
		||||
const SWUtils = preload("res://addons/silent_wolf/utils/SWUtils.gd")
 | 
			
		||||
const SWLogger = preload("res://addons/silent_wolf/utils/SWLogger.gd")
 | 
			
		||||
 | 
			
		||||
signal sw_save_player_data_complete
 | 
			
		||||
signal sw_get_player_data_complete
 | 
			
		||||
signal sw_delete_player_data_complete
 | 
			
		||||
 | 
			
		||||
var GetPlayerData = null
 | 
			
		||||
var SavePlayerData = null
 | 
			
		||||
var DeletePlayerData = null
 | 
			
		||||
 | 
			
		||||
# wekrefs
 | 
			
		||||
var wrGetPlayerData = null
 | 
			
		||||
var wrSavePlayerData = null
 | 
			
		||||
var wrDeletePlayerData = null
 | 
			
		||||
 | 
			
		||||
var player_name = null
 | 
			
		||||
var player_data = null
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func save_player_data(player_name: String, player_data: Dictionary, overwrite: bool=true) -> Node:
 | 
			
		||||
	if player_name == "":
 | 
			
		||||
		SWLogger.error("player_name cannot be empty when calling SilentWolf.Players.save_player_data(player_name, player_data)")
 | 
			
		||||
	elif typeof(player_data) != TYPE_DICTIONARY:
 | 
			
		||||
		SWLogger.error("Player data should be of type Dictionary, instead it is of type: " + str(typeof(player_data)))
 | 
			
		||||
	else:
 | 
			
		||||
		var prepared_http_req = SilentWolf.prepare_http_request()
 | 
			
		||||
		SavePlayerData = prepared_http_req.request
 | 
			
		||||
		wrSavePlayerData = prepared_http_req.weakref
 | 
			
		||||
		SavePlayerData.request_completed.connect(_on_SavePlayerData_request_completed)
 | 
			
		||||
		SWLogger.info("Calling SilentWolf to post player data")
 | 
			
		||||
		var payload = { "game_id": SilentWolf.config.game_id, "player_name": player_name, "player_data": player_data, "overwrite": overwrite }
 | 
			
		||||
		var request_url = "https://api.silentwolf.com/push_player_data"
 | 
			
		||||
		SilentWolf.send_post_request(SavePlayerData, request_url, payload)
 | 
			
		||||
	return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_SavePlayerData_request_completed(result, response_code, headers, body) -> void:
 | 
			
		||||
	var status_check = SWUtils.check_http_response(response_code, headers, body)
 | 
			
		||||
	if is_instance_valid(SavePlayerData): 
 | 
			
		||||
		SilentWolf.free_request(wrSavePlayerData, SavePlayerData)
 | 
			
		||||
	
 | 
			
		||||
	if status_check:
 | 
			
		||||
		var json_body = JSON.parse_string(body.get_string_from_utf8())
 | 
			
		||||
		var sw_result: Dictionary = SilentWolf.build_result(json_body)
 | 
			
		||||
		if json_body.success:
 | 
			
		||||
			SWLogger.info("SilentWolf save player data success for player: " + str(json_body.player_name))
 | 
			
		||||
			var player_name = json_body.player_name
 | 
			
		||||
			#sw_result["player_name"] = player_name 
 | 
			
		||||
		else:
 | 
			
		||||
			SWLogger.error("SilentWolf save player data failure: " + str(json_body.error))
 | 
			
		||||
		sw_save_player_data_complete.emit(sw_result) 
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func get_player_data(player_name: String) -> Node:
 | 
			
		||||
	if player_name == "":
 | 
			
		||||
		SWLogger.error("player_name cannot be empty when calling SilentWolf.Players.get_player_data(player_name)")
 | 
			
		||||
	else:
 | 
			
		||||
		var prepared_http_req = SilentWolf.prepare_http_request()
 | 
			
		||||
		GetPlayerData = prepared_http_req.request
 | 
			
		||||
		wrGetPlayerData = prepared_http_req.weakref
 | 
			
		||||
		GetPlayerData.request_completed.connect(_on_GetPlayerData_request_completed)
 | 
			
		||||
		SWLogger.info("Calling SilentWolf to get player data")
 | 
			
		||||
		var request_url = "https://api.silentwolf.com/get_player_data/" + str(SilentWolf.config.game_id) + "/" + str(player_name)
 | 
			
		||||
		SilentWolf.send_get_request(GetPlayerData, request_url)
 | 
			
		||||
	return self
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
func _on_GetPlayerData_request_completed(result, response_code, headers, body) -> void:
 | 
			
		||||
	var status_check = SWUtils.check_http_response(response_code, headers, body)
 | 
			
		||||
	if is_instance_valid(GetPlayerData): 
 | 
			
		||||
		SilentWolf.free_request(wrGetPlayerData, GetPlayerData)
 | 
			
		||||
	
 | 
			
		||||
	if status_check:
 | 
			
		||||
		var json_body = JSON.parse_string(body.get_string_from_utf8())
 | 
			
		||||
		var sw_result: Dictionary = SilentWolf.build_result(json_body)
 | 
			
		||||
		if json_body.success:
 | 
			
		||||
			SWLogger.info("SilentWolf get player data success for player: " + str(json_body.player_name))
 | 
			
		||||
			player_name = json_body.player_name
 | 
			
		||||
			player_data = json_body.player_data
 | 
			
		||||
			SWLogger.debug("Player data: " + str(player_data))
 | 
			
		||||
			sw_result["player_data"] = player_data
 | 
			
		||||
		else:
 | 
			
		||||
			SWLogger.error("SilentWolf get player data failure: " + str(json_body.error))
 | 
			
		||||
		sw_get_player_data_complete.emit(sw_result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func delete_player_weapons(player_name: String) -> void:
 | 
			
		||||
	var weapons = { "Weapons": [] }
 | 
			
		||||
	delete_player_data(player_name, weapons)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func remove_player_money(player_name: String) -> void:
 | 
			
		||||
	var money = { "Money": 0 }
 | 
			
		||||
	delete_player_data(player_name, money)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func delete_player_items(player_name: String, item_name: String) -> void:
 | 
			
		||||
	var item = { item_name: "" }
 | 
			
		||||
	delete_player_data(player_name, item)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func delete_all_player_data(player_name: String) -> void:
 | 
			
		||||
	delete_player_data(player_name, {})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func delete_player_data(player_name: String, player_data: Dictionary) -> Node:
 | 
			
		||||
	var prepared_http_req = SilentWolf.prepare_http_request()
 | 
			
		||||
	DeletePlayerData = prepared_http_req.request
 | 
			
		||||
	wrDeletePlayerData = prepared_http_req.weakref
 | 
			
		||||
	DeletePlayerData.request_completed.connect(_on_DeletePlayerData_request_completed)
 | 
			
		||||
	SWLogger.info("Calling SilentWolf to remove player data")
 | 
			
		||||
	var payload = { "game_id": SilentWolf.config.game_id, "player_name": player_name, "player_data": player_data }
 | 
			
		||||
	var query = JSON.stringify(payload)
 | 
			
		||||
	var request_url = "https://api.silentwolf.com/remove_player_data"
 | 
			
		||||
	SilentWolf.send_post_request(DeletePlayerData, request_url, payload)
 | 
			
		||||
	return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_DeletePlayerData_request_completed(result, response_code, headers, body) -> void:
 | 
			
		||||
	var status_check = SWUtils.check_http_response(response_code, headers, body)
 | 
			
		||||
	if is_instance_valid(DeletePlayerData): 
 | 
			
		||||
		DeletePlayerData.queue_free()
 | 
			
		||||
	
 | 
			
		||||
	if status_check:
 | 
			
		||||
		var json_body = JSON.parse_string(body.get_string_from_utf8())
 | 
			
		||||
		var sw_result: Dictionary = SilentWolf.build_result(json_body)
 | 
			
		||||
		if json_body.success:
 | 
			
		||||
			SWLogger.info("SilentWolf delete player data success for player: " + str(json_body.player_name))
 | 
			
		||||
			var player_name = json_body.player_name
 | 
			
		||||
			# return player_data after (maybe partial) removal
 | 
			
		||||
			var player_data = json_body.player_data
 | 
			
		||||
			sw_result["player_name"] = player_name
 | 
			
		||||
			sw_result["player_data"] = player_data
 | 
			
		||||
		else:
 | 
			
		||||
			SWLogger.error("SilentWolf delete player data failure: " + str(json_body.error))
 | 
			
		||||
		sw_delete_player_data_complete.emit(sw_result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func get_stats() -> Dictionary:
 | 
			
		||||
	var stats = null
 | 
			
		||||
	if player_data:
 | 
			
		||||
		stats = {
 | 
			
		||||
			"strength": player_data.strength,
 | 
			
		||||
			"speed": player_data.speed,
 | 
			
		||||
			"reflexes": player_data.reflexes,
 | 
			
		||||
			"max_health": player_data.max_health,
 | 
			
		||||
			"career": player_data.career
 | 
			
		||||
		}
 | 
			
		||||
	return stats
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func get_inventory() -> Dictionary:
 | 
			
		||||
	var inventory = null
 | 
			
		||||
	if player_data:
 | 
			
		||||
		inventory = {
 | 
			
		||||
			"weapons": player_data.weapons,
 | 
			
		||||
			"gold": player_data.gold
 | 
			
		||||
		}
 | 
			
		||||
	return inventory
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func set_player_data(new_player_data: Dictionary) -> void:
 | 
			
		||||
	player_data = new_player_data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func clear_player_data() -> void:
 | 
			
		||||
	player_name = null
 | 
			
		||||
	player_data = null
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
uid://fd0sa4db1qey
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,127 @@
 | 
			
		|||
@tool
 | 
			
		||||
extends Node2D
 | 
			
		||||
 | 
			
		||||
const ScoreItem = preload("ScoreItem.tscn")
 | 
			
		||||
const SWLogger = preload("res://addons/silent_wolf/utils/SWLogger.gd")
 | 
			
		||||
 | 
			
		||||
var list_index = 0
 | 
			
		||||
# Replace the leaderboard name if you're not using the default leaderboard
 | 
			
		||||
var ld_name = "main"
 | 
			
		||||
var max_scores = 10
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _ready():
 | 
			
		||||
	print("SilentWolf.Scores.leaderboards: " + str(SilentWolf.Scores.leaderboards))
 | 
			
		||||
	print("SilentWolf.Scores.ldboard_config: " + str(SilentWolf.Scores.ldboard_config))
 | 
			
		||||
	var scores = SilentWolf.Scores.scores
 | 
			
		||||
	#var scores = []
 | 
			
		||||
	if ld_name in SilentWolf.Scores.leaderboards:
 | 
			
		||||
		scores = SilentWolf.Scores.leaderboards[ld_name]
 | 
			
		||||
	var local_scores = SilentWolf.Scores.local_scores
 | 
			
		||||
	
 | 
			
		||||
	if len(scores) > 0: 
 | 
			
		||||
		render_board(scores, local_scores)
 | 
			
		||||
	else:
 | 
			
		||||
		# use a signal to notify when the high scores have been returned, and show a "loading" animation until it's the case...
 | 
			
		||||
		add_loading_scores_message()
 | 
			
		||||
		var sw_result = await SilentWolf.Scores.get_scores().sw_get_scores_complete
 | 
			
		||||
		scores = sw_result.scores
 | 
			
		||||
		hide_message()
 | 
			
		||||
		render_board(scores, local_scores)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func render_board(scores: Array, local_scores: Array) -> void:
 | 
			
		||||
	var all_scores = scores
 | 
			
		||||
	if ld_name in SilentWolf.Scores.ldboard_config and is_default_leaderboard(SilentWolf.Scores.ldboard_config[ld_name]):
 | 
			
		||||
		all_scores = merge_scores_with_local_scores(scores, local_scores, max_scores)
 | 
			
		||||
		if scores.is_empty() and local_scores.is_empty():
 | 
			
		||||
			add_no_scores_message()
 | 
			
		||||
	else:
 | 
			
		||||
		if scores.is_empty():
 | 
			
		||||
			add_no_scores_message()
 | 
			
		||||
	if all_scores.is_empty():
 | 
			
		||||
		for score in scores:
 | 
			
		||||
			add_item(score.player_name, str(int(score.score)))
 | 
			
		||||
	else:
 | 
			
		||||
		for score in all_scores:
 | 
			
		||||
			add_item(score.player_name, str(int(score.score)))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func is_default_leaderboard(ld_config: Dictionary) -> bool:
 | 
			
		||||
	var default_insert_opt = (ld_config.insert_opt == "keep")
 | 
			
		||||
	var not_time_based = !("time_based" in ld_config)
 | 
			
		||||
	return default_insert_opt and not_time_based
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func merge_scores_with_local_scores(scores: Array, local_scores: Array, max_scores: int=10) -> Array:
 | 
			
		||||
	if local_scores:
 | 
			
		||||
		for score in local_scores:
 | 
			
		||||
			var in_array = score_in_score_array(scores, score)
 | 
			
		||||
			if !in_array:
 | 
			
		||||
				scores.append(score)
 | 
			
		||||
			scores.sort_custom(sort_by_score);
 | 
			
		||||
	if scores.size() > max_scores:
 | 
			
		||||
		var new_size = scores.resize(max_scores)
 | 
			
		||||
	return scores
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func sort_by_score(a: Dictionary, b: Dictionary) -> bool:
 | 
			
		||||
	if a.score > b.score:
 | 
			
		||||
		return true;
 | 
			
		||||
	else:
 | 
			
		||||
		if a.score < b.score:
 | 
			
		||||
			return false;
 | 
			
		||||
		else:
 | 
			
		||||
			return true;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func score_in_score_array(scores: Array, new_score: Dictionary) -> bool:
 | 
			
		||||
	var in_score_array =  false
 | 
			
		||||
	if !new_score.is_empty() and !scores.is_empty():
 | 
			
		||||
		for score in scores:
 | 
			
		||||
			if score.score_id == new_score.score_id: # score.player_name == new_score.player_name and score.score == new_score.score:
 | 
			
		||||
				in_score_array = true
 | 
			
		||||
	return in_score_array
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func add_item(player_name: String, score_value: String) -> void:
 | 
			
		||||
	var item = ScoreItem.instantiate()
 | 
			
		||||
	list_index += 1
 | 
			
		||||
	item.get_node("PlayerName").text = str(list_index) + str(". ") + player_name
 | 
			
		||||
	item.get_node("Score").text = score_value
 | 
			
		||||
	item.offset_top = list_index * 100
 | 
			
		||||
	$"Board/HighScores/ScoreItemContainer".add_child(item)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func add_no_scores_message() -> void:
 | 
			
		||||
	var item = $"Board/MessageContainer/TextMessage"
 | 
			
		||||
	item.text = "No scores yet!"
 | 
			
		||||
	$"Board/MessageContainer".show()
 | 
			
		||||
	item.offset_top = 135
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func add_loading_scores_message() -> void:
 | 
			
		||||
	var item = $"Board/MessageContainer/TextMessage"
 | 
			
		||||
	item.text = "Loading scores..."
 | 
			
		||||
	$"Board/MessageContainer".show()
 | 
			
		||||
	item.offset_top = 135
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func hide_message() -> void:
 | 
			
		||||
	$"Board/MessageContainer".hide()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func clear_leaderboard() -> void:
 | 
			
		||||
	var score_item_container = $"Board/HighScores/ScoreItemContainer"
 | 
			
		||||
	if score_item_container.get_child_count() > 0:
 | 
			
		||||
		var children = score_item_container.get_children()
 | 
			
		||||
		for c in children:
 | 
			
		||||
			score_item_container.remove_child(c)
 | 
			
		||||
			c.queue_free()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_CloseButton_pressed() -> void:
 | 
			
		||||
	var scene_name = SilentWolf.scores_config.open_scene_on_close
 | 
			
		||||
	SWLogger.info("Closing SilentWolf leaderboard, switching to scene: " + str(scene_name))
 | 
			
		||||
	#global.reset()
 | 
			
		||||
	get_tree().change_scene_to_file(scene_name)
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
uid://bvcpbbvn2ydyc
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,63 @@
 | 
			
		|||
[gd_scene load_steps=5 format=3 uid="uid://b2h8ok8yfc7wb"]
 | 
			
		||||
 | 
			
		||||
[ext_resource type="Script" uid="uid://bvcpbbvn2ydyc" path="res://addons/silent_wolf/Scores/Leaderboard.gd" id="1"]
 | 
			
		||||
[ext_resource type="Theme" uid="uid://d2eakbmaefnt6" path="res://addons/silent_wolf/assets/themes/sw_theme.tres" id="2_ixaq4"]
 | 
			
		||||
[ext_resource type="PackedScene" uid="uid://clllbf6am8qdf" path="res://addons/silent_wolf/common/SWButton.tscn" id="4"]
 | 
			
		||||
 | 
			
		||||
[sub_resource type="Theme" id="Theme_j1gah"]
 | 
			
		||||
 | 
			
		||||
[node name="Leaderboard" type="Node2D"]
 | 
			
		||||
script = ExtResource("1")
 | 
			
		||||
 | 
			
		||||
[node name="OldBoard" type="MarginContainer" parent="."]
 | 
			
		||||
visible = false
 | 
			
		||||
 | 
			
		||||
[node name="HighScores" type="TextureRect" parent="OldBoard"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="Board" type="VBoxContainer" parent="."]
 | 
			
		||||
offset_left = 232.0
 | 
			
		||||
offset_top = 3.0
 | 
			
		||||
offset_right = 1738.0
 | 
			
		||||
offset_bottom = 1070.0
 | 
			
		||||
size_flags_horizontal = 3
 | 
			
		||||
size_flags_vertical = 3
 | 
			
		||||
theme = ExtResource("2_ixaq4")
 | 
			
		||||
theme_override_constants/separation = 48
 | 
			
		||||
alignment = 1
 | 
			
		||||
 | 
			
		||||
[node name="TitleContainer" type="CenterContainer" parent="Board"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="Label" type="Label" parent="Board/TitleContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_font_sizes/font_size = 72
 | 
			
		||||
text = "Leaderboard"
 | 
			
		||||
horizontal_alignment = 1
 | 
			
		||||
 | 
			
		||||
[node name="MessageContainer" type="CenterContainer" parent="Board"]
 | 
			
		||||
visible = false
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="TextMessage" type="Label" parent="Board/MessageContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_font_sizes/font_size = 36
 | 
			
		||||
text = "Loading scores..."
 | 
			
		||||
 | 
			
		||||
[node name="HighScores" type="CenterContainer" parent="Board"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme = SubResource("Theme_j1gah")
 | 
			
		||||
 | 
			
		||||
[node name="ScoreItemContainer" type="VBoxContainer" parent="Board/HighScores"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="CloseButtonContainer" type="CenterContainer" parent="Board"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="CloseButton" parent="Board/CloseButtonContainer" instance=ExtResource("4")]
 | 
			
		||||
custom_minimum_size = Vector2(600, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_font_sizes/font_size = 32
 | 
			
		||||
text = "Close Leaderboard"
 | 
			
		||||
 | 
			
		||||
[connection signal="pressed" from="Board/CloseButtonContainer/CloseButton" to="." method="_on_CloseButton_pressed"]
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,25 @@
 | 
			
		|||
[gd_scene format=3 uid="uid://2wy4d8d5av0l"]
 | 
			
		||||
 | 
			
		||||
[node name="ScoreItem" type="Panel"]
 | 
			
		||||
custom_minimum_size = Vector2(500, 50)
 | 
			
		||||
grow_horizontal = 0
 | 
			
		||||
grow_vertical = 0
 | 
			
		||||
 | 
			
		||||
[node name="PlayerName" type="RichTextLabel" parent="."]
 | 
			
		||||
custom_minimum_size = Vector2(100, 25)
 | 
			
		||||
layout_mode = 0
 | 
			
		||||
offset_left = 8.0
 | 
			
		||||
offset_right = 359.0
 | 
			
		||||
offset_bottom = 50.0
 | 
			
		||||
theme_override_font_sizes/normal_font_size = 32
 | 
			
		||||
text = "1. Godzilla"
 | 
			
		||||
 | 
			
		||||
[node name="Score" type="Label" parent="."]
 | 
			
		||||
layout_mode = 0
 | 
			
		||||
offset_left = 359.0
 | 
			
		||||
offset_top = 1.0
 | 
			
		||||
offset_right = 485.0
 | 
			
		||||
offset_bottom = 49.0
 | 
			
		||||
theme_override_font_sizes/font_size = 32
 | 
			
		||||
text = "13"
 | 
			
		||||
horizontal_alignment = 2
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,389 @@
 | 
			
		|||
extends Node
 | 
			
		||||
 | 
			
		||||
const SWLogger = preload("res://addons/silent_wolf/utils/SWLogger.gd")
 | 
			
		||||
const UUID = preload("res://addons/silent_wolf/utils/UUID.gd")
 | 
			
		||||
const SWHashing = preload("res://addons/silent_wolf/utils/SWHashing.gd")
 | 
			
		||||
const SWUtils = preload("res://addons/silent_wolf/utils/SWUtils.gd")
 | 
			
		||||
 | 
			
		||||
# new signals
 | 
			
		||||
signal sw_get_scores_complete
 | 
			
		||||
signal sw_get_player_scores_complete
 | 
			
		||||
signal sw_top_player_score_complete
 | 
			
		||||
signal sw_get_position_complete
 | 
			
		||||
signal sw_get_scores_around_complete
 | 
			
		||||
signal sw_save_score_complete
 | 
			
		||||
signal sw_wipe_leaderboard_complete
 | 
			
		||||
signal sw_delete_score_complete
 | 
			
		||||
 | 
			
		||||
# leaderboard scores by leaderboard name
 | 
			
		||||
var leaderboards = {}
 | 
			
		||||
# leaderboard scores from past periods by leaderboard name and period_offset (negative integers)
 | 
			
		||||
var leaderboards_past_periods = {}
 | 
			
		||||
# leaderboard configurations by leaderboard name
 | 
			
		||||
var ldboard_config = {}
 | 
			
		||||
 | 
			
		||||
# contains only the scores from one leaderboard at a time
 | 
			
		||||
var scores = []
 | 
			
		||||
var player_scores = []
 | 
			
		||||
var player_top_score = null
 | 
			
		||||
var local_scores = []
 | 
			
		||||
#var custom_local_scores = []
 | 
			
		||||
var score_id = ""
 | 
			
		||||
var position = 0
 | 
			
		||||
var scores_above = []
 | 
			
		||||
var scores_below  = []
 | 
			
		||||
 | 
			
		||||
#var request_timeout = 3
 | 
			
		||||
#var request_timer = null
 | 
			
		||||
 | 
			
		||||
# latest number of scores to be fetched from the backend
 | 
			
		||||
var latest_max = 10
 | 
			
		||||
 | 
			
		||||
var SaveScore = null
 | 
			
		||||
var GetScores = null
 | 
			
		||||
var ScorePosition = null
 | 
			
		||||
var ScoresAround = null
 | 
			
		||||
var ScoresByPlayer = null
 | 
			
		||||
var TopScoreByPlayer = null
 | 
			
		||||
var WipeLeaderboard = null
 | 
			
		||||
var DeleteScore = null
 | 
			
		||||
 | 
			
		||||
# wekrefs
 | 
			
		||||
var wrSaveScore = null
 | 
			
		||||
var wrGetScores = null
 | 
			
		||||
var wrScorePosition = null
 | 
			
		||||
var wrScoresAround = null
 | 
			
		||||
var wrScoresByPlayer = null
 | 
			
		||||
var wrTopScoreByPlayer = null
 | 
			
		||||
var wrWipeLeaderboard = null
 | 
			
		||||
var wrDeleteScore = null
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# metadata, if included should be a dictionary
 | 
			
		||||
# The score attribute could be either a score_value (int) or score_id (String)
 | 
			
		||||
func save_score(player_name: String, score, ldboard_name: String="main", metadata: Dictionary={}) -> Node:
 | 
			
		||||
	# player_name must be present
 | 
			
		||||
	if player_name == null or player_name == "":
 | 
			
		||||
		SWLogger.error("ERROR in SilentWolf.Scores.persist_score - please enter a valid player name")
 | 
			
		||||
	elif typeof(ldboard_name) != TYPE_STRING:
 | 
			
		||||
		# check that ldboard_name, if present is a String
 | 
			
		||||
		SWLogger.error("ERROR in SilentWolf.Scores.persist_score - leaderboard name must be a String")
 | 
			
		||||
	elif typeof(metadata) != TYPE_DICTIONARY:
 | 
			
		||||
		# check that metadata, if present, is a dictionary
 | 
			
		||||
		SWLogger.error("ERROR in SilentWolf.Scores.persist_score - metadata must be a dictionary")
 | 
			
		||||
	else:
 | 
			
		||||
		var prepared_http_req = SilentWolf.prepare_http_request()
 | 
			
		||||
		SaveScore = prepared_http_req.request
 | 
			
		||||
		wrSaveScore = prepared_http_req.weakref
 | 
			
		||||
		SaveScore.request_completed.connect(_on_SaveScore_request_completed)
 | 
			
		||||
		SWLogger.info("Calling SilentWolf backend to post new score...")
 | 
			
		||||
		var game_id = SilentWolf.config.game_id
 | 
			
		||||
		
 | 
			
		||||
		var score_uuid = UUID.generate_uuid_v4()
 | 
			
		||||
		score_id = score_uuid
 | 
			
		||||
		var payload = { 
 | 
			
		||||
			"score_id" : score_id, 
 | 
			
		||||
			"player_name" : player_name, 
 | 
			
		||||
			"game_id": game_id,  
 | 
			
		||||
			"score": score, 
 | 
			
		||||
			"ldboard_name": ldboard_name 
 | 
			
		||||
		}
 | 
			
		||||
		print("!metadata.empty(): " + str(!metadata.is_empty()))
 | 
			
		||||
		if !metadata.is_empty():
 | 
			
		||||
			print("metadata: " + str(metadata))
 | 
			
		||||
			payload["metadata"] = metadata
 | 
			
		||||
		SWLogger.debug("payload: " + str(payload))
 | 
			
		||||
		# also add to local scores
 | 
			
		||||
		add_to_local_scores(payload)
 | 
			
		||||
		var request_url = "https://api.silentwolf.com/save_score"
 | 
			
		||||
		SilentWolf.send_post_request(SaveScore, request_url, payload)
 | 
			
		||||
	return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_SaveScore_request_completed(result, response_code, headers, body) -> void:
 | 
			
		||||
	var status_check = SWUtils.check_http_response(response_code, headers, body)
 | 
			
		||||
	SilentWolf.free_request(wrSaveScore, SaveScore)
 | 
			
		||||
	
 | 
			
		||||
	if status_check:
 | 
			
		||||
		var json_body = JSON.parse_string(body.get_string_from_utf8())
 | 
			
		||||
		var sw_result: Dictionary = SilentWolf.build_result(json_body)
 | 
			
		||||
		if json_body.success:
 | 
			
		||||
			SWLogger.info("SilentWolf save score success.")
 | 
			
		||||
			sw_result["score_id"] = json_body.score_id
 | 
			
		||||
		else:
 | 
			
		||||
			SWLogger.error("SilentWolf save score failure: " + str(json_body.error))
 | 
			
		||||
		sw_save_score_complete.emit(sw_result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func get_scores(maximum: int=10, ldboard_name: String="main", period_offset: int=0) -> Node:
 | 
			
		||||
	var prepared_http_req = SilentWolf.prepare_http_request()
 | 
			
		||||
	GetScores = prepared_http_req.request
 | 
			
		||||
	wrGetScores = prepared_http_req.weakref
 | 
			
		||||
	GetScores.request_completed.connect(_on_GetScores_request_completed)
 | 
			
		||||
	SWLogger.info("Calling SilentWolf backend to get scores...")
 | 
			
		||||
	# resetting the latest_number value in case the first requests times out, we need to request the same amount of top scores in the retry
 | 
			
		||||
	latest_max = maximum
 | 
			
		||||
	var request_url = "https://api.silentwolf.com/get_scores/" + str(SilentWolf.config.game_id) + "?max=" + str(maximum)  + "&ldboard_name=" + str(ldboard_name) + "&period_offset=" + str(period_offset)
 | 
			
		||||
	SilentWolf.send_get_request(GetScores, request_url)
 | 
			
		||||
	return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_GetScores_request_completed(result, response_code, headers, body) -> void:
 | 
			
		||||
	var status_check = SWUtils.check_http_response(response_code, headers, body)
 | 
			
		||||
	SilentWolf.free_request(wrGetScores, GetScores)
 | 
			
		||||
	
 | 
			
		||||
	if status_check:
 | 
			
		||||
		var json_body = JSON.parse_string(body.get_string_from_utf8())
 | 
			
		||||
		var sw_result: Dictionary = SilentWolf.build_result(json_body)
 | 
			
		||||
		if json_body.success:
 | 
			
		||||
			SWLogger.info("SilentWolf get scores success, found " + str(json_body.top_scores.size()) + " scores.")
 | 
			
		||||
			scores = translate_score_fields_in_array(json_body.top_scores)
 | 
			
		||||
			SWLogger.debug("Scores: " + str(scores))
 | 
			
		||||
			var ld_name = json_body.ld_name
 | 
			
		||||
			var ld_config = json_body.ld_config
 | 
			
		||||
			if "period_offset" in json_body:
 | 
			
		||||
				var period_offset = str(json_body["period_offset"])
 | 
			
		||||
				leaderboards_past_periods[ld_name + ";" + period_offset] = scores
 | 
			
		||||
			else:
 | 
			
		||||
				leaderboards[ld_name] = scores
 | 
			
		||||
			ldboard_config[ld_name] = ld_config
 | 
			
		||||
			sw_result["scores"] = scores
 | 
			
		||||
			sw_result["ld_name"] = ld_name
 | 
			
		||||
		else:
 | 
			
		||||
			SWLogger.error("SilentWolf get scores failure: " + str(json_body.error))
 | 
			
		||||
		sw_get_scores_complete.emit(sw_result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func get_scores_by_player(player_name: String, maximum: int=10, ldboard_name: String="main", period_offset: int=0) -> Node:
 | 
			
		||||
	if player_name == null:
 | 
			
		||||
		SWLogger.error("Error in SilentWolf.Scores.get_scores_by_player: provided player_name is null")
 | 
			
		||||
	else:
 | 
			
		||||
		var prepared_http_req = SilentWolf.prepare_http_request()
 | 
			
		||||
		ScoresByPlayer = prepared_http_req.request
 | 
			
		||||
		wrScoresByPlayer = prepared_http_req.weakref
 | 
			
		||||
		ScoresByPlayer.request_completed.connect(_on_GetScoresByPlayer_request_completed)
 | 
			
		||||
		SWLogger.info("Calling SilentWolf backend to get scores for player: " + str(player_name))
 | 
			
		||||
		# resetting the latest_number value in case the first requests times out, we need to request the same amount of top scores in the retry
 | 
			
		||||
		latest_max = maximum
 | 
			
		||||
		var request_url = "https://api.silentwolf.com/get_scores_by_player/" + str(SilentWolf.config.game_id) + "?max=" + str(maximum)  + "&ldboard_name=" + str(ldboard_name.uri_encode()) + "&player_name=" + str(player_name.uri_encode()) + "&period_offset=" + str(period_offset)
 | 
			
		||||
		SilentWolf.send_get_request(ScoresByPlayer, request_url)
 | 
			
		||||
	return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_GetScoresByPlayer_request_completed(result, response_code, headers, body) -> void:
 | 
			
		||||
	var status_check = SWUtils.check_http_response(response_code, headers, body)
 | 
			
		||||
	SilentWolf.free_request(wrScoresByPlayer, ScoresByPlayer)
 | 
			
		||||
	
 | 
			
		||||
	if status_check:
 | 
			
		||||
		var json_body = JSON.parse_string(body.get_string_from_utf8())
 | 
			
		||||
		var sw_result: Dictionary = SilentWolf.build_result(json_body)
 | 
			
		||||
		if json_body.success:
 | 
			
		||||
			SWLogger.info("SilentWolf get scores by player success, found " + str(json_body.top_scores.size()) + " scores.")
 | 
			
		||||
			player_scores = translate_score_fields_in_array(json_body.top_scores)
 | 
			
		||||
			SWLogger.debug("Scores for " + json_body.player_name +  ": " + str(player_scores))
 | 
			
		||||
			var ld_name = json_body.ld_name
 | 
			
		||||
			var ld_config = json_body.ld_config
 | 
			
		||||
			var player_name = json_body.player_name
 | 
			
		||||
			sw_result["scores"] = player_scores
 | 
			
		||||
		else:
 | 
			
		||||
			SWLogger.error("SilentWolf get scores by player failure: " + str(json_body.error))
 | 
			
		||||
		sw_get_player_scores_complete.emit(sw_result)	
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func get_top_score_by_player(player_name: String, maximum: int=10, ldboard_name: String="main", period_offset: int=0) -> Node:
 | 
			
		||||
	if player_name == null:
 | 
			
		||||
		SWLogger.error("Error in SilentWolf.Scores.get_top_score_by_player: provided player_name is null")
 | 
			
		||||
	else:
 | 
			
		||||
		var prepared_http_req = SilentWolf.prepare_http_request()
 | 
			
		||||
		TopScoreByPlayer = prepared_http_req.request
 | 
			
		||||
		wrTopScoreByPlayer = prepared_http_req.weakref
 | 
			
		||||
		TopScoreByPlayer.request_completed.connect(_on_GetTopScoreByPlayer_request_completed)
 | 
			
		||||
		SWLogger.info("Calling SilentWolf backend to get top score for player: " + str(player_name))
 | 
			
		||||
		# resetting the latest_number value in case the first requests times out, we need to request the same amount of top scores in the retry
 | 
			
		||||
		latest_max = maximum
 | 
			
		||||
		var request_url = "https://api.silentwolf.com/get_top_score_by_player/" + str(SilentWolf.config.game_id) + "?max=" + str(maximum)  + "&ldboard_name=" + str(ldboard_name.uri_encode()) + "&player_name=" + str(player_name.uri_encode()) + "&period_offset=" + str(period_offset)
 | 
			
		||||
		SilentWolf.send_get_request(TopScoreByPlayer, request_url)
 | 
			
		||||
	return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_GetTopScoreByPlayer_request_completed(result, response_code, headers, body) -> void:
 | 
			
		||||
	var status_check = SWUtils.check_http_response(response_code, headers, body)
 | 
			
		||||
	SilentWolf.free_request(wrTopScoreByPlayer, TopScoreByPlayer)
 | 
			
		||||
	
 | 
			
		||||
	if status_check:
 | 
			
		||||
		var json_body = JSON.parse_string(body.get_string_from_utf8())
 | 
			
		||||
		var sw_result: Dictionary = SilentWolf.build_result(json_body)
 | 
			
		||||
		if json_body.success:
 | 
			
		||||
			SWLogger.info("SilentWolf get top score by player success, found top score? " + str(!json_body.top_score.is_empty()))
 | 
			
		||||
			if !json_body.top_score.is_empty():
 | 
			
		||||
				player_top_score = translate_score_fields(json_body.top_score)
 | 
			
		||||
				SWLogger.debug("Top score for " + json_body.player_name +  ": " + str(player_top_score))
 | 
			
		||||
				var ld_name = json_body.ld_name
 | 
			
		||||
				var ld_config = json_body.ld_config
 | 
			
		||||
				var player_name = json_body.player_name
 | 
			
		||||
				sw_result["top_score"] = player_top_score
 | 
			
		||||
		else:
 | 
			
		||||
			SWLogger.error("SilentWolf get top score by player failure: " + str(json_body.error))
 | 
			
		||||
		sw_top_player_score_complete.emit(sw_result)	
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# The score attribute could be either a score_value (int) or score_id (Sstring)
 | 
			
		||||
func get_score_position(score, ldboard_name: String="main") -> Node:
 | 
			
		||||
	var score_id = null
 | 
			
		||||
	var score_value = null
 | 
			
		||||
	print("score: " + str(score))
 | 
			
		||||
	if UUID.is_uuid(str(score)):
 | 
			
		||||
		score_id = score 
 | 
			
		||||
	else:
 | 
			
		||||
		score_value = score
 | 
			
		||||
	var prepared_http_req = SilentWolf.prepare_http_request()
 | 
			
		||||
	ScorePosition = prepared_http_req.request
 | 
			
		||||
	wrScorePosition = prepared_http_req.weakref
 | 
			
		||||
	ScorePosition.request_completed.connect(_on_GetScorePosition_request_completed)
 | 
			
		||||
	SWLogger.info("Calling SilentWolf to get score position")
 | 
			
		||||
	var payload = { "game_id": SilentWolf.config.game_id, "ldboard_name": ldboard_name }
 | 
			
		||||
	if score_id:
 | 
			
		||||
		payload["score_id"] = score_id
 | 
			
		||||
	if score_value:
 | 
			
		||||
		payload["score_value"] = score_value
 | 
			
		||||
	var request_url = "https://api.silentwolf.com/get_score_position"
 | 
			
		||||
	SilentWolf.send_post_request(ScorePosition, request_url, payload)
 | 
			
		||||
	return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_GetScorePosition_request_completed(result, response_code, headers, body) -> void:
 | 
			
		||||
	var status_check = SWUtils.check_http_response(response_code, headers, body)
 | 
			
		||||
	SilentWolf.free_request(wrScorePosition, ScorePosition)
 | 
			
		||||
	
 | 
			
		||||
	if status_check:
 | 
			
		||||
		var json_body = JSON.parse_string(body.get_string_from_utf8())
 | 
			
		||||
		var sw_result: Dictionary = SilentWolf.build_result(json_body)
 | 
			
		||||
		if json_body.success:
 | 
			
		||||
			SWLogger.info("SilentWolf get score position success: " + str(json_body.position))
 | 
			
		||||
			sw_result["position"] =  int(json_body.position)
 | 
			
		||||
		else:
 | 
			
		||||
			SWLogger.error("SilentWolf get score position failure: " + str(json_body.error))
 | 
			
		||||
		sw_get_position_complete.emit(sw_result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# The score attribute couldd be either a score_value (int) or score_id (Sstring)
 | 
			
		||||
func get_scores_around(score, scores_to_fetch=3, ldboard_name: String="main") -> Node:
 | 
			
		||||
	var score_id = "Null"
 | 
			
		||||
	var score_value = "Null"
 | 
			
		||||
	print("score: " + str(score))
 | 
			
		||||
	if UUID.is_uuid(str(score)):
 | 
			
		||||
		score_id = score 
 | 
			
		||||
	else:
 | 
			
		||||
		score_value = score
 | 
			
		||||
	var prepared_http_req = SilentWolf.prepare_http_request()
 | 
			
		||||
	ScoresAround = prepared_http_req.request
 | 
			
		||||
	wrScoresAround = prepared_http_req.weakref
 | 
			
		||||
	ScoresAround.request_completed.connect(_on_ScoresAround_request_completed)
 | 
			
		||||
	
 | 
			
		||||
	SWLogger.info("Calling SilentWolf backend to scores above and below a certain score...")
 | 
			
		||||
	# resetting the latest_number value in case the first requests times out, we need to request the same amount of top scores in the retry
 | 
			
		||||
	#latest_max = maximum
 | 
			
		||||
	var request_url = "https://api.silentwolf.com/get_scores_around/" + str(SilentWolf.config.game_id) + "?scores_to_fetch=" + str(scores_to_fetch)  + "&ldboard_name=" + str(ldboard_name) + "&score_id=" + str(score_id) + "&score_value=" + str(score_value)
 | 
			
		||||
	SilentWolf.send_get_request(ScoresAround, request_url)
 | 
			
		||||
	return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_ScoresAround_request_completed(result, response_code, headers, body) -> void:
 | 
			
		||||
	var status_check = SWUtils.check_http_response(response_code, headers, body)
 | 
			
		||||
	
 | 
			
		||||
	SilentWolf.free_request(wrScoresAround, ScoresAround)
 | 
			
		||||
	
 | 
			
		||||
	if status_check:
 | 
			
		||||
		var json_body = JSON.parse_string(body.get_string_from_utf8())
 | 
			
		||||
		var sw_result: Dictionary = SilentWolf.build_result(json_body)
 | 
			
		||||
		if json_body.success:
 | 
			
		||||
			SWLogger.info("SilentWolf get scores around success.")
 | 
			
		||||
			sw_result["scores_above"] = translate_score_fields_in_array(json_body.scores_above)
 | 
			
		||||
			sw_result["scores_below"] = translate_score_fields_in_array(json_body.scores_below)
 | 
			
		||||
			if "score_position" in json_body:
 | 
			
		||||
				sw_result["position"] = json_body.score_position
 | 
			
		||||
		else:
 | 
			
		||||
			SWLogger.error("SilentWolf get scores around failure: " + str(json_body.error))
 | 
			
		||||
		sw_get_scores_around_complete.emit(sw_result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func delete_score(score_id: String, ldboard_name: String='main') -> Node:
 | 
			
		||||
	var prepared_http_req = SilentWolf.prepare_http_request()
 | 
			
		||||
	DeleteScore = prepared_http_req.request
 | 
			
		||||
	wrDeleteScore = prepared_http_req.weakref
 | 
			
		||||
	DeleteScore.request_completed.connect(_on_DeleteScore_request_completed)
 | 
			
		||||
	SWLogger.info("Calling SilentWolf to delete a score")
 | 
			
		||||
	var request_url = "https://api.silentwolf.com/delete_score?game_id=" + str(SilentWolf.config.game_id) + "&ldboard_name=" + str(ldboard_name) + "&score_id=" + str(score_id)
 | 
			
		||||
	SilentWolf.send_get_request(DeleteScore, request_url)
 | 
			
		||||
	return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_DeleteScore_request_completed(result, response_code, headers, body) -> void:
 | 
			
		||||
	var status_check = SWUtils.check_http_response(response_code, headers, body)
 | 
			
		||||
	SilentWolf.free_request(wrDeleteScore, DeleteScore)
 | 
			
		||||
	
 | 
			
		||||
	if status_check:
 | 
			
		||||
		var json_body = JSON.parse_string(body.get_string_from_utf8())
 | 
			
		||||
		var sw_result: Dictionary = SilentWolf.build_result(json_body)
 | 
			
		||||
		if json_body.success:
 | 
			
		||||
			SWLogger.info("SilentWolf delete score success")
 | 
			
		||||
		else:
 | 
			
		||||
			SWLogger.error("SilentWolf delete score failure: " + str(json_body.error))
 | 
			
		||||
		sw_delete_score_complete.emit(sw_result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Deletes all your scores for your game
 | 
			
		||||
# Scores are permanently deleted, no going back!
 | 
			
		||||
func wipe_leaderboard(ldboard_name: String='main') -> Node:
 | 
			
		||||
	var prepared_http_req = SilentWolf.prepare_http_request()
 | 
			
		||||
	WipeLeaderboard = prepared_http_req.request
 | 
			
		||||
	wrWipeLeaderboard = prepared_http_req.weakref
 | 
			
		||||
	WipeLeaderboard.request_completed.connect(_on_WipeLeaderboard_request_completed)
 | 
			
		||||
	SWLogger.info("Calling SilentWolf backend to wipe leaderboard...")
 | 
			
		||||
	var payload = { "game_id": SilentWolf.config.game_id, "ldboard_name": ldboard_name }
 | 
			
		||||
	var request_url = "https://api.silentwolf.com/wipe_leaderboard"
 | 
			
		||||
	SilentWolf.send_post_request(WipeLeaderboard, request_url, payload)
 | 
			
		||||
	return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_WipeLeaderboard_request_completed(result, response_code, headers, body) -> void:
 | 
			
		||||
	var status_check = SWUtils.check_http_response(response_code, headers, body)
 | 
			
		||||
	SilentWolf.free_request(wrWipeLeaderboard, WipeLeaderboard)
 | 
			
		||||
	
 | 
			
		||||
	if status_check:
 | 
			
		||||
		var json_body = JSON.parse_string(body.get_string_from_utf8())
 | 
			
		||||
		var sw_result: Dictionary = SilentWolf.build_result(json_body)
 | 
			
		||||
		if json_body.success:
 | 
			
		||||
			SWLogger.info("SilentWolf wipe leaderboard success.")
 | 
			
		||||
		else:
 | 
			
		||||
			SWLogger.error("SilentWolf wipe leaderboard failure: " + str(json_body.error))
 | 
			
		||||
		sw_wipe_leaderboard_complete.emit(sw_result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func add_to_local_scores(game_result: Dictionary, ld_name: String="main") -> void:
 | 
			
		||||
	var local_score = { "score_id": game_result.score_id, "game_id" : game_result.game_id, "player_name": game_result.player_name, "score": game_result.score }
 | 
			
		||||
	local_scores.append(local_score)
 | 
			
		||||
	SWLogger.debug("local scores: " + str(local_scores))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func translate_score_fields_in_array(scores: Array) -> Array:
 | 
			
		||||
	var translated_scores = []
 | 
			
		||||
	for score in scores:
 | 
			
		||||
		var new_score = translate_score_fields(score)
 | 
			
		||||
		translated_scores.append(new_score)
 | 
			
		||||
	return translated_scores
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func translate_score_fields(score: Dictionary) -> Dictionary:
 | 
			
		||||
	var translated_score = {}
 | 
			
		||||
	translated_score["score_id"] = score["sid"]
 | 
			
		||||
	translated_score["score"] = score["s"]
 | 
			
		||||
	translated_score["player_name"] = score["pn"]
 | 
			
		||||
	if "md" in score:
 | 
			
		||||
		translated_score["metadata"] = score["md"]
 | 
			
		||||
	if "position" in score:
 | 
			
		||||
		translated_score["position"] = score["position"]
 | 
			
		||||
	if "t" in score:
 | 
			
		||||
		translated_score["timestamp"] = score["t"]
 | 
			
		||||
	return translated_score
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
uid://cgoyt6qo5vgsq
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,248 @@
 | 
			
		|||
extends Node
 | 
			
		||||
 | 
			
		||||
const version = "0.9.9"
 | 
			
		||||
var godot_version = Engine.get_version_info().string
 | 
			
		||||
 | 
			
		||||
const SWUtils = preload("res://addons/silent_wolf/utils/SWUtils.gd")
 | 
			
		||||
const SWHashing = preload("res://addons/silent_wolf/utils/SWHashing.gd")
 | 
			
		||||
const SWLogger = preload("res://addons/silent_wolf/utils/SWLogger.gd")
 | 
			
		||||
 | 
			
		||||
#var Auth = Node.new()
 | 
			
		||||
#var Scores = Node.new()
 | 
			
		||||
#var Players = Node.new()
 | 
			
		||||
#var Multiplayer = Node.new()
 | 
			
		||||
 | 
			
		||||
@onready var Auth = Node.new()
 | 
			
		||||
@onready var Scores = Node.new()
 | 
			
		||||
@onready var Players = Node.new()
 | 
			
		||||
@onready var Multiplayer = Node.new()
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# SILENTWOLF CONFIG: THE CONFIG VARIABLES BELOW WILL BE OVERRIDED THE 
 | 
			
		||||
# NEXT TIME YOU UPDATE YOUR PLUGIN!
 | 
			
		||||
#
 | 
			
		||||
# As a best practice, use SilentWolf.configure from your game's
 | 
			
		||||
# code instead to set the SilentWolf configuration.
 | 
			
		||||
#
 | 
			
		||||
# See https://silentwolf.com for more details
 | 
			
		||||
#
 | 
			
		||||
var config = {
 | 
			
		||||
	"api_key": "FmKF4gtm0Z2RbUAEU62kZ2OZoYLj4PYOURAPIKEY",
 | 
			
		||||
	"game_id": "YOURGAMEID",
 | 
			
		||||
	"log_level": 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var scores_config = {
 | 
			
		||||
	"open_scene_on_close": "res://scenes/Splash.tscn"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var auth_config = {
 | 
			
		||||
	"redirect_to_scene": "res://scenes/Splash.tscn",
 | 
			
		||||
	"login_scene": "res://addons/silent_wolf/Auth/Login.tscn",
 | 
			
		||||
	"email_confirmation_scene": "res://addons/silent_wolf/Auth/ConfirmEmail.tscn",
 | 
			
		||||
	"reset_password_scene": "res://addons/silent_wolf/Auth/ResetPassword.tscn",
 | 
			
		||||
	"session_duration_seconds": 0,
 | 
			
		||||
	"saved_session_expiration_days": 30
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var auth_script = load("res://addons/silent_wolf/Auth/Auth.gd")
 | 
			
		||||
var scores_script = load("res://addons/silent_wolf/Scores/Scores.gd")
 | 
			
		||||
var players_script = load("res://addons/silent_wolf/Players/Players.gd")
 | 
			
		||||
#var multiplayer_script = load("res://addons/silent_wolf/Multiplayer/Multiplayer.gd")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _init():
 | 
			
		||||
	print("SW Init timestamp: " + str(SWUtils.get_timestamp()))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _ready():
 | 
			
		||||
	# The following line would keep SilentWolf working even if the game tree is paused.
 | 
			
		||||
	#pause_mode = Node.PAUSE_MODE_PROCESS
 | 
			
		||||
	print("SW ready start timestamp: " + str(SWUtils.get_timestamp()))
 | 
			
		||||
	Auth.set_script(auth_script)
 | 
			
		||||
	add_child(Auth)
 | 
			
		||||
	Scores.set_script(scores_script)
 | 
			
		||||
	add_child(Scores)
 | 
			
		||||
	Players.set_script(players_script)
 | 
			
		||||
	add_child(Players)
 | 
			
		||||
	#Multiplayer.set_script(multiplayer_script)
 | 
			
		||||
	#add_child(Multiplayer)
 | 
			
		||||
	print("SW ready end timestamp: " + str(SWUtils.get_timestamp()))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func configure(json_config):
 | 
			
		||||
	config = json_config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func configure_api_key(api_key):
 | 
			
		||||
	config.apiKey = api_key
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func configure_game_id(game_id):
 | 
			
		||||
	config.game_id = game_id
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func configure_game_version(game_version):
 | 
			
		||||
	config.game_version = game_version
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
##################################################################
 | 
			
		||||
# Log levels:
 | 
			
		||||
# 0 - error (only log errors)
 | 
			
		||||
# 1 - info (log errors and the main actions taken by the SilentWolf plugin) - default setting
 | 
			
		||||
# 2 - debug (detailed logs, including the above and much more, to be used when investigating a problem). This shouldn't be the default setting in production.
 | 
			
		||||
##################################################################
 | 
			
		||||
func configure_log_level(log_level):
 | 
			
		||||
	config.log_level = log_level
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func configure_scores(json_scores_config):
 | 
			
		||||
	scores_config = json_scores_config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func configure_scores_open_scene_on_close(scene):
 | 
			
		||||
	scores_config.open_scene_on_close = scene
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func configure_auth(json_auth_config):
 | 
			
		||||
	auth_config = json_auth_config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func configure_auth_redirect_to_scene(scene):
 | 
			
		||||
	auth_config.open_scene_on_close = scene
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func configure_auth_session_duration(duration):
 | 
			
		||||
	auth_config.session_duration = duration
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func free_request(weak_ref, object):
 | 
			
		||||
	if (weak_ref.get_ref()):
 | 
			
		||||
		object.queue_free()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func prepare_http_request() -> Dictionary:
 | 
			
		||||
	var request = HTTPRequest.new()
 | 
			
		||||
	var weakref = weakref(request)
 | 
			
		||||
	if OS.get_name() != "Web":
 | 
			
		||||
		request.set_use_threads(true)
 | 
			
		||||
	request.process_mode = Node.PROCESS_MODE_ALWAYS
 | 
			
		||||
	get_tree().get_root().call_deferred("add_child", request)
 | 
			
		||||
	var return_dict = {
 | 
			
		||||
		"request": request, 
 | 
			
		||||
		"weakref": weakref
 | 
			
		||||
	}
 | 
			
		||||
	return return_dict
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func send_get_request(http_node: HTTPRequest, request_url: String):
 | 
			
		||||
	var headers = [
 | 
			
		||||
		"x-api-key: " + SilentWolf.config.api_key, 
 | 
			
		||||
		"x-sw-game-id: " + SilentWolf.config.game_id,
 | 
			
		||||
		"x-sw-plugin-version: " + SilentWolf.version,
 | 
			
		||||
		"x-sw-godot-version: " + godot_version 
 | 
			
		||||
	]
 | 
			
		||||
	headers = add_jwt_token_headers(headers)
 | 
			
		||||
	print("GET headers: " + str(headers))
 | 
			
		||||
	if !http_node.is_inside_tree():
 | 
			
		||||
		await get_tree().create_timer(0.01).timeout
 | 
			
		||||
	SWLogger.debug("Method: GET")
 | 
			
		||||
	SWLogger.debug("request_url: " + str(request_url))
 | 
			
		||||
	SWLogger.debug("headers: " + str(headers))
 | 
			
		||||
	http_node.request(request_url, headers) 
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func send_post_request(http_node, request_url, payload):
 | 
			
		||||
	var headers = [
 | 
			
		||||
		"Content-Type: application/json", 
 | 
			
		||||
		"x-api-key: " + SilentWolf.config.api_key, 
 | 
			
		||||
		"x-sw-game-id: " + SilentWolf.config.game_id,
 | 
			
		||||
		"x-sw-plugin-version: " + SilentWolf.version,
 | 
			
		||||
		"x-sw-godot-version: " + godot_version 
 | 
			
		||||
	]
 | 
			
		||||
	headers = add_jwt_token_headers(headers)
 | 
			
		||||
	print("POST headers: " + str(headers))
 | 
			
		||||
	# TODO: This should in fact be the case for all POST requests, make the following code more generic
 | 
			
		||||
	#var post_request_paths: Array[String] = ["post_new_score", "push_player_data"]
 | 
			
		||||
	var paths_with_values_to_hash: Dictionary = {
 | 
			
		||||
		"save_score": ["player_name", "score"],
 | 
			
		||||
		"push_player_data": ["player_name", "player_data"]
 | 
			
		||||
	}
 | 
			
		||||
	for path in paths_with_values_to_hash:
 | 
			
		||||
		var values_to_hash = []
 | 
			
		||||
		if check_string_in_url(path, request_url):
 | 
			
		||||
			SWLogger.debug("Computing hash for " + str(path))
 | 
			
		||||
			var fields_to_hash = paths_with_values_to_hash[path]
 | 
			
		||||
			for field in fields_to_hash:
 | 
			
		||||
				var value = payload[field]
 | 
			
		||||
				# if the data is a dictionary (e.g. player data, stringify it before hashing)
 | 
			
		||||
				if typeof(payload[field]) == TYPE_DICTIONARY:
 | 
			
		||||
					value = JSON.stringify(payload[field])
 | 
			
		||||
				values_to_hash = values_to_hash + [value]
 | 
			
		||||
			var timestamp = SWUtils.get_timestamp()
 | 
			
		||||
			values_to_hash = values_to_hash + [timestamp]
 | 
			
		||||
			SWLogger.debug(str(path) + " to_be_hashed: " + str(values_to_hash))
 | 
			
		||||
			var hashed = SWHashing.hash_values(values_to_hash)
 | 
			
		||||
			SWLogger.debug("hash value: " + str(hashed))
 | 
			
		||||
			headers.append("x-sw-act-tmst: " + str(timestamp))
 | 
			
		||||
			headers.append("x-sw-act-dig: " + hashed)
 | 
			
		||||
			break
 | 
			
		||||
	var use_ssl = true
 | 
			
		||||
	if !http_node.is_inside_tree():
 | 
			
		||||
		await get_tree().create_timer(0.01).timeout
 | 
			
		||||
	var query = JSON.stringify(payload)
 | 
			
		||||
	SWLogger.debug("Method: POST")
 | 
			
		||||
	SWLogger.debug("request_url: " + str(request_url))
 | 
			
		||||
	SWLogger.debug("headers: " + str(headers))
 | 
			
		||||
	SWLogger.debug("query: " + str(query))
 | 
			
		||||
	http_node.request(request_url, headers, HTTPClient.METHOD_POST, query)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func add_jwt_token_headers(headers: Array) -> Array:
 | 
			
		||||
	if Auth.sw_id_token != null:
 | 
			
		||||
		headers.append("x-sw-id-token: " + Auth.sw_id_token)
 | 
			
		||||
	if Auth.sw_access_token != null:
 | 
			
		||||
		headers.append("x-sw-access-token: " + Auth.sw_access_token)
 | 
			
		||||
	return headers
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func check_string_in_url(test_string: String, url: String) -> bool:
 | 
			
		||||
	return test_string in url
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func build_result(body: Dictionary) -> Dictionary:
 | 
			
		||||
	var error = null
 | 
			
		||||
	var success = false
 | 
			
		||||
	if "error" in body:
 | 
			
		||||
		error = body.error
 | 
			
		||||
	if "success" in body:
 | 
			
		||||
		success = body.success
 | 
			
		||||
	return {
 | 
			
		||||
		"success": success,
 | 
			
		||||
		"error": error
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func check_auth_ready():
 | 
			
		||||
	if !Auth:
 | 
			
		||||
		await get_tree().create_timer(0.01).timeout
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func check_scores_ready():
 | 
			
		||||
	if !Scores:
 | 
			
		||||
		await get_tree().create_timer(0.01).timeout
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func check_players_ready():
 | 
			
		||||
	if !Players:
 | 
			
		||||
		await get_tree().create_timer(0.01).timeout
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func check_multiplayer_ready():
 | 
			
		||||
	if !Multiplayer:
 | 
			
		||||
		await get_tree().create_timer(0.01).timeout
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func check_sw_ready():
 | 
			
		||||
	if !Auth or !Scores or !Players or !Multiplayer:
 | 
			
		||||
		await get_tree().create_timer(0.01).timeout
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
uid://6vtjmk436rp3
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,35 @@
 | 
			
		|||
[remap]
 | 
			
		||||
 | 
			
		||||
importer="font_data_dynamic"
 | 
			
		||||
type="FontFile"
 | 
			
		||||
uid="uid://d4kabkagmfeq"
 | 
			
		||||
path="res://.godot/imported/Comfortaa-Bold.ttf-4fa420dde6e624fdca7fe038f7939233.fontdata"
 | 
			
		||||
 | 
			
		||||
[deps]
 | 
			
		||||
 | 
			
		||||
source_file="res://addons/silent_wolf/assets/fonts/Comfortaa-Bold.ttf"
 | 
			
		||||
dest_files=["res://.godot/imported/Comfortaa-Bold.ttf-4fa420dde6e624fdca7fe038f7939233.fontdata"]
 | 
			
		||||
 | 
			
		||||
[params]
 | 
			
		||||
 | 
			
		||||
Rendering=null
 | 
			
		||||
antialiasing=1
 | 
			
		||||
generate_mipmaps=false
 | 
			
		||||
disable_embedded_bitmaps=true
 | 
			
		||||
multichannel_signed_distance_field=false
 | 
			
		||||
msdf_pixel_range=8
 | 
			
		||||
msdf_size=48
 | 
			
		||||
allow_system_fallback=true
 | 
			
		||||
force_autohinter=false
 | 
			
		||||
hinting=1
 | 
			
		||||
subpixel_positioning=1
 | 
			
		||||
keep_rounding_remainders=true
 | 
			
		||||
oversampling=0.0
 | 
			
		||||
Fallbacks=null
 | 
			
		||||
fallbacks=[]
 | 
			
		||||
Compress=null
 | 
			
		||||
compress=true
 | 
			
		||||
preload=[]
 | 
			
		||||
language_support={}
 | 
			
		||||
script_support={}
 | 
			
		||||
opentype_features={}
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 813 B  | 
| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
[remap]
 | 
			
		||||
 | 
			
		||||
importer="texture"
 | 
			
		||||
type="CompressedTexture2D"
 | 
			
		||||
uid="uid://bpn62aabunxva"
 | 
			
		||||
path="res://.godot/imported/checkbox_checked.png-a8704d0614a98c80620c9451baa9e021.ctex"
 | 
			
		||||
metadata={
 | 
			
		||||
"vram_texture": false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[deps]
 | 
			
		||||
 | 
			
		||||
source_file="res://addons/silent_wolf/assets/gfx/checkbox_checked.png"
 | 
			
		||||
dest_files=["res://.godot/imported/checkbox_checked.png-a8704d0614a98c80620c9451baa9e021.ctex"]
 | 
			
		||||
 | 
			
		||||
[params]
 | 
			
		||||
 | 
			
		||||
compress/mode=0
 | 
			
		||||
compress/high_quality=false
 | 
			
		||||
compress/lossy_quality=0.7
 | 
			
		||||
compress/hdr_compression=1
 | 
			
		||||
compress/normal_map=0
 | 
			
		||||
compress/channel_pack=0
 | 
			
		||||
mipmaps/generate=false
 | 
			
		||||
mipmaps/limit=-1
 | 
			
		||||
roughness/mode=0
 | 
			
		||||
roughness/src_normal=""
 | 
			
		||||
process/fix_alpha_border=true
 | 
			
		||||
process/premult_alpha=false
 | 
			
		||||
process/normal_map_invert_y=false
 | 
			
		||||
process/hdr_as_srgb=false
 | 
			
		||||
process/hdr_clamp_exposure=false
 | 
			
		||||
process/size_limit=0
 | 
			
		||||
detect_3d/compress_to=1
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 215 B  | 
| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
[remap]
 | 
			
		||||
 | 
			
		||||
importer="texture"
 | 
			
		||||
type="CompressedTexture2D"
 | 
			
		||||
uid="uid://bgs8a50ilk5cj"
 | 
			
		||||
path="res://.godot/imported/checkbox_unchecked.png-747244fa66895a7a282a8f1df36959ae.ctex"
 | 
			
		||||
metadata={
 | 
			
		||||
"vram_texture": false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[deps]
 | 
			
		||||
 | 
			
		||||
source_file="res://addons/silent_wolf/assets/gfx/checkbox_unchecked.png"
 | 
			
		||||
dest_files=["res://.godot/imported/checkbox_unchecked.png-747244fa66895a7a282a8f1df36959ae.ctex"]
 | 
			
		||||
 | 
			
		||||
[params]
 | 
			
		||||
 | 
			
		||||
compress/mode=0
 | 
			
		||||
compress/high_quality=false
 | 
			
		||||
compress/lossy_quality=0.7
 | 
			
		||||
compress/hdr_compression=1
 | 
			
		||||
compress/normal_map=0
 | 
			
		||||
compress/channel_pack=0
 | 
			
		||||
mipmaps/generate=false
 | 
			
		||||
mipmaps/limit=-1
 | 
			
		||||
roughness/mode=0
 | 
			
		||||
roughness/src_normal=""
 | 
			
		||||
process/fix_alpha_border=true
 | 
			
		||||
process/premult_alpha=false
 | 
			
		||||
process/normal_map_invert_y=false
 | 
			
		||||
process/hdr_as_srgb=false
 | 
			
		||||
process/hdr_clamp_exposure=false
 | 
			
		||||
process/size_limit=0
 | 
			
		||||
detect_3d/compress_to=1
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 158 B  | 
| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
[remap]
 | 
			
		||||
 | 
			
		||||
importer="texture"
 | 
			
		||||
type="CompressedTexture2D"
 | 
			
		||||
uid="uid://gdw18po2h7hb"
 | 
			
		||||
path="res://.godot/imported/dummy_info_icon_small.png-dbbedcd89d38767ae4fbec73495e5426.ctex"
 | 
			
		||||
metadata={
 | 
			
		||||
"vram_texture": false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[deps]
 | 
			
		||||
 | 
			
		||||
source_file="res://addons/silent_wolf/assets/gfx/dummy_info_icon_small.png"
 | 
			
		||||
dest_files=["res://.godot/imported/dummy_info_icon_small.png-dbbedcd89d38767ae4fbec73495e5426.ctex"]
 | 
			
		||||
 | 
			
		||||
[params]
 | 
			
		||||
 | 
			
		||||
compress/mode=0
 | 
			
		||||
compress/high_quality=false
 | 
			
		||||
compress/lossy_quality=0.7
 | 
			
		||||
compress/hdr_compression=1
 | 
			
		||||
compress/normal_map=0
 | 
			
		||||
compress/channel_pack=0
 | 
			
		||||
mipmaps/generate=false
 | 
			
		||||
mipmaps/limit=-1
 | 
			
		||||
roughness/mode=0
 | 
			
		||||
roughness/src_normal=""
 | 
			
		||||
process/fix_alpha_border=true
 | 
			
		||||
process/premult_alpha=false
 | 
			
		||||
process/normal_map_invert_y=false
 | 
			
		||||
process/hdr_as_srgb=false
 | 
			
		||||
process/hdr_clamp_exposure=false
 | 
			
		||||
process/size_limit=0
 | 
			
		||||
detect_3d/compress_to=1
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 23 KiB  | 
| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
[remap]
 | 
			
		||||
 | 
			
		||||
importer="texture"
 | 
			
		||||
type="CompressedTexture2D"
 | 
			
		||||
uid="uid://bex7pcjbbiqh8"
 | 
			
		||||
path="res://.godot/imported/info_icon.png-75dcfef383fc397e1798b32243763c67.ctex"
 | 
			
		||||
metadata={
 | 
			
		||||
"vram_texture": false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[deps]
 | 
			
		||||
 | 
			
		||||
source_file="res://addons/silent_wolf/assets/gfx/info_icon.png"
 | 
			
		||||
dest_files=["res://.godot/imported/info_icon.png-75dcfef383fc397e1798b32243763c67.ctex"]
 | 
			
		||||
 | 
			
		||||
[params]
 | 
			
		||||
 | 
			
		||||
compress/mode=0
 | 
			
		||||
compress/high_quality=false
 | 
			
		||||
compress/lossy_quality=0.7
 | 
			
		||||
compress/hdr_compression=1
 | 
			
		||||
compress/normal_map=0
 | 
			
		||||
compress/channel_pack=0
 | 
			
		||||
mipmaps/generate=false
 | 
			
		||||
mipmaps/limit=-1
 | 
			
		||||
roughness/mode=0
 | 
			
		||||
roughness/src_normal=""
 | 
			
		||||
process/fix_alpha_border=true
 | 
			
		||||
process/premult_alpha=false
 | 
			
		||||
process/normal_map_invert_y=false
 | 
			
		||||
process/hdr_as_srgb=false
 | 
			
		||||
process/hdr_clamp_exposure=false
 | 
			
		||||
process/size_limit=0
 | 
			
		||||
detect_3d/compress_to=1
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 15 KiB  | 
| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
[remap]
 | 
			
		||||
 | 
			
		||||
importer="texture"
 | 
			
		||||
type="CompressedTexture2D"
 | 
			
		||||
uid="uid://dh8fem1lgom04"
 | 
			
		||||
path="res://.godot/imported/info_icon_small.png-8b621bcc49afb4260b6f80b5f252e0c7.ctex"
 | 
			
		||||
metadata={
 | 
			
		||||
"vram_texture": false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[deps]
 | 
			
		||||
 | 
			
		||||
source_file="res://addons/silent_wolf/assets/gfx/info_icon_small.png"
 | 
			
		||||
dest_files=["res://.godot/imported/info_icon_small.png-8b621bcc49afb4260b6f80b5f252e0c7.ctex"]
 | 
			
		||||
 | 
			
		||||
[params]
 | 
			
		||||
 | 
			
		||||
compress/mode=0
 | 
			
		||||
compress/high_quality=false
 | 
			
		||||
compress/lossy_quality=0.7
 | 
			
		||||
compress/hdr_compression=1
 | 
			
		||||
compress/normal_map=0
 | 
			
		||||
compress/channel_pack=0
 | 
			
		||||
mipmaps/generate=false
 | 
			
		||||
mipmaps/limit=-1
 | 
			
		||||
roughness/mode=0
 | 
			
		||||
roughness/src_normal=""
 | 
			
		||||
process/fix_alpha_border=true
 | 
			
		||||
process/premult_alpha=false
 | 
			
		||||
process/normal_map_invert_y=false
 | 
			
		||||
process/hdr_as_srgb=false
 | 
			
		||||
process/hdr_clamp_exposure=false
 | 
			
		||||
process/size_limit=0
 | 
			
		||||
detect_3d/compress_to=1
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
[gd_resource type="Theme" load_steps=6 format=3 uid="uid://d2eakbmaefnt6"]
 | 
			
		||||
 | 
			
		||||
[ext_resource type="FontFile" uid="uid://d4kabkagmfeq" path="res://addons/silent_wolf/assets/fonts/Comfortaa-Bold.ttf" id="1_noq36"]
 | 
			
		||||
 | 
			
		||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3vpgq"]
 | 
			
		||||
 | 
			
		||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fq0jv"]
 | 
			
		||||
bg_color = Color(0.827451, 0.203922, 0.219608, 1)
 | 
			
		||||
corner_radius_top_left = 12
 | 
			
		||||
corner_radius_top_right = 12
 | 
			
		||||
corner_radius_bottom_right = 12
 | 
			
		||||
corner_radius_bottom_left = 12
 | 
			
		||||
corner_detail = 17
 | 
			
		||||
 | 
			
		||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_0fslg"]
 | 
			
		||||
bg_color = Color(1, 1, 1, 1)
 | 
			
		||||
corner_radius_top_left = 12
 | 
			
		||||
corner_radius_top_right = 12
 | 
			
		||||
corner_radius_bottom_right = 12
 | 
			
		||||
corner_radius_bottom_left = 12
 | 
			
		||||
 | 
			
		||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_237x8"]
 | 
			
		||||
bg_color = Color(1, 1, 1, 1)
 | 
			
		||||
 | 
			
		||||
[resource]
 | 
			
		||||
resource_name = "SW_THEME"
 | 
			
		||||
default_font = ExtResource("1_noq36")
 | 
			
		||||
default_font_size = 36
 | 
			
		||||
BackButton/base_type = &"Button"
 | 
			
		||||
BackButton/styles/normal = SubResource("StyleBoxFlat_3vpgq")
 | 
			
		||||
Button/colors/font_color = Color(0.827451, 0.203922, 0.219608, 1)
 | 
			
		||||
Button/colors/font_focus_color = Color(1, 1, 1, 1)
 | 
			
		||||
Button/colors/font_outline_color = Color(0.827451, 0.203922, 0.219608, 1)
 | 
			
		||||
Button/colors/font_pressed_color = Color(0.827451, 0.203922, 0.219608, 1)
 | 
			
		||||
Button/colors/icon_pressed_color = Color(0.827451, 0.203922, 0.219608, 1)
 | 
			
		||||
Button/styles/hover = SubResource("StyleBoxFlat_fq0jv")
 | 
			
		||||
Button/styles/normal = SubResource("StyleBoxFlat_0fslg")
 | 
			
		||||
Button/styles/pressed = SubResource("StyleBoxFlat_237x8")
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,4 @@
 | 
			
		|||
[gd_scene format=3 uid="uid://clllbf6am8qdf"]
 | 
			
		||||
 | 
			
		||||
[node name="SWButton" type="Button"]
 | 
			
		||||
text = "Sample text"
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,92 @@
 | 
			
		|||
@tool
 | 
			
		||||
extends Node2D
 | 
			
		||||
 | 
			
		||||
const ScoreItem = preload("res://addons/silent_wolf/Scores/ScoreItem.tscn")
 | 
			
		||||
const SWLogger = preload("res://addons/silent_wolf/utils/SWLogger.gd")
 | 
			
		||||
 | 
			
		||||
var list_index = 0
 | 
			
		||||
var ld_name = "main"
 | 
			
		||||
 | 
			
		||||
func _ready():
 | 
			
		||||
	var scores = []
 | 
			
		||||
	if ld_name in SilentWolf.Scores.leaderboards:
 | 
			
		||||
		scores = SilentWolf.Scores.leaderboards[ld_name]
 | 
			
		||||
	
 | 
			
		||||
	if len(scores) > 0: 
 | 
			
		||||
		render_board(scores)
 | 
			
		||||
	else:
 | 
			
		||||
		# use a signal to notify when the high scores have been returned, and show a "loading" animation until it's the case...
 | 
			
		||||
		add_loading_scores_message()
 | 
			
		||||
		var sw_result = await SilentWolf.Scores.get_high_scores().sw_get_scores_complete
 | 
			
		||||
		scores = sw_result["scores"]
 | 
			
		||||
		hide_message()
 | 
			
		||||
		render_board(scores)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func render_board(scores: Array) -> void:
 | 
			
		||||
	if scores.is_empty():
 | 
			
		||||
		add_no_scores_message()
 | 
			
		||||
	else:
 | 
			
		||||
		if len(scores) > 1 and scores[0].score > scores[-1].score:
 | 
			
		||||
			scores.reverse()
 | 
			
		||||
		for i in range(len(scores)):
 | 
			
		||||
			var score = scores[i]
 | 
			
		||||
			add_item(score.player_name, str(int(score.score)))
 | 
			
		||||
			
 | 
			
		||||
			#var time = display_time(scores[i].score)
 | 
			
		||||
			#add_item(score.player_name, time)
 | 
			
		||||
			
 | 
			
		||||
#func display_time(time_in_millis):
 | 
			
		||||
#	var minutes = int(floor(time_in_millis / 60000))
 | 
			
		||||
#	var seconds = int(floor((time_in_millis % 60000) / 1000))
 | 
			
		||||
#	var millis = time_in_millis - minutes*60000 - seconds*1000
 | 
			
		||||
#	var displayable_time = str(minutes) + ":" + str(seconds) + ":" + str(millis)
 | 
			
		||||
#	return displayable_time
 | 
			
		||||
			
 | 
			
		||||
 | 
			
		||||
func reverse_order(scores: Array) -> Array:
 | 
			
		||||
	if len(scores) > 1 and scores[0].score > scores[-1].score:
 | 
			
		||||
		scores.reverse()
 | 
			
		||||
	return scores
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func sort_by_score(a: Dictionary, b: Dictionary) -> bool:
 | 
			
		||||
	if a.score > b.score:
 | 
			
		||||
		return true;
 | 
			
		||||
	else:
 | 
			
		||||
		if a.score < b.score:
 | 
			
		||||
			return false;
 | 
			
		||||
		else:
 | 
			
		||||
			return true;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func add_item(player_name: String, score_value: String) -> void:
 | 
			
		||||
	var item = ScoreItem.instance()
 | 
			
		||||
	list_index += 1
 | 
			
		||||
	item.get_node("PlayerName").text = str(list_index) + str(". ") + player_name
 | 
			
		||||
	item.get_node("Score").text = score_value
 | 
			
		||||
	item.offset_top = list_index * 100
 | 
			
		||||
	$"Board/HighScores/ScoreItemContainer".add_child(item)
 | 
			
		||||
 | 
			
		||||
func add_no_scores_message():
 | 
			
		||||
	var item = $"Board/MessageContainer/TextMessage"
 | 
			
		||||
	item.text = "No scores yet!"
 | 
			
		||||
	$"Board/MessageContainer".show()
 | 
			
		||||
	item.offset_top = 135
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func add_loading_scores_message() -> void:
 | 
			
		||||
	var item = $"Board/MessageContainer/TextMessage"
 | 
			
		||||
	item.text = "Loading scores..."
 | 
			
		||||
	$"Board/MessageContainer".show()
 | 
			
		||||
	item.offset_top = 135
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func hide_message() -> void:
 | 
			
		||||
	$"Board/MessageContainer".hide()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_CloseButton_pressed() -> void:
 | 
			
		||||
	var scene_name = SilentWolf.scores_config.open_scene_on_close
 | 
			
		||||
	print("scene_name: " + str(scene_name))
 | 
			
		||||
	get_tree().change_scene_to_file(scene_name)
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
uid://dwb4mjp33o4ye
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,51 @@
 | 
			
		|||
[gd_scene load_steps=5 format=3 uid="uid://de5nks52pxc80"]
 | 
			
		||||
 | 
			
		||||
[ext_resource type="Theme" uid="uid://d2eakbmaefnt6" path="res://addons/silent_wolf/assets/themes/sw_theme.tres" id="2_1tgjj"]
 | 
			
		||||
[ext_resource type="PackedScene" uid="uid://clllbf6am8qdf" path="res://addons/silent_wolf/common/SWButton.tscn" id="4"]
 | 
			
		||||
[ext_resource type="Script" uid="uid://dwb4mjp33o4ye" path="res://addons/silent_wolf/examples/CustomLeaderboards/ReverseLeaderboard.gd" id="5"]
 | 
			
		||||
 | 
			
		||||
[sub_resource type="Theme" id="2"]
 | 
			
		||||
 | 
			
		||||
[node name="ReverseLeaderboard" type="Node2D"]
 | 
			
		||||
script = ExtResource("5")
 | 
			
		||||
 | 
			
		||||
[node name="Board" type="VBoxContainer" parent="."]
 | 
			
		||||
offset_left = 722.0
 | 
			
		||||
offset_top = 293.0
 | 
			
		||||
offset_right = 1287.0
 | 
			
		||||
offset_bottom = 669.0
 | 
			
		||||
theme = ExtResource("2_1tgjj")
 | 
			
		||||
theme_override_constants/separation = 48
 | 
			
		||||
 | 
			
		||||
[node name="TitleContainer" type="CenterContainer" parent="Board"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="Label" type="Label" parent="Board/TitleContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_font_sizes/font_size = 72
 | 
			
		||||
text = "Reverse Leaderboard"
 | 
			
		||||
 | 
			
		||||
[node name="MessageContainer" type="CenterContainer" parent="Board"]
 | 
			
		||||
visible = false
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="TextMessage" type="Label" parent="Board/MessageContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
text = "Loading scores..."
 | 
			
		||||
 | 
			
		||||
[node name="HighScores" type="CenterContainer" parent="Board"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme = SubResource("2")
 | 
			
		||||
 | 
			
		||||
[node name="ScoreItemContainer" type="VBoxContainer" parent="Board/HighScores"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="CloseButtonContainer" type="CenterContainer" parent="Board"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="CloseButton" parent="Board/CloseButtonContainer" instance=ExtResource("4")]
 | 
			
		||||
custom_minimum_size = Vector2(600, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
text = "Close Leaderboard"
 | 
			
		||||
 | 
			
		||||
[connection signal="pressed" from="Board/CloseButtonContainer/CloseButton" to="." method="_on_CloseButton_pressed"]
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
[gd_scene load_steps=2 format=3 uid="uid://bqv70cuvl5eel"]
 | 
			
		||||
 | 
			
		||||
[ext_resource type="PackedScene" uid="uid://2wy4d8d5av0l" path="res://addons/silent_wolf/Scores/ScoreItem.tscn" id="1"]
 | 
			
		||||
 | 
			
		||||
[node name="ScoreItem" instance=ExtResource("1")]
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,87 @@
 | 
			
		|||
extends Node2D
 | 
			
		||||
 | 
			
		||||
const ScoreItem = preload("SmallScoreItem.tscn")
 | 
			
		||||
const SWLogger = preload("res://addons/silent_wolf/utils/SWLogger.gd")
 | 
			
		||||
 | 
			
		||||
var ld_names = ["Weekly", "Monthly", "main"]
 | 
			
		||||
 | 
			
		||||
var scores = []
 | 
			
		||||
 | 
			
		||||
func _ready():
 | 
			
		||||
	SilentWolf.Scores.sw_get_scores_complete.connect(_on_scores_received)
 | 
			
		||||
	
 | 
			
		||||
	#var scores = SilentWolf.Scores.scores
 | 
			
		||||
	add_loading_scores_message()
 | 
			
		||||
	var sw_result = SilentWolf.Scores.get_scores(10, "main")
 | 
			
		||||
	scores = sw_result.scores
 | 
			
		||||
	# the other leaderboard scores will be called once the main call in finished 
 | 
			
		||||
	# (see signal connected above and _on_scores_received function below)
 | 
			
		||||
	# when all the scores are loaded the leaderboard scene can be opened
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func render_boards(leaderboards: Array) -> void:
 | 
			
		||||
	#print("leaderboards: " + str(leaderboards))
 | 
			
		||||
	var board_number = 0
 | 
			
		||||
	for board in leaderboards:
 | 
			
		||||
		var list_index = 1
 | 
			
		||||
		#print("ld name: " + str(ld_names[board_number]))
 | 
			
		||||
		#print("ld scores: " + str(board))
 | 
			
		||||
		for score in board:
 | 
			
		||||
			add_item(ld_names[board_number], score.player_name, str(int(score.score)), list_index)
 | 
			
		||||
			list_index += 1
 | 
			
		||||
		board_number += 1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func add_item(ld_name: String, player_name: String, score: String, list_index: int) -> void:
 | 
			
		||||
	var item = ScoreItem.instantiate()
 | 
			
		||||
	item.get_node("PlayerName").text = str(list_index) + str(". ") + player_name
 | 
			
		||||
	item.get_node("Score").text = score
 | 
			
		||||
	item.offset_top = list_index * 100
 | 
			
		||||
	get_node("MainContainer/Boards/" + ld_name + "/HighScores/ScoreItemContainer").add_child(item)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func add_no_scores_message() -> void:
 | 
			
		||||
	var item = $"MainContainer/MessageContainer/TextMessage"
 | 
			
		||||
	item.text = "No scores yet!"
 | 
			
		||||
	$"MainContainer/MessageContainer".show()
 | 
			
		||||
	item.offset_top = 135
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func add_loading_scores_message() -> void:
 | 
			
		||||
	var item = $"MainContainer/MessageContainer/TextMessage"
 | 
			
		||||
	item.text = "Loading scores..."
 | 
			
		||||
	$"MainContainer/MessageContainer".show()
 | 
			
		||||
	item.offset_top = 135
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func hide_message() -> void:
 | 
			
		||||
	$"MainContainer/MessageContainer".hide()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_CloseButton_pressed() -> void:
 | 
			
		||||
	var scene_name = SilentWolf.scores_config.open_scene_on_close
 | 
			
		||||
	SWLogger.info("Closing SilentWolf leaderboard, switching to scene: " + str(scene_name))
 | 
			
		||||
	get_tree().change_scene_to_file(scene_name)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_scores_received(get_scores_result: Dictionary) -> void:
 | 
			
		||||
	var ld_name: String = get_scores_result.ld_name
 | 
			
		||||
	var scores: Array = get_scores_result.scores
 | 
			
		||||
	
 | 
			
		||||
	if ld_name == "main":
 | 
			
		||||
		SilentWolf.Scores.get_scores(10, "Weekly")
 | 
			
		||||
		#SilentWolf.Scores.get_scores(10, "Weekly", -1)
 | 
			
		||||
	elif ld_name == "Weekly":
 | 
			
		||||
		SilentWolf.Scores.get_scores(10, "Monthly")
 | 
			
		||||
	else:
 | 
			
		||||
		#print("SilentWolf.Scores.leaderboards: " + str(SilentWolf.Scores.leaderboards))
 | 
			
		||||
		var ld_scores = []
 | 
			
		||||
		for i in [0, 1, 2]:
 | 
			
		||||
			if ld_names[i] in SilentWolf.Scores.leaderboards:
 | 
			
		||||
				ld_scores.append(SilentWolf.Scores.leaderboards[ld_names[i]])
 | 
			
		||||
			#elif (ld_names[i] + ";-1") in SilentWolf.Scores.leaderboards_past_periods:
 | 
			
		||||
			#	ld_scores.append(SilentWolf.Scores.leaderboards_past_periods[(ld_names[i] + ";-1")])
 | 
			
		||||
			else:
 | 
			
		||||
				ld_scores.append([])
 | 
			
		||||
		hide_message()
 | 
			
		||||
		render_boards(ld_scores)
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
uid://dvc1hed63vviq
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,114 @@
 | 
			
		|||
[gd_scene load_steps=5 format=3 uid="uid://bsxtktsci8a4t"]
 | 
			
		||||
 | 
			
		||||
[ext_resource type="Theme" uid="uid://d2eakbmaefnt6" path="res://addons/silent_wolf/assets/themes/sw_theme.tres" id="2_xs0b2"]
 | 
			
		||||
[ext_resource type="PackedScene" uid="uid://clllbf6am8qdf" path="res://addons/silent_wolf/common/SWButton.tscn" id="4"]
 | 
			
		||||
[ext_resource type="Script" uid="uid://dvc1hed63vviq" path="res://addons/silent_wolf/examples/CustomLeaderboards/TimeBasedLboards.gd" id="5"]
 | 
			
		||||
 | 
			
		||||
[sub_resource type="Theme" id="3"]
 | 
			
		||||
 | 
			
		||||
[node name="TimeBasedLBoards" type="Node2D"]
 | 
			
		||||
script = ExtResource("5")
 | 
			
		||||
 | 
			
		||||
[node name="MainContainer" type="VBoxContainer" parent="."]
 | 
			
		||||
offset_left = 412.0
 | 
			
		||||
offset_top = 95.0
 | 
			
		||||
offset_right = 1516.0
 | 
			
		||||
offset_bottom = 734.0
 | 
			
		||||
theme = ExtResource("2_xs0b2")
 | 
			
		||||
theme_override_constants/separation = 128
 | 
			
		||||
 | 
			
		||||
[node name="TitleContainer" type="CenterContainer" parent="MainContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="Label2" type="Label" parent="MainContainer/TitleContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_font_sizes/font_size = 72
 | 
			
		||||
text = "Leaderboard"
 | 
			
		||||
 | 
			
		||||
[node name="Boards" type="HBoxContainer" parent="MainContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_constants/separation = 192
 | 
			
		||||
alignment = 1
 | 
			
		||||
 | 
			
		||||
[node name="Weekly" type="VBoxContainer" parent="MainContainer/Boards"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_horizontal = 3
 | 
			
		||||
 | 
			
		||||
[node name="TitleContainer" type="CenterContainer" parent="MainContainer/Boards/Weekly"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="Label" type="Label" parent="MainContainer/Boards/Weekly/TitleContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_horizontal = 4
 | 
			
		||||
theme_override_font_sizes/font_size = 36
 | 
			
		||||
text = "This week"
 | 
			
		||||
 | 
			
		||||
[node name="HighScores" type="CenterContainer" parent="MainContainer/Boards/Weekly"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_horizontal = 4
 | 
			
		||||
theme = SubResource("3")
 | 
			
		||||
 | 
			
		||||
[node name="ScoreItemContainer" type="VBoxContainer" parent="MainContainer/Boards/Weekly/HighScores"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="Monthly" type="VBoxContainer" parent="MainContainer/Boards"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_horizontal = 3
 | 
			
		||||
 | 
			
		||||
[node name="TitleContainer" type="CenterContainer" parent="MainContainer/Boards/Monthly"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="Label" type="Label" parent="MainContainer/Boards/Monthly/TitleContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_font_sizes/font_size = 36
 | 
			
		||||
text = "This month"
 | 
			
		||||
 | 
			
		||||
[node name="HighScores" type="CenterContainer" parent="MainContainer/Boards/Monthly"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_horizontal = 4
 | 
			
		||||
theme = SubResource("3")
 | 
			
		||||
 | 
			
		||||
[node name="ScoreItemContainer" type="VBoxContainer" parent="MainContainer/Boards/Monthly/HighScores"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="main" type="VBoxContainer" parent="MainContainer/Boards"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_horizontal = 3
 | 
			
		||||
 | 
			
		||||
[node name="TitleContainer" type="CenterContainer" parent="MainContainer/Boards/main"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="Label" type="Label" parent="MainContainer/Boards/main/TitleContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_font_sizes/font_size = 36
 | 
			
		||||
text = "All time"
 | 
			
		||||
 | 
			
		||||
[node name="HighScores" type="CenterContainer" parent="MainContainer/Boards/main"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
size_flags_horizontal = 4
 | 
			
		||||
theme = SubResource("3")
 | 
			
		||||
 | 
			
		||||
[node name="ScoreItemContainer" type="VBoxContainer" parent="MainContainer/Boards/main/HighScores"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="MessageContainer" type="CenterContainer" parent="MainContainer"]
 | 
			
		||||
visible = false
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="TextMessage" type="Label" parent="MainContainer/MessageContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
text = "Loading scores..."
 | 
			
		||||
 | 
			
		||||
[node name="CenterContainer" type="CenterContainer" parent="MainContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="CloseButtonContainer" type="CenterContainer" parent="MainContainer/CenterContainer"]
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
 | 
			
		||||
[node name="CloseButton" parent="MainContainer/CenterContainer/CloseButtonContainer" instance=ExtResource("4")]
 | 
			
		||||
custom_minimum_size = Vector2(600, 80)
 | 
			
		||||
layout_mode = 2
 | 
			
		||||
theme_override_font_sizes/font_size = 36
 | 
			
		||||
text = "Close Leaderboard"
 | 
			
		||||
 | 
			
		||||
[connection signal="pressed" from="MainContainer/CenterContainer/CloseButtonContainer/CloseButton" to="." method="_on_CloseButton_pressed"]
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
[plugin]
 | 
			
		||||
 | 
			
		||||
name="Silent Wolf plugin"
 | 
			
		||||
description="Backend services for Godot Engine."
 | 
			
		||||
author="Brass Harpooner"
 | 
			
		||||
version="0.9.9"
 | 
			
		||||
script="silent_wolf.gd"
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
@tool
 | 
			
		||||
extends EditorPlugin
 | 
			
		||||
 | 
			
		||||
func _enter_tree():
 | 
			
		||||
	add_autoload_singleton("SilentWolf", "res://addons/silent_wolf/SilentWolf.gd")
 | 
			
		||||
 | 
			
		||||
func _exit_tree():
 | 
			
		||||
	remove_autoload_singleton("SilentWolf")
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
uid://dd0nkn2y0uspg
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
extends Node
 | 
			
		||||
 | 
			
		||||
static func hash_values(values: Array) -> String:
 | 
			
		||||
	var to_be_hashed = ""
 | 
			
		||||
	for value in values:
 | 
			
		||||
		to_be_hashed = to_be_hashed + str(value)
 | 
			
		||||
	var hashed = to_be_hashed.md5_text()
 | 
			
		||||
	#print("Computed hashed: " + str(hashed))
 | 
			
		||||
	return hashed
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
uid://cf1c2aoqmrxf0
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
extends Node
 | 
			
		||||
 | 
			
		||||
const SWLogger = preload("res://addons/silent_wolf/utils/SWLogger.gd")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
uid://2mllvd5kk6df
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,49 @@
 | 
			
		|||
extends Node
 | 
			
		||||
 | 
			
		||||
const SWLogger = preload("res://addons/silent_wolf/utils/SWLogger.gd") 
 | 
			
		||||
 | 
			
		||||
# Retrieves data stored as JSON in local storage
 | 
			
		||||
# example path: "user://swsession.save"
 | 
			
		||||
 | 
			
		||||
# store lookup (not logged in player name) and validator in local file
 | 
			
		||||
static func save_data(path: String, data: Dictionary, debug_message: String='Saving data to file in local storage: ') -> bool:
 | 
			
		||||
	var save_success = false
 | 
			
		||||
	var file = FileAccess.open(path, FileAccess.WRITE)
 | 
			
		||||
	file.store_string(str(data))
 | 
			
		||||
	save_success = true
 | 
			
		||||
	SWLogger.debug(debug_message + str(data))
 | 
			
		||||
	return save_success
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static func remove_data(path: String, debug_message: String='Removing data from file in local storage: ') -> bool:
 | 
			
		||||
	var delete_success = false
 | 
			
		||||
	if FileAccess.file_exists(path):
 | 
			
		||||
		var file = FileAccess.open(path, FileAccess.WRITE)
 | 
			
		||||
		var data = {}
 | 
			
		||||
		file.store_var(data)
 | 
			
		||||
		delete_success = true
 | 
			
		||||
	SWLogger.debug(debug_message)
 | 
			
		||||
	return delete_success
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static func does_file_exist(path: String) -> bool:
 | 
			
		||||
	return FileAccess.file_exists(path)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static func get_data(path: String) -> Dictionary:
 | 
			
		||||
	var content = {}
 | 
			
		||||
	print("path: " + str(path))
 | 
			
		||||
	if FileAccess.file_exists(path):
 | 
			
		||||
		var file = FileAccess.open(path, FileAccess.READ)
 | 
			
		||||
		var text_content = file.get_as_text()
 | 
			
		||||
		if text_content == null or text_content == '' or text_content.length() == 0:
 | 
			
		||||
			SWLogger.debug("Empty file in local storage at " + str(path))
 | 
			
		||||
		else:
 | 
			
		||||
			var data = JSON.parse_string(text_content)
 | 
			
		||||
			if typeof(data) == TYPE_DICTIONARY:
 | 
			
		||||
				content = data
 | 
			
		||||
			else:
 | 
			
		||||
				SWLogger.debug("Invalid data in local storage at " + str(path))
 | 
			
		||||
	else:
 | 
			
		||||
		SWLogger.debug("Could not find any data at: " + str(path))
 | 
			
		||||
	return content
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
uid://cvhnkacjrdi1b
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
extends Node
 | 
			
		||||
 | 
			
		||||
const SWUtils = preload("res://addons/silent_wolf/utils/SWUtils.gd")
 | 
			
		||||
 | 
			
		||||
static func get_log_level():
 | 
			
		||||
	var log_level = 1
 | 
			
		||||
	if SilentWolf.config.has('log_level'):
 | 
			
		||||
		log_level = SilentWolf.config.log_level
 | 
			
		||||
	else:
 | 
			
		||||
		error("Couldn't find SilentWolf.config.log_level, defaulting to 1") 
 | 
			
		||||
	return log_level
 | 
			
		||||
	
 | 
			
		||||
static func error(text):
 | 
			
		||||
	printerr(str(text))
 | 
			
		||||
	push_error(str(text))
 | 
			
		||||
 | 
			
		||||
static func info(text):
 | 
			
		||||
	if get_log_level() > 0:
 | 
			
		||||
		print(str(text))
 | 
			
		||||
	
 | 
			
		||||
static func debug(text):
 | 
			
		||||
	if get_log_level() > 1:
 | 
			
		||||
		print(str(text))
 | 
			
		||||
		
 | 
			
		||||
static func log_time(log_text, log_level='INFO'):
 | 
			
		||||
	var timestamp = SWUtils.get_timestamp()
 | 
			
		||||
	if log_level == 'ERROR':
 | 
			
		||||
		error(log_text + ": " + str(timestamp))
 | 
			
		||||
	elif log_level == 'INFO':
 | 
			
		||||
		info(log_text + ": " + str(timestamp))
 | 
			
		||||
	else:
 | 
			
		||||
		debug(log_text + ": " + str(timestamp))
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
uid://fgn4wk1houa6
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,35 @@
 | 
			
		|||
extends Node
 | 
			
		||||
 | 
			
		||||
const SWLogger = preload("res://addons/silent_wolf/utils/SWLogger.gd")
 | 
			
		||||
 | 
			
		||||
static func get_timestamp() -> int:
 | 
			
		||||
	var unix_time: float = Time.get_unix_time_from_system()
 | 
			
		||||
	var unix_time_int: int = unix_time
 | 
			
		||||
	var timestamp = round((unix_time - unix_time_int) * 1000.0)
 | 
			
		||||
	return timestamp
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static func check_http_response(response_code, headers, body):
 | 
			
		||||
	SWLogger.debug("response code: " + str(response_code))
 | 
			
		||||
	SWLogger.debug("response headers: " + str(headers))
 | 
			
		||||
	SWLogger.debug("response body: " + str(body.get_string_from_utf8()))
 | 
			
		||||
 | 
			
		||||
	var check_ok = true
 | 
			
		||||
	if response_code == 0:
 | 
			
		||||
		no_connection_error()
 | 
			
		||||
		check_ok = false
 | 
			
		||||
	elif response_code == 403:
 | 
			
		||||
		forbidden_error()
 | 
			
		||||
	return check_ok
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static func no_connection_error():
 | 
			
		||||
	SWLogger.error("Godot couldn't connect to the SilentWolf backend. There are several reasons why this might happen. See https://silentwolf.com/troubleshooting for more details. If the problem persists you can reach out to us: https://silentwolf.com/contact")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static func forbidden_error():
 | 
			
		||||
	SWLogger.error("You are not authorized to call the SilentWolf API - check your API key configuration or contact us: https://silentwolf.com/contact")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static func obfuscate_string(string: String) -> String:
 | 
			
		||||
	return string.replace(".", "*")
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
uid://4c6y0rpjclhh
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,46 @@
 | 
			
		|||
static func get_random_int(max_value: int) -> int:
 | 
			
		||||
	randomize()
 | 
			
		||||
	return randi() % max_value
 | 
			
		||||
 | 
			
		||||
static func random_bytes(n: int) -> Array:
 | 
			
		||||
	var r = []
 | 
			
		||||
	for index in range(0, n):
 | 
			
		||||
		r.append(get_random_int(256))
 | 
			
		||||
	return r
 | 
			
		||||
 | 
			
		||||
static func uuid_bin() -> Array:
 | 
			
		||||
	var b = random_bytes(16)
 | 
			
		||||
	b[6] = (b[6] & 0x0f) | 0x40
 | 
			
		||||
	b[8] = (b[8] & 0x3f) | 0x80
 | 
			
		||||
	return b
 | 
			
		||||
 | 
			
		||||
static func generate_uuid_v4() -> String:
 | 
			
		||||
	var b = uuid_bin()
 | 
			
		||||
	var low = "%02x%02x%02x%02x" % [b[0], b[1], b[2], b[3]]
 | 
			
		||||
	var mid = "%02x%02x" % [b[4], b[5]]
 | 
			
		||||
	var hi = "%02x%02x" % [b[6], b[7]]
 | 
			
		||||
	var clock = "%02x%02x" % [b[8], b[9]]
 | 
			
		||||
	var node = "%02x%02x%02x%02x%02x%02x" % [b[10], b[11], b[12], b[13], b[14], b[15]]
 | 
			
		||||
	return "%s-%s-%s-%s-%s" % [low, mid, hi, clock, node]
 | 
			
		||||
 | 
			
		||||
static func is_uuid(test_string: String) -> bool:
 | 
			
		||||
	return test_string.count("-") == 4 and test_string.length() == 36
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# MIT License
 | 
			
		||||
 | 
			
		||||
# Copyright (c) 2018 Xavier Sellier
 | 
			
		||||
 | 
			
		||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
# of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
# in the Software without restriction, including without limitation the rights
 | 
			
		||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
# copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
# furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
# The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
# copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
# Changes made:
 | 
			
		||||
# - Refactored variable names and function signatures to follow snake case naming convention
 | 
			
		||||
# - Added type hints to all variables and function attributes
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
uid://chlwc7n1acfrb
 | 
			
		||||
| 
						 | 
				
			
			@ -21,6 +21,7 @@ GridManager="*res://Scripts/grid_manager.gd"
 | 
			
		|||
GameManager="*res://Scripts/game_manager.gd"
 | 
			
		||||
MusicPlayer="*res://Scenes/music_player.tscn"
 | 
			
		||||
SoundPlayer="*res://Scripts/sound_player.gd"
 | 
			
		||||
SilentWolf="*res://addons/silent_wolf/SilentWolf.gd"
 | 
			
		||||
 | 
			
		||||
[display]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -73,4 +74,5 @@ up={
 | 
			
		|||
textures/canvas_textures/default_texture_filter=3
 | 
			
		||||
renderer/rendering_method="gl_compatibility"
 | 
			
		||||
renderer/rendering_method.mobile="gl_compatibility"
 | 
			
		||||
textures/vram_compression/import_etc2_astc=true
 | 
			
		||||
2d/snap/snap_2d_transforms_to_pixel=true
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue