diff --git a/main/main.gd b/main/main.gd index 1b3023b..7fc2a00 100644 --- a/main/main.gd +++ b/main/main.gd @@ -392,6 +392,7 @@ func _initialize_after_data_root() -> void: _asset_reservations, _network_profile_service, _network_player_list, + _settings_manager, ) _game_ui.setup_data_and_identity( _data_root, diff --git a/network/network_chat_service.gd b/network/network_chat_service.gd index 5920a0d..ebb65e9 100644 --- a/network/network_chat_service.gd +++ b/network/network_chat_service.gd @@ -6,6 +6,7 @@ const WINDOW_COUNT: int = 5 const WINDOW_SECONDS: float = 10.0 signal message_received(message: Dictionary) +signal local_message_confirmed(message: Dictionary) signal send_rejected(message: String) signal history_replaced(messages: Array[Dictionary]) @@ -222,6 +223,12 @@ func _apply_message(data: Dictionary) -> void: _history.pop_front() if _message_is_visible(stored_message): message_received.emit(stored_message.duplicate(true)) + if ( + kind == NetworkChatProtocol.Kind.PLAYER + and int(stored_message["sender_peer_id"]) + == _session.get_local_peer_id() + ): + local_message_confirmed.emit(stored_message.duplicate(true)) func _send_rejection(peer_id: int, message: String) -> void: diff --git a/player/player.gd b/player/player.gd index dd04b41..22c587c 100644 --- a/player/player.gd +++ b/player/player.gd @@ -579,6 +579,23 @@ func get_body_center_position() -> Vector3: return global_position + Vector3.UP * body_center_height +func get_chat_anchor_position() -> Vector3: + var highest_y := -INF + for node: Node in _visuals.find_children("*", "MeshInstance3D", true, false): + var mesh_instance := node as MeshInstance3D + if mesh_instance == null or not mesh_instance.is_visible_in_tree(): + continue + var bounds := mesh_instance.get_aabb() + for endpoint_index: int in range(8): + var world_point := ( + mesh_instance.global_transform * bounds.get_endpoint(endpoint_index) + ) + highest_y = maxf(highest_y, world_point.y) + if not is_finite(highest_y): + highest_y = get_body_center_position().y + body_center_height + return Vector3(global_position.x, highest_y + 0.22, global_position.z) + + func get_body_center_height() -> float: return body_center_height diff --git a/settings/player_settings.gd b/settings/player_settings.gd index 70cceb1..49b5e50 100644 --- a/settings/player_settings.gd +++ b/settings/player_settings.gd @@ -13,6 +13,7 @@ const DEFAULT_WORLD_PIXEL_SIZE: int = 3 const MIN_UI_PIXEL_SIZE: int = 1 const MAX_UI_PIXEL_SIZE: int = 5 const DEFAULT_UI_PIXEL_SIZE: int = 3 +const MAX_CHAT_DRAFT_CHARACTERS: int = 280 const COMPACT_DISPLAY_HEIGHT: int = 560 const WORLD_DESKTOP_GRID_HEIGHTS: Array[int] = [0, 540, 360, 216, 96] const WORLD_COMPACT_GRID_HEIGHTS: Array[int] = [0, 360, 240, 144, 72] @@ -25,6 +26,8 @@ const UI_COMPACT_RENDER_HEIGHTS: Array[int] = [0, 408, 336, 264, 192] @export_range(0.001, 0.012, 0.0005) var mouse_camera_sensitivity: float = 0.005 @export_range(0.5, 5.0, 0.1) var controller_camera_sensitivity: float = 2.5 @export var invert_camera_y: bool = false +@export var chat_draft: String = "" +@export var chat_collapsed: bool = false @export_range(1, 5, 1) var world_pixel_size: int = DEFAULT_WORLD_PIXEL_SIZE @export_range(1, 5, 1) var ui_pixel_size: int = DEFAULT_UI_PIXEL_SIZE @@ -40,6 +43,7 @@ func is_valid() -> bool: and is_finite(controller_camera_sensitivity) and controller_camera_sensitivity >= MIN_CONTROLLER_SENSITIVITY and controller_camera_sensitivity <= MAX_CONTROLLER_SENSITIVITY + and chat_draft.length() <= MAX_CHAT_DRAFT_CHARACTERS and world_pixel_size >= MIN_WORLD_PIXEL_SIZE and world_pixel_size <= MAX_WORLD_PIXEL_SIZE and ui_pixel_size >= MIN_UI_PIXEL_SIZE @@ -55,6 +59,8 @@ func copy() -> PlayerSettings: result.mouse_camera_sensitivity = mouse_camera_sensitivity result.controller_camera_sensitivity = controller_camera_sensitivity result.invert_camera_y = invert_camera_y + result.chat_draft = chat_draft + result.chat_collapsed = chat_collapsed result.world_pixel_size = world_pixel_size result.ui_pixel_size = ui_pixel_size return result diff --git a/settings/player_settings_manager.gd b/settings/player_settings_manager.gd index 3cf737e..363c398 100644 --- a/settings/player_settings_manager.gd +++ b/settings/player_settings_manager.gd @@ -49,6 +49,14 @@ func load_settings() -> bool: accessibility.has("use_readable_interface_font") and typeof(accessibility["use_readable_interface_font"]) != TYPE_BOOL ) + or ( + presentation.has("chat_draft") + and typeof(presentation["chat_draft"]) != TYPE_STRING + ) + or ( + presentation.has("chat_collapsed") + and typeof(presentation["chat_collapsed"]) != TYPE_BOOL + ) ): return _use_defaults_after_corruption("Player settings values are invalid.") var loaded := PlayerSettings.new() @@ -87,6 +95,10 @@ func load_settings() -> bool: PlayerSettings.MAX_UI_PIXEL_SIZE, PlayerSettings.DEFAULT_UI_PIXEL_SIZE ) + loaded.chat_draft = str(presentation.get("chat_draft", "")).left( + PlayerSettings.MAX_CHAT_DRAFT_CHARACTERS + ) + loaded.chat_collapsed = bool(presentation.get("chat_collapsed", false)) if not loaded.is_valid(): return _use_defaults_after_corruption("Player settings values are invalid.") current_settings = loaded @@ -123,6 +135,22 @@ func apply_pixelation(world_pixel_size: int, ui_pixel_size: int) -> bool: return apply_settings(edited) +func update_chat_preferences(draft: String, collapsed: bool) -> void: + var clean_draft := draft.left(PlayerSettings.MAX_CHAT_DRAFT_CHARACTERS) + if ( + current_settings.chat_draft == clean_draft + and current_settings.chat_collapsed == collapsed + ): + return + current_settings.chat_draft = clean_draft + current_settings.chat_collapsed = collapsed + _is_dirty = true + + +func save_chat_preferences() -> bool: + return save_if_dirty() + + func save_now() -> bool: if current_settings == null or not current_settings.is_valid(): return false @@ -143,8 +171,12 @@ func save_now() -> bool: "presentation": { "world_pixel_size": current_settings.world_pixel_size, "ui_pixel_size": current_settings.ui_pixel_size, + "chat_draft": current_settings.chat_draft, + "chat_collapsed": current_settings.chat_collapsed, }, } + if current_settings.chat_draft.is_empty(): + (data["presentation"] as Dictionary).erase("chat_draft") var file := FileAccess.open(TEMP_PATH, FileAccess.WRITE) if file == null: return false diff --git a/ui/chat_ui.gd b/ui/chat_ui.gd index 8d34a5d..54c9f1a 100644 --- a/ui/chat_ui.gd +++ b/ui/chat_ui.gd @@ -2,23 +2,39 @@ class_name ChatUI extends Control const INPUT_OWNER: StringName = &"chat" -const COMPACT_SECONDS: float = 8.0 +const RECENT_SECONDS: float = 8.0 const SPEECH_SECONDS: float = 6.0 +const DRAFT_SAVE_DELAY: float = 0.4 +const IDLE_ALPHA: float = 0.58 +const PANEL_POSITION := Vector2(30, 376) +const PANEL_SIZE := Vector2(390, 250) var _service: NetworkChatService var _session: NetworkSession var _spawn: PlayerSpawnService var _player: Player var _fishing_spot: FishingSpot +var _settings: PlayerSettingsManager var _history: Label var _entry: LineEdit -var _chat_button: Button +var _status: Label var _panel: PanelContainer +var _collapse_button: Button +var _unread_indicator: Label +var _hint: Label var _speech_layer: Control var _speech: Dictionary[int, Dictionary] = {} +var _draft_save_timer: Timer +var _opacity_tween: Tween var _opened: bool = false var _available: bool = false +var _collapsed: bool = false +var _panel_hovered: bool = false +var _collapsed_has_unread: bool = false var _last_message_time: float = -INF +var _target_alpha: float = -1.0 +var _send_pending: bool = false +var _pending_send_body: String = "" var _prior_movement: bool = true var _prior_camera: bool = true @@ -27,6 +43,7 @@ func _ready() -> void: set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) mouse_filter = Control.MOUSE_FILTER_IGNORE _build_ui() + set_process(true) func setup( @@ -35,16 +52,22 @@ func setup( spawn: PlayerSpawnService, player: Player, fishing_spot: FishingSpot, + settings: PlayerSettingsManager, ) -> void: _service = service _session = session _spawn = spawn _player = player _fishing_spot = fishing_spot + _settings = settings _service.message_received.connect(_on_message) + _service.local_message_confirmed.connect(_on_local_message_confirmed) _service.history_replaced.connect(_on_history) _service.send_rejected.connect(_on_rejected) _session.peer_removed.connect(_on_peer_removed) + _entry.text = _settings.current_settings.chat_draft + _entry.caret_column = _entry.text.length() + _set_collapsed(_settings.current_settings.chat_collapsed, false) _refresh_history() @@ -54,26 +77,32 @@ func open_chat() -> void: or not _session.is_gameplay_session_active() ): return + _set_collapsed(false) _opened = true _prior_movement = _player.is_movement_enabled() _prior_camera = _player.is_camera_input_enabled() _player.set_movement_enabled(false) _player.set_camera_input_enabled(false) _fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, true) - # Send the host a zeroed frame immediately so it cannot keep applying the - # last movement state while this LineEdit owns keyboard input. + # Clear the last authoritative input immediately. Merely disabling local + # prediction would let a remote host continue the last held movement. _session.submit_neutral_local_movement() - _panel.show() _entry.show() _entry.grab_focus() + _hint.hide() + _update_panel_opacity(true) Input.mouse_mode = Input.MOUSE_MODE_VISIBLE func set_available(value: bool) -> void: _available = value - _chat_button.visible = value if not value: + _send_pending = false + _pending_send_body = "" + _entry.editable = true close_chat() + _flush_draft() + _refresh_visibility() func close_chat() -> void: @@ -85,6 +114,7 @@ func close_chat() -> void: _player.set_movement_enabled(_prior_movement) _player.set_camera_input_enabled(_prior_camera) _fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, false) + _flush_draft() _refresh_visibility() @@ -109,99 +139,203 @@ func _unhandled_input(event: InputEvent) -> void: func _process(_delta: float) -> void: _refresh_visibility() - var now := Time.get_ticks_msec() / 1000.0 - for peer_id: int in _speech.keys(): - var state: Dictionary = _speech[peer_id] - var label := state.get("label") as Label - if label == null or now >= float(state.get("expires", 0.0)): - if label != null: - label.queue_free() - _speech.erase(peer_id) - continue - var avatar := _spawn.get_avatar(peer_id) - var camera := _player.get_gameplay_camera() - if avatar == null or camera == null: - label.hide() - continue - var world_position := avatar.get_body_center_position() + Vector3.UP * 1.2 - if camera.is_position_behind(world_position): - label.hide() - continue - var screen_position := camera.unproject_position(world_position) - label.position = screen_position - Vector2(label.size.x * 0.5, 42.0) - label.show() + _update_panel_opacity() + _update_speech() + + +func _exit_tree() -> void: + _flush_draft() func _build_ui() -> void: _panel = PanelContainer.new() - _panel.position = Vector2(18, 390) - _panel.size = Vector2(390, 240) + _panel.position = PANEL_POSITION + _panel.size = PANEL_SIZE _panel.mouse_filter = Control.MOUSE_FILTER_STOP + _panel.add_theme_stylebox_override("panel", _chat_panel_style()) + _panel.mouse_entered.connect(func() -> void: + _panel_hovered = true + _update_panel_opacity(true) + ) + _panel.mouse_exited.connect(func() -> void: + _panel_hovered = false + _update_panel_opacity(true) + ) add_child(_panel) var box := VBoxContainer.new() box.add_theme_constant_override("separation", 6) _panel.add_child(box) _history = Label.new() - _history.custom_minimum_size = Vector2(360, 150) + _history.custom_minimum_size = Vector2(360, 158) _history.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART _history.vertical_alignment = VERTICAL_ALIGNMENT_BOTTOM _history.mouse_filter = Control.MOUSE_FILTER_IGNORE + _history.add_theme_font_override("font", UtilityPageStyle.TuffyFont) + _history.add_theme_color_override("font_color", UtilityPageStyle.LIGHT_TEXT) box.add_child(_history) + _status = Label.new() + _status.custom_minimum_size = Vector2(360, 20) + _status.mouse_filter = Control.MOUSE_FILTER_IGNORE + _status.add_theme_font_override("font", UtilityPageStyle.TuffyFont) + _status.add_theme_color_override("font_color", Color("ffd6a1")) + box.add_child(_status) _entry = LineEdit.new() _entry.placeholder_text = "type a message…" _entry.max_length = NetworkChatProtocol.MAX_VISIBLE_CHARACTERS _entry.text_submitted.connect(func(_value: String) -> void: _send()) - _entry.hide() + _entry.text_changed.connect(_on_draft_changed) + UtilityPageStyle.apply_line_edit(_entry) box.add_child(_entry) - _chat_button = Button.new() - _chat_button.text = "chat" - _chat_button.position = Vector2(18, 570) - _chat_button.size = Vector2(76, 54) - _chat_button.pressed.connect(open_chat) - add_child(_chat_button) + _entry.hide() + _collapse_button = Button.new() + _collapse_button.position = Vector2(2, PANEL_POSITION.y + 101) + _collapse_button.size = Vector2(34, 48) + _collapse_button.tooltip_text = "Collapse chat" + _collapse_button.pressed.connect(func() -> void: + _set_collapsed(not _collapsed) + ) + _collapse_button.focus_entered.connect(func() -> void: + _update_panel_opacity(true) + ) + UtilityPageStyle.apply_button(_collapse_button) + add_child(_collapse_button) + _unread_indicator = Label.new() + _unread_indicator.text = "•" + _unread_indicator.position = Vector2(8, PANEL_POSITION.y + 86) + _unread_indicator.size = Vector2(22, 20) + _unread_indicator.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + _unread_indicator.mouse_filter = Control.MOUSE_FILTER_IGNORE + _unread_indicator.add_theme_font_override( + "font", UtilityPageStyle.TuffyFont + ) + _unread_indicator.add_theme_font_size_override("font_size", 20) + _unread_indicator.add_theme_color_override("font_color", Color("ffe08a")) + add_child(_unread_indicator) + _hint = Label.new() + _hint.text = "Press Enter to chat" + _hint.position = Vector2(30, 655) + _hint.size = Vector2(260, 28) + _hint.mouse_filter = Control.MOUSE_FILTER_IGNORE + _hint.add_theme_font_override("font", UtilityPageStyle.TuffyFont) + _hint.add_theme_color_override( + "font_color", Color(0.94, 0.91, 0.80, 0.72) + ) + _hint.add_theme_color_override("font_shadow_color", Color(0, 0, 0, 0.8)) + _hint.add_theme_constant_override("shadow_offset_x", 1) + _hint.add_theme_constant_override("shadow_offset_y", 2) + add_child(_hint) _speech_layer = Control.new() _speech_layer.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) _speech_layer.mouse_filter = Control.MOUSE_FILTER_IGNORE add_child(_speech_layer) + _draft_save_timer = Timer.new() + _draft_save_timer.one_shot = true + _draft_save_timer.wait_time = DRAFT_SAVE_DELAY + _draft_save_timer.timeout.connect(_flush_draft) + add_child(_draft_save_timer) _panel.hide() + _collapse_button.hide() + _unread_indicator.hide() + _hint.hide() + + +func _chat_panel_style() -> StyleBoxFlat: + var style := UtilityPageStyle.button_style(Color(0.025, 0.13, 0.19, 0.94)) + style.border_color = Color(0.75, 0.84, 0.74, 0.66) + style.set_corner_radius_all(10) + style.content_margin_left = 12 + style.content_margin_right = 12 + style.content_margin_top = 10 + style.content_margin_bottom = 10 + return style + + +func _speech_panel_style() -> StyleBoxFlat: + var style := StyleBoxFlat.new() + style.bg_color = Color(UtilityPageStyle.PAPER_ALT, 0.96) + style.border_color = Color(UtilityPageStyle.BORDER, 0.88) + style.set_border_width_all(2) + style.set_corner_radius_all(12) + style.content_margin_left = 10 + style.content_margin_right = 10 + style.content_margin_top = 6 + style.content_margin_bottom = 6 + style.shadow_color = Color(0, 0, 0, 0.32) + style.shadow_size = 4 + style.shadow_offset = Vector2(2, 3) + return style func _send() -> void: + if _send_pending: + return var body := _entry.text - if _service.send_local_message(body): - _entry.clear() - close_chat() + _send_pending = true + _pending_send_body = NetworkChatProtocol.sanitize_body(body) + _entry.editable = false + if not _service.send_local_message(body): + _send_pending = false + _pending_send_body = "" + _entry.editable = true + elif _send_pending: + _status.text = "Sending…" + + +func _on_local_message_confirmed(message: Dictionary) -> void: + if ( + not _send_pending + or str(message.get("body", "")) != _pending_send_body + ): + return + _send_pending = false + _pending_send_body = "" + _entry.editable = true + _entry.clear() + _status.text = "" + _flush_draft() + close_chat() func _on_message(message: Dictionary) -> void: _last_message_time = Time.get_ticks_msec() / 1000.0 _refresh_history() + if _collapsed: + _collapsed_has_unread = true + _unread_indicator.show() + _update_panel_opacity(true) if int(message["kind"]) != NetworkChatProtocol.Kind.PLAYER: return var peer_id: int = message["sender_peer_id"] _on_peer_removed(peer_id) + var bubble := PanelContainer.new() + bubble.mouse_filter = Control.MOUSE_FILTER_IGNORE + bubble.custom_minimum_size = Vector2(270, 0) + bubble.size = Vector2(270, 72) + bubble.add_theme_stylebox_override("panel", _speech_panel_style()) var label := Label.new() - label.text = "%s: %s" % [ - str(message["sender_display_name"]), str(message["body"]).left(120), - ] - label.custom_minimum_size = Vector2(80, 34) - label.size = Vector2(280, 70) + label.text = str(message["body"]).left(120) + label.custom_minimum_size = Vector2(246, 46) + label.size = Vector2(246, 62) label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER label.mouse_filter = Control.MOUSE_FILTER_IGNORE - _speech_layer.add_child(label) + label.add_theme_font_override("font", UtilityPageStyle.TuffyFont) + label.add_theme_color_override("font_color", UtilityPageStyle.INK) + bubble.add_child(label) + _speech_layer.add_child(bubble) _speech[peer_id] = { - "label": label, + "bubble": bubble, "expires": Time.get_ticks_msec() / 1000.0 + SPEECH_SECONDS, "fingerprint": str(message.get("sender_fingerprint", "")), } -func _on_history(_messages: Array) -> void: +func _on_history(messages: Array) -> void: for peer_id: int in _speech.keys(): var state: Dictionary = _speech[peer_id] if ( - _messages.is_empty() + messages.is_empty() or _service.is_sender_filtered( str(state.get("fingerprint", "")) ) @@ -211,7 +345,10 @@ func _on_history(_messages: Array) -> void: func _on_rejected(message: String) -> void: - _entry.placeholder_text = message + _send_pending = false + _pending_send_body = "" + _entry.editable = true + _status.text = message if not _opened: open_chat() @@ -235,20 +372,111 @@ func _refresh_history() -> void: func _refresh_visibility() -> void: if not _available: _panel.hide() + _collapse_button.hide() + _unread_indicator.hide() + _hint.hide() return - if _opened: - _panel.show() + _collapse_button.show() + _panel.visible = not _collapsed + _unread_indicator.visible = _collapsed and _collapsed_has_unread + _hint.visible = not _opened + + +func _set_collapsed(value: bool, persist: bool = true) -> void: + _collapsed = value + _collapse_button.text = "›" if _collapsed else "‹" + _collapse_button.tooltip_text = ( + "Expand chat" if _collapsed else "Collapse chat" + ) + if not _collapsed: + _collapsed_has_unread = false + _unread_indicator.hide() + if persist and _settings != null: + _settings.update_chat_preferences(_entry.text, _collapsed) + _draft_save_timer.start() + _refresh_visibility() + + +func _update_panel_opacity(immediate_recheck: bool = false) -> void: + if _panel == null or _collapsed or not _available: return var recent := ( - Time.get_ticks_msec() / 1000.0 - _last_message_time - < COMPACT_SECONDS + Time.get_ticks_msec() / 1000.0 - _last_message_time < RECENT_SECONDS ) - _panel.visible = recent and _service != null and not _service.get_history().is_empty() + var focus_owner := get_viewport().gui_get_focus_owner() + var wants_full := ( + _opened or recent or _panel_hovered + or focus_owner == _collapse_button + or focus_owner == _entry + ) + var desired := 1.0 if wants_full else IDLE_ALPHA + if not immediate_recheck and is_equal_approx(desired, _target_alpha): + return + _target_alpha = desired + if _opacity_tween != null and _opacity_tween.is_valid(): + _opacity_tween.kill() + _opacity_tween = create_tween() + _opacity_tween.tween_property( + _panel, "modulate:a", desired, 0.18 + ).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT) + + +func _update_speech() -> void: + var now := Time.get_ticks_msec() / 1000.0 + for peer_id: int in _speech.keys(): + var state: Dictionary = _speech[peer_id] + var bubble := state.get("bubble") as PanelContainer + if bubble == null or now >= float(state.get("expires", 0.0)): + if bubble != null: + bubble.queue_free() + _speech.erase(peer_id) + continue + var avatar := _spawn.get_avatar(peer_id) + var camera := _player.get_gameplay_camera() + if avatar == null or camera == null: + bubble.hide() + continue + var world_position := avatar.get_chat_anchor_position() + if camera.is_position_behind(world_position): + bubble.hide() + continue + var screen_position := camera.unproject_position(world_position) + var viewport_size := get_viewport_rect().size + if ( + screen_position.x < -80.0 + or screen_position.x > viewport_size.x + 80.0 + or screen_position.y < -80.0 + or screen_position.y > viewport_size.y + 80.0 + ): + bubble.hide() + continue + var desired := screen_position - Vector2( + bubble.size.x * 0.5, bubble.size.y + 8.0 + ) + bubble.position = Vector2( + clampf(desired.x, 8.0, viewport_size.x - bubble.size.x - 8.0), + clampf(desired.y, 8.0, viewport_size.y - bubble.size.y - 8.0), + ) + bubble.show() + + +func _on_draft_changed(_value: String) -> void: + if _settings == null: + return + _settings.update_chat_preferences(_entry.text, _collapsed) + _draft_save_timer.start() + + +func _flush_draft() -> void: + if _settings == null or _entry == null: + return + _settings.update_chat_preferences(_entry.text, _collapsed) + _settings.save_chat_preferences() func _on_peer_removed(peer_id: int) -> void: var state: Dictionary = _speech.get(peer_id, {}) - var label := state.get("label") as Label - if label != null: - label.queue_free() + var bubble := state.get("bubble") as PanelContainer + if bubble != null: + bubble.queue_free() _speech.erase(peer_id) diff --git a/ui/game_ui.gd b/ui/game_ui.gd index 7d8d109..f391c7f 100644 --- a/ui/game_ui.gd +++ b/ui/game_ui.gd @@ -32,6 +32,9 @@ const PlayerCoolerCapacityType = preload( "res://progression/player_cooler_capacity.gd" ) const ChatUIType = preload("res://ui/chat_ui.gd") +const PlayerSettingsManagerType = preload( + "res://settings/player_settings_manager.gd" +) signal pixelation_settings_visibility_changed(is_visible: bool) signal crisp_reset_focus_requested @@ -114,12 +117,13 @@ func setup( reservations: PlayerAssetReservationService, network_profile_service: NetworkProfileService, network_player_list: NetworkPlayerListService, + settings_manager: PlayerSettingsManagerType, ) -> void: _fishing_spot = fishing_spot _item_effects = item_effects _chat_ui.setup( network_chat_service, network_session, spawn_service, player, - fishing_spot + fishing_spot, settings_manager ) _title_settings_panel.setup_network_profile( network_profile, network_session