forked from xiangwang25/whale-town-front-v2
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:
@@ -21,6 +21,7 @@ const REMOTE_SKIN_MAX_RETRIES: int = 3
|
||||
const REMOTE_SKIN_CACHE_DIR: String = "user://skin_cache"
|
||||
const AVATAR_CORNER_RADIUS_RATIO: float = 0.22
|
||||
const DEFAULT_CHARACTER_TEXTURE_PATH: String = "res://assets/characters/player_pixel_spritesheet.png"
|
||||
const AVATAR_MASK_SHADER = preload("res://assets/shaders/avatar_round_mask.gdshader")
|
||||
const NetworkConfig = preload("res://_Core/utils/NetworkConfig.gd")
|
||||
|
||||
const SKINS: Array[Dictionary] = [
|
||||
@@ -99,9 +100,12 @@ var _remoteSkinRetryCounts: Dictionary = {}
|
||||
var _accountAvatarUrl: String = ""
|
||||
var _accountAvatarTexture: Texture2D
|
||||
var _profileSyncSuppressed: bool = false
|
||||
var _confirmedSkinId: String = FALLBACK_SKIN_ID
|
||||
var _pendingSkinId: String = ""
|
||||
|
||||
func _ready() -> void:
|
||||
_load()
|
||||
_confirmedSkinId = selectedSkinId
|
||||
_connect_auth_events()
|
||||
_emit_profile_changed()
|
||||
|
||||
@@ -226,12 +230,17 @@ func set_selected_skin(skinId: String, allowLocked: bool = false) -> bool:
|
||||
if normalizedId == selectedSkinId:
|
||||
return true
|
||||
|
||||
var shouldSync := not _profileSyncSuppressed and _is_account_authenticated()
|
||||
if shouldSync:
|
||||
_pendingSkinId = normalizedId
|
||||
selectedSkinId = normalizedId
|
||||
_save()
|
||||
_emit_skin_changed()
|
||||
_emit_profile_changed()
|
||||
if not _profileSyncSuppressed:
|
||||
if shouldSync:
|
||||
_sync_account_skin_profile()
|
||||
else:
|
||||
_confirmedSkinId = normalizedId
|
||||
return true
|
||||
|
||||
func apply_account_profile(profile: Dictionary) -> void:
|
||||
@@ -240,23 +249,28 @@ func apply_account_profile(profile: Dictionary) -> void:
|
||||
_clear_session_custom_skin()
|
||||
var skinId := str(profile.get("skin_id", "")).strip_edges()
|
||||
_accountAvatarUrl = _absolute_backend_url(str(profile.get("avatar_url", "")).strip_edges())
|
||||
_accountAvatarTexture = _texture_from_base64(str(profile.get("avatar_base64", "")).strip_edges())
|
||||
_accountAvatarTexture = _texture_from_base64(str(profile.get("avatar_base64", "")).strip_edges(), true)
|
||||
var ownedSkinsVariant: Variant = profile.get("owned_skins", [])
|
||||
if ownedSkinsVariant is Array:
|
||||
apply_account_skin_assets(ownedSkinsVariant as Array)
|
||||
var ownedVariant: Variant = profile.get("owned_skin_ids", [])
|
||||
if ownedVariant is Array:
|
||||
set_owned_skin_ids(ownedVariant)
|
||||
if not skinId.is_empty():
|
||||
var shouldApplySkin := _pendingSkinId.is_empty() or skinId == _pendingSkinId
|
||||
if not skinId.is_empty() and shouldApplySkin:
|
||||
grant_owned_skin(skinId)
|
||||
set_selected_skin(skinId, true)
|
||||
else:
|
||||
_confirmedSkinId = skinId
|
||||
if skinId == _pendingSkinId:
|
||||
_pendingSkinId = ""
|
||||
elif skinId.is_empty() and _pendingSkinId.is_empty():
|
||||
var fallbackSkinId := _first_owned_skin_id()
|
||||
if selectedSkinId != fallbackSkinId:
|
||||
selectedSkinId = fallbackSkinId
|
||||
_save()
|
||||
_emit_skin_changed()
|
||||
_emit_profile_changed()
|
||||
_confirmedSkinId = fallbackSkinId
|
||||
_profileSyncSuppressed = false
|
||||
if _accountAvatarTexture != null or not _accountAvatarUrl.is_empty():
|
||||
selectedAvatarId = CUSTOM_AVATAR_ID
|
||||
@@ -306,7 +320,7 @@ func apply_account_avatar_base64(avatarUrl: String, avatarBase64: String) -> voi
|
||||
_accountAvatarUrl = _absolute_backend_url(avatarUrl.strip_edges())
|
||||
_customAvatarTexture = null
|
||||
_customAvatarActive = false
|
||||
_accountAvatarTexture = _texture_from_base64(avatarBase64)
|
||||
_accountAvatarTexture = _texture_from_base64(avatarBase64, true)
|
||||
if _accountAvatarTexture != null or not _accountAvatarUrl.is_empty():
|
||||
selectedAvatarId = CUSTOM_AVATAR_ID
|
||||
else:
|
||||
@@ -715,11 +729,45 @@ func _sync_account_skin_profile() -> void:
|
||||
|
||||
func _connect_auth_events() -> void:
|
||||
var eventSystem := get_node_or_null("/root/EventSystem")
|
||||
if eventSystem == null:
|
||||
if eventSystem != null:
|
||||
eventSystem.call("connect_event", EventNames.AUTH_LOGOUT, _on_auth_logout, self)
|
||||
var authManager := get_node_or_null("/root/AuthManager")
|
||||
if authManager != null and authManager.has_signal("appearance_update_succeeded"):
|
||||
authManager.connect("appearance_update_succeeded", _on_appearance_update_succeeded)
|
||||
if authManager != null and authManager.has_signal("appearance_update_failed"):
|
||||
authManager.connect("appearance_update_failed", _on_appearance_update_failed)
|
||||
|
||||
func _on_appearance_update_succeeded(requestedSkinId: String, profile: Dictionary) -> void:
|
||||
if requestedSkinId != _pendingSkinId:
|
||||
return
|
||||
eventSystem.call("connect_event", EventNames.AUTH_LOGOUT, _on_auth_logout, self)
|
||||
var confirmedSkinId := str(profile.get("skin_id", requestedSkinId)).strip_edges()
|
||||
if confirmedSkinId.is_empty():
|
||||
confirmedSkinId = requestedSkinId
|
||||
_pendingSkinId = ""
|
||||
_confirmedSkinId = confirmedSkinId
|
||||
if selectedSkinId != confirmedSkinId:
|
||||
_apply_selected_skin_locally(confirmedSkinId)
|
||||
|
||||
func _on_appearance_update_failed(requestedSkinId: String, _message: String) -> void:
|
||||
if requestedSkinId != _pendingSkinId:
|
||||
return
|
||||
_pendingSkinId = ""
|
||||
if not _confirmedSkinId.is_empty() and selectedSkinId == requestedSkinId:
|
||||
_apply_selected_skin_locally(_confirmedSkinId)
|
||||
|
||||
func _apply_selected_skin_locally(skinId: String) -> void:
|
||||
selectedSkinId = skinId
|
||||
_save()
|
||||
_emit_skin_changed()
|
||||
_emit_profile_changed()
|
||||
|
||||
func _is_account_authenticated() -> bool:
|
||||
var authManager := get_node_or_null("/root/AuthManager")
|
||||
return authManager != null and authManager.has_method("is_authenticated") and bool(authManager.call("is_authenticated"))
|
||||
|
||||
func _on_auth_logout(_data: Variant = null) -> void:
|
||||
_pendingSkinId = ""
|
||||
_confirmedSkinId = FALLBACK_SKIN_ID
|
||||
clear_account_profile()
|
||||
|
||||
func get_profile_payload() -> Dictionary:
|
||||
@@ -762,11 +810,19 @@ func _get_or_create_avatar_texture_rect(panel: PanelContainer) -> TextureRect:
|
||||
textureRect.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR
|
||||
textureRect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
textureRect.stretch_mode = TextureRect.STRETCH_SCALE
|
||||
textureRect.material = _create_avatar_mask_material()
|
||||
textureRect.custom_minimum_size = panel.custom_minimum_size
|
||||
textureRect.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
panel.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 _normalize_account_skin_asset(skin: Dictionary) -> Dictionary:
|
||||
var normalized := skin.duplicate(true)
|
||||
var skinId := str(normalized.get("id", normalized.get("skin_id", ""))).strip_edges()
|
||||
@@ -784,7 +840,7 @@ func _normalize_account_skin_asset(skin: Dictionary) -> Dictionary:
|
||||
normalized["account_asset"] = true
|
||||
return normalized
|
||||
|
||||
func _texture_from_base64(base64Text: String) -> Texture2D:
|
||||
func _texture_from_base64(base64Text: String, roundAvatar: bool = false) -> Texture2D:
|
||||
var bytes := Marshalls.base64_to_raw(base64Text.strip_edges())
|
||||
if bytes.is_empty():
|
||||
return null
|
||||
@@ -792,6 +848,9 @@ func _texture_from_base64(base64Text: String) -> Texture2D:
|
||||
var err := image.load_png_from_buffer(bytes)
|
||||
if err != OK:
|
||||
return null
|
||||
if roundAvatar:
|
||||
image = _create_square_avatar_image(image)
|
||||
_apply_rounded_rect_alpha(image)
|
||||
return ImageTexture.create_from_image(image)
|
||||
|
||||
func _absolute_backend_url(path: String) -> String:
|
||||
|
||||
Reference in New Issue
Block a user