diff --git a/addons/silent_wolf/.DS_Store b/addons/silent_wolf/.DS_Store new file mode 100644 index 0000000..2d80e47 Binary files /dev/null and b/addons/silent_wolf/.DS_Store differ diff --git a/addons/silent_wolf/Auth/Auth.gd b/addons/silent_wolf/Auth/Auth.gd new file mode 100644 index 0000000..9dc7857 --- /dev/null +++ b/addons/silent_wolf/Auth/Auth.gd @@ -0,0 +1,652 @@ +extends Node + +const CommonErrors = preload("res://addons/silent_wolf/common/CommonErrors.gd") +const SWLocalFileStorage = preload("res://addons/silent_wolf/utils/SWLocalFileStorage.gd") +const SWLogger = preload("res://addons/silent_wolf/utils/SWLogger.gd") +const UUID = preload("res://addons/silent_wolf/utils/UUID.gd") + +signal sw_login_succeeded +signal sw_login_failed +signal sw_logout_succeeded +signal sw_registration_succeeded +signal sw_registration_user_pwd_succeeded +signal sw_registration_failed +signal sw_email_verif_succeeded +signal sw_email_verif_failed +signal sw_resend_conf_code_succeeded +signal sw_resend_conf_code_failed +signal sw_session_check_complete +signal sw_request_password_reset_succeeded +signal sw_request_password_reset_failed +signal sw_reset_password_succeeded +signal sw_reset_password_failed +signal sw_get_player_details_succeeded +signal sw_get_player_details_failed + +var tmp_username = null +var logged_in_player = null +var logged_in_player_email = null +var logged_in_anon = false +var token = null +var 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 _ready(): + pass + + +func set_player_logged_in(player_name): + 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 logout_player(): + logged_in_player = null + # remove any player data if present + SilentWolf.Players.clear_player_data() + # remove stored session if any + remove_stored_session() + emit_signal("sw_logout_succeeded") + + +func register_player_anon(player_name=null) -> Node: + var user_local_id: String = get_anon_user_id() + RegisterPlayer = HTTPRequest.new() + wrRegisterPlayer = weakref(RegisterPlayer) + if OS.get_name() != "HTML5": + RegisterPlayer.set_use_threads(true) + get_tree().get_root().add_child(RegisterPlayer) + RegisterPlayer.connect("request_completed", self, "_on_RegisterPlayer_request_completed") + SWLogger.info("Calling SilentWolf to register an anonymous player") + var game_id = SilentWolf.config.game_id + var game_version = SilentWolf.config.game_version + var api_key = SilentWolf.config.api_key + var payload = { "game_id": game_id, "anon": true, "player_name": player_name, "user_local_id": user_local_id } + var query = JSON.print(payload) + var headers = [ + "Content-Type: application/json", + "x-api-key: " + api_key, + "x-sw-plugin-version: " + SilentWolf.version, + "x-sw-game-id: " + SilentWolf.config.game_id, + "x-sw-godot-version: " + SilentWolf.godot_version + ] + #print("register_player headers: " + str(headers)) + RegisterPlayer.request("https://api.silentwolf.com/create_new_player", headers, true, HTTPClient.METHOD_POST, query) + return self + + +func register_player(player_name, email, password, confirm_password): + tmp_username = player_name + RegisterPlayer = HTTPRequest.new() + wrRegisterPlayer = weakref(RegisterPlayer) + if OS.get_name() != "HTML5": + RegisterPlayer.set_use_threads(true) + get_tree().get_root().add_child(RegisterPlayer) + RegisterPlayer.connect("request_completed", self, "_on_RegisterPlayer_request_completed") + SWLogger.info("Calling SilentWolf to register a player") + var game_id = SilentWolf.config.game_id + var game_version = SilentWolf.config.game_version + var api_key = SilentWolf.config.api_key + var payload = { "game_id": game_id, "anon": false, "player_name": player_name, "email": email, "password": password, "confirm_password": confirm_password } + var query = JSON.print(payload) + var headers = [ + "Content-Type: application/json", + "x-api-key: " + api_key, + "x-sw-plugin-version: " + SilentWolf.version, + "x-sw-game-id: " + SilentWolf.config.game_id, + "x-sw-godot-version: " + SilentWolf.godot_version + ] + #print("register_player headers: " + str(headers)) + RegisterPlayer.request("https://api.silentwolf.com/create_new_player", headers, true, HTTPClient.METHOD_POST, query) + return self + + +func register_player_user_password(player_name, password, confirm_password): + tmp_username = player_name + RegisterPlayer = HTTPRequest.new() + wrRegisterPlayer = weakref(RegisterPlayer) + if OS.get_name() != "HTML5": + RegisterPlayer.set_use_threads(true) + get_tree().get_root().add_child(RegisterPlayer) + RegisterPlayer.connect("request_completed", self, "_on_RegisterPlayerUserPassword_request_completed") + SWLogger.info("Calling SilentWolf to register a player") + var game_id = SilentWolf.config.game_id + var game_version = SilentWolf.config.game_version + var api_key = SilentWolf.config.api_key + var payload = { "game_id": game_id, "player_name": player_name, "password": password, "confirm_password": confirm_password } + var query = JSON.print(payload) + var headers = [ + "Content-Type: application/json", + "x-api-key: " + api_key, + "x-sw-plugin-version: " + SilentWolf.version, + "x-sw-game-id: " + SilentWolf.config.game_id, + "x-sw-godot-version: " + SilentWolf.godot_version + ] + #print("register_player headers: " + str(headers)) + RegisterPlayer.request("https://api.silentwolf.com/create_new_player", headers, true, HTTPClient.METHOD_POST, query) + return self + + +func verify_email(player_name, code): + tmp_username = player_name + VerifyEmail = HTTPRequest.new() + wrVerifyEmail = weakref(VerifyEmail) + if OS.get_name() != "HTML5": + VerifyEmail.set_use_threads(true) + get_tree().get_root().add_child(VerifyEmail) + VerifyEmail.connect("request_completed", self, "_on_VerifyEmail_request_completed") + SWLogger.info("Calling SilentWolf to verify email address for: " + str(player_name)) + var game_id = SilentWolf.config.game_id + var game_version = SilentWolf.config.game_version + var api_key = SilentWolf.config.api_key + var payload = { "game_id": game_id, "username": player_name, "code": code } + var query = JSON.print(payload) + var headers = [ + "Content-Type: application/json", + "x-api-key: " + api_key, + "x-sw-plugin-version: " + SilentWolf.version, + "x-sw-game-id: " + SilentWolf.config.game_id, + "x-sw-godot-version: " + SilentWolf.godot_version + ] + #print("register_player headers: " + str(headers)) + VerifyEmail.request("https://api.silentwolf.com/confirm_verif_code", headers, true, HTTPClient.METHOD_POST, query) + return self + + +func resend_conf_code(player_name): + ResendConfCode = HTTPRequest.new() + wrResendConfCode = weakref(ResendConfCode) + if OS.get_name() != "HTML5": + ResendConfCode.set_use_threads(true) + get_tree().get_root().add_child(ResendConfCode) + ResendConfCode.connect("request_completed", self, "_on_ResendConfCode_request_completed") + SWLogger.info("Calling SilentWolf to resend confirmation code for: " + str(player_name)) + var game_id = SilentWolf.config.game_id + var game_version = SilentWolf.config.game_version + var api_key = SilentWolf.config.api_key + var payload = { "game_id": game_id, "username": player_name } + var query = JSON.print(payload) + var headers = [ + "Content-Type: application/json", + "x-api-key: " + api_key, + "x-sw-plugin-version: " + SilentWolf.version, + "x-sw-game-id: " + SilentWolf.config.game_id, + "x-sw-godot-version: " + SilentWolf.godot_version + ] + #print("register_player headers: " + str(headers)) + ResendConfCode.request("https://api.silentwolf.com/resend_conf_code", headers, true, HTTPClient.METHOD_POST, query) + return self + + +func login_player(username, password, remember_me=false): + tmp_username = username + LoginPlayer = HTTPRequest.new() + wrLoginPlayer = weakref(LoginPlayer) + print("OS name: " + str(OS.get_name())) + if OS.get_name() != "HTML5": + LoginPlayer.set_use_threads(true) + print("get_tree().get_root(): " + str(get_tree().get_root())) + get_tree().get_root().add_child(LoginPlayer) + LoginPlayer.connect("request_completed", self, "_on_LoginPlayer_request_completed") + SWLogger.info("Calling SilentWolf to log in a player") + var game_id = SilentWolf.config.game_id + var api_key = SilentWolf.config.api_key + var payload = { "game_id": 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) + SWLogger.debug("SilentWolf login player payload: " + str(payload)) + var query = JSON.print(payload) + var headers = [ + "Content-Type: application/json", + "x-api-key: " + api_key, + "x-sw-plugin-version: " + SilentWolf.version, + "x-sw-game-id: " + SilentWolf.config.game_id, + "x-sw-godot-version: " + SilentWolf.godot_version + ]#print("login_player headers: " + str(headers)) + LoginPlayer.request("https://api.silentwolf.com/login_player", headers, true, HTTPClient.METHOD_POST, query) + return self + + +func request_player_password_reset(player_name): + RequestPasswordReset = HTTPRequest.new() + wrRequestPasswordReset = weakref(RequestPasswordReset) + print("OS name: " + str(OS.get_name())) + if OS.get_name() != "HTML5": + RequestPasswordReset.set_use_threads(true) + get_tree().get_root().add_child(RequestPasswordReset) + RequestPasswordReset.connect("request_completed", self, "_on_RequestPasswordReset_request_completed") + SWLogger.info("Calling SilentWolf to request a password reset for: " + str(player_name)) + var game_id = SilentWolf.config.game_id + var api_key = SilentWolf.config.api_key + var payload = { "game_id": game_id, "player_name": player_name } + SWLogger.debug("SilentWolf request player password reset payload: " + str(payload)) + var query = JSON.print(payload) + var headers = [ + "Content-Type: application/json", + "x-api-key: " + api_key, + "x-sw-plugin-version: " + SilentWolf.version, + "x-sw-game-id: " + SilentWolf.config.game_id, + "x-sw-godot-version: " + SilentWolf.godot_version + ] + RequestPasswordReset.request("https://api.silentwolf.com/request_player_password_reset", headers, true, HTTPClient.METHOD_POST, query) + return self + + +func reset_player_password(player_name, conf_code, new_password, confirm_password): + ResetPassword = HTTPRequest.new() + wrResetPassword = weakref(ResetPassword) + if OS.get_name() != "HTML5": + ResetPassword.set_use_threads(true) + get_tree().get_root().add_child(ResetPassword) + ResetPassword.connect("request_completed", self, "_on_ResetPassword_request_completed") + SWLogger.info("Calling SilentWolf to reset password for: " + str(player_name)) + var game_id = SilentWolf.config.game_id + var api_key = SilentWolf.config.api_key + var payload = { "game_id": 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 query = JSON.print(payload) + var headers = [ + "Content-Type: application/json", + "x-api-key: " + api_key, + "x-sw-plugin-version: " + SilentWolf.version, + "x-sw-game-id: " + SilentWolf.config.game_id, + "x-sw-godot-version: " + SilentWolf.godot_version + ] + ResetPassword.request("https://api.silentwolf.com/reset_player_password", headers, true, HTTPClient.METHOD_POST, query) + return self + + +func get_player_details(player_name): + GetPlayerDetails = HTTPRequest.new() + wrRegisterPlayer = weakref(GetPlayerDetails) + if OS.get_name() != "HTML5": + GetPlayerDetails.set_use_threads(true) + get_tree().get_root().add_child(GetPlayerDetails) + GetPlayerDetails.connect("request_completed", self, "_on_GetPlayerDetails_request_completed") + SWLogger.info("Calling SilentWolf to get player details") + var game_id = SilentWolf.config.game_id + var game_version = SilentWolf.config.game_version + var api_key = SilentWolf.config.api_key + var payload = { "game_id": game_id, "player_name": player_name } + var query = JSON.print(payload) + var headers = [ + "Content-Type: application/json", + "x-api-key: " + api_key, + "x-sw-plugin-version: " + SilentWolf.version, + "x-sw-game-id: " + SilentWolf.config.game_id, + "x-sw-godot-version: " + SilentWolf.godot_version + ] + #print("register_player headers: " + str(headers)) + GetPlayerDetails.request("https://api.silentwolf.com/get_player_details", headers, true, HTTPClient.METHOD_POST, query) + return self + + +func _on_LoginPlayer_request_completed( result, response_code, headers, body ): + SWLogger.info("LoginPlayer request completed") + var status_check = CommonErrors.check_status_code(response_code) + #LoginPlayer.queue_free() + SilentWolf.free_request(wrLoginPlayer, LoginPlayer) + SWLogger.debug("response headers: " + str(response_code)) + SWLogger.debug("response headers: " + str(headers)) + #SWLogger.debug("response body: " + str(body.get_string_from_utf8())) + + if status_check: + var json = JSON.parse(body.get_string_from_utf8()) + var response = json.result + if "message" in response.keys() and response.message == "Forbidden": + SWLogger.error("You are not authorized to call the SilentWolf API - check your API key configuration: https://silentwolf.com/leaderboard") + else: + if "lookup" in response.keys(): + print("remember me lookup: " + str(response.lookup)) + save_session(response.lookup, response.validator) + if "validator" in response.keys(): + print("remember me validator: " + str(response.validator)) + SWLogger.info("SilentWolf login player success? : " + str(response.success)) + # TODO: get JWT token and store it + # send a different signal depending on login success or failure + if response.success: + token = response.swtoken + #id_token = response.swidtoken + SWLogger.debug("token: " + token) + set_player_logged_in(tmp_username) + emit_signal("sw_login_succeeded") + else: + emit_signal("sw_login_failed", response.error) + + +func _on_RegisterPlayer_request_completed( result, response_code, headers, body ): + SWLogger.info("RegisterPlayer request completed") + var status_check = CommonErrors.check_status_code(response_code) + #RegisterPlayer.queue_free() + SilentWolf.free_request(wrRegisterPlayer, RegisterPlayer) + SWLogger.debug("response headers: " + str(response_code)) + SWLogger.debug("response headers: " + str(headers)) + SWLogger.debug("response body: " + str(body.get_string_from_utf8())) + + if status_check: + var json = JSON.parse(body.get_string_from_utf8()) + var response = json.result + SWLogger.debug("reponse: " + str(response)) + if "message" in response.keys() and response.message == "Forbidden": + SWLogger.error("You are not authorized to call the SilentWolf API - check your API key configuration: https://silentwolf.com/leaderboard") + else: + SWLogger.info("SilentWolf create new player success? : " + str(response.success)) + # also get a JWT token here + # send a different signal depending on registration success or failure + if response.success: + var anon = response.anon + if anon: + SWLogger.info("Anonymous Player registration succeeded") + logged_in_anon = true + if 'player_name' in response: + logged_in_player = response.player_name + elif 'player_local_id' in response: + logged_in_player = str("anon##" + response.player_local_id) + else: + logged_in_player = "anon##unknown" + print("Anon 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 = response.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 + emit_signal("sw_registration_succeeded") + else: + emit_signal("sw_registration_failed", response.error) + + +func _on_RegisterPlayerUserPassword_request_completed( result, response_code, headers, body ): + SWLogger.info("RegisterPlayerUserPassword request completed") + var status_check = CommonErrors.check_status_code(response_code) + #RegisterPlayer.queue_free() + SilentWolf.free_request(wrRegisterPlayer, RegisterPlayer) + SWLogger.debug("response headers: " + str(response_code)) + SWLogger.debug("response headers: " + str(headers)) + SWLogger.debug("response body: " + str(body.get_string_from_utf8())) + + if status_check: + var json = JSON.parse(body.get_string_from_utf8()) + var response = json.result + SWLogger.debug("reponse: " + str(response)) + if "message" in response.keys() and response.message == "Forbidden": + SWLogger.error("You are not authorized to call the SilentWolf API - check your API key configuration: https://silentwolf.com/leaderboard") + else: + SWLogger.info("SilentWolf create new player success? : " + str(response.success)) + # also get a JWT token here + # send a different signal depending on registration success or failure + if response.success: + # if email confirmation is enabled for the game, we can't log in the player just yet + var email_conf_enabled = response.email_conf_enabled + SWLogger.info("Player registration with username/password succeeded, player account autoconfirmed") + logged_in_player = tmp_username + emit_signal("sw_registration_user_pwd_succeeded") + else: + emit_signal("sw_registration_failed", response.error) + + +func _on_VerifyEmail_request_completed( result, response_code, headers, body ): + SWLogger.info("VerifyEmail request completed") + var status_check = CommonErrors.check_status_code(response_code) + SilentWolf.free_request(wrVerifyEmail, VerifyEmail) + SWLogger.debug("response headers: " + str(response_code)) + SWLogger.debug("response headers: " + str(headers)) + SWLogger.debug("response body: " + str(body.get_string_from_utf8())) + + if status_check: + var json = JSON.parse(body.get_string_from_utf8()) + var response = json.result + SWLogger.debug("reponse: " + str(response)) + if "message" in response.keys() and response.message == "Forbidden": + SWLogger.error("You are not authorized to call the SilentWolf API - check your API key configuration: https://silentwolf.com/playerauth") + else: + SWLogger.info("SilentWolf verify email success? : " + str(response.success)) + # also get a JWT token here + # send a different signal depending on registration success or failure + if response.success: + logged_in_player = tmp_username + emit_signal("sw_email_verif_succeeded") + else: + emit_signal("sw_email_verif_failed", response.error) + + +func _on_ResendConfCode_request_completed( result, response_code, headers, body ): + SWLogger.info("ResendConfCode request completed") + var status_check = CommonErrors.check_status_code(response_code) + SilentWolf.free_request(wrResendConfCode, ResendConfCode) + SWLogger.debug("response headers: " + str(response_code)) + SWLogger.debug("response headers: " + str(headers)) + SWLogger.debug("response body: " + str(body.get_string_from_utf8())) + + if status_check: + var json = JSON.parse(body.get_string_from_utf8()) + var response = json.result + SWLogger.debug("reponse: " + str(response)) + if "message" in response.keys() and response.message == "Forbidden": + SWLogger.error("You are not authorized to call the SilentWolf API - check your API key configuration: https://silentwolf.com/playerauth") + else: + SWLogger.info("SilentWolf resend conf code success? : " + str(response.success)) + # also get a JWT token here + # send a different signal depending on registration success or failure + if response.success: + emit_signal("sw_resend_conf_code_succeeded") + else: + emit_signal("sw_resend_conf_code_failed", response.error) + + +func _on_RequestPasswordReset_request_completed( result, response_code, headers, body ): + SWLogger.info("RequestPasswordReset request completed") + var status_check = CommonErrors.check_status_code(response_code) + SilentWolf.free_request(wrRequestPasswordReset, RequestPasswordReset) + SWLogger.debug("response headers: " + str(response_code)) + SWLogger.debug("response headers: " + str(headers)) + SWLogger.debug("response body: " + str(body.get_string_from_utf8())) + + if status_check: + var json = JSON.parse(body.get_string_from_utf8()) + var response = json.result + SWLogger.debug("reponse: " + str(response)) + if "message" in response.keys() and response.message == "Forbidden": + SWLogger.error("You are not authorized to call the SilentWolf API - check your API key configuration: https://silentwolf.com/playerauth") + else: + SWLogger.info("SilentWolf request player password reset success? : " + str(response.success)) + if response.success: + emit_signal("sw_request_password_reset_succeeded") + else: + emit_signal("sw_request_password_reset_failed", response.error) + + +func _on_ResetPassword_request_completed( result, response_code, headers, body ): + SWLogger.info("ResetPassword request completed") + var status_check = CommonErrors.check_status_code(response_code) + SilentWolf.free_request(wrResetPassword, ResetPassword) + SWLogger.debug("response headers: " + str(response_code)) + SWLogger.debug("response headers: " + str(headers)) + SWLogger.debug("response body: " + str(body.get_string_from_utf8())) + + if status_check: + var json = JSON.parse(body.get_string_from_utf8()) + var response = json.result + SWLogger.debug("reponse: " + str(response)) + if "message" in response.keys() and response.message == "Forbidden": + SWLogger.error("You are not authorized to call the SilentWolf API - check your API key configuration: https://silentwolf.com/playerauth") + else: + SWLogger.info("SilentWolf reset player password success? : " + str(response.success)) + if response.success: + emit_signal("sw_reset_password_succeeded") + else: + emit_signal("sw_reset_password_failed", response.error) + + +func setup_login_timer(): + login_timer = Timer.new() + login_timer.set_one_shot(true) + login_timer.set_wait_time(login_timeout) + login_timer.connect("timeout", self, "on_login_timeout_complete") + add_child(login_timer) + + +func on_login_timeout_complete(): + logout_player() + + +# store lookup (not logged in player name) and validator in local file +func save_session(lookup, validator): + var path = "user://swsession.save" + var session_data = { + "lookup": lookup, + "validator": validator + } + SWLocalFileStorage.save_data("user://swsession.save", session_data, "Saving SilentWolf session: ") + + +func remove_stored_session(): + var path = "user://swsession.save" + SWLocalFileStorage.remove_data(path, "Removing SilentWolf session if any: " ) + + +# 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 + + +func auto_login_player(): + var sw_session_data = load_session() + 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 + + +# Signal can't be emitted directly from auto_login_player() function +# otherwise it won't connect back to calling script +func complete_session_check(return_value=null): + SWLogger.debug("emitting signal....") + emit_signal("sw_session_check_complete", return_value) + + +func validate_player_session(lookup, validator, scene=get_tree().get_current_scene()): + ValidateSession = HTTPRequest.new() + wrValidateSession = weakref(ValidateSession) + if OS.get_name() != "HTML5": + ValidateSession.set_use_threads(true) + scene.add_child(ValidateSession) + ValidateSession.connect("request_completed", self, "_on_ValidateSession_request_completed") + SWLogger.info("Calling SilentWolf to validate an existing player session") + var game_id = SilentWolf.config.game_id + var api_key = SilentWolf.config.api_key + var payload = { "game_id": game_id, "lookup": lookup, "validator": validator } + SWLogger.debug("Validate session payload: " + str(payload)) + var query = JSON.print(payload) + var headers = ["Content-Type: application/json", "x-api-key: " + api_key, "x-sw-plugin-version: " + SilentWolf.version] + ValidateSession.request("https://api.silentwolf.com/validate_remember_me", headers, true, HTTPClient.METHOD_POST, query) + return self + + +func _on_ValidateSession_request_completed( result, response_code, headers, body ): + SWLogger.info("SilentWolf - ValidateSession request completed") + var status_check = CommonErrors.check_status_code(response_code) + #ValidateSession.queue_free() + SilentWolf.free_request(wrValidateSession, ValidateSession) + SWLogger.debug("response headers: " + str(response_code)) + SWLogger.debug("response headers: " + str(headers)) + SWLogger.debug("response body: " + str(body.get_string_from_utf8())) + + if status_check: + var json = JSON.parse(body.get_string_from_utf8()) + var response = json.result + SWLogger.debug("reponse: " + str(response)) + if "message" in response.keys() and response.message == "Forbidden": + SWLogger.error("You are not authorized to call the SilentWolf API - check your API key configuration: https://silentwolf.com/leaderboard") + else: + SWLogger.info("SilentWolf validate session success? : " + str(response.success)) + if response.success: + set_player_logged_in(response.player_name) + complete_session_check(logged_in_player) + else: + complete_session_check(response.error) + + +func _on_GetPlayerDetails_request_completed( result, response_code, headers, body ): + SWLogger.info("SilentWolf - GetPlayerDetails request completed") + var status_check = CommonErrors.check_status_code(response_code) + #ValidateSession.queue_free() + SilentWolf.free_request(wrGetPlayerDetails, GetPlayerDetails) + SWLogger.debug("response headers: " + str(response_code)) + SWLogger.debug("response headers: " + str(headers)) + SWLogger.debug("response body: " + str(body.get_string_from_utf8())) + + if status_check: + var json = JSON.parse(body.get_string_from_utf8()) + var response = json.result + SWLogger.debug("reponse: " + str(response)) + if "message" in response.keys() and response.message == "Forbidden": + SWLogger.error("You are not authorized to call the SilentWolf API - check your API key configuration: https://silentwolf.com/playerauth") + else: + SWLogger.info("SilentWolf get player details success? : " + str(response.success)) + if response.success: + emit_signal("sw_get_player_details_succeeded", response.player_details) + else: + emit_signal("sw_get_player_details_failed") + + +func setup_complete_session_check_wait_timer(): + 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.connect("timeout", self, "complete_session_check") + add_child(complete_session_check_wait_timer) diff --git a/addons/silent_wolf/Auth/ConfirmEmail.gd b/addons/silent_wolf/Auth/ConfirmEmail.gd new file mode 100644 index 0000000..92e6bf6 --- /dev/null +++ b/addons/silent_wolf/Auth/ConfirmEmail.gd @@ -0,0 +1,60 @@ +extends TextureRect + + +const SWLogger = preload("res://addons/silent_wolf/utils/SWLogger.gd") + + +func _ready(): + SilentWolf.Auth.connect("sw_email_verif_succeeded", self, "_on_confirmation_succeeded") + SilentWolf.Auth.connect("sw_email_verif_failed", self, "_on_confirmation_failed") + SilentWolf.Auth.connect("sw_resend_conf_code_succeeded", self, "_on_resend_code_succeeded") + SilentWolf.Auth.connect("sw_resend_conf_code_failed", self, "_on_resend_code_failed") + + +func _on_confirmation_succeeded(): + 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(scene_name) + + +func _on_confirmation_failed(error): + hide_processing_label() + SWLogger.info("email verification failed: " + str(error)) + $"FormContainer/ErrorMessage".text = error + $"FormContainer/ErrorMessage".show() + + +func _on_resend_code_succeeded(): + 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 _on_resend_code_failed(): + 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(): + $"FormContainer/ProcessingLabel".show() + + +func hide_processing_label(): + $"FormContainer/ProcessingLabel".hide() + + +func _on_ConfirmButton_pressed(): + 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(): + var username = SilentWolf.Auth.tmp_username + SWLogger.debug("Requesting confirmation code resend") + SilentWolf.Auth.resend_conf_code(username) + show_processing_label() diff --git a/addons/silent_wolf/Auth/ConfirmEmail.tscn b/addons/silent_wolf/Auth/ConfirmEmail.tscn new file mode 100644 index 0000000..ce87602 --- /dev/null +++ b/addons/silent_wolf/Auth/ConfirmEmail.tscn @@ -0,0 +1,143 @@ +[gd_scene load_steps=13 format=2] + +[ext_resource path="res://addons/silent_wolf/Auth/ConfirmEmail.gd" type="Script" id=1] +[ext_resource path="res://addons/silent_wolf/common/SWButton.tscn" type="PackedScene" id=2] +[ext_resource path="res://addons/silent_wolf/assets/fonts/Comfortaa-Bold.ttf" type="DynamicFontData" id=3] + +[sub_resource type="DynamicFontData" id=13] +font_path = "res://addons/silent_wolf/assets/fonts/Comfortaa-Bold.ttf" + +[sub_resource type="DynamicFont" id=1] +size = 64 +font_data = SubResource( 13 ) + +[sub_resource type="DynamicFont" id=2] +size = 48 +font_data = SubResource( 13 ) + +[sub_resource type="DynamicFont" id=3] +size = 32 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=4] +size = 32 +font_data = SubResource( 13 ) + +[sub_resource type="StyleBoxFlat" id=5] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.831373, 0.415686, 0.415686, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="StyleBoxFlat" id=6] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.831373, 0.415686, 0.415686, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="StyleBoxFlat" id=7] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.482353, 0.458824, 0.458824, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="DynamicFont" id=8] +size = 64 +font_data = ExtResource( 3 ) + +[node name="ConfirmEmail" type="TextureRect"] +margin_right = 40.0 +margin_bottom = 40.0 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="FormContainer" type="VBoxContainer" parent="."] +margin_left = 565.0 +margin_top = 197.0 +margin_right = 1500.0 +margin_bottom = 798.0 +custom_constants/separation = 100 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Label" type="Label" parent="FormContainer"] +margin_right = 935.0 +margin_bottom = 72.0 +custom_fonts/font = SubResource( 1 ) +text = "Confirm your email address" +align = 1 + +[node name="CodeContainer" type="HBoxContainer" parent="FormContainer"] +margin_top = 172.0 +margin_right = 935.0 +margin_bottom = 247.0 +custom_constants/separation = 20 + +[node name="Label" type="Label" parent="FormContainer/CodeContainer"] +margin_top = 10.0 +margin_right = 133.0 +margin_bottom = 65.0 +custom_fonts/font = SubResource( 2 ) +text = "Code" + +[node name="VerifCode" type="LineEdit" parent="FormContainer/CodeContainer"] +margin_left = 153.0 +margin_right = 483.0 +margin_bottom = 75.0 +rect_min_size = Vector2( 330, 75 ) +custom_fonts/font = SubResource( 3 ) +max_length = 30 + +[node name="ErrorMessage" type="Label" parent="FormContainer"] +visible = false +margin_top = 522.0 +margin_right = 648.0 +margin_bottom = 559.0 +custom_fonts/font = SubResource( 4 ) +custom_colors/font_color = Color( 0.866667, 0.101961, 0.101961, 1 ) +autowrap = true + +[node name="ConfirmButton" parent="FormContainer" instance=ExtResource( 2 )] +margin_top = 347.0 +margin_right = 935.0 +margin_bottom = 465.0 +text = "Submit" + +[node name="ResendConfCodeButton" parent="FormContainer" instance=ExtResource( 2 )] +margin_top = 565.0 +margin_right = 935.0 +margin_bottom = 683.0 +custom_styles/hover = SubResource( 5 ) +custom_styles/pressed = SubResource( 6 ) +custom_styles/normal = SubResource( 7 ) +custom_fonts/font = SubResource( 8 ) +text = "Resend code" + +[node name="ProcessingLabel" type="Label" parent="FormContainer"] +visible = false +margin_top = 740.0 +margin_right = 648.0 +margin_bottom = 812.0 +custom_fonts/font = SubResource( 1 ) +text = "Processing..." +align = 1 +[connection signal="pressed" from="FormContainer/ConfirmButton" to="." method="_on_ConfirmButton_pressed"] +[connection signal="pressed" from="FormContainer/ResendConfCodeButton" to="." method="_on_ResendConfCodeButton_pressed"] diff --git a/addons/silent_wolf/Auth/Login.gd b/addons/silent_wolf/Auth/Login.gd new file mode 100644 index 0000000..3015d60 --- /dev/null +++ b/addons/silent_wolf/Auth/Login.gd @@ -0,0 +1,41 @@ +extends TextureRect + +const SWLogger = preload("res://addons/silent_wolf/utils/SWLogger.gd") + +func _ready(): + #var auth_node = get_tree().get_root().get_node("res://addons/silent_wolf/Auth/Auth") + SilentWolf.Auth.connect("sw_login_succeeded", self, "_on_login_succeeded") + SilentWolf.Auth.connect("sw_login_failed", self, "_on_login_failed") + +func _on_LoginButton_pressed(): + 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_succeeded(): + var scene_name = SilentWolf.auth_config.redirect_to_scene + SWLogger.info("logged in as: " + str(SilentWolf.Auth.logged_in_player)) + get_tree().change_scene(scene_name) + +func _on_login_failed(error): + hide_processing_label() + SWLogger.info("log in failed: " + str(error)) + $"FormContainer/ErrorMessage".text = error + $"FormContainer/ErrorMessage".show() + +func _on_BackButton_pressed(): + get_tree().change_scene(SilentWolf.auth_config.redirect_to_scene) + +func show_processing_label(): + $"FormContainer/ProcessingLabel".show() + $"FormContainer/ProcessingLabel".show() + +func hide_processing_label(): + $"FormContainer/ProcessingLabel".hide() + + +func _on_LinkButton_pressed(): + get_tree().change_scene(SilentWolf.auth_config.reset_password_scene) diff --git a/addons/silent_wolf/Auth/Login.tscn b/addons/silent_wolf/Auth/Login.tscn new file mode 100644 index 0000000..fa7eef2 --- /dev/null +++ b/addons/silent_wolf/Auth/Login.tscn @@ -0,0 +1,208 @@ +[gd_scene load_steps=19 format=2] + +[ext_resource path="res://addons/silent_wolf/Auth/Login.gd" type="Script" id=1] +[ext_resource path="res://addons/silent_wolf/common/SWButton.tscn" type="PackedScene" id=2] +[ext_resource path="res://addons/silent_wolf/assets/fonts/Comfortaa-Bold.ttf" type="DynamicFontData" id=3] +[ext_resource path="res://addons/silent_wolf/assets/gfx/checkbox_unchecked.png" type="Texture" id=5] +[ext_resource path="res://addons/silent_wolf/assets/gfx/checkbox_checked.png" type="Texture" id=6] + +[sub_resource type="StyleBoxFlat" id=1] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.831373, 0.415686, 0.415686, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="StyleBoxFlat" id=2] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.831373, 0.415686, 0.415686, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="StyleBoxFlat" id=3] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.482353, 0.458824, 0.458824, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="DynamicFont" id=4] +size = 64 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFontData" id=13] +font_path = "res://addons/silent_wolf/assets/fonts/Comfortaa-Bold.ttf" + +[sub_resource type="DynamicFont" id=5] +size = 64 +font_data = SubResource( 13 ) + +[sub_resource type="DynamicFont" id=6] +size = 48 +font_data = SubResource( 13 ) + +[sub_resource type="DynamicFont" id=7] +size = 32 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=8] +size = 32 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=9] +size = 32 +outline_color = Color( 0.211765, 0.25098, 0.937255, 1 ) +font_data = SubResource( 13 ) + +[sub_resource type="StyleBoxFlat" id=10] +content_margin_left = 5.0 +bg_color = Color( 0.6, 0.6, 0.6, 0 ) + +[sub_resource type="DynamicFont" id=11] +size = 32 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=12] +size = 32 +font_data = SubResource( 13 ) + +[node name="Login" type="TextureRect"] +margin_right = 40.0 +margin_bottom = 40.0 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="BackButton" parent="." instance=ExtResource( 2 )] +margin_left = 339.0 +margin_top = 117.0 +margin_right = 667.0 +margin_bottom = 235.0 +custom_styles/hover = SubResource( 1 ) +custom_styles/pressed = SubResource( 2 ) +custom_styles/normal = SubResource( 3 ) +custom_fonts/font = SubResource( 4 ) +text = "← Back" + +[node name="FormContainer" type="VBoxContainer" parent="."] +margin_left = 679.0 +margin_top = 196.75 +margin_right = 1327.0 +margin_bottom = 933.75 +custom_constants/separation = 80 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Label" type="Label" parent="FormContainer"] +margin_right = 648.0 +margin_bottom = 72.0 +custom_fonts/font = SubResource( 5 ) +text = "Log in" +align = 1 + +[node name="UsernameContainer" type="HBoxContainer" parent="FormContainer"] +margin_top = 152.0 +margin_right = 648.0 +margin_bottom = 227.0 +custom_constants/separation = 20 + +[node name="Label" type="Label" parent="FormContainer/UsernameContainer"] +margin_top = 10.0 +margin_right = 269.0 +margin_bottom = 65.0 +custom_fonts/font = SubResource( 6 ) +text = "Username:" + +[node name="Username" type="LineEdit" parent="FormContainer/UsernameContainer"] +margin_left = 289.0 +margin_right = 619.0 +margin_bottom = 75.0 +rect_min_size = Vector2( 330, 75 ) +custom_fonts/font = SubResource( 7 ) +max_length = 30 + +[node name="PasswordContainer" type="HBoxContainer" parent="FormContainer"] +margin_top = 307.0 +margin_right = 648.0 +margin_bottom = 382.0 +custom_constants/separation = 40 + +[node name="Label" type="Label" parent="FormContainer/PasswordContainer"] +margin_top = 10.0 +margin_right = 247.0 +margin_bottom = 65.0 +custom_fonts/font = SubResource( 6 ) +text = "Password:" + +[node name="Password" type="LineEdit" parent="FormContainer/PasswordContainer"] +margin_left = 287.0 +margin_right = 617.0 +margin_bottom = 75.0 +rect_min_size = Vector2( 330, 75 ) +custom_fonts/font = SubResource( 8 ) +max_length = 30 +secret = true + +[node name="LinkButton" type="LinkButton" parent="FormContainer"] +margin_top = 462.0 +margin_right = 648.0 +margin_bottom = 499.0 +custom_fonts/font = SubResource( 9 ) +custom_colors/font_color = Color( 0.321569, 0.360784, 0.92549, 1 ) +text = "Forgot Password?" + +[node name="RememberMeCheckBox" type="CheckBox" parent="FormContainer"] +margin_top = 579.0 +margin_right = 648.0 +margin_bottom = 629.0 +rect_min_size = Vector2( 50, 50 ) +focus_mode = 0 +custom_icons/checked = ExtResource( 6 ) +custom_icons/unchecked = ExtResource( 5 ) +custom_styles/normal = SubResource( 10 ) +custom_fonts/font = SubResource( 11 ) +custom_constants/hseparation = 15 +text = "Stay signed in for 30 days" +expand_icon = true + +[node name="ErrorMessage" type="Label" parent="FormContainer"] +visible = false +margin_top = 522.0 +margin_right = 648.0 +margin_bottom = 559.0 +custom_fonts/font = SubResource( 12 ) +custom_colors/font_color = Color( 0.866667, 0.101961, 0.101961, 1 ) +autowrap = true + +[node name="LoginButton" parent="FormContainer" instance=ExtResource( 2 )] +margin_top = 709.0 +margin_right = 648.0 +margin_bottom = 827.0 +text = "Submit" + +[node name="ProcessingLabel" type="Label" parent="FormContainer"] +visible = false +margin_top = 740.0 +margin_right = 648.0 +margin_bottom = 812.0 +custom_fonts/font = SubResource( 5 ) +text = "Processing..." +align = 1 +[connection signal="pressed" from="BackButton" to="." method="_on_BackButton_pressed"] +[connection signal="pressed" from="FormContainer/LinkButton" to="." method="_on_LinkButton_pressed"] +[connection signal="pressed" from="FormContainer/LoginButton" to="." method="_on_LoginButton_pressed"] diff --git a/addons/silent_wolf/Auth/Register.gd b/addons/silent_wolf/Auth/Register.gd new file mode 100644 index 0000000..48a54c2 --- /dev/null +++ b/addons/silent_wolf/Auth/Register.gd @@ -0,0 +1,81 @@ +extends TextureRect + +const SWLogger = preload("res://addons/silent_wolf/utils/SWLogger.gd") + +func _ready(): + SilentWolf.check_auth_ready() + SilentWolf.Auth.connect("sw_registration_succeeded", self, "_on_registration_succeeded") + SilentWolf.Auth.connect("sw_registration_user_pwd_succeeded", self, "_on_registration_succeeded") + SilentWolf.Auth.connect("sw_registration_failed", self, "_on_registration_failed") + + +func _on_RegisterButton_pressed(): + 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(): + 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_succeeded(): + #get_tree().change_scene("res://addons/silent_wolf/Auth/Login.tscn") + # 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(scene_name) + + +func _on_registration_user_pwd_succeeded(): + var scene_name = SilentWolf.auth_config.redirect_to_scene + get_tree().change_scene(scene_name) + + +func _on_registration_failed(error): + hide_processing_label() + SWLogger.info("registration failed: " + str(error)) + $"FormContainer/ErrorMessage".text = error + $"FormContainer/ErrorMessage".show() + + +func _on_BackButton_pressed(): + get_tree().change_scene(SilentWolf.auth_config.redirect_to_scene) + + +func show_processing_label(): + $"FormContainer/ProcessingLabel".show() + + +func hide_processing_label(): + $"FormContainer/ProcessingLabel".hide() + + +func _on_UsernameToolButton_mouse_entered(): + $"FormContainer/InfoBox".text = "Username should contain at least 6 characters (letters or numbers) and no spaces." + $"FormContainer/InfoBox".show() + + +func _on_UsernameToolButton_mouse_exited(): + $"FormContainer/InfoBox".hide() + + +func _on_PasswordToolButton_mouse_entered(): + $"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(): + $"FormContainer/InfoBox".hide() diff --git a/addons/silent_wolf/Auth/Register.tscn b/addons/silent_wolf/Auth/Register.tscn new file mode 100644 index 0000000..9703000 --- /dev/null +++ b/addons/silent_wolf/Auth/Register.tscn @@ -0,0 +1,285 @@ +[gd_scene load_steps=21 format=2] + +[ext_resource path="res://addons/silent_wolf/Auth/Register.gd" type="Script" id=1] +[ext_resource path="res://addons/silent_wolf/common/SWButton.tscn" type="PackedScene" id=2] +[ext_resource path="res://addons/silent_wolf/assets/fonts/Comfortaa-Bold.ttf" type="DynamicFontData" id=3] +[ext_resource path="res://assets/fonts/Comfortaa-Bold.ttf" type="DynamicFontData" id=4] +[ext_resource path="res://addons/silent_wolf/assets/gfx/info_icon_small.png" type="Texture" id=5] +[ext_resource path="res://addons/silent_wolf/assets/gfx/dummy_info_icon_small.png" type="Texture" id=6] + +[sub_resource type="StyleBoxFlat" id=1] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.831373, 0.415686, 0.415686, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="StyleBoxFlat" id=2] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.831373, 0.415686, 0.415686, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="StyleBoxFlat" id=3] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.482353, 0.458824, 0.458824, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="DynamicFont" id=4] +size = 64 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=5] +size = 64 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=6] +size = 64 +font_data = ExtResource( 4 ) + +[sub_resource type="DynamicFont" id=7] +size = 32 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=8] +size = 32 +font_data = ExtResource( 4 ) + +[sub_resource type="DynamicFont" id=9] +size = 32 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=10] +size = 32 +font_data = ExtResource( 3 ) + +[sub_resource type="StyleBoxFlat" id=11] +content_margin_left = 30.0 +content_margin_right = 30.0 +content_margin_top = 30.0 +content_margin_bottom = 30.0 +bg_color = Color( 1, 1, 1, 1 ) +border_width_left = 3 +border_width_top = 3 +border_width_right = 3 +border_width_bottom = 3 +border_color = Color( 0.666667, 0.223529, 0.223529, 1 ) +corner_radius_top_left = 10 +corner_radius_top_right = 10 +corner_radius_bottom_right = 10 +corner_radius_bottom_left = 10 + +[sub_resource type="DynamicFont" id=12] +size = 32 +outline_color = Color( 0.666667, 0.223529, 0.223529, 1 ) +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=13] +size = 32 +outline_color = Color( 0.854902, 0.0901961, 0.0901961, 1 ) +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=14] +size = 32 +font_data = ExtResource( 3 ) + +[node name="Register" type="TextureRect"] +margin_right = 40.0 +margin_bottom = 40.0 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="BackButton" parent="." instance=ExtResource( 2 )] +margin_left = 241.0 +margin_top = 54.0 +margin_right = 529.0 +margin_bottom = 172.0 +custom_styles/hover = SubResource( 1 ) +custom_styles/pressed = SubResource( 2 ) +custom_styles/normal = SubResource( 3 ) +custom_fonts/font = SubResource( 4 ) +text = "← Back" + +[node name="FormContainer" type="VBoxContainer" parent="."] +margin_left = 396.0 +margin_top = 200.0 +margin_right = 1665.0 +margin_bottom = 1060.0 +grow_horizontal = 0 +grow_vertical = 0 +custom_constants/separation = 60 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Label" type="Label" parent="FormContainer"] +margin_right = 1269.0 +margin_bottom = 72.0 +custom_fonts/font = SubResource( 5 ) +text = "Sign up" +align = 1 + +[node name="MainFormContainer" type="HBoxContainer" parent="FormContainer"] +margin_top = 132.0 +margin_right = 1269.0 +margin_bottom = 525.0 +custom_constants/separation = 30 + +[node name="FormLabels" type="VBoxContainer" parent="FormContainer/MainFormContainer"] +margin_right = 625.0 +margin_bottom = 393.0 +custom_constants/separation = 30 + +[node name="PlayerNameLabel" type="Label" parent="FormContainer/MainFormContainer/FormLabels"] +margin_right = 625.0 +margin_bottom = 80.0 +rect_min_size = Vector2( 0, 80 ) +custom_fonts/font = SubResource( 6 ) +text = "Username:" + +[node name="EmailLabel" type="Label" parent="FormContainer/MainFormContainer/FormLabels"] +margin_top = 110.0 +margin_right = 625.0 +margin_bottom = 182.0 +custom_fonts/font = SubResource( 6 ) +text = "Email:" + +[node name="PasswordLabel" type="Label" parent="FormContainer/MainFormContainer/FormLabels"] +margin_top = 212.0 +margin_right = 625.0 +margin_bottom = 284.0 +custom_fonts/font = SubResource( 6 ) +text = "Password:" + +[node name="ConfirmPasswordLabel" type="Label" parent="FormContainer/MainFormContainer/FormLabels"] +margin_top = 314.0 +margin_right = 625.0 +margin_bottom = 386.0 +custom_fonts/font = SubResource( 6 ) +text = "Confirm password:" + +[node name="FormInputFields" type="VBoxContainer" parent="FormContainer/MainFormContainer"] +margin_left = 655.0 +margin_right = 1155.0 +margin_bottom = 393.0 +custom_constants/separation = 30 + +[node name="PlayerName" type="LineEdit" parent="FormContainer/MainFormContainer/FormInputFields"] +margin_right = 500.0 +margin_bottom = 78.0 +rect_min_size = Vector2( 500, 78 ) +custom_fonts/font = SubResource( 7 ) +max_length = 30 + +[node name="Email" type="LineEdit" parent="FormContainer/MainFormContainer/FormInputFields"] +margin_top = 108.0 +margin_right = 500.0 +margin_bottom = 183.0 +rect_min_size = Vector2( 360, 75 ) +custom_fonts/font = SubResource( 8 ) +max_length = 50 + +[node name="Password" type="LineEdit" parent="FormContainer/MainFormContainer/FormInputFields"] +margin_top = 213.0 +margin_right = 500.0 +margin_bottom = 288.0 +rect_min_size = Vector2( 360, 75 ) +custom_fonts/font = SubResource( 9 ) +max_length = 30 +secret = true + +[node name="ConfirmPassword" type="LineEdit" parent="FormContainer/MainFormContainer/FormInputFields"] +margin_top = 318.0 +margin_right = 500.0 +margin_bottom = 393.0 +rect_min_size = Vector2( 360, 75 ) +custom_fonts/font = SubResource( 10 ) +max_length = 30 +secret = true + +[node name="InfoLabels" type="VBoxContainer" parent="FormContainer/MainFormContainer"] +margin_left = 1185.0 +margin_right = 1269.0 +margin_bottom = 393.0 +rect_min_size = Vector2( 40, 0 ) +custom_constants/separation = 24 + +[node name="UsernameToolButton" type="ToolButton" parent="FormContainer/MainFormContainer/InfoLabels"] +margin_right = 84.0 +margin_bottom = 80.0 +icon = ExtResource( 5 ) + +[node name="DummyToolButton1" type="ToolButton" parent="FormContainer/MainFormContainer/InfoLabels"] +margin_top = 104.0 +margin_right = 84.0 +margin_bottom = 184.0 +icon = ExtResource( 6 ) + +[node name="PasswordToolButton" type="ToolButton" parent="FormContainer/MainFormContainer/InfoLabels"] +margin_top = 208.0 +margin_right = 84.0 +margin_bottom = 288.0 +icon = ExtResource( 5 ) + +[node name="DummyToolButton2" type="ToolButton" parent="FormContainer/MainFormContainer/InfoLabels"] +margin_top = 312.0 +margin_right = 84.0 +margin_bottom = 392.0 +icon = ExtResource( 6 ) + +[node name="InfoBox" type="Label" parent="FormContainer"] +visible = false +margin_right = 1269.0 +margin_bottom = 137.0 +rect_min_size = Vector2( 250, 0 ) +custom_styles/normal = SubResource( 11 ) +custom_fonts/font = SubResource( 12 ) +custom_colors/font_color = Color( 0.666667, 0.223529, 0.223529, 1 ) +custom_colors/font_outline_modulate = Color( 0.937255, 0.917647, 0.917647, 1 ) +text = "Password should contain at least 8 characters including uppercase and lowercase letters, numbers and (optionally) special characters." +autowrap = true + +[node name="ErrorMessage" type="Label" parent="FormContainer"] +visible = false +margin_right = 1189.0 +margin_bottom = 37.0 +custom_fonts/font = SubResource( 13 ) +custom_colors/font_color = Color( 0.866667, 0.101961, 0.101961, 1 ) +autowrap = true + +[node name="RegisterButton" parent="FormContainer" instance=ExtResource( 2 )] +margin_top = 585.0 +margin_right = 1269.0 +margin_bottom = 703.0 +text = "Submit" + +[node name="ProcessingLabel" type="Label" parent="FormContainer"] +visible = false +margin_right = 1189.0 +margin_bottom = 72.0 +custom_fonts/font = SubResource( 14 ) +text = "Processing..." +align = 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/RegisterButton" to="." method="_on_RegisterButton_pressed"] diff --git a/addons/silent_wolf/Auth/RegisterUsernamePassword.tscn b/addons/silent_wolf/Auth/RegisterUsernamePassword.tscn new file mode 100644 index 0000000..099c342 --- /dev/null +++ b/addons/silent_wolf/Auth/RegisterUsernamePassword.tscn @@ -0,0 +1,266 @@ +[gd_scene load_steps=20 format=2] + +[ext_resource path="res://addons/silent_wolf/Auth/Register.gd" type="Script" id=1] +[ext_resource path="res://addons/silent_wolf/common/SWButton.tscn" type="PackedScene" id=2] +[ext_resource path="res://addons/silent_wolf/assets/fonts/Comfortaa-Bold.ttf" type="DynamicFontData" id=3] +[ext_resource path="res://assets/fonts/Comfortaa-Bold.ttf" type="DynamicFontData" id=4] +[ext_resource path="res://addons/silent_wolf/assets/gfx/info_icon_small.png" type="Texture" id=5] +[ext_resource path="res://addons/silent_wolf/assets/gfx/dummy_info_icon_small.png" type="Texture" id=6] + +[sub_resource type="StyleBoxFlat" id=1] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.831373, 0.415686, 0.415686, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="StyleBoxFlat" id=2] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.831373, 0.415686, 0.415686, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="StyleBoxFlat" id=3] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.482353, 0.458824, 0.458824, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="DynamicFont" id=4] +size = 64 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=5] +size = 64 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=6] +size = 64 +font_data = ExtResource( 4 ) + +[sub_resource type="DynamicFont" id=7] +size = 32 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=8] +size = 32 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=9] +size = 32 +font_data = ExtResource( 3 ) + +[sub_resource type="StyleBoxFlat" id=10] +content_margin_left = 30.0 +content_margin_right = 30.0 +content_margin_top = 30.0 +content_margin_bottom = 30.0 +bg_color = Color( 1, 1, 1, 1 ) +border_width_left = 3 +border_width_top = 3 +border_width_right = 3 +border_width_bottom = 3 +border_color = Color( 0.666667, 0.223529, 0.223529, 1 ) +corner_radius_top_left = 10 +corner_radius_top_right = 10 +corner_radius_bottom_right = 10 +corner_radius_bottom_left = 10 + +[sub_resource type="DynamicFont" id=11] +size = 32 +outline_color = Color( 0.666667, 0.223529, 0.223529, 1 ) +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=12] +size = 32 +outline_color = Color( 0.854902, 0.0901961, 0.0901961, 1 ) +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=13] +size = 32 +font_data = ExtResource( 3 ) + +[node name="Register" type="TextureRect"] +margin_right = 40.0 +margin_bottom = 40.0 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="BackButton" parent="." instance=ExtResource( 2 )] +margin_left = 241.0 +margin_top = 54.0 +margin_right = 529.0 +margin_bottom = 172.0 +custom_styles/hover = SubResource( 1 ) +custom_styles/pressed = SubResource( 2 ) +custom_styles/normal = SubResource( 3 ) +custom_fonts/font = SubResource( 4 ) +text = "← Back" + +[node name="FormContainer" type="VBoxContainer" parent="."] +margin_left = 396.0 +margin_top = 200.0 +margin_right = 1665.0 +margin_bottom = 1060.0 +grow_horizontal = 0 +grow_vertical = 0 +custom_constants/separation = 60 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Label" type="Label" parent="FormContainer"] +margin_right = 1269.0 +margin_bottom = 72.0 +custom_fonts/font = SubResource( 5 ) +text = "Sign up" +align = 1 + +[node name="MainFormContainer" type="HBoxContainer" parent="FormContainer"] +margin_top = 132.0 +margin_right = 1269.0 +margin_bottom = 524.0 +custom_constants/separation = 30 + +[node name="FormLabels" type="VBoxContainer" parent="FormContainer/MainFormContainer"] +margin_right = 625.0 +margin_bottom = 392.0 +custom_constants/separation = 30 + +[node name="PlayerNameLabel" type="Label" parent="FormContainer/MainFormContainer/FormLabels"] +margin_right = 625.0 +margin_bottom = 80.0 +rect_min_size = Vector2( 0, 80 ) +custom_fonts/font = SubResource( 6 ) +text = "Username:" + +[node name="PasswordLabel" type="Label" parent="FormContainer/MainFormContainer/FormLabels"] +margin_top = 110.0 +margin_right = 625.0 +margin_bottom = 182.0 +custom_fonts/font = SubResource( 6 ) +text = "Password:" + +[node name="ConfirmPasswordLabel" type="Label" parent="FormContainer/MainFormContainer/FormLabels"] +margin_top = 212.0 +margin_right = 625.0 +margin_bottom = 284.0 +custom_fonts/font = SubResource( 6 ) +text = "Confirm password:" + +[node name="FormInputFields" type="VBoxContainer" parent="FormContainer/MainFormContainer"] +margin_left = 655.0 +margin_right = 1155.0 +margin_bottom = 392.0 +custom_constants/separation = 30 + +[node name="PlayerName" type="LineEdit" parent="FormContainer/MainFormContainer/FormInputFields"] +margin_right = 500.0 +margin_bottom = 78.0 +rect_min_size = Vector2( 500, 78 ) +custom_fonts/font = SubResource( 7 ) +max_length = 30 + +[node name="Password" type="LineEdit" parent="FormContainer/MainFormContainer/FormInputFields"] +margin_top = 108.0 +margin_right = 500.0 +margin_bottom = 183.0 +rect_min_size = Vector2( 360, 75 ) +custom_fonts/font = SubResource( 8 ) +max_length = 30 +secret = true + +[node name="ConfirmPassword" type="LineEdit" parent="FormContainer/MainFormContainer/FormInputFields"] +margin_top = 213.0 +margin_right = 500.0 +margin_bottom = 288.0 +rect_min_size = Vector2( 360, 75 ) +custom_fonts/font = SubResource( 9 ) +max_length = 30 +secret = true + +[node name="InfoLabels" type="VBoxContainer" parent="FormContainer/MainFormContainer"] +margin_left = 1185.0 +margin_right = 1269.0 +margin_bottom = 392.0 +rect_min_size = Vector2( 40, 0 ) +custom_constants/separation = 24 + +[node name="UsernameToolButton" type="ToolButton" parent="FormContainer/MainFormContainer/InfoLabels"] +margin_right = 84.0 +margin_bottom = 80.0 +icon = ExtResource( 5 ) + +[node name="DummyToolButton1" type="ToolButton" parent="FormContainer/MainFormContainer/InfoLabels"] +margin_top = 104.0 +margin_right = 84.0 +margin_bottom = 184.0 +icon = ExtResource( 6 ) + +[node name="PasswordToolButton" type="ToolButton" parent="FormContainer/MainFormContainer/InfoLabels"] +margin_top = 208.0 +margin_right = 84.0 +margin_bottom = 288.0 +icon = ExtResource( 5 ) + +[node name="DummyToolButton2" type="ToolButton" parent="FormContainer/MainFormContainer/InfoLabels"] +margin_top = 312.0 +margin_right = 84.0 +margin_bottom = 392.0 +icon = ExtResource( 6 ) + +[node name="InfoBox" type="Label" parent="FormContainer"] +visible = false +margin_right = 1269.0 +margin_bottom = 137.0 +rect_min_size = Vector2( 250, 0 ) +custom_styles/normal = SubResource( 10 ) +custom_fonts/font = SubResource( 11 ) +custom_colors/font_color = Color( 0.666667, 0.223529, 0.223529, 1 ) +custom_colors/font_outline_modulate = Color( 0.937255, 0.917647, 0.917647, 1 ) +text = "Password should contain at least 8 characters including uppercase and lowercase letters, numbers and (optionally) special characters." +autowrap = true + +[node name="ErrorMessage" type="Label" parent="FormContainer"] +visible = false +margin_right = 1189.0 +margin_bottom = 37.0 +custom_fonts/font = SubResource( 12 ) +custom_colors/font_color = Color( 0.866667, 0.101961, 0.101961, 1 ) +autowrap = true + +[node name="RegisterUPButton" parent="FormContainer" instance=ExtResource( 2 )] +margin_top = 584.0 +margin_right = 1269.0 +margin_bottom = 702.0 +text = "Submit" + +[node name="ProcessingLabel" type="Label" parent="FormContainer"] +visible = false +margin_right = 1189.0 +margin_bottom = 72.0 +custom_fonts/font = SubResource( 13 ) +text = "Processing..." +align = 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"] diff --git a/addons/silent_wolf/Auth/ResetPassword.gd b/addons/silent_wolf/Auth/ResetPassword.gd new file mode 100644 index 0000000..1c75f51 --- /dev/null +++ b/addons/silent_wolf/Auth/ResetPassword.gd @@ -0,0 +1,63 @@ +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.connect("sw_request_password_reset_succeeded", self, "_on_send_code_succeeded") + SilentWolf.Auth.connect("sw_request_password_reset_failed", self, "_on_send_code_failed") + SilentWolf.Auth.connect("sw_reset_password_succeeded", self, "_on_reset_succeeded") + SilentWolf.Auth.connect("sw_reset_password_failed", self, "_on_reset_failed") + if "login_scene" in SilentWolf.Auth: + login_scene = SilentWolf.Auth.login_scene + +func _on_BackButton_pressed(): + get_tree().change_scene(login_scene) + + +func _on_PlayerNameSubmitButton_pressed(): + player_name = $"RequestFormContainer/FormContainer/FormInputFields/PlayerName".text + SilentWolf.Auth.request_player_password_reset(player_name) + $"RequestFormContainer/ProcessingLabel".show() + + +func _on_send_code_succeeded(): + $"RequestFormContainer/ProcessingLabel".hide() + $"RequestFormContainer".hide() + $"PwdResetFormContainer".show() + + +func _on_send_code_failed(error): + $"RequestFormContainer/ProcessingLabel".hide() + $"RequestFormContainer/ErrorMessage".text = "Could not send confirmation code. " + str(error) + $"RequestFormContainer/ErrorMessage".show() + + +func _on_NewPasswordSubmitButton_pressed(): + 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_succeeded(): + $"PwdResetFormContainer/ProcessingLabel".hide() + $"PwdResetFormContainer".hide() + $"PasswordChangedContainer".show() + + +func _on_reset_failed(error): + $"PwdResetFormContainer/ProcessingLabel".hide() + $"PwdResetFormContainer/ErrorMessage".text = "Could not reset password. " + str(error) + $"PwdResetFormContainer/ErrorMessage".show() + + +func _on_CloseButton_pressed(): + get_tree().change_scene(login_scene) diff --git a/addons/silent_wolf/Auth/ResetPassword.tscn b/addons/silent_wolf/Auth/ResetPassword.tscn new file mode 100644 index 0000000..1996d65 --- /dev/null +++ b/addons/silent_wolf/Auth/ResetPassword.tscn @@ -0,0 +1,395 @@ +[gd_scene load_steps=27 format=2] + +[ext_resource path="res://addons/silent_wolf/Auth/ResetPassword.gd" type="Script" id=1] +[ext_resource path="res://addons/silent_wolf/common/SWButton.tscn" type="PackedScene" id=2] +[ext_resource path="res://addons/silent_wolf/assets/fonts/Comfortaa-Bold.ttf" type="DynamicFontData" id=3] +[ext_resource path="res://assets/fonts/Comfortaa-Bold.ttf" type="DynamicFontData" id=6] + +[sub_resource type="StyleBoxFlat" id=1] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.831373, 0.415686, 0.415686, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="StyleBoxFlat" id=2] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.831373, 0.415686, 0.415686, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="StyleBoxFlat" id=3] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.482353, 0.458824, 0.458824, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="DynamicFont" id=4] +size = 64 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=5] +size = 64 +font_data = ExtResource( 6 ) + +[sub_resource type="DynamicFont" id=6] +size = 32 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=7] +size = 64 +font_data = ExtResource( 6 ) + +[sub_resource type="DynamicFont" id=8] +size = 32 +font_data = ExtResource( 6 ) + +[sub_resource type="DynamicFont" id=9] +size = 32 +font_data = ExtResource( 6 ) + +[sub_resource type="StyleBoxFlat" id=10] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.831373, 0.415686, 0.415686, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="StyleBoxFlat" id=11] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.831373, 0.415686, 0.415686, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="StyleBoxFlat" id=12] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.666667, 0.223529, 0.223529, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="DynamicFont" id=13] +size = 64 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=14] +size = 64 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=15] +size = 32 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=16] +size = 64 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=17] +size = 64 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=18] +size = 64 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=19] +size = 32 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=20] +size = 32 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=21] +size = 32 +outline_color = Color( 0.854902, 0.0901961, 0.0901961, 1 ) +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=22] +size = 32 +font_data = ExtResource( 3 ) + +[node name="ResetPassword" type="TextureRect"] +margin_right = 40.0 +margin_bottom = 40.0 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="BackButton" parent="." instance=ExtResource( 2 )] +margin_left = 339.0 +margin_top = 117.0 +margin_right = 667.0 +margin_bottom = 235.0 +custom_styles/hover = SubResource( 1 ) +custom_styles/pressed = SubResource( 2 ) +custom_styles/normal = SubResource( 3 ) +custom_fonts/font = SubResource( 4 ) +text = "← Back" + +[node name="RequestFormContainer" type="VBoxContainer" parent="."] +visible = false +margin_left = 679.0 +margin_top = 196.75 +margin_right = 1327.0 +margin_bottom = 933.75 +custom_constants/separation = 65 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Label" type="Label" parent="RequestFormContainer"] +margin_right = 1035.0 +margin_bottom = 72.0 +custom_fonts/font = SubResource( 5 ) +text = "Reset password" +align = 1 + +[node name="LabelExplainer" type="Label" parent="RequestFormContainer"] +margin_top = 137.0 +margin_right = 1035.0 +margin_bottom = 174.0 +custom_fonts/font = SubResource( 6 ) +text = "Please enter your player name below." +autowrap = true + +[node name="FormContainer" type="HBoxContainer" parent="RequestFormContainer"] +margin_top = 239.0 +margin_right = 1035.0 +margin_bottom = 314.0 +custom_constants/separation = 50 + +[node name="FormLabels" type="VBoxContainer" parent="RequestFormContainer/FormContainer"] +margin_right = 426.0 +margin_bottom = 75.0 +custom_constants/separation = 30 + +[node name="PlayerNameLabel" type="Label" parent="RequestFormContainer/FormContainer/FormLabels"] +margin_right = 426.0 +margin_bottom = 72.0 +custom_fonts/font = SubResource( 7 ) +text = "Player name:" + +[node name="FormInputFields" type="VBoxContainer" parent="RequestFormContainer/FormContainer"] +margin_left = 476.0 +margin_right = 836.0 +margin_bottom = 75.0 +custom_constants/separation = 30 + +[node name="PlayerName" type="LineEdit" parent="RequestFormContainer/FormContainer/FormInputFields"] +margin_right = 360.0 +margin_bottom = 75.0 +rect_min_size = Vector2( 360, 75 ) +custom_fonts/font = SubResource( 8 ) +max_length = 50 + +[node name="ErrorMessage" type="Label" parent="RequestFormContainer"] +visible = false +margin_top = 522.0 +margin_right = 648.0 +margin_bottom = 559.0 +custom_fonts/font = SubResource( 9 ) +custom_colors/font_color = Color( 0.866667, 0.101961, 0.101961, 1 ) +autowrap = true + +[node name="PlayerNameSubmitButton" parent="RequestFormContainer" instance=ExtResource( 2 )] +margin_top = 379.0 +margin_right = 1035.0 +margin_bottom = 497.0 +custom_styles/hover = SubResource( 10 ) +custom_styles/pressed = SubResource( 11 ) +custom_styles/normal = SubResource( 12 ) +custom_fonts/font = SubResource( 13 ) +text = "Submit" + +[node name="ProcessingLabel" type="Label" parent="RequestFormContainer"] +visible = false +margin_top = 740.0 +margin_right = 648.0 +margin_bottom = 812.0 +custom_fonts/font = SubResource( 5 ) +text = "Processing..." +align = 1 + +[node name="PwdResetFormContainer" type="VBoxContainer" parent="."] +margin_left = 679.0 +margin_top = 196.75 +margin_right = 1714.0 +margin_bottom = 973.75 +custom_constants/separation = 70 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Label" type="Label" parent="PwdResetFormContainer"] +margin_right = 1035.0 +margin_bottom = 72.0 +custom_fonts/font = SubResource( 14 ) +text = "Reset password" +align = 1 + +[node name="LabelExplainer" type="Label" parent="PwdResetFormContainer"] +margin_top = 142.0 +margin_right = 1035.0 +margin_bottom = 219.0 +custom_fonts/font = SubResource( 15 ) +text = "Please enter below the code we sent you by email and your new password twice." +autowrap = true + +[node name="FormContainer" type="HBoxContainer" parent="PwdResetFormContainer"] +margin_top = 289.0 +margin_right = 1035.0 +margin_bottom = 574.0 +custom_constants/separation = 50 + +[node name="FormLabels" type="VBoxContainer" parent="PwdResetFormContainer/FormContainer"] +margin_right = 625.0 +margin_bottom = 285.0 +custom_constants/separation = 30 + +[node name="CodeLabel" type="Label" parent="PwdResetFormContainer/FormContainer/FormLabels"] +margin_right = 625.0 +margin_bottom = 72.0 +custom_fonts/font = SubResource( 16 ) +text = "Code:" + +[node name="PasswordLabel" type="Label" parent="PwdResetFormContainer/FormContainer/FormLabels"] +margin_top = 102.0 +margin_right = 625.0 +margin_bottom = 174.0 +custom_fonts/font = SubResource( 17 ) +text = "Password:" + +[node name="ConfirmPasswordLabel" type="Label" parent="PwdResetFormContainer/FormContainer/FormLabels"] +margin_top = 204.0 +margin_right = 625.0 +margin_bottom = 276.0 +custom_fonts/font = SubResource( 18 ) +text = "Confirm password:" + +[node name="FormInputFields" type="VBoxContainer" parent="PwdResetFormContainer/FormContainer"] +margin_left = 675.0 +margin_right = 1035.0 +margin_bottom = 285.0 +custom_constants/separation = 30 + +[node name="Code" type="LineEdit" parent="PwdResetFormContainer/FormContainer/FormInputFields"] +margin_right = 360.0 +margin_bottom = 75.0 +rect_min_size = Vector2( 360, 75 ) +custom_fonts/font = SubResource( 8 ) +max_length = 50 + +[node name="Password" type="LineEdit" parent="PwdResetFormContainer/FormContainer/FormInputFields"] +margin_top = 105.0 +margin_right = 360.0 +margin_bottom = 180.0 +rect_min_size = Vector2( 360, 75 ) +custom_fonts/font = SubResource( 19 ) +max_length = 30 +secret = true + +[node name="ConfirmPassword" type="LineEdit" parent="PwdResetFormContainer/FormContainer/FormInputFields"] +margin_top = 210.0 +margin_right = 360.0 +margin_bottom = 285.0 +rect_min_size = Vector2( 360, 75 ) +custom_fonts/font = SubResource( 20 ) +max_length = 30 +secret = true + +[node name="ErrorMessage" type="Label" parent="PwdResetFormContainer"] +visible = false +margin_top = 522.0 +margin_right = 648.0 +margin_bottom = 559.0 +custom_fonts/font = SubResource( 21 ) +custom_colors/font_color = Color( 0.866667, 0.101961, 0.101961, 1 ) +autowrap = true + +[node name="NewPasswordSubmitButton" parent="PwdResetFormContainer" instance=ExtResource( 2 )] +margin_top = 644.0 +margin_right = 1035.0 +margin_bottom = 762.0 +custom_styles/hover = SubResource( 10 ) +custom_styles/pressed = SubResource( 11 ) +custom_styles/normal = SubResource( 12 ) +custom_fonts/font = SubResource( 13 ) +text = "Submit" + +[node name="ProcessingLabel" type="Label" parent="PwdResetFormContainer"] +visible = false +margin_top = 740.0 +margin_right = 648.0 +margin_bottom = 812.0 +custom_fonts/font = SubResource( 22 ) +text = "Processing..." +align = 1 + +[node name="PasswordChangedContainer" type="VBoxContainer" parent="."] +visible = false +margin_left = 679.0 +margin_top = 312.0 +margin_right = 1327.0 +margin_bottom = 933.0 +custom_constants/separation = 100 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="PwdChanedLabel" type="Label" parent="PasswordChangedContainer"] +margin_right = 524.0 +margin_bottom = 72.0 +custom_fonts/font = SubResource( 5 ) +text = "Password reset" +align = 1 + +[node name="PasswordChangedLabelExplainer" type="Label" parent="PasswordChangedContainer"] +margin_top = 76.0 +margin_right = 524.0 +margin_bottom = 153.0 +custom_fonts/font = SubResource( 15 ) +text = "Your password was changed successfully." +autowrap = true + +[node name="CloseButton" parent="PasswordChangedContainer" instance=ExtResource( 2 )] +margin_top = 157.0 +margin_right = 524.0 +margin_bottom = 275.0 +custom_styles/hover = SubResource( 10 ) +custom_styles/pressed = SubResource( 11 ) +custom_styles/normal = SubResource( 12 ) +custom_fonts/font = SubResource( 13 ) +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"] diff --git a/addons/silent_wolf/Multiplayer/.DS_Store b/addons/silent_wolf/Multiplayer/.DS_Store new file mode 100644 index 0000000..936f460 Binary files /dev/null and b/addons/silent_wolf/Multiplayer/.DS_Store differ diff --git a/addons/silent_wolf/Multiplayer/Multiplayer.gd b/addons/silent_wolf/Multiplayer/Multiplayer.gd new file mode 100644 index 0000000..b15864a --- /dev/null +++ b/addons/silent_wolf/Multiplayer/Multiplayer.gd @@ -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) diff --git a/addons/silent_wolf/Multiplayer/ws/WSClient.gd b/addons/silent_wolf/Multiplayer/ws/WSClient.gd new file mode 100644 index 0000000..1ce35b0 --- /dev/null +++ b/addons/silent_wolf/Multiplayer/ws/WSClient.gd @@ -0,0 +1,81 @@ +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()) + + +func _process(delta): + # Call this in _process or _physics_process. Data transfer, and signals + # emission will only happen when calling this function. + _client.poll() + + +# 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.print(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 diff --git a/addons/silent_wolf/Players/Players.gd b/addons/silent_wolf/Players/Players.gd new file mode 100644 index 0000000..eac735c --- /dev/null +++ b/addons/silent_wolf/Players/Players.gd @@ -0,0 +1,199 @@ +extends Node + +const CommonErrors = preload("res://addons/silent_wolf/common/CommonErrors.gd") +const SWLogger = preload("res://addons/silent_wolf/utils/SWLogger.gd") + +signal sw_player_data_received +signal sw_player_data_posted +signal sw_player_data_removed + +var GetPlayerData = null +var PushPlayerData = null +var RemovePlayerData = null + +# wekrefs +var wrGetPlayerData = null +var wrPushPlayerData = null +var wrRemovePlayerData = null + +var player_name = null +var player_data = null + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + +func set_player_data(new_player_data): + player_data = new_player_data + +func clear_player_data(): + player_name = null + player_data = null + +func get_stats(): + 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(): + var inventory = null + if player_data: + inventory = { + "weapons": player_data.weapons, + "gold": player_data.gold + } + return inventory + +func get_player_data(player_name): + GetPlayerData = HTTPRequest.new() + wrGetPlayerData = weakref(GetPlayerData) + if OS.get_name() != "HTML5": + GetPlayerData.set_use_threads(true) + get_tree().get_root().add_child(GetPlayerData) + GetPlayerData.connect("request_completed", self, "_on_GetPlayerData_request_completed") + SWLogger.info("Calling SilentWolf to get player data") + var game_id = SilentWolf.config.game_id + var game_version = SilentWolf.config.game_version + var api_key = SilentWolf.config.api_key + var headers = [ + "x-api-key: " + api_key, + "x-sw-plugin-version: " + SilentWolf.version, + "x-sw-game-id: " + SilentWolf.config.game_id, + "x-sw-godot-version: " + SilentWolf.godot_version + ] + GetPlayerData.request("https://api.silentwolf.com/get_player_data/" + game_id + "/" + player_name, headers, true, HTTPClient.METHOD_GET) + return self + + +func post_player_data(player_name, player_data, overwrite=true): + if typeof(player_data) != TYPE_DICTIONARY: + SWLogger.error("Player data should be of type Dictionary, instead it is of type: " + str(typeof(player_data))) + PushPlayerData = HTTPRequest.new() + wrPushPlayerData = weakref(PushPlayerData) + if OS.get_name() != "HTML5": + PushPlayerData.set_use_threads(true) + get_tree().get_root().add_child(PushPlayerData) + PushPlayerData.connect("request_completed", self, "_on_PushPlayerData_request_completed") + SWLogger.info("Calling SilentWolf to post player data") + var game_id = SilentWolf.config.game_id + var game_version = SilentWolf.config.game_version + var api_key = SilentWolf.config.api_key + var headers = [ + "Content-Type: application/json", + "x-api-key: " + api_key, + "x-sw-plugin-version: " + SilentWolf.version, + "x-sw-game-id: " + SilentWolf.config.game_id, + "x-sw-godot-version: " + SilentWolf.godot_version + ] + var payload = { "game_id": game_id, "game_version": game_version, "player_name": player_name, "player_data": player_data, "overwrite": overwrite } + var query = JSON.print(payload) + PushPlayerData.request("https://api.silentwolf.com/push_player_data", headers, true, HTTPClient.METHOD_POST, query) + return self + +func delete_player_weapons(player_name): + var weapons = { "Weapons": [] } + delete_player_data(player_name, weapons) + +func remove_player_money(player_name): + var money = { "Money": 0 } + delete_player_data(player_name, money) + +func delete_player_items(player_name, item_name): + var item = { item_name: "" } + delete_player_data(player_name, item) + +func delete_all_player_data(player_name): + delete_player_data(player_name, "") + +func delete_player_data(player_name, player_data): + RemovePlayerData = HTTPRequest.new() + wrRemovePlayerData = weakref(RemovePlayerData) + if OS.get_name() != "HTML5": + RemovePlayerData.set_use_threads(true) + get_tree().get_root().add_child(RemovePlayerData) + RemovePlayerData.connect("request_completed", self, "_on_RemovePlayerData_request_completed") + SWLogger.info("Calling SilentWolf to remove player data") + var game_id = SilentWolf.config.game_id + var api_key = SilentWolf.config.api_key + var headers = [ + "Content-Type: application/json", + "x-api-key: " + api_key, + "x-sw-plugin-version: " + SilentWolf.version, + "x-sw-game-id: " + SilentWolf.config.game_id, + "x-sw-godot-version: " + SilentWolf.godot_version + ] + var payload = { "game_id": game_id, "player_name": player_name, "player_data": player_data } + var query = JSON.print(payload) + RemovePlayerData.request("https://api.silentwolf.com/remove_player_data", headers, true, HTTPClient.METHOD_POST, query) + return self + +func _on_GetPlayerData_request_completed(result, response_code, headers, body): + SWLogger.info("GetPlayerData request completed") + var status_check = CommonErrors.check_status_code(response_code) + #print("client status: " + str(GetPlayerData.get_http_client_status())) + if is_instance_valid(GetPlayerData): + SilentWolf.free_request(wrGetPlayerData, GetPlayerData) + #GetPlayerData.queue_free() + SWLogger.debug("response headers: " + str(response_code)) + SWLogger.debug("response headers: " + str(headers)) + SWLogger.debug("response body: " + str(body.get_string_from_utf8())) + + if status_check: + var json = JSON.parse(body.get_string_from_utf8()) + var response = json.result + if "message" in response.keys() and response.message == "Forbidden": + SWLogger.error("You are not authorized to call the SilentWolf API - check your API key configuration: https://silentwolf.com/playerdata") + else: + SWLogger.info("SilentWolf get player data success") + player_name = response.player_name + player_data = response.player_data + SWLogger.debug("Request completed: Player data: " + str(player_data)) + emit_signal("sw_player_data_received", player_name, player_data) + +func _on_PushPlayerData_request_completed(result, response_code, headers, body): + SWLogger.info("PushPlayerData request completed") + var status_check = CommonErrors.check_status_code(response_code) + if is_instance_valid(PushPlayerData): + #PushPlayerData.queue_free() + SilentWolf.free_request(wrPushPlayerData, PushPlayerData) + SWLogger.debug("response headers: " + str(response_code)) + SWLogger.debug("response headers: " + str(headers)) + SWLogger.debug("response body: " + str(body.get_string_from_utf8())) + + if status_check: + var json = JSON.parse(body.get_string_from_utf8()) + var response = json.result + if "message" in response.keys() and response.message == "Forbidden": + SWLogger.error("You are not authorized to call the SilentWolf API - check your API key configuration: https://silentwolf.com/playerdata") + else: + SWLogger.info("SilentWolf post player data score success: " + str(response_code)) + var player_name = response.player_name + emit_signal("sw_player_data_posted", player_name) + +func _on_RemovePlayerData_request_completed(result, response_code, headers, body): + SWLogger.info("RemovePlayerData request completed") + var status_check = CommonErrors.check_status_code(response_code) + if is_instance_valid(RemovePlayerData): + RemovePlayerData.queue_free() + SWLogger.debug("response headers: " + str(response_code)) + SWLogger.debug("response headers: " + str(headers)) + SWLogger.debug("response body: " + str(body.get_string_from_utf8())) + + if status_check: + var json = JSON.parse(body.get_string_from_utf8()) + var response = json.result + if "message" in response.keys() and response.message == "Forbidden": + SWLogger.error("You are not authorized to call the SilentWolf API - check your API key configuration: https://silentwolf.com/playerdata") + else: + SWLogger.info("SilentWolf post player data score success: " + str(response_code)) + var player_name = response.player_name + # return player_data after (maybe partial) removal + var player_data = response.player_data + emit_signal("sw_player_data_removed", player_name, player_data) diff --git a/addons/silent_wolf/Scores/.DS_Store b/addons/silent_wolf/Scores/.DS_Store new file mode 100644 index 0000000..b3c2758 Binary files /dev/null and b/addons/silent_wolf/Scores/.DS_Store differ diff --git a/addons/silent_wolf/Scores/Leaderboard.gd b/addons/silent_wolf/Scores/Leaderboard.gd new file mode 100644 index 0000000..b6cee45 --- /dev/null +++ b/addons/silent_wolf/Scores/Leaderboard.gd @@ -0,0 +1,126 @@ +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() + yield(SilentWolf.Scores.get_high_scores(), "sw_scores_received") + hide_message() + render_board(SilentWolf.Scores.scores, local_scores) + + +func render_board(scores, local_scores): + 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 and !local_scores: + add_no_scores_message() + else: + if !scores: + add_no_scores_message() + if !all_scores: + 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): + 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, local_scores, max_scores=10): + 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(self, "sort_by_score"); + var return_scores = scores + if scores.size() > max_scores: + return_scores = scores.resize(max_scores) + return return_scores + + +func sort_by_score(a, b): + if a.score > b.score: + return true; + else: + if a.score < b.score: + return false; + else: + return true; + + +func score_in_score_array(scores, new_score): + var in_score_array = false + if new_score and scores: + 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, score): + var item = ScoreItem.instance() + list_index += 1 + item.get_node("PlayerName").text = str(list_index) + str(". ") + player_name + item.get_node("Score").text = score + item.margin_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.margin_top = 135 + + +func add_loading_scores_message(): + var item = $"Board/MessageContainer/TextMessage" + item.text = "Loading scores..." + $"Board/MessageContainer".show() + item.margin_top = 135 + + +func hide_message(): + $"Board/MessageContainer".hide() + + +func clear_leaderboard(): + 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(): + 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(scene_name) diff --git a/addons/silent_wolf/Scores/Leaderboard.tscn b/addons/silent_wolf/Scores/Leaderboard.tscn new file mode 100644 index 0000000..1065998 --- /dev/null +++ b/addons/silent_wolf/Scores/Leaderboard.tscn @@ -0,0 +1,88 @@ +[gd_scene load_steps=8 format=2] + +[ext_resource path="res://addons/silent_wolf/Scores/Leaderboard.gd" type="Script" id=1] +[ext_resource path="res://addons/silent_wolf/Scores/assets/fonts/Comfortaa-Bold.ttf" type="DynamicFontData" id=2] +[ext_resource path="res://addons/silent_wolf/assets/fonts/Comfortaa-Bold.ttf" type="DynamicFontData" id=3] +[ext_resource path="res://addons/silent_wolf/common/SWButton.tscn" type="PackedScene" id=4] + +[sub_resource type="DynamicFont" id=1] +size = 76 +font_data = ExtResource( 2 ) + +[sub_resource type="DynamicFont" id=2] +size = 32 +font_data = ExtResource( 3 ) + +[sub_resource type="Theme" id=3] + +[node name="Leaderboard" type="Node2D"] +script = ExtResource( 1 ) + +[node name="OldBoard" type="MarginContainer" parent="."] +visible = false +margin_left = 50.0 +margin_right = 50.0 +margin_bottom = 40.0 + +[node name="HighScores" type="TextureRect" parent="OldBoard"] +margin_bottom = 40.0 + +[node name="Board" type="VBoxContainer" parent="."] +margin_left = 20.0 +margin_top = 20.0 +margin_right = 1884.0 +margin_bottom = 1071.0 +custom_constants/separation = 0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="TitleContainer" type="CenterContainer" parent="Board"] +margin_right = 1864.0 +margin_bottom = 85.0 + +[node name="Label" type="Label" parent="Board/TitleContainer"] +margin_left = 667.0 +margin_right = 1196.0 +margin_bottom = 85.0 +custom_fonts/font = SubResource( 1 ) +text = "Leaderboard" + +[node name="MessageContainer" type="CenterContainer" parent="Board"] +visible = false +margin_top = 89.0 +margin_right = 1864.0 +margin_bottom = 126.0 + +[node name="TextMessage" type="Label" parent="Board/MessageContainer"] +margin_left = 789.0 +margin_right = 1075.0 +margin_bottom = 37.0 +custom_fonts/font = SubResource( 2 ) +text = "Loading scores..." +valign = 1 + +[node name="HighScores" type="CenterContainer" parent="Board"] +margin_top = 85.0 +margin_right = 1864.0 +margin_bottom = 185.0 +rect_min_size = Vector2( 0, 100 ) +theme = SubResource( 3 ) + +[node name="ScoreItemContainer" type="VBoxContainer" parent="Board/HighScores"] +margin_left = 932.0 +margin_top = 50.0 +margin_right = 932.0 +margin_bottom = 50.0 + +[node name="CloseButtonContainer" type="CenterContainer" parent="Board"] +margin_top = 185.0 +margin_right = 1864.0 +margin_bottom = 303.0 + +[node name="CloseButton" parent="Board/CloseButtonContainer" instance=ExtResource( 4 )] +margin_left = 582.0 +margin_right = 1281.0 +margin_bottom = 118.0 +text = "Close Leaderboard" +[connection signal="pressed" from="Board/CloseButtonContainer/CloseButton" to="." method="_on_CloseButton_pressed"] diff --git a/addons/silent_wolf/Scores/ScoreItem.tscn b/addons/silent_wolf/Scores/ScoreItem.tscn new file mode 100644 index 0000000..67f91fd --- /dev/null +++ b/addons/silent_wolf/Scores/ScoreItem.tscn @@ -0,0 +1,48 @@ +[gd_scene load_steps=5 format=2] + +[ext_resource path="res://addons/silent_wolf/assets/fonts/Comfortaa-Bold.ttf" type="DynamicFontData" id=2] + +[sub_resource type="StyleBoxFlat" id=1] +bg_color = Color( 0.831373, 0.415686, 0.415686, 1 ) + +[sub_resource type="DynamicFont" id=2] +size = 32 +font_data = ExtResource( 2 ) + +[sub_resource type="DynamicFont" id=3] +size = 40 +use_mipmaps = true +use_filter = true +font_data = ExtResource( 2 ) + +[node name="ScoreItem" type="Panel"] +margin_right = 782.0 +margin_bottom = 76.0 +grow_horizontal = 0 +grow_vertical = 0 +rect_min_size = Vector2( 782, 76 ) +custom_styles/panel = SubResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="PlayerName" type="RichTextLabel" parent="."] +margin_left = 22.0 +margin_top = 18.0 +margin_right = 589.0 +margin_bottom = 73.0 +custom_fonts/normal_font = SubResource( 2 ) +text = "1. Godzilla" + +[node name="Score" type="Label" parent="."] +margin_left = 671.0 +margin_top = 17.0 +margin_right = 756.0 +margin_bottom = 65.0 +custom_fonts/font = SubResource( 3 ) +text = "13" +align = 2 +valign = 1 +__meta__ = { +"_edit_use_anchors_": false +} diff --git a/addons/silent_wolf/Scores/Scores.gd b/addons/silent_wolf/Scores/Scores.gd new file mode 100644 index 0000000..b47262d --- /dev/null +++ b/addons/silent_wolf/Scores/Scores.gd @@ -0,0 +1,528 @@ +extends Node + +const CommonErrors = preload("res://addons/silent_wolf/common/CommonErrors.gd") +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") + +# legacy signals +signal scores_received +signal position_received +signal score_posted + +# new signals +signal sw_scores_received +signal sw_player_scores_received +signal sw_top_player_score_received +signal sw_position_received +signal sw_scores_around_received +signal sw_score_posted +signal sw_leaderboard_wiped +signal sw_score_deleted + +# 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 ScorePosition = null +var ScoresAround = null +var HighScores = null +var ScoresByPlayer = null +var TopScoreByPlayer = null +var PostScore = null +var WipeLeaderboard = null +var DeleteScore = null + +# wekrefs +var wrScorePosition = null +var wrScoresAround = null +var wrHighScores = null +var wrScoresByPlayer = null +var wrTopScoreByPlayer = null +var wrPostScore = null +var wrWipeLeaderboard = null +var wrDeleteScore = null + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass + #connect("request_completed", self, "_on_Scores_request_completed") + #setup_request_timer() + +func get_score_position(score, ldboard_name="main"): + 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 + ScorePosition = HTTPRequest.new() + wrScorePosition = weakref(ScorePosition) + if OS.get_name() != "HTML5": + ScorePosition.set_use_threads(true) + get_tree().get_root().call_deferred("add_child", ScorePosition) + ScorePosition.connect("request_completed", self, "_on_GetScorePosition_request_completed") + SWLogger.info("Calling SilentWolf to get score position") + var game_id = SilentWolf.config.game_id + var game_version = SilentWolf.config.game_version + var payload = { "game_id": game_id, "game_version": game_version, "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" + send_post_request(ScorePosition, request_url, payload) + return self + + +func get_scores_around(score, scores_to_fetch=3, ldboard_name="main"): + 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 + ScoresAround = HTTPRequest.new() + wrScoresAround = weakref(ScoresAround) + if OS.get_name() != "HTML5": + ScoresAround.set_use_threads(true) + get_tree().get_root().call_deferred("add_child",ScoresAround) + ScoresAround.connect("request_completed", self, "_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 game_id = SilentWolf.config.game_id + var game_version = SilentWolf.config.game_version + var request_url = "https://api.silentwolf.com/get_scores_around/" + str(game_id) + "?version=" + str(game_version) + "&scores_to_fetch=" + str(scores_to_fetch) + "&ldboard_name=" + str(ldboard_name) + "&score_id=" + str(score_id) + "&score_value=" + str(score_value) + send_get_request(ScoresAround, request_url) + return self + +func get_high_scores(maximum=10, ldboard_name="main", period_offset=0): + HighScores = HTTPRequest.new() + wrHighScores = weakref(HighScores) + if OS.get_name() != "HTML5": + HighScores.set_use_threads(true) + get_tree().get_root().call_deferred("add_child", HighScores) + HighScores.connect("request_completed", self, "_on_GetHighScores_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 game_id = SilentWolf.config.game_id + var game_version = SilentWolf.config.game_version + var request_url = "https://api.silentwolf.com/get_top_scores/" + str(game_id) + "?version=" + str(game_version) + "&max=" + str(maximum) + "&ldboard_name=" + str(ldboard_name) + "&period_offset=" + str(period_offset) + send_get_request(HighScores, request_url) + return self + + +func get_scores_by_player(player_name, maximum=10, ldboard_name="main", period_offset=0): + print("get_scores_by_player, player_name = " + str(player_name)) + if player_name == null: + SWLogger.error("Error in SilentWolf.Scores.get_scores_by_player: provided player_name is null") + else: + ScoresByPlayer = HTTPRequest.new() + wrScoresByPlayer = weakref(ScoresByPlayer) + if OS.get_name() != "HTML5": + ScoresByPlayer.set_use_threads(true) + get_tree().get_root().call_deferred("add_child", ScoresByPlayer) + ScoresByPlayer.connect("request_completed", self, "_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 game_id = SilentWolf.config.game_id + var game_version = SilentWolf.config.game_version + print("ldboard_name: " + str(ldboard_name)) + var request_url = "https://api.silentwolf.com/get_scores_by_player/" + str(game_id) + "?version=" + str(game_version) + "&max=" + str(maximum) + "&ldboard_name=" + str(ldboard_name.percent_encode()) + "&player_name=" + str(player_name.percent_encode()) + "&period_offset=" + str(period_offset) + send_get_request(ScoresByPlayer, request_url) + return self + + +func get_top_score_by_player(player_name, maximum=10, ldboard_name="main", period_offset=0): + SWLogger.info("get_top_score_by_player, player_name = " + str(player_name)) + if player_name == null: + SWLogger.error("Error in SilentWolf.Scores.get_top_score_by_player: provided player_name is null") + else: + TopScoreByPlayer = HTTPRequest.new() + wrTopScoreByPlayer = weakref(TopScoreByPlayer) + if OS.get_name() != "HTML5": + TopScoreByPlayer.set_use_threads(true) + get_tree().get_root().call_deferred("add_child", TopScoreByPlayer) + TopScoreByPlayer.connect("request_completed", self, "_on_GetTopScoreByPlayer_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 game_id = SilentWolf.config.game_id + var game_version = SilentWolf.config.game_version + var request_url = "https://api.silentwolf.com/get_top_score_by_player/" + str(game_id) + "?version=" + str(game_version) + "&max=" + str(maximum) + "&ldboard_name=" + str(ldboard_name.percent_encode()) + "&player_name=" + str(player_name.percent_encode()) + "&period_offset=" + str(period_offset) + send_get_request(TopScoreByPlayer, request_url) + return self + + +func add_to_local_scores(game_result, ld_name="main"): + var local_score = { "score_id": game_result.score_id, "game_id_version" : game_result.game_id + ";" + game_result.game_version, "player_name": game_result.player_name, "score": game_result.score } + local_scores.append(local_score) + #if ld_name == "main": + # TODO: even here, since the main leader board can be customized, we can't just blindly write to the local_scores variable and pull up the scores later + # we need to know what type of leader board it is, or local caching is useless + #local_scores.append(local_score) + #else: + #if ld_name in custom_local_scores: + # TODO: problem: can't just append here - what if it's a highest/latest/accumulator/time-based leaderboard? + # maybe don't use local scores for these special cases? performance? + #custom_local_scores[ld_name].append(local_score) + #else: + #custom_local_scores[ld_name] = [local_score] + SWLogger.debug("local scores: " + str(local_scores)) + + +# metadata, if included should be a dictionary +func persist_score(player_name, score, ldboard_name="main", metadata={}): + # 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 ilentWolf.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: + PostScore = HTTPRequest.new() + wrPostScore = weakref(PostScore) + if OS.get_name() != "HTML5": + PostScore.set_use_threads(true) + get_tree().get_root().call_deferred("add_child", PostScore) + PostScore.connect("request_completed", self, "_on_PostNewScore_request_completed") + SWLogger.info("Calling SilentWolf backend to post new score...") + var game_id = SilentWolf.config.game_id + var game_version = SilentWolf.config.game_version + + 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, + "game_version": game_version, + "score": score, + "ldboard_name": ldboard_name + } + print("!metadata.empty(): " + str(!metadata.empty())) + if !metadata.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/post_new_score" + send_post_request(PostScore, request_url, payload) + return self + +# Deletes all your scores for your game and version +# Scores are permanently deleted, no going back! +func wipe_leaderboard(ldboard_name='main'): + WipeLeaderboard = HTTPRequest.new() + wrWipeLeaderboard = weakref(WipeLeaderboard) + if OS.get_name() != "HTML5": + WipeLeaderboard.set_use_threads(true) + get_tree().get_root().call_deferred("add_child", WipeLeaderboard) + WipeLeaderboard.connect("request_completed", self, "_on_WipeLeaderboard_request_completed") + SWLogger.info("Calling SilentWolf backend to wipe leaderboard...") + var game_id = SilentWolf.config.game_id + var game_version = SilentWolf.config.game_version + var payload = { "game_id": game_id, "game_version": game_version, "ldboard_name": ldboard_name } + var request_url = "https://api.silentwolf.com/wipe_leaderboard" + send_post_request(WipeLeaderboard, request_url, payload) + return self + + +func delete_score(score_id): + DeleteScore = HTTPRequest.new() + wrDeleteScore = weakref(DeleteScore) + if OS.get_name() != "HTML5": + DeleteScore.set_use_threads(true) + get_tree().get_root().call_deferred("add_child", DeleteScore) + DeleteScore.connect("request_completed", self, "_on_DeleteScore_request_completed") + SWLogger.info("Calling SilentWolf to delete a score") + var game_id = SilentWolf.config.game_id + var game_version = SilentWolf.config.game_version + var request_url = "https://api.silentwolf.com/delete_score?game_id=" + str(game_id) + "&game_version=" + str(game_version) + "&score_id=" + str(score_id) + send_get_request(DeleteScore, request_url) + return self + + +func _on_GetScoresByPlayer_request_completed(result, response_code, headers, body): + SWLogger.info("GetScoresByPlayer request completed") + var status_check = CommonErrors.check_status_code(response_code) + #print("client status: " + str(HighScores.get_http_client_status())) + #HighScores.queue_free() + SilentWolf.free_request(wrScoresByPlayer, ScoresByPlayer) + SWLogger.debug("response code: " + str(response_code)) + SWLogger.debug("response headers: " + str(headers)) + SWLogger.debug("response body: " + str(body.get_string_from_utf8())) + + if status_check: + var json = JSON.parse(body.get_string_from_utf8()) + print("json: " + str(json)) + var response = json.result + if response == null: + SWLogger.error("No data returned in GetScoresByPlayer response. Leaderboard may be empty") + emit_signal("sw_player_scores_received", 'No Leaderboard found', scores) + elif "message" in response.keys() and response.message == "Forbidden": + SWLogger.error("You are not authorized to call the SilentWolf API - check your API key configuration: https://silentwolf.com/leaderboard") + else: + SWLogger.info("SilentWolf get scores by player success") + if "top_scores" in response: + player_scores = response.top_scores + SWLogger.debug("scores: " + str(scores)) + var ld_name = response.ld_name + #print("ld_name: " + str(ld_name)) + var ld_config = response.ld_config + var player_name = response.player_name + #print("latest_scores: " + str(leaderboards)) + emit_signal("sw_player_scores_received", player_scores) + + +func _on_GetTopScoreByPlayer_request_completed(result, response_code, headers, body): + SWLogger.info("GetTopScoreByPlayer request completed") + var status_check = CommonErrors.check_status_code(response_code) + SilentWolf.free_request(wrTopScoreByPlayer, TopScoreByPlayer) + SWLogger.debug("response code: " + str(response_code)) + SWLogger.debug("response headers: " + str(headers)) + SWLogger.debug("response body: " + str(body.get_string_from_utf8())) + + if status_check: + var json = JSON.parse(body.get_string_from_utf8()) + print("json: " + str(json)) + var response = json.result + if response == null: + SWLogger.error("No data returned in GetTopScoreByPlayer response. There was a problem getting the response from the backend.") + emit_signal("sw_player_scores_received", 'No Leaderboard found', scores) + elif "message" in response.keys() and response.message == "Forbidden": + SWLogger.error("You are not authorized to call the SilentWolf API - check your API key configuration: https://silentwolf.com/leaderboard") + else: + SWLogger.info("SilentWolf get top score by player success") + if "top_score" in response: + print("top score from response: " + str(response.top_score)) + if response.top_score == {}: + player_top_score = {} + else: + player_top_score = response.top_score + SWLogger.debug("top score: " + str(player_top_score)) + var ld_name = response.ld_name + #print("ld_name: " + str(ld_name)) + var ld_config = response.ld_config + var player_name = response.player_name + #print("latest_scores: " + str(leaderboards)) + emit_signal("sw_top_player_score_received", player_top_score) + + + +func _on_GetHighScores_request_completed(result, response_code, headers, body): + SWLogger.info("GetHighScores request completed") + var status_check = CommonErrors.check_status_code(response_code) + #print("client status: " + str(HighScores.get_http_client_status())) + #HighScores.queue_free() + SilentWolf.free_request(wrHighScores, HighScores) + SWLogger.debug("response code: " + str(response_code)) + SWLogger.debug("response headers: " + str(headers)) + SWLogger.debug("response body: " + str(body.get_string_from_utf8())) + + if status_check: + var json = JSON.parse(body.get_string_from_utf8()) + var response = json.result + if response == null: + SWLogger.error("No data returned in GetHighScores response. Leaderboard may be empty") + emit_signal("sw_scores_received", 'No Leaderboard found', scores) + emit_signal("scores_received", scores) + elif "message" in response.keys() and response.message == "Forbidden": + SWLogger.error("You are not authorized to call the SilentWolf API - check your API key configuration: https://silentwolf.com/leaderboard") + else: + SWLogger.info("SilentWolf get high score success") + if "top_scores" in response: + scores = response.top_scores + SWLogger.debug("scores: " + str(scores)) + var ld_name = response.ld_name + #print("ld_name: " + str(ld_name)) + var ld_config = response.ld_config + #print("ld_config: " + str(ld_config)) + if "period_offset" in response: + var period_offset = str(response["period_offset"]) + leaderboards_past_periods[ld_name + ";" + period_offset] = scores + else: + leaderboards[ld_name] = scores + ldboard_config[ld_name] = ld_config + #print("latest_scores: " + str(leaderboards)) + emit_signal("sw_scores_received", ld_name, scores) + emit_signal("scores_received", scores) + #var retries = 0 + #request_timer.stop() + +func _on_DeleteScore_request_completed(result, response_code, headers, body): + SWLogger.info("DeleteScore request completed") + var status_check = CommonErrors.check_status_code(response_code) + SilentWolf.free_request(wrDeleteScore, DeleteScore) + SWLogger.debug("response headers: " + str(response_code)) + SWLogger.debug("response headers: " + str(headers)) + SWLogger.debug("response body: " + str(body.get_string_from_utf8())) + + if status_check: + var json = JSON.parse(body.get_string_from_utf8()) + var response = json.result + if "message" in response.keys() and response.message == "Forbidden": + SWLogger.error("You are not authorized to call the SilentWolf API - check your API key configuration: https://silentwolf.com/leaderboard") + else: + SWLogger.info("SilentWolf delete score success") + emit_signal("sw_score_deleted") + + +func _on_PostNewScore_request_completed(result, response_code, headers, body): + SWLogger.info("PostNewScore request completed") + var status_check = CommonErrors.check_status_code(response_code) + #PostScore.queue_free() + SilentWolf.free_request(wrPostScore, PostScore) + SWLogger.debug("response headers: " + str(response_code)) + SWLogger.debug("response headers: " + str(headers)) + SWLogger.debug("response body: " + str(body.get_string_from_utf8())) + + if status_check: + var json = JSON.parse(body.get_string_from_utf8()) + var response = json.result + if "message" in response.keys() and response.message == "Forbidden": + SWLogger.error("You are not authorized to call the SilentWolf API - check your API key configuration: https://silentwolf.com/leaderboard") + else: + SWLogger.info("SilentWolf post score success: " + str(response_code)) + if "score_id" in response: + emit_signal("sw_score_posted", response["score_id"]) + else: + emit_signal("sw_score_posted") + emit_signal("score_posted") + + +func _on_GetScorePosition_request_completed(result, response_code, headers, body): + SWLogger.info("GetScorePosition request completed") + var status_check = CommonErrors.check_status_code(response_code) + #ScorePosition.queue_free() + SilentWolf.free_request(wrScorePosition, ScorePosition) + SWLogger.debug("response headers: " + str(response_code)) + SWLogger.debug("response headers: " + str(headers)) + SWLogger.debug("response body: " + str(body.get_string_from_utf8())) + + if status_check: + var json = JSON.parse(body.get_string_from_utf8()) + var response = json.result + if "message" in response.keys() and response.message == "Forbidden": + SWLogger.error("You are not authorized to call the SilentWolf API - check your API key configuration: https://silentwolf.com/leaderboard") + else: + SWLogger.info("SilentWolf find score position success.") + position = int(response.position) + emit_signal("sw_position_received", position) + emit_signal("position_received", position) + + +func _on_ScoresAround_request_completed(result, response_code, headers, body): + SWLogger.info("ScoresAround request completed") + var status_check = CommonErrors.check_status_code(response_code) + + SilentWolf.free_request(wrScoresAround, ScoresAround) + SWLogger.debug("response headers: " + str(response_code)) + SWLogger.debug("response headers: " + str(headers)) + SWLogger.debug("response body: " + str(body.get_string_from_utf8())) + + if status_check: + var json = JSON.parse(body.get_string_from_utf8()) + var response = json.result + if "message" in response.keys() and response.message == "Forbidden": + SWLogger.error("You are not authorized to call the SilentWolf API - check your API key configuration: https://silentwolf.com/leaderboard") + else: + SWLogger.info("SilentWolf get scores around success") + if "scores_above" in response: + scores_above = response.scores_above + scores_below = response.scores_below + var ld_name = response.ld_name + #print("ld_name: " + str(ld_name)) + var ld_config = response.ld_config + #print("ld_config: " + str(ld_config)) + ldboard_config[ld_name] = ld_config + if "score_position" in response: + position = response.score_position + + emit_signal("sw_scores_around_received", scores_above, scores_below, position) + + +func _on_WipeLeaderboard_request_completed(result, response_code, headers, body): + SWLogger.info("WipeLeaderboard request completed") + var status_check = CommonErrors.check_status_code(response_code) + #WipeLeaderboard.queue_free() + SilentWolf.free_request(wrWipeLeaderboard, WipeLeaderboard) + SWLogger.debug("response headers: " + str(response_code)) + SWLogger.debug("response headers: " + str(headers)) + SWLogger.debug("response body: " + str(body.get_string_from_utf8())) + + if status_check: + var json = JSON.parse(body.get_string_from_utf8()) + var response = json.result + if "message" in response.keys() and response.message == "Forbidden": + SWLogger.error("You are not authorized to call the SilentWolf API - check your API key configuration: https://silentwolf.com/leaderboard") + else: + SWLogger.info("SilentWolf wipe leaderboard success.") + emit_signal("sw_leaderboard_wiped") + + +func send_get_request(http_node, request_url): + var headers = ["x-api-key: " + SilentWolf.config.api_key, "x-sw-plugin-version: " + SilentWolf.version] + if !http_node.is_inside_tree(): + yield(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-plugin-version: " + SilentWolf.version + ] + if "post_new_score" in request_url: + SWLogger.info("We're doing a post score") + var player_name = payload["player_name"] + var player_score = payload["score"] + var timestamp = OS.get_system_time_msecs() + var to_be_hashed = [player_name, player_score, timestamp] + SWLogger.debug("send_post_request to_be_hashed: " + str(to_be_hashed)) + var hashed = SWHashing.hash_values(to_be_hashed) + SWLogger.debug("send_post_request hashed: " + str(hashed)) + headers.append("x-sw-act-tmst: " + str(timestamp)) + headers.append("x-sw-act-dig: " + hashed) + var use_ssl = true + if !http_node.is_inside_tree(): + yield(get_tree().create_timer(0.01), "timeout") + var query = JSON.print(payload) + SWLogger.info("Method: POST") + SWLogger.info("request_url: " + str(request_url)) + SWLogger.info("headers: " + str(headers)) + SWLogger.info("query: " + str(query)) + http_node.request(request_url, headers, use_ssl, HTTPClient.METHOD_POST, query) diff --git a/addons/silent_wolf/Scores/assets/.DS_Store b/addons/silent_wolf/Scores/assets/.DS_Store new file mode 100644 index 0000000..3232312 Binary files /dev/null and b/addons/silent_wolf/Scores/assets/.DS_Store differ diff --git a/addons/silent_wolf/Scores/assets/fonts/Comfortaa-Bold.ttf b/addons/silent_wolf/Scores/assets/fonts/Comfortaa-Bold.ttf new file mode 100644 index 0000000..9c42b2f Binary files /dev/null and b/addons/silent_wolf/Scores/assets/fonts/Comfortaa-Bold.ttf differ diff --git a/addons/silent_wolf/SilentWolf.gd b/addons/silent_wolf/SilentWolf.gd new file mode 100644 index 0000000..7acb1ce --- /dev/null +++ b/addons/silent_wolf/SilentWolf.gd @@ -0,0 +1,178 @@ +extends Node + +const version = "0.6.20" +var godot_version = Engine.get_version_info().string + +const SWHashing = preload("res://addons/silent_wolf/utils/SWHashing.gd") + +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", + "game_version": "0.0.0", + "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 SWLogger = load("res://addons/silent_wolf/utils/SWLogger.gd") + +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(OS.get_time())) + +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(OS.get_time())) + 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(OS.get_time())) + +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 send_get_request(http_node, request_url): + 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 + ] + if !http_node.is_inside_tree(): + yield(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 + ] + # TODO: this os specific to post_new_score - should be made generic + # or make a section for each type of post request with inetgrity check + # (e.g. also push player data) + if "post_new_score" in request_url: + SWLogger.info("We're doing a post score") + var player_name = payload["player_name"] + var player_score = payload["score"] + var timestamp = OS.get_system_time_msecs() + var to_be_hashed = [player_name, player_score, timestamp] + SWLogger.debug("send_post_request to_be_hashed: " + str(to_be_hashed)) + var hashed = SWHashing.hash_values(to_be_hashed) + SWLogger.debug("send_post_request hashed: " + str(hashed)) + headers.append("x-sw-act-tmst: " + str(timestamp)) + headers.append("x-sw-act-dig: " + hashed) + var use_ssl = true + if !http_node.is_inside_tree(): + yield(get_tree().create_timer(0.01), "timeout") + var query = JSON.print(payload) + SWLogger.info("Method: POST") + SWLogger.info("request_url: " + str(request_url)) + SWLogger.info("headers: " + str(headers)) + SWLogger.info("query: " + str(query)) + http_node.request(request_url, headers, use_ssl, HTTPClient.METHOD_POST, query) + + +func check_auth_ready(): + if !Auth: + yield(get_tree().create_timer(0.01), "timeout") + + +func check_scores_ready(): + if !Scores: + yield(get_tree().create_timer(0.01), "timeout") + + +func check_players_ready(): + if !Players: + yield(get_tree().create_timer(0.01), "timeout") + + +func check_multiplayer_ready(): + if !Multiplayer: + yield(get_tree().create_timer(0.01), "timeout") + + +func check_sw_ready(): + if !Auth or !Scores or !Players or !Multiplayer: + yield(get_tree().create_timer(0.01), "timeout") diff --git a/addons/silent_wolf/assets/.DS_Store b/addons/silent_wolf/assets/.DS_Store new file mode 100644 index 0000000..1bc0903 Binary files /dev/null and b/addons/silent_wolf/assets/.DS_Store differ diff --git a/addons/silent_wolf/assets/fonts/Comfortaa-Bold.ttf b/addons/silent_wolf/assets/fonts/Comfortaa-Bold.ttf new file mode 100644 index 0000000..9c42b2f Binary files /dev/null and b/addons/silent_wolf/assets/fonts/Comfortaa-Bold.ttf differ diff --git a/addons/silent_wolf/assets/gfx/.DS_Store b/addons/silent_wolf/assets/gfx/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/addons/silent_wolf/assets/gfx/.DS_Store differ diff --git a/addons/silent_wolf/assets/gfx/checkbox_checked.png b/addons/silent_wolf/assets/gfx/checkbox_checked.png new file mode 100644 index 0000000..8528d2c Binary files /dev/null and b/addons/silent_wolf/assets/gfx/checkbox_checked.png differ diff --git a/addons/silent_wolf/assets/gfx/checkbox_unchecked.png b/addons/silent_wolf/assets/gfx/checkbox_unchecked.png new file mode 100644 index 0000000..c7dd873 Binary files /dev/null and b/addons/silent_wolf/assets/gfx/checkbox_unchecked.png differ diff --git a/addons/silent_wolf/assets/gfx/dummy_info_icon_small.png b/addons/silent_wolf/assets/gfx/dummy_info_icon_small.png new file mode 100644 index 0000000..368c4b7 Binary files /dev/null and b/addons/silent_wolf/assets/gfx/dummy_info_icon_small.png differ diff --git a/addons/silent_wolf/assets/gfx/info_icon.png b/addons/silent_wolf/assets/gfx/info_icon.png new file mode 100644 index 0000000..151a923 Binary files /dev/null and b/addons/silent_wolf/assets/gfx/info_icon.png differ diff --git a/addons/silent_wolf/assets/gfx/info_icon_small.png b/addons/silent_wolf/assets/gfx/info_icon_small.png new file mode 100644 index 0000000..6bf1377 Binary files /dev/null and b/addons/silent_wolf/assets/gfx/info_icon_small.png differ diff --git a/addons/silent_wolf/common/CommonErrors.gd b/addons/silent_wolf/common/CommonErrors.gd new file mode 100644 index 0000000..0dd9812 --- /dev/null +++ b/addons/silent_wolf/common/CommonErrors.gd @@ -0,0 +1,17 @@ +extends Node + +const SWLogger = preload("res://addons/silent_wolf/utils/SWLogger.gd") + +func _ready(): + pass + +static func check_status_code(status_code): + SWLogger.debug("status_code: " + str(status_code)) + var check_ok = true + if status_code == 0: + no_connection_error() + check_ok = false + 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 at support@silentwolf.com") diff --git a/addons/silent_wolf/common/SWButton.tscn b/addons/silent_wolf/common/SWButton.tscn new file mode 100644 index 0000000..364de09 --- /dev/null +++ b/addons/silent_wolf/common/SWButton.tscn @@ -0,0 +1,49 @@ +[gd_scene load_steps=6 format=2] + +[ext_resource path="res://addons/silent_wolf/assets/fonts/Comfortaa-Bold.ttf" type="DynamicFontData" id=1] + +[sub_resource type="StyleBoxFlat" id=1] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.831373, 0.415686, 0.415686, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="StyleBoxFlat" id=2] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.831373, 0.415686, 0.415686, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="StyleBoxFlat" id=3] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.666667, 0.223529, 0.223529, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="DynamicFont" id=4] +size = 64 +font_data = ExtResource( 1 ) + +[node name="SWButton" type="Button"] +margin_right = 12.0 +margin_bottom = 20.0 +custom_styles/hover = SubResource( 1 ) +custom_styles/pressed = SubResource( 2 ) +custom_styles/normal = SubResource( 3 ) +custom_fonts/font = SubResource( 4 ) +text = "Sample text" diff --git a/addons/silent_wolf/examples/.DS_Store b/addons/silent_wolf/examples/.DS_Store new file mode 100644 index 0000000..73a9cbb Binary files /dev/null and b/addons/silent_wolf/examples/.DS_Store differ diff --git a/addons/silent_wolf/examples/CustomLeaderboards/ReverseLeaderboard.gd b/addons/silent_wolf/examples/CustomLeaderboards/ReverseLeaderboard.gd new file mode 100644 index 0000000..2da5bf8 --- /dev/null +++ b/addons/silent_wolf/examples/CustomLeaderboards/ReverseLeaderboard.gd @@ -0,0 +1,87 @@ +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() + yield(SilentWolf.Scores.get_high_scores(0), "sw_scores_received") + hide_message() + render_board(SilentWolf.Scores.scores) + +func render_board(scores): + if !scores: + add_no_scores_message() + else: + if len(scores) > 1 and scores[0].score > scores[-1].score: + scores.invert() + 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): + var reverted_scores = scores + if len(scores) > 1 and scores[0].score > scores[-1].score: + reverted_scores = scores.invert() + return reverted_scores + + +func sort_by_score(a, b): + if a.score > b.score: + return true; + else: + if a.score < b.score: + return false; + else: + return true; + +func add_item(player_name, score): + var item = ScoreItem.instance() + list_index += 1 + item.get_node("PlayerName").text = str(list_index) + str(". ") + player_name + item.get_node("Score").text = score + item.margin_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.margin_top = 135 + +func add_loading_scores_message(): + var item = $"Board/MessageContainer/TextMessage" + item.text = "Loading scores..." + $"Board/MessageContainer".show() + item.margin_top = 135 + +func hide_message(): + $"Board/MessageContainer".hide() + +func _on_CloseButton_pressed(): + var scene_name = SilentWolf.scores_config.open_scene_on_close + print("scene_name: " + str(scene_name)) + get_tree().change_scene(scene_name) diff --git a/addons/silent_wolf/examples/CustomLeaderboards/ReverseLeaderboard.tscn b/addons/silent_wolf/examples/CustomLeaderboards/ReverseLeaderboard.tscn new file mode 100644 index 0000000..cfb1170 --- /dev/null +++ b/addons/silent_wolf/examples/CustomLeaderboards/ReverseLeaderboard.tscn @@ -0,0 +1,119 @@ +[gd_scene load_steps=12 format=2] + +[ext_resource path="res://addons/silent_wolf/assets/fonts/Comfortaa-Bold.ttf" type="DynamicFontData" id=2] +[ext_resource path="res://addons/silent_wolf/Scores/assets/fonts/Comfortaa-Bold.ttf" type="DynamicFontData" id=3] +[ext_resource path="res://addons/silent_wolf/common/SWButton.tscn" type="PackedScene" id=4] +[ext_resource path="res://addons/silent_wolf/examples/CustomLeaderboards/ReverseLeaderboard.gd" type="Script" id=5] + +[sub_resource type="DynamicFont" id=1] +size = 76 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=7] +size = 32 +font_data = ExtResource( 2 ) + +[sub_resource type="Theme" id=2] + +[sub_resource type="StyleBoxFlat" id=3] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.831373, 0.415686, 0.415686, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="StyleBoxFlat" id=4] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.831373, 0.415686, 0.415686, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="StyleBoxFlat" id=5] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.666667, 0.223529, 0.223529, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="DynamicFont" id=6] +size = 64 +font_data = ExtResource( 2 ) + +[node name="ReverseLeaderboard" type="Node2D"] +script = ExtResource( 5 ) + +[node name="Board" type="VBoxContainer" parent="."] +margin_left = 20.0 +margin_top = 20.0 +margin_right = 1884.0 +margin_bottom = 1071.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="TitleContainer" type="CenterContainer" parent="Board"] +margin_right = 1864.0 +margin_bottom = 85.0 + +[node name="Label" type="Label" parent="Board/TitleContainer"] +margin_left = 502.0 +margin_right = 1362.0 +margin_bottom = 85.0 +custom_fonts/font = SubResource( 1 ) +text = "Reverse Leaderboard" + +[node name="MessageContainer" type="CenterContainer" parent="Board"] +visible = false +margin_top = 89.0 +margin_right = 1864.0 +margin_bottom = 126.0 + +[node name="TextMessage" type="Label" parent="Board/MessageContainer"] +margin_left = 789.0 +margin_right = 1075.0 +margin_bottom = 37.0 +custom_fonts/font = SubResource( 7 ) +text = "Loading scores..." +valign = 1 + +[node name="HighScores" type="CenterContainer" parent="Board"] +margin_top = 89.0 +margin_right = 1864.0 +margin_bottom = 189.0 +rect_min_size = Vector2( 0, 100 ) +theme = SubResource( 2 ) + +[node name="ScoreItemContainer" type="VBoxContainer" parent="Board/HighScores"] +margin_left = 932.0 +margin_top = 50.0 +margin_right = 932.0 +margin_bottom = 50.0 + +[node name="CloseButtonContainer" type="CenterContainer" parent="Board"] +margin_top = 193.0 +margin_right = 1864.0 +margin_bottom = 311.0 + +[node name="CloseButton" parent="Board/CloseButtonContainer" instance=ExtResource( 4 )] +margin_left = 582.0 +margin_right = 1281.0 +margin_bottom = 118.0 +custom_styles/hover = SubResource( 3 ) +custom_styles/pressed = SubResource( 4 ) +custom_styles/normal = SubResource( 5 ) +custom_fonts/font = SubResource( 6 ) +text = "Close Leaderboard" +[connection signal="pressed" from="Board/CloseButtonContainer/CloseButton" to="." method="_on_CloseButton_pressed"] diff --git a/addons/silent_wolf/examples/CustomLeaderboards/SmallScoreItem.tscn b/addons/silent_wolf/examples/CustomLeaderboards/SmallScoreItem.tscn new file mode 100644 index 0000000..8c1316f --- /dev/null +++ b/addons/silent_wolf/examples/CustomLeaderboards/SmallScoreItem.tscn @@ -0,0 +1,14 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://addons/silent_wolf/Scores/ScoreItem.tscn" type="PackedScene" id=1] + +[sub_resource type="StyleBoxFlat" id=1] +bg_color = Color( 0.831373, 0.415686, 0.415686, 1 ) + +[node name="ScoreItem" index="0" instance=ExtResource( 1 )] +rect_min_size = Vector2( 434, 64 ) +custom_styles/panel = SubResource( 1 ) + +[node name="Score" parent="." index="1"] +margin_left = 360.0 +margin_right = 427.0 diff --git a/addons/silent_wolf/examples/CustomLeaderboards/TimeBasedLboards.gd b/addons/silent_wolf/examples/CustomLeaderboards/TimeBasedLboards.gd new file mode 100644 index 0000000..fc7328c --- /dev/null +++ b/addons/silent_wolf/examples/CustomLeaderboards/TimeBasedLboards.gd @@ -0,0 +1,81 @@ +extends Node2D + +const ScoreItem = preload("SmallScoreItem.tscn") +const SWLogger = preload("res://addons/silent_wolf/utils/SWLogger.gd") + +var ld_names = ["Weekly", "Monthly", "main"] + +func _ready(): + SilentWolf.Scores.connect("sw_scores_received", self, "_on_scores_received") + #var scores = SilentWolf.Scores.scores + add_loading_scores_message() + SilentWolf.Scores.get_high_scores(10, "main") + # 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): + #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, player_name, score, list_index): + var item = ScoreItem.instance() + item.get_node("PlayerName").text = str(list_index) + str(". ") + player_name + item.get_node("Score").text = score + item.margin_top = list_index * 100 + get_node("MainContainer/Boards/" + ld_name + "/HighScores/ScoreItemContainer").add_child(item) + + +func add_no_scores_message(): + var item = $"MainContainer/MessageContainer/TextMessage" + item.text = "No scores yet!" + $"MainContainer/MessageContainer".show() + item.margin_top = 135 + + +func add_loading_scores_message(): + var item = $"MainContainer/MessageContainer/TextMessage" + item.text = "Loading scores..." + $"MainContainer/MessageContainer".show() + item.margin_top = 135 + + +func hide_message(): + $"MainContainer/MessageContainer".hide() + + +func _on_CloseButton_pressed(): + 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(scene_name) + + +func _on_scores_received(ld_name, scores): + if ld_name == "main": + SilentWolf.Scores.get_high_scores(10, "Weekly") + #SilentWolf.Scores.get_high_scores(10, "Weekly", -1) + elif ld_name == "Weekly": + SilentWolf.Scores.get_high_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) diff --git a/addons/silent_wolf/examples/CustomLeaderboards/TimeBasedLboards.tscn b/addons/silent_wolf/examples/CustomLeaderboards/TimeBasedLboards.tscn new file mode 100644 index 0000000..af4f978 --- /dev/null +++ b/addons/silent_wolf/examples/CustomLeaderboards/TimeBasedLboards.tscn @@ -0,0 +1,228 @@ +[gd_scene load_steps=13 format=2] + +[ext_resource path="res://addons/silent_wolf/assets/fonts/Comfortaa-Bold.ttf" type="DynamicFontData" id=2] +[ext_resource path="res://addons/silent_wolf/Scores/assets/fonts/Comfortaa-Bold.ttf" type="DynamicFontData" id=3] +[ext_resource path="res://addons/silent_wolf/common/SWButton.tscn" type="PackedScene" id=4] +[ext_resource path="res://addons/silent_wolf/examples/CustomLeaderboards/TimeBasedLboards.gd" type="Script" id=5] + +[sub_resource type="DynamicFont" id=1] +size = 120 +font_data = ExtResource( 3 ) + +[sub_resource type="DynamicFont" id=2] +size = 76 +font_data = ExtResource( 3 ) + +[sub_resource type="Theme" id=3] + +[sub_resource type="DynamicFont" id=8] +size = 32 +font_data = ExtResource( 2 ) + +[sub_resource type="StyleBoxFlat" id=4] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.831373, 0.415686, 0.415686, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="StyleBoxFlat" id=5] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.831373, 0.415686, 0.415686, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="StyleBoxFlat" id=6] +content_margin_left = 23.0 +content_margin_right = 23.0 +content_margin_top = 23.0 +content_margin_bottom = 23.0 +bg_color = Color( 0.666667, 0.223529, 0.223529, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 + +[sub_resource type="DynamicFont" id=7] +size = 64 +font_data = ExtResource( 2 ) + +[node name="TimeBasedLBoards" type="Node2D"] +script = ExtResource( 5 ) + +[node name="MainContainer" type="VBoxContainer" parent="."] +margin_right = 1918.0 +margin_bottom = 1078.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="TitleContainer" type="CenterContainer" parent="MainContainer"] +margin_right = 1918.0 +margin_bottom = 135.0 + +[node name="Label2" type="Label" parent="MainContainer/TitleContainer"] +margin_left = 540.0 +margin_right = 1378.0 +margin_bottom = 135.0 +custom_fonts/font = SubResource( 1 ) +text = "Leaderboard" + +[node name="Boards" type="HBoxContainer" parent="MainContainer"] +margin_top = 139.0 +margin_right = 1918.0 +margin_bottom = 328.0 +custom_constants/separation = 160 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Weekly" type="VBoxContainer" parent="MainContainer/Boards"] +margin_right = 532.0 +margin_bottom = 189.0 +size_flags_horizontal = 3 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="TitleContainer" type="CenterContainer" parent="MainContainer/Boards/Weekly"] +margin_right = 532.0 +margin_bottom = 85.0 + +[node name="Label" type="Label" parent="MainContainer/Boards/Weekly/TitleContainer"] +margin_left = 76.0 +margin_right = 456.0 +margin_bottom = 85.0 +size_flags_horizontal = 4 +custom_fonts/font = SubResource( 2 ) +text = "This week" + +[node name="HighScores" type="CenterContainer" parent="MainContainer/Boards/Weekly"] +margin_left = 266.0 +margin_top = 89.0 +margin_right = 266.0 +margin_bottom = 189.0 +rect_min_size = Vector2( 0, 100 ) +size_flags_horizontal = 4 +theme = SubResource( 3 ) + +[node name="ScoreItemContainer" type="VBoxContainer" parent="MainContainer/Boards/Weekly/HighScores"] +margin_top = 50.0 +margin_bottom = 50.0 + +[node name="Monthly" type="VBoxContainer" parent="MainContainer/Boards"] +margin_left = 692.0 +margin_right = 1224.0 +margin_bottom = 189.0 +size_flags_horizontal = 3 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="TitleContainer" type="CenterContainer" parent="MainContainer/Boards/Monthly"] +margin_right = 532.0 +margin_bottom = 85.0 + +[node name="Label" type="Label" parent="MainContainer/Boards/Monthly/TitleContainer"] +margin_left = 44.0 +margin_right = 488.0 +margin_bottom = 85.0 +custom_fonts/font = SubResource( 2 ) +text = "This month" + +[node name="HighScores" type="CenterContainer" parent="MainContainer/Boards/Monthly"] +margin_left = 266.0 +margin_top = 89.0 +margin_right = 266.0 +margin_bottom = 189.0 +rect_min_size = Vector2( 0, 100 ) +size_flags_horizontal = 4 +theme = SubResource( 3 ) + +[node name="ScoreItemContainer" type="VBoxContainer" parent="MainContainer/Boards/Monthly/HighScores"] +margin_top = 50.0 +margin_bottom = 50.0 + +[node name="main" type="VBoxContainer" parent="MainContainer/Boards"] +margin_left = 1384.0 +margin_right = 1918.0 +margin_bottom = 189.0 +size_flags_horizontal = 3 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="TitleContainer" type="CenterContainer" parent="MainContainer/Boards/main"] +margin_right = 534.0 +margin_bottom = 85.0 + +[node name="Label" type="Label" parent="MainContainer/Boards/main/TitleContainer"] +margin_left = 121.0 +margin_right = 413.0 +margin_bottom = 85.0 +custom_fonts/font = SubResource( 2 ) +text = "All time" +align = 1 + +[node name="HighScores" type="CenterContainer" parent="MainContainer/Boards/main"] +margin_left = 267.0 +margin_top = 89.0 +margin_right = 267.0 +margin_bottom = 189.0 +rect_min_size = Vector2( 0, 100 ) +size_flags_horizontal = 4 +theme = SubResource( 3 ) + +[node name="ScoreItemContainer" type="VBoxContainer" parent="MainContainer/Boards/main/HighScores"] +margin_top = 50.0 +margin_bottom = 50.0 + +[node name="MessageContainer" type="CenterContainer" parent="MainContainer"] +visible = false +margin_left = 1144.0 +margin_top = 228.0 +margin_right = 3008.0 +margin_bottom = 265.0 + +[node name="TextMessage" type="Label" parent="MainContainer/MessageContainer"] +margin_left = 789.0 +margin_right = 1075.0 +margin_bottom = 37.0 +custom_fonts/font = SubResource( 8 ) +text = "Loading scores..." +valign = 1 + +[node name="CenterContainer" type="CenterContainer" parent="MainContainer"] +margin_top = 332.0 +margin_right = 1918.0 +margin_bottom = 450.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="CloseButtonContainer" type="CenterContainer" parent="MainContainer/CenterContainer"] +margin_left = 609.0 +margin_right = 1308.0 +margin_bottom = 118.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="CloseButton" parent="MainContainer/CenterContainer/CloseButtonContainer" instance=ExtResource( 4 )] +margin_right = 699.0 +margin_bottom = 118.0 +custom_styles/hover = SubResource( 4 ) +custom_styles/pressed = SubResource( 5 ) +custom_styles/normal = SubResource( 6 ) +custom_fonts/font = SubResource( 7 ) +text = "Close Leaderboard" +[connection signal="pressed" from="MainContainer/CenterContainer/CloseButtonContainer/CloseButton" to="." method="_on_CloseButton_pressed"] diff --git a/addons/silent_wolf/plugin.cfg b/addons/silent_wolf/plugin.cfg new file mode 100644 index 0000000..ecef28c --- /dev/null +++ b/addons/silent_wolf/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Silent Wolf plugin" +description="Backend services for Godot Engine." +author="Brass Harpooner" +version="0.6.6" +script="silent_wolf.gd" diff --git a/addons/silent_wolf/silent_wolf.gd b/addons/silent_wolf/silent_wolf.gd new file mode 100644 index 0000000..ed9e19d --- /dev/null +++ b/addons/silent_wolf/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") diff --git a/addons/silent_wolf/utils/SWHashing.gd b/addons/silent_wolf/utils/SWHashing.gd new file mode 100644 index 0000000..d6d74b8 --- /dev/null +++ b/addons/silent_wolf/utils/SWHashing.gd @@ -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 diff --git a/addons/silent_wolf/utils/SWLocalFileStorage.gd b/addons/silent_wolf/utils/SWLocalFileStorage.gd new file mode 100644 index 0000000..b72137e --- /dev/null +++ b/addons/silent_wolf/utils/SWLocalFileStorage.gd @@ -0,0 +1,45 @@ +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: ') -> void: + var local_file = File.new() + local_file.open(path, File.WRITE) + SWLogger.debug(debug_message + str(data)) + local_file.store_line(to_json(data)) + local_file.close() + + +static func remove_data(path: String, debug_message: String='Removing data from file in local storage: ') -> void: + var local_file = File.new() + local_file.open(path, File.WRITE) + var data = {} + SWLogger.debug(debug_message + str(data)) + local_file.store_line(to_json(data)) + local_file.close() + + +static func does_file_exist(path: String, file: File=null) -> bool: + var local_file = file + if local_file == null: + local_file = File.new() + return local_file.file_exists(path) + + +static func get_data(path: String) -> Dictionary: + var local_file = File.new() + var return_data = null + if does_file_exist(path, local_file): + local_file.open(path, File.READ) + var data = parse_json(local_file.get_as_text()) + if typeof(data) == TYPE_DICTIONARY: + return_data = data + else: + SWLogger.debug("Invalid data in local storage") + else: + SWLogger.debug("Could not find any data at: " + str(path)) + return return_data diff --git a/addons/silent_wolf/utils/SWLogger.gd b/addons/silent_wolf/utils/SWLogger.gd new file mode 100644 index 0000000..708bdd2 --- /dev/null +++ b/addons/silent_wolf/utils/SWLogger.gd @@ -0,0 +1,21 @@ +extends Node + +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)) diff --git a/addons/silent_wolf/utils/UUID.gd b/addons/silent_wolf/utils/UUID.gd new file mode 100644 index 0000000..bde2f5d --- /dev/null +++ b/addons/silent_wolf/utils/UUID.gd @@ -0,0 +1,47 @@ +static func getRandomInt(max_value): + randomize() + return randi() % max_value + +static func randomBytes(n): + var r = [] + for index in range(0, n): + r.append(getRandomInt(256)) + return r + +static func uuidbin(): + var b = randomBytes(16) + + b[6] = (b[6] & 0x0f) | 0x40 + b[8] = (b[8] & 0x3f) | 0x80 + return b + +static func generate_uuid_v4(): + var b = uuidbin() + + 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] + + +# argument must be of type string! +static func is_uuid(test_string): + # if length of string is 36 and contains exactly 4 dashes, it's a UUID + return test_string.length() == 36 and test_string.count("-") == 4 + + +# 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.