extends Node # ============================================================================ # NotificationSoundManager.gd - 提醒音效管理器 # ============================================================================ # 监听聊天和好友事件,播放项目内正式 UI 提醒音效资源。 # ============================================================================ const MIN_PLAY_INTERVAL_SEC: float = 0.18 const WORLD_NOTIFICATION_PATH: String = "res://assets/audio/ui/notify_world.wav" const PRIVATE_NOTIFICATION_PATH: String = "res://assets/audio/ui/notify_private.wav" const FRIEND_NOTIFICATION_PATH: String = "res://assets/audio/ui/notify_friend.wav" var _player: AudioStreamPlayer var _worldStream: AudioStreamWAV var _privateStream: AudioStreamWAV var _friendStream: AudioStreamWAV var _lastPlayedAt: float = 0.0 var _seenFriendRequestKeys: Dictionary = {} func _ready() -> void: _build_player() _load_streams() _subscribe_to_events() func _exit_tree() -> void: var eventSystem := _get_event_system() if eventSystem != null: eventSystem.call("disconnect_event", EventNames.CHAT_MESSAGE_RECEIVED, _on_chat_message_received, self) eventSystem.call("disconnect_event", EventNames.CHAT_FRIENDS_UPDATED, _on_friends_updated, self) eventSystem.call("disconnect_event", EventNames.CHAT_ERROR_OCCURRED, _on_chat_error, self) func play_notification(kind: String = "world") -> void: if _settings_bool("mute_ui_sfx", false): return var now := Time.get_unix_time_from_system() if now - _lastPlayedAt < MIN_PLAY_INTERVAL_SEC: return _lastPlayedAt = now if not is_instance_valid(_player): return var stream := _stream_for_kind(kind) if stream == null: return _player.stream = stream _player.play() func should_play_message_notification(data: Dictionary) -> bool: if bool(data.get("is_self", false)): return false var scope := str(data.get("scope", "")).strip_edges().to_lower() var isPrivate := bool(data.get("is_private", false)) or scope == "private" if isPrivate: return _settings_bool("private_notifications", true) return _settings_bool("world_notifications", true) func get_notification_asset_path(kind: String = "world") -> String: match kind: "private": return PRIVATE_NOTIFICATION_PATH "friend": return FRIEND_NOTIFICATION_PATH _: return WORLD_NOTIFICATION_PATH func _build_player() -> void: _player = AudioStreamPlayer.new() _player.name = "NotificationAudioPlayer" _player.bus = "SFX" add_child(_player) func _load_streams() -> void: _worldStream = _load_wav_stream(WORLD_NOTIFICATION_PATH) _privateStream = _load_wav_stream(PRIVATE_NOTIFICATION_PATH) _friendStream = _load_wav_stream(FRIEND_NOTIFICATION_PATH) func _load_wav_stream(path: String) -> AudioStreamWAV: var stream := AudioStreamWAV.load_from_file(ProjectSettings.globalize_path(path)) if stream == null: push_warning("NotificationSoundManager: 提醒音效加载失败: %s" % path) return stream func _stream_for_kind(kind: String) -> AudioStream: match kind: "private": return _privateStream "friend": return _friendStream _: return _worldStream func _subscribe_to_events() -> void: var eventSystem := _get_event_system() if eventSystem == null: push_warning("NotificationSoundManager: EventSystem autoload is not available.") return eventSystem.call("connect_event", EventNames.CHAT_MESSAGE_RECEIVED, _on_chat_message_received, self) eventSystem.call("connect_event", EventNames.CHAT_FRIENDS_UPDATED, _on_friends_updated, self) eventSystem.call("connect_event", EventNames.CHAT_ERROR_OCCURRED, _on_chat_error, self) func _on_chat_message_received(data: Dictionary) -> void: if not should_play_message_notification(data): return var scope := str(data.get("scope", "")).strip_edges().to_lower() var isPrivate := bool(data.get("is_private", false)) or scope == "private" play_notification("private" if isPrivate else "world") func _on_friends_updated(data: Dictionary) -> void: if not _settings_bool("friend_request_notifications", true): return var requestsVariant: Variant = data.get("requests", []) if not (requestsVariant is Array): return var shouldPlay := false for requestVariant in requestsVariant: if not (requestVariant is Dictionary): continue var key := _friend_request_key(requestVariant as Dictionary) if key.is_empty() or _seenFriendRequestKeys.has(key): continue _seenFriendRequestKeys[key] = true shouldPlay = true if shouldPlay: play_notification("friend") func _on_chat_error(data: Dictionary) -> void: var errorCode := str(data.get("error_code", "")) if errorCode == "FRIEND_REQUEST_SENT" and _settings_bool("friend_request_notifications", true): play_notification("friend") func _friend_request_key(request: Dictionary) -> String: var userId := str(request.get("user_id", request.get("userId", ""))).strip_edges() if not userId.is_empty(): return userId return str(request.get("username", "")).strip_edges() func _settings_bool(key: String, defaultValue: bool) -> bool: var settingsManager := get_node_or_null("/root/SettingsManager") if settingsManager != null and settingsManager.has_method("get_bool"): return bool(settingsManager.call("get_bool", key)) return defaultValue func _get_event_system() -> Node: return get_node_or_null("/root/EventSystem")