feat: expand multiplayer, chat, and release support
- add network NPC synchronization and dialogue interactions - add world bulletin publishing and display - improve authentication, session refresh, and appearance sync - synchronize player direction and movement animations - improve input focus and progressive Web loading - add macOS/Windows builds and deployment configuration - include required fonts, shaders, and runtime assets
This commit is contained in:
@@ -25,12 +25,12 @@ const REGISTRATION_CHOICE_SPRITESHEET_GRID_BOX_PATH: String = REGISTRATION_CHOIC
|
||||
const REGISTRATION_CHOICE_REFERENCE_UPLOAD_BOX_PATH: String = REGISTRATION_CHOICE_ASSET_DIR + "/reference_upload_box.png"
|
||||
const REGISTRATION_CHOICE_IMAGE_PLACEHOLDER_BOX_PATH: String = REGISTRATION_CHOICE_ASSET_DIR + "/image_placeholder_box.png"
|
||||
const UI_FONT_PATH: String = "res://assets/fonts/msyh-web.ttf"
|
||||
const AVATAR_MASK_SHADER = preload("res://assets/shaders/avatar_round_mask.gdshader")
|
||||
const NetworkConfig = preload("res://_Core/utils/NetworkConfig.gd")
|
||||
const WebFilePicker = preload("res://_Core/utils/WebFilePicker.gd")
|
||||
const SKIN_GENERATION_CREATE_ENDPOINT: String = "/api/skin-generation/jobs"
|
||||
const SKIN_GENERATION_POLL_ENDPOINT_TEMPLATE: String = "/api/skin-generation/jobs/%s"
|
||||
const SKIN_GENERATION_POLL_INTERVAL: float = 2.0
|
||||
const SKIN_GENERATION_REQUEST_TIMEOUT: float = 24.0
|
||||
const AVATAR_NATIVE_FILE_FILTERS: Array[String] = ["*.png,*.jpg,*.jpeg,*.webp"]
|
||||
const SKIN_NATIVE_FILE_FILTERS: Array[String] = ["*.png"]
|
||||
const WEB_FILE_MAX_BYTES: int = 8 * 1024 * 1024
|
||||
@@ -46,7 +46,7 @@ const AUTH_STAGE_VIEWPORT_RATIO: float = 0.92
|
||||
const LOGIN_FRAME_RECT: Rect2 = Rect2(270, 40, 780, 680)
|
||||
const REGISTER_FRAME_RECT: Rect2 = Rect2(150, 15, 1020, 727)
|
||||
const LOGIN_FORM_RECT: Rect2 = Rect2(432, 210, 430, 430)
|
||||
const REGISTER_FORM_RECT: Rect2 = Rect2(292, 155, 446, 500)
|
||||
const REGISTER_FORM_RECT: Rect2 = Rect2(292, 128, 446, 500)
|
||||
|
||||
@onready var whale_frame: TextureRect = %WhaleFrame
|
||||
@onready var login_panel: PanelContainer = %LoginPanel
|
||||
@@ -55,8 +55,10 @@ const REGISTER_FORM_RECT: Rect2 = Rect2(292, 155, 446, 500)
|
||||
@onready var login_identifier_input: LineEdit = %LoginIdentifierInput
|
||||
@onready var login_password_input: LineEdit = %LoginPasswordInput
|
||||
@onready var login_button: Button = %LoginButton
|
||||
@onready var guest_button: Button = %GuestButton
|
||||
@onready var show_register_button: Button = %ShowRegisterButton
|
||||
@onready var register_username_input: LineEdit = %RegisterUsernameInput
|
||||
@onready var register_invitation_code_input: LineEdit = %RegisterInvitationCodeInput
|
||||
@onready var register_email_input: LineEdit = %RegisterEmailInput
|
||||
@onready var send_register_code_button: Button = %SendRegisterCodeButton
|
||||
@onready var register_verification_code_input: LineEdit = %RegisterVerificationCodeInput
|
||||
@@ -96,8 +98,6 @@ var _brand_font: SystemFont
|
||||
var _choice_font: Font
|
||||
var _resuming_cached_session: bool = false
|
||||
var _cached_resume_start_generation: int = 0
|
||||
var _skin_generation_create_request: HTTPRequest
|
||||
var _skin_generation_poll_request: HTTPRequest
|
||||
var _skin_generation_active: bool = false
|
||||
var _skin_generation_job_id: String = ""
|
||||
var _skin_generation_poll_elapsed: float = 0.0
|
||||
@@ -110,6 +110,7 @@ var _registration_official_skins: Array = []
|
||||
var _registration_official_skin_index: int = 0
|
||||
var _registration_avatar_source_path: String = ""
|
||||
var _registration_skin_source_path: String = ""
|
||||
var _registration_avatar_controls_visible: bool = false
|
||||
var _workshop_source_image_path: String = ""
|
||||
var _awaiting_registration_skin_generation: bool = false
|
||||
var _awaiting_registration_skin_choice: bool = false
|
||||
@@ -210,7 +211,7 @@ func _build_login_form(parent: Control) -> void:
|
||||
box.offset_right = -12
|
||||
parent.add_child(box)
|
||||
|
||||
var title := _brand_label("TitleLabel", "WhaleTown V2")
|
||||
var title := _brand_label("TitleLabel", "WhaleTown")
|
||||
title.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
box.add_child(title)
|
||||
box.add_child(HSeparator.new())
|
||||
@@ -247,6 +248,9 @@ func _build_login_form(parent: Control) -> void:
|
||||
var loginButton := _primary_button("LoginButton", "进入小镇", 25, Vector2(396, 58))
|
||||
loginButton.unique_name_in_owner = true
|
||||
box.add_child(loginButton)
|
||||
var guestButton := _secondary_button("GuestButton", "游客参观", 17, Vector2(396, 40))
|
||||
guestButton.unique_name_in_owner = true
|
||||
box.add_child(guestButton)
|
||||
|
||||
var links := HBoxContainer.new()
|
||||
links.name = "BottomLinks"
|
||||
@@ -268,7 +272,7 @@ func _build_register_form(parent: Control) -> void:
|
||||
var box := VBoxContainer.new()
|
||||
box.name = "RegisterVBox"
|
||||
box.add_theme_constant_override("separation", 3)
|
||||
_place(box, Rect2(8, 40, 422, 448))
|
||||
_place(box, Rect2(8, 20, 422, 478))
|
||||
content.add_child(box)
|
||||
|
||||
var title := _label("RegisterTitleLabel", "注册新居民", 22, TEXT_COLOR, HORIZONTAL_ALIGNMENT_CENTER)
|
||||
@@ -281,6 +285,12 @@ func _build_register_form(parent: Control) -> void:
|
||||
usernameInput.unique_name_in_owner = true
|
||||
box.add_child(usernameInput)
|
||||
|
||||
box.add_child(_register_form_label("邀请码"))
|
||||
var invitationCodeInput := _register_line_edit("RegisterInvitationCodeInput", "输入邀请码", false)
|
||||
invitationCodeInput.unique_name_in_owner = true
|
||||
invitationCodeInput.max_length = 30
|
||||
box.add_child(invitationCodeInput)
|
||||
|
||||
box.add_child(_register_form_label("邮箱"))
|
||||
var emailRow := HBoxContainer.new()
|
||||
emailRow.name = "RegisterEmailRow"
|
||||
@@ -312,7 +322,7 @@ func _build_register_form(parent: Control) -> void:
|
||||
|
||||
var buttonSpacer := Control.new()
|
||||
buttonSpacer.name = "RegisterButtonSpacer"
|
||||
buttonSpacer.custom_minimum_size = Vector2(0, 12)
|
||||
buttonSpacer.custom_minimum_size = Vector2(0, 4)
|
||||
box.add_child(buttonSpacer)
|
||||
|
||||
var buttons := HBoxContainer.new()
|
||||
@@ -692,11 +702,12 @@ func _ready() -> void:
|
||||
_scene_manager = get_node_or_null("/root/SceneManager")
|
||||
_appearance_manager = get_node_or_null("/root/AppearanceManager")
|
||||
|
||||
_setup_skin_generation_requests()
|
||||
_connect_signals()
|
||||
_refresh_appearance_ui()
|
||||
_show_login()
|
||||
_notify_web_shell_ready()
|
||||
if _scene_manager != null and _scene_manager.has_method("preload_scene_pack"):
|
||||
_scene_manager.call("preload_scene_pack", "square")
|
||||
|
||||
if _auth_manager != null and bool(_auth_manager.call("is_authenticated")) and _should_auto_resume_cached_session():
|
||||
_resume_cached_session()
|
||||
@@ -711,6 +722,7 @@ func _ready() -> void:
|
||||
|
||||
func _connect_signals() -> void:
|
||||
login_button.pressed.connect(_on_login_pressed)
|
||||
guest_button.pressed.connect(_on_guest_pressed)
|
||||
show_register_button.pressed.connect(_show_register)
|
||||
send_register_code_button.pressed.connect(_on_send_register_code_pressed)
|
||||
register_button.pressed.connect(_on_register_pressed)
|
||||
@@ -757,19 +769,6 @@ func _complete_browser_bootstrap(kind: String) -> void:
|
||||
return
|
||||
_on_login_succeeded(_auth_manager.call("get_current_user"))
|
||||
|
||||
func _setup_skin_generation_requests() -> void:
|
||||
_skin_generation_create_request = HTTPRequest.new()
|
||||
_skin_generation_create_request.name = "SkinGenerationCreateRequest"
|
||||
_skin_generation_create_request.timeout = SKIN_GENERATION_REQUEST_TIMEOUT
|
||||
_skin_generation_create_request.request_completed.connect(_on_skin_generation_create_completed)
|
||||
add_child(_skin_generation_create_request)
|
||||
|
||||
_skin_generation_poll_request = HTTPRequest.new()
|
||||
_skin_generation_poll_request.name = "SkinGenerationPollRequest"
|
||||
_skin_generation_poll_request.timeout = SKIN_GENERATION_REQUEST_TIMEOUT
|
||||
_skin_generation_poll_request.request_completed.connect(_on_skin_generation_poll_completed)
|
||||
add_child(_skin_generation_poll_request)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if not _skin_generation_active or _skin_generation_job_id.is_empty():
|
||||
return
|
||||
@@ -779,10 +778,6 @@ func _process(delta: float) -> void:
|
||||
_poll_skin_generation_job()
|
||||
|
||||
func _exit_tree() -> void:
|
||||
if is_instance_valid(_skin_generation_create_request):
|
||||
_skin_generation_create_request.cancel_request()
|
||||
if is_instance_valid(_skin_generation_poll_request):
|
||||
_skin_generation_poll_request.cancel_request()
|
||||
_skin_generation_active = false
|
||||
_skin_generation_job_id = ""
|
||||
|
||||
@@ -936,6 +931,7 @@ func _set_registration_character_controls_visible(visible: bool) -> void:
|
||||
_refresh_appearance_ui()
|
||||
|
||||
func _set_registration_avatar_controls_visible(visible: bool) -> void:
|
||||
_registration_avatar_controls_visible = visible
|
||||
if not is_instance_valid(registration_character_area):
|
||||
return
|
||||
var controlNames := [
|
||||
@@ -968,6 +964,7 @@ func _refresh_character_preview() -> void:
|
||||
character_sprite.modulate = Color.WHITE
|
||||
character_sprite.frame = 0
|
||||
character_sprite.scale = _sprite_scale_for_height(character_sprite.texture, character_sprite.vframes, MAIN_PREVIEW_HEIGHT)
|
||||
character_sprite.position = Vector2(50, 70) + _sprite_visual_center_offset(character_sprite.texture, character_sprite.hframes, character_sprite.vframes, character_sprite.frame, character_sprite.scale)
|
||||
_clear_workshop_preview()
|
||||
return
|
||||
if _appearance_manager.has_method("apply_skin_to_sprite"):
|
||||
@@ -976,6 +973,7 @@ func _refresh_character_preview() -> void:
|
||||
_appearance_manager.call("apply_skin_to_sprite", character_sprite, previewSkinId)
|
||||
character_sprite.frame = 0
|
||||
character_sprite.scale = _sprite_scale_for_height(character_sprite.texture, character_sprite.vframes, MAIN_PREVIEW_HEIGHT)
|
||||
character_sprite.position = Vector2(50, 70) + _sprite_visual_center_offset(character_sprite.texture, character_sprite.hframes, character_sprite.vframes, character_sprite.frame, character_sprite.scale)
|
||||
_clear_workshop_preview()
|
||||
|
||||
func _clear_workshop_preview() -> void:
|
||||
@@ -1006,6 +1004,7 @@ func _show_workshop_generated_preview_from_image(image: Image) -> void:
|
||||
workshop_preview_sprite.vframes = 4
|
||||
workshop_preview_sprite.frame = 0
|
||||
workshop_preview_sprite.scale = _sprite_scale_for_height(texture, 4, 156.0)
|
||||
workshop_preview_sprite.position = Vector2(75, 105) + _sprite_visual_center_offset(texture, 8, 4, workshop_preview_sprite.frame, workshop_preview_sprite.scale)
|
||||
workshop_preview_sprite.visible = true
|
||||
|
||||
func _show_workshop_source_preview(path: String) -> void:
|
||||
@@ -1102,6 +1101,7 @@ func _create_skin_button(skin: Dictionary, selectedSkinId: String) -> Button:
|
||||
preview.frame = 0
|
||||
preview.position = Vector2(90, 99)
|
||||
preview.scale = _sprite_scale_for_frame_bounds(preview.texture, preview.hframes, preview.vframes, Vector2(136, SKIN_THUMB_HEIGHT))
|
||||
preview.position += _sprite_visual_center_offset(preview.texture, preview.hframes, preview.vframes, preview.frame, preview.scale)
|
||||
preview.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR
|
||||
button.add_child(preview)
|
||||
|
||||
@@ -1170,6 +1170,9 @@ func _skin_texture(skin: Dictionary) -> Texture2D:
|
||||
|
||||
func _refresh_avatar_preview() -> void:
|
||||
avatar_preview.custom_minimum_size = Vector2(34, 34)
|
||||
if not _registration_avatar_controls_visible:
|
||||
avatar_preview.hide()
|
||||
return
|
||||
if _registration_avatar_texture == null:
|
||||
_clear_registration_avatar_preview()
|
||||
return
|
||||
@@ -1202,7 +1205,7 @@ func _show_registration_avatar_preview(texture: Texture2D) -> void:
|
||||
return
|
||||
avatar_preview.show()
|
||||
avatar_preview.clip_contents = true
|
||||
avatar_preview.add_theme_stylebox_override("panel", _panel_style(Color.WHITE, 18))
|
||||
avatar_preview.add_theme_stylebox_override("panel", _panel_style(Color.WHITE, 22, Color(0.45, 0.66, 0.84, 0.55), 1))
|
||||
if is_instance_valid(avatar_label):
|
||||
avatar_label.hide()
|
||||
var textureRect := _get_or_create_registration_avatar_texture_rect()
|
||||
@@ -1218,11 +1221,19 @@ func _get_or_create_registration_avatar_texture_rect() -> TextureRect:
|
||||
textureRect.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
textureRect.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR
|
||||
textureRect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
textureRect.stretch_mode = TextureRect.STRETCH_SCALE
|
||||
textureRect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED
|
||||
textureRect.material = _create_avatar_mask_material()
|
||||
textureRect.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
avatar_preview.add_child(textureRect)
|
||||
return textureRect
|
||||
|
||||
func _create_avatar_mask_material() -> ShaderMaterial:
|
||||
var material := ShaderMaterial.new()
|
||||
material.shader = AVATAR_MASK_SHADER
|
||||
material.set_shader_parameter("corner_radius", 0.22)
|
||||
material.set_shader_parameter("edge_feather", 0.008)
|
||||
return material
|
||||
|
||||
func _load_registration_avatar_texture(path: String) -> Texture2D:
|
||||
var cropped := _load_registration_avatar_image(path)
|
||||
if cropped == null:
|
||||
@@ -1432,23 +1443,19 @@ func _on_generate_skin_pressed() -> void:
|
||||
"source_image_base64": _image_to_png_base64(sourceImage),
|
||||
"source_mime_type": "image/png",
|
||||
}
|
||||
var err := _skin_generation_create_request.request(
|
||||
"%s%s" % [NetworkConfig.get_api_base_url(), SKIN_GENERATION_CREATE_ENDPOINT],
|
||||
_auth_json_headers(),
|
||||
HTTPClient.METHOD_POST,
|
||||
JSON.stringify(payload)
|
||||
)
|
||||
if err != OK:
|
||||
var apiClient := get_node_or_null("/root/ApiClient")
|
||||
if apiClient == null or not apiClient.has_method("post_json"):
|
||||
_skin_generation_active = false
|
||||
_set_skin_generation_controls_enabled(true)
|
||||
_set_workshop_generation_status("角色生成请求发送失败:%s" % error_string(err))
|
||||
_set_workshop_generation_status("角色生成服务未加载")
|
||||
return
|
||||
apiClient.call("post_json", SKIN_GENERATION_CREATE_ENDPOINT, payload, _on_skin_generation_create_completed, true)
|
||||
|
||||
func _on_skin_generation_create_completed(result: int, responseCode: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||
var parsed := _parse_skin_generation_response(result, responseCode, body)
|
||||
if not bool(parsed.get("ok", false)):
|
||||
func _on_skin_generation_create_completed(success: bool, response: Dictionary, errorInfo: Dictionary) -> void:
|
||||
if not success:
|
||||
_skin_generation_active = false
|
||||
_set_skin_generation_controls_enabled(true)
|
||||
var errorMessage := str(parsed.get("message", "角色生成任务创建失败"))
|
||||
var errorMessage := str(errorInfo.get("message", "角色生成任务创建失败"))
|
||||
if errorMessage.contains("已经使用过注册角色生成机会") or errorMessage.contains("没有可用的注册角色生成机会"):
|
||||
_awaiting_registration_skin_generation = false
|
||||
_set_workshop_generation_status("该账号已完成注册角色生成,正在进入小镇...")
|
||||
@@ -1457,7 +1464,13 @@ func _on_skin_generation_create_completed(result: int, responseCode: int, _heade
|
||||
_set_workshop_generation_status(errorMessage)
|
||||
return
|
||||
|
||||
var data: Dictionary = parsed.get("data", {})
|
||||
var dataVariant: Variant = response.get("data", {})
|
||||
if not (dataVariant is Dictionary):
|
||||
_skin_generation_active = false
|
||||
_set_skin_generation_controls_enabled(true)
|
||||
_set_workshop_generation_status("服务器返回的生成任务格式错误")
|
||||
return
|
||||
var data: Dictionary = dataVariant
|
||||
_skin_generation_job_id = str(data.get("job_id", "")).strip_edges()
|
||||
if _skin_generation_job_id.is_empty():
|
||||
_skin_generation_active = false
|
||||
@@ -1473,26 +1486,30 @@ func _poll_skin_generation_job() -> void:
|
||||
|
||||
_skin_generation_poll_in_flight = true
|
||||
var endpoint := SKIN_GENERATION_POLL_ENDPOINT_TEMPLATE % _skin_generation_job_id
|
||||
var err := _skin_generation_poll_request.request(
|
||||
"%s%s" % [NetworkConfig.get_api_base_url(), endpoint],
|
||||
_auth_json_headers(),
|
||||
HTTPClient.METHOD_GET
|
||||
)
|
||||
if err != OK:
|
||||
var apiClient := get_node_or_null("/root/ApiClient")
|
||||
if apiClient == null or not apiClient.has_method("get_json"):
|
||||
_skin_generation_poll_in_flight = false
|
||||
_set_workshop_generation_status("查询生成状态失败:%s" % error_string(err))
|
||||
_set_workshop_generation_status("角色生成服务未加载")
|
||||
return
|
||||
apiClient.call("get_json", endpoint, _on_skin_generation_poll_completed, true)
|
||||
|
||||
func _on_skin_generation_poll_completed(result: int, responseCode: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||
func _on_skin_generation_poll_completed(success: bool, response: Dictionary, errorInfo: Dictionary) -> void:
|
||||
_skin_generation_poll_in_flight = false
|
||||
var parsed := _parse_skin_generation_response(result, responseCode, body)
|
||||
if not bool(parsed.get("ok", false)):
|
||||
if not success:
|
||||
var responseCode := int(errorInfo.get("response_code", 0))
|
||||
if responseCode == 401 or responseCode == 403 or responseCode == 404:
|
||||
_skin_generation_active = false
|
||||
_set_skin_generation_controls_enabled(true)
|
||||
_set_workshop_generation_status(str(parsed.get("message", "查询生成状态失败")))
|
||||
_set_workshop_generation_status(str(errorInfo.get("message", "查询生成状态失败")))
|
||||
return
|
||||
|
||||
var data: Dictionary = parsed.get("data", {})
|
||||
var dataVariant: Variant = response.get("data", {})
|
||||
if not (dataVariant is Dictionary):
|
||||
_skin_generation_active = false
|
||||
_set_skin_generation_controls_enabled(true)
|
||||
_set_workshop_generation_status("服务器返回的生成状态格式错误")
|
||||
return
|
||||
var data: Dictionary = dataVariant
|
||||
var status := str(data.get("status", "")).strip_edges()
|
||||
var message := str(data.get("message", "")).strip_edges()
|
||||
if not message.is_empty():
|
||||
@@ -1578,74 +1595,6 @@ func _set_skin_generation_controls_enabled(enabled: bool) -> void:
|
||||
if is_instance_valid(workshop_generate_button):
|
||||
workshop_generate_button.disabled = not enabled
|
||||
|
||||
func _json_headers() -> PackedStringArray:
|
||||
return PackedStringArray([
|
||||
"Content-Type: application/json",
|
||||
"Accept: application/json",
|
||||
])
|
||||
|
||||
func _auth_json_headers() -> PackedStringArray:
|
||||
var headers := _json_headers()
|
||||
if _auth_manager != null and _auth_manager.has_method("get_access_token"):
|
||||
var token := str(_auth_manager.call("get_access_token")).strip_edges()
|
||||
if not token.is_empty():
|
||||
headers.append("Authorization: Bearer %s" % token)
|
||||
return headers
|
||||
|
||||
func _parse_skin_generation_response(result: int, responseCode: int, body: PackedByteArray) -> Dictionary:
|
||||
if result != HTTPRequest.RESULT_SUCCESS:
|
||||
return {
|
||||
"ok": false,
|
||||
"message": "网络请求失败:%s" % _http_result_to_string(result),
|
||||
}
|
||||
|
||||
var bodyText := body.get_string_from_utf8()
|
||||
var json := JSON.new()
|
||||
var error := json.parse(bodyText)
|
||||
if error != OK or not (json.data is Dictionary):
|
||||
return {
|
||||
"ok": false,
|
||||
"message": "服务器响应解析失败",
|
||||
}
|
||||
|
||||
var response: Dictionary = json.data as Dictionary
|
||||
var success := responseCode >= 200 and responseCode < 300 and bool(response.get("success", true))
|
||||
if not success:
|
||||
return {
|
||||
"ok": false,
|
||||
"message": str(response.get("message", "请求失败")),
|
||||
"response_code": responseCode,
|
||||
}
|
||||
|
||||
var dataVariant: Variant = response.get("data", response)
|
||||
if not (dataVariant is Dictionary):
|
||||
return {
|
||||
"ok": false,
|
||||
"message": "服务器响应格式错误",
|
||||
}
|
||||
|
||||
return {
|
||||
"ok": true,
|
||||
"data": dataVariant as Dictionary,
|
||||
}
|
||||
|
||||
func _http_result_to_string(result: int) -> String:
|
||||
match result:
|
||||
HTTPRequest.RESULT_SUCCESS:
|
||||
return "SUCCESS"
|
||||
HTTPRequest.RESULT_TIMEOUT:
|
||||
return "TIMEOUT"
|
||||
HTTPRequest.RESULT_CANT_CONNECT:
|
||||
return "CANT_CONNECT"
|
||||
HTTPRequest.RESULT_CANT_RESOLVE:
|
||||
return "CANT_RESOLVE"
|
||||
HTTPRequest.RESULT_CONNECTION_ERROR:
|
||||
return "CONNECTION_ERROR"
|
||||
HTTPRequest.RESULT_TLS_HANDSHAKE_ERROR:
|
||||
return "TLS_HANDSHAKE_ERROR"
|
||||
_:
|
||||
return "UNKNOWN_%d" % result
|
||||
|
||||
func _hide_skin_workshop() -> void:
|
||||
if _awaiting_registration_skin_generation:
|
||||
_awaiting_registration_skin_generation = false
|
||||
@@ -1953,7 +1902,7 @@ func _on_send_register_code_pressed() -> void:
|
||||
_is_sending_register_code = true
|
||||
send_register_code_button.disabled = true
|
||||
status_label.text = "正在发送邮箱验证码..."
|
||||
_auth_manager.call("send_email_verification", register_email_input.text)
|
||||
_auth_manager.call("send_email_verification", register_email_input.text, register_invitation_code_input.text)
|
||||
|
||||
func _on_register_pressed() -> void:
|
||||
if _is_submitting:
|
||||
@@ -1976,7 +1925,8 @@ func _on_register_pressed() -> void:
|
||||
"",
|
||||
register_email_input.text,
|
||||
register_verification_code_input.text,
|
||||
_registration_backend_initial_skin_id()
|
||||
_registration_backend_initial_skin_id(),
|
||||
register_invitation_code_input.text
|
||||
)
|
||||
|
||||
func _on_login_succeeded(_user: Dictionary) -> void:
|
||||
@@ -1987,6 +1937,17 @@ func _on_login_succeeded(_user: Dictionary) -> void:
|
||||
_auth_manager.call("fetch_profile")
|
||||
_enter_square()
|
||||
|
||||
func _on_guest_pressed() -> void:
|
||||
if _is_submitting:
|
||||
return
|
||||
var chatManager := get_node_or_null("/root/ChatManager")
|
||||
if chatManager == null or not chatManager.has_method("start_guest_session"):
|
||||
status_label.text = "旁观服务未加载"
|
||||
return
|
||||
_set_submitting(true, "正在进入游客参观模式...")
|
||||
chatManager.call("start_guest_session")
|
||||
SceneManager.change_scene("square")
|
||||
|
||||
func _on_login_failed(message: String) -> void:
|
||||
_set_submitting(false, message)
|
||||
login_password_input.grab_focus()
|
||||
@@ -2192,6 +2153,7 @@ func _set_submitting(is_submitting: bool, message: String) -> void:
|
||||
_is_submitting = is_submitting
|
||||
status_label.text = message
|
||||
login_button.disabled = is_submitting
|
||||
guest_button.disabled = is_submitting
|
||||
register_button.disabled = is_submitting
|
||||
send_register_code_button.disabled = is_submitting or _is_sending_register_code
|
||||
show_register_button.disabled = is_submitting
|
||||
@@ -2453,3 +2415,35 @@ func _sprite_scale_for_frame_bounds(texture: Texture2D, hframes: int, vframes: i
|
||||
return Vector2.ONE
|
||||
var scale: float = minf(bounds.x / frameSize.x, bounds.y / frameSize.y)
|
||||
return Vector2(scale, scale)
|
||||
|
||||
func _sprite_visual_center_offset(texture: Texture2D, hframes: int, vframes: int, frame: int, scale: Vector2) -> Vector2:
|
||||
if texture == null:
|
||||
return Vector2.ZERO
|
||||
var image := texture.get_image()
|
||||
if image == null:
|
||||
return Vector2.ZERO
|
||||
var safeHframes := maxi(1, hframes)
|
||||
var safeVframes := maxi(1, vframes)
|
||||
var frameWidth := image.get_width() / safeHframes
|
||||
var frameHeight := image.get_height() / safeVframes
|
||||
if frameWidth <= 0 or frameHeight <= 0:
|
||||
return Vector2.ZERO
|
||||
var safeFrame := clampi(frame, 0, safeHframes * safeVframes - 1)
|
||||
var frameOrigin := Vector2i((safeFrame % safeHframes) * frameWidth, (safeFrame / safeHframes) * frameHeight)
|
||||
var minX := frameWidth
|
||||
var minY := frameHeight
|
||||
var maxX := -1
|
||||
var maxY := -1
|
||||
for y in range(frameHeight):
|
||||
for x in range(frameWidth):
|
||||
if image.get_pixel(frameOrigin.x + x, frameOrigin.y + y).a <= 0.08:
|
||||
continue
|
||||
minX = mini(minX, x)
|
||||
minY = mini(minY, y)
|
||||
maxX = maxi(maxX, x)
|
||||
maxY = maxi(maxY, y)
|
||||
if maxX < minX or maxY < minY:
|
||||
return Vector2.ZERO
|
||||
var visualCenter := Vector2((float(minX) + float(maxX) + 1.0) * 0.5, (float(minY) + float(maxY) + 1.0) * 0.5)
|
||||
var frameCenter := Vector2(float(frameWidth), float(frameHeight)) * 0.5
|
||||
return (frameCenter - visualCenter) * scale
|
||||
|
||||
Reference in New Issue
Block a user