diff --git a/main/main.gd b/main/main.gd index 7a01295..343435c 100644 --- a/main/main.gd +++ b/main/main.gd @@ -200,8 +200,6 @@ var _server_trust_dialog: ConfirmationDialog var _pending_trust_changed: bool = false var _identity_notice_dialog: AcceptDialog var _data_setup_dialog: ConfirmationDialog -var _data_setup_choose_button: Button -var _data_setup_current_button: Button var _data_folder_dialog: FileDialog var _data_folder_picker_generation: int = 0 var _restore_data_setup_after_picker: bool = false @@ -659,10 +657,10 @@ func _show_data_root_setup() -> void: _data_setup_dialog.cancel_button_text = "Quit" _data_setup_dialog.confirmed.connect(_use_default_data_root) _data_setup_dialog.canceled.connect(get_tree().quit) - _data_setup_choose_button = _data_setup_dialog.add_button( + _data_setup_dialog.add_button( "Choose Another Folder", false, "choose" ) - _data_setup_current_button = _data_setup_dialog.add_button( + _data_setup_dialog.add_button( "Keep Current Location", false, "current" ) _data_setup_dialog.custom_action.connect(_on_data_setup_action) @@ -677,54 +675,20 @@ func _show_data_root_setup() -> void: % (default_path if not default_path.is_empty() else "Choose a folder") ) _data_setup_dialog.ok_button_text = ( - ( - "Move to Documents/NETFISHING" - if legacy else "Use This Folder" - ) - if not default_path.is_empty() - else "Keep Current Location" + "Move to Documents/NETFISHING" if legacy else "Use This Folder" ) - _data_setup_current_button.visible = not default_path.is_empty() if not _data_root.error_message.is_empty(): _data_setup_dialog.dialog_text = ( "Your NETfishing data folder is unavailable.\n\n%s" % _data_root.error_message ) _data_setup_dialog.popup_centered(Vector2i(640, 360)) - _focus_data_setup_actions.call_deferred() - - -func _focus_data_setup_actions() -> void: - if _data_setup_dialog == null or not _data_setup_dialog.visible: - return - var actions: Array[Button] = [ - _data_setup_dialog.get_ok_button(), - _data_setup_choose_button, - ] - if _data_setup_current_button.visible: - actions.append(_data_setup_current_button) - actions.append(_data_setup_dialog.get_cancel_button()) - for index: int in actions.size(): - var action: Button = actions[index] - var previous: Button = actions[posmod(index - 1, actions.size())] - var next: Button = actions[(index + 1) % actions.size()] - action.focus_mode = Control.FOCUS_ALL - action.focus_neighbor_left = action.get_path_to(previous) - action.focus_neighbor_top = action.focus_neighbor_left - action.focus_neighbor_right = action.get_path_to(next) - action.focus_neighbor_bottom = action.focus_neighbor_right - _data_setup_dialog.get_ok_button().grab_focus() func _use_default_data_root() -> void: var path: String = _data_root.default_visible_path() if path.is_empty(): - _activate_selected_data_path( - ProjectSettings.globalize_path( - PlayerDataRoot.APP_DATA_PORTABLE_PATH - ), - true, - ) + _show_folder_picker() return _activate_selected_data_path(path) @@ -773,7 +737,7 @@ func _open_folder_picker_after_modal(generation: int) -> void: if not _data_root.default_visible_path().is_empty() else OS.get_system_dir(OS.SYSTEM_DIR_DOCUMENTS) ) - _interface_fonts.popup_file_dialog(_data_folder_dialog) + _data_folder_dialog.popup_centered_ratio(0.75) func _on_data_folder_picker_canceled() -> void: @@ -970,9 +934,6 @@ func _on_peer_identity_observed(_peer_id: int, status: String) -> void: func _input(event: InputEvent) -> void: - if _handle_data_root_controller_input(event): - get_viewport().set_input_as_handled() - return if _game_ui.is_controller_mapping_capturing(): return if ( @@ -1011,64 +972,6 @@ func _input(event: InputEvent) -> void: get_viewport().set_input_as_handled() -func _handle_data_root_controller_input(event: InputEvent) -> bool: - var button_event := event as InputEventJoypadButton - if button_event == null or not button_event.pressed: - return false - var setup_visible: bool = ( - _data_setup_dialog != null and _data_setup_dialog.visible - ) - var picker_visible: bool = ( - _data_folder_dialog != null and _data_folder_dialog.visible - ) - if not setup_visible and not picker_visible: - return false - var use_mapping: bool = _controller_mapping_manager.has_custom_mapping() - var accept_pressed: bool = ( - _controller_mapping_manager.event_matches_role( - event, - ControllerMappingManagerType.ROLE_A, - ) - if use_mapping - else ( - button_event.button_index == JOY_BUTTON_A - or event.is_action_pressed("ui_accept") - ) - ) - var cancel_pressed: bool = ( - _controller_mapping_manager.event_matches_role( - event, - ControllerMappingManagerType.ROLE_B, - ) - if use_mapping - else ( - button_event.button_index == JOY_BUTTON_B - or event.is_action_pressed("ui_cancel") - ) - ) - if cancel_pressed: - if picker_visible: - _on_data_folder_picker_canceled() - else: - _data_setup_dialog.get_cancel_button().pressed.emit() - return true - if not accept_pressed: - return false - var focused: Control = ( - _data_folder_dialog.gui_get_focus_owner() - if picker_visible - else _data_setup_dialog.gui_get_focus_owner() - ) - var focused_button := focused as BaseButton - if focused_button != null and not focused_button.disabled: - focused_button.pressed.emit() - return true - if setup_visible: - _data_setup_dialog.get_ok_button().pressed.emit() - return true - return false - - func _is_pause_open_request(event: InputEvent) -> bool: return ( event.is_action_pressed("open_system_menu") diff --git a/network/network_chat_protocol.gd b/network/network_chat_protocol.gd index ef7294d..da1af63 100644 --- a/network/network_chat_protocol.gd +++ b/network/network_chat_protocol.gd @@ -3,8 +3,8 @@ extends RefCounted const CAPABILITY: StringName = &"chat_v1" const RELIABLE_CHANNEL: int = NetworkProtocol.CHAT_RELIABLE_CHANNEL -const MAX_VISIBLE_CHARACTERS: int = 256 -const MAX_UTF8_BYTES: int = 1024 +const MAX_VISIBLE_CHARACTERS: int = 300 +const MAX_UTF8_BYTES: int = 1200 const MAX_HISTORY: int = 100 const LATE_JOIN_HISTORY: int = 50 const MAX_ID_LENGTH: int = 96 diff --git a/network/network_session.gd b/network/network_session.gd index 27b599f..1083c24 100644 --- a/network/network_session.gd +++ b/network/network_session.gd @@ -1446,10 +1446,6 @@ func _is_valid_movement_input(data: Dictionary) -> bool: or typeof(data.get("sprint")) != TYPE_BOOL or typeof(data.get("sneak")) != TYPE_BOOL or typeof(data.get("slow_walk")) != TYPE_BOOL - or ( - data.has("sitting") - and typeof(data.get("sitting")) != TYPE_BOOL - ) or typeof(data.get("camera_yaw")) not in [TYPE_FLOAT, TYPE_INT] ): return false diff --git a/network/player_data_root.gd b/network/player_data_root.gd index ba831f4..c0fe26e 100644 --- a/network/player_data_root.gd +++ b/network/player_data_root.gd @@ -72,17 +72,7 @@ func resolve() -> bool: func default_visible_path() -> String: var documents: String = OS.get_system_dir(OS.SYSTEM_DIR_DOCUMENTS) - if documents.is_empty(): - return "" - var candidate: String = _normalize(documents.path_join("NETFISHING")) - if not candidate.is_absolute_path(): - return "" - var classification: StringName = classify_candidate_path( - candidate, - ProjectSettings.globalize_path("res://"), - OS.get_executable_path().get_base_dir(), - ) - return candidate if classification == PATH_ALLOWED else "" + return documents.path_join("NETFISHING") if not documents.is_empty() else "" func select_new_root(path: String, app_data: bool = false) -> bool: @@ -242,21 +232,14 @@ func _validate_candidate(path: String, create: bool) -> bool: var normalized: String = _normalize(path) if normalized.is_empty() or not normalized.is_absolute_path(): return _fail("Choose an absolute filesystem folder.") - var app_data_path: String = _normalize( - ProjectSettings.globalize_path(APP_DATA_PORTABLE_PATH) - ) - var exported_app_data: bool = ( - normalized == app_data_path - and not OS.has_feature("editor") - ) var classification: StringName = classify_candidate_path( normalized, ProjectSettings.globalize_path("res://"), OS.get_executable_path().get_base_dir(), ) - if classification == PATH_SOURCE_PROJECT and not exported_app_data: + if classification == PATH_SOURCE_PROJECT: return _fail("The project folder cannot be used as the player data folder.") - if classification == PATH_INSTALLATION and not exported_app_data: + if classification == PATH_INSTALLATION: return _fail( "The application installation folder cannot be used as the player data folder." ) diff --git a/player/player.gd b/player/player.gd index 9c79b2f..e5a5076 100644 --- a/player/player.gd +++ b/player/player.gd @@ -533,7 +533,6 @@ func capture_network_input(sequence: int) -> Dictionary: "sprint": false, "sneak": false, "slow_walk": false, - "sitting": _sitting, } var axis: Vector2 = Input.get_vector( "move_left", @@ -549,7 +548,6 @@ func capture_network_input(sequence: int) -> Dictionary: "sprint": Input.is_action_pressed("sprint"), "sneak": Input.is_action_pressed("sneak"), "slow_walk": Input.is_action_pressed("slow_walk"), - "sitting": _sitting, } @@ -567,7 +565,6 @@ func apply_authoritative_network_input(data: Dictionary) -> void: _network_sprint = bool(data.get("sprint", false)) _network_sneak = bool(data.get("sneak", false)) _network_slow_walk = bool(data.get("slow_walk", false)) - _set_sitting(bool(data.get("sitting", false))) func make_network_snapshot(peer_id: int) -> Dictionary: @@ -578,7 +575,6 @@ func make_network_snapshot(peer_id: int) -> Dictionary: "velocity": [velocity.x, velocity.y, velocity.z], "visual_yaw": _visuals.rotation.y, "grounded": is_on_floor(), - "sitting": _sitting, } @@ -589,7 +585,6 @@ func push_network_snapshot(snapshot: Dictionary) -> void: _network_target_position = parsed["position"] _network_target_velocity = parsed["velocity"] _network_target_visual_yaw = parsed["visual_yaw"] - _set_sitting(bool(parsed["sitting"])) if not _network_snapshot_ready: global_position = _network_target_position velocity = _network_target_velocity @@ -601,7 +596,6 @@ func apply_local_prediction_correction(snapshot: Dictionary) -> void: var parsed: Dictionary = _parse_network_snapshot(snapshot) if parsed.is_empty(): return - _set_sitting(bool(parsed["sitting"])) var authoritative_position: Vector3 = parsed["position"] var error_distance: float = global_position.distance_to( authoritative_position @@ -619,7 +613,6 @@ func apply_network_teleport(snapshot: Dictionary) -> void: global_position = parsed["position"] velocity = parsed["velocity"] _visuals.rotation.y = parsed["visual_yaw"] - _set_sitting(bool(parsed["sitting"])) _network_target_position = global_position _network_target_velocity = velocity _network_target_visual_yaw = _visuals.rotation.y @@ -647,9 +640,6 @@ func _parse_network_snapshot(snapshot: Dictionary) -> Dictionary: float(network_velocity[2]) ) var visual_yaw: float = float(snapshot.get("visual_yaw", 0.0)) - if snapshot.has("sitting") and typeof(snapshot.get("sitting")) != TYPE_BOOL: - return {} - var sitting: bool = bool(snapshot.get("sitting", false)) if ( not parsed_position.is_finite() or not parsed_velocity.is_finite() @@ -660,7 +650,6 @@ func _parse_network_snapshot(snapshot: Dictionary) -> Dictionary: "position": parsed_position, "velocity": parsed_velocity, "visual_yaw": visual_yaw, - "sitting": sitting, } diff --git a/settings/player_settings.gd b/settings/player_settings.gd index 2a8697b..4a7343f 100644 --- a/settings/player_settings.gd +++ b/settings/player_settings.gd @@ -13,7 +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 = 256 +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] diff --git a/ui/chat_ui.gd b/ui/chat_ui.gd index fe7f731..0d61506 100644 --- a/ui/chat_ui.gd +++ b/ui/chat_ui.gd @@ -20,7 +20,6 @@ const HANDLE_SIZE := Vector2(34, 42) const HANDLE_GAP: float = 6.0 const COLLAPSED_REVEAL_WIDTH: float = 8.0 const HINT_EDGE_MARGIN: float = 4.0 -const SPEECH_BUBBLE_WIDTH: float = 320.0 const MOBILE_COMPACT_WIDTH: float = 620.0 const MOBILE_EXPANDED_WIDTH: float = 820.0 const MOBILE_COMPACT_HEIGHT: float = 220.0 @@ -79,7 +78,6 @@ var _pending_send_body: String = "" var _prior_movement: bool = true var _prior_camera: bool = true var _controller_refocused: bool = false -var _input_lock_applied: bool = false var _output_scale: float = 1.0 var _dock_right: bool = false var _mobile_mode: bool = false @@ -166,7 +164,6 @@ func open_chat() -> void: text_entry_ownership_changed.emit(true) _prior_movement = _player.is_movement_enabled() _prior_camera = _player.is_camera_input_enabled() - _input_lock_applied = true _player.set_movement_enabled(false) _player.set_camera_input_enabled(false) _fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, true) @@ -194,18 +191,16 @@ func set_available(value: bool) -> void: func close_chat() -> void: - var ownership_was_active: bool = _opened or _input_lock_applied + if not _opened: + return _opened = false + text_entry_ownership_changed.emit(false) _entry.virtual_keyboard_enabled = false _entry.release_focus() _entry.hide() - if _input_lock_applied: - _player.set_movement_enabled(_prior_movement) - _player.set_camera_input_enabled(_prior_camera) - _fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, false) - _input_lock_applied = false - if ownership_was_active: - text_entry_ownership_changed.emit(false) + _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() _refresh_input_ownership() @@ -343,7 +338,7 @@ func _build_ui() -> void: _history.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART _history.fit_content = false _history.scroll_active = true - _history.scroll_following = true + _history.scroll_following = false _history.vertical_alignment = VERTICAL_ALIGNMENT_BOTTOM _history.mouse_filter = Control.MOUSE_FILTER_STOP _history.add_theme_font_override("normal_font", UtilityPageStyle.TuffyFont) @@ -628,14 +623,13 @@ func _on_message(message: Dictionary) -> void: _on_peer_removed(peer_id) var bubble := PanelContainer.new() bubble.mouse_filter = Control.MOUSE_FILTER_IGNORE - bubble.custom_minimum_size = Vector2(SPEECH_BUBBLE_WIDTH, 0.0) + 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 = str(message["body"]) - label.custom_minimum_size = Vector2( - SPEECH_BUBBLE_WIDTH - 20.0, - 0.0, - ) + 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 @@ -676,6 +670,7 @@ func _on_rejected(message: String) -> void: func _refresh_history() -> void: if _service == null: return + var scroll_state := _capture_history_scroll() var lines: Array[String] = [] var messages := _service.get_history() for message: Dictionary in messages: @@ -686,7 +681,7 @@ func _refresh_history() -> void: str(message["sender_display_name"]), str(message["body"]), ]) _history.text = "\n".join(lines) - _scroll_history_to_bottom.call_deferred() + _restore_history_scroll.call_deferred(scroll_state) func _refresh_visibility() -> void: @@ -1039,6 +1034,7 @@ func _layout_presentation(animate: bool) -> void: _unread_indicator.position = unread_position _hint.position = hint_position return + var scroll_state := _capture_history_scroll() _height_tween = create_tween().set_parallel(true) _height_tween.tween_property( _panel, "position", target_position, UIMotion.CHAT_RESIZE_DURATION @@ -1077,18 +1073,38 @@ func _layout_presentation(animate: bool) -> void: UIMotion.CHAT_RESIZE_DURATION, ).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT) _height_tween.finished.connect(func() -> void: - _scroll_history_to_bottom() + _restore_history_scroll(scroll_state) ) _hint.position = hint_position -func _scroll_history_to_bottom() -> void: +func _capture_history_scroll() -> Dictionary: + if _history == null: + return {"pinned": true, "value": 0.0} + var scroll := _history.get_v_scroll_bar() + if scroll == null: + return {"pinned": true, "value": 0.0} + var bottom := maxf(scroll.min_value, scroll.max_value - scroll.page) + return { + "pinned": scroll.value >= bottom - 2.0, + "value": scroll.value, + } + + +func _restore_history_scroll(state: Dictionary) -> void: if _history == null: return var scroll := _history.get_v_scroll_bar() if scroll == null: return - scroll.value = maxf(scroll.min_value, scroll.max_value - scroll.page) + if bool(state.get("pinned", true)): + scroll.value = maxf(scroll.min_value, scroll.max_value - scroll.page) + else: + scroll.value = clampf( + float(state.get("value", 0.0)), + scroll.min_value, + maxf(scroll.min_value, scroll.max_value - scroll.page), + ) func _on_viewport_resized() -> void: diff --git a/ui/interface_font_controller.gd b/ui/interface_font_controller.gd index f8db71e..239ebbd 100644 --- a/ui/interface_font_controller.gd +++ b/ui/interface_font_controller.gd @@ -2,13 +2,9 @@ class_name InterfaceFontController extends Node const STANDARD_FONT: Font = preload("res://ui/fonts/Tuffy_Bold.otf") -const COMPACT_FILE_DIALOG_LIMIT := Vector2i(800, 600) -const COMPACT_FILE_DIALOG_MARGIN := Vector2i(12, 12) -const COMPACT_FILE_DIALOG_FONT_SIZE: int = 17 var _game_theme: Theme = preload("res://ui/game_theme.tres") var _utility_theme: Theme -var _compact_file_dialog_theme: Theme func _ready() -> void: @@ -44,66 +40,5 @@ func apply_utility_theme(themed_node: Node) -> void: (themed_node as Window).theme = _utility_theme -func popup_file_dialog(dialog: FileDialog) -> void: - if dialog == null or not dialog.is_inside_tree(): - return - var host_window: Window = dialog.get_parent().get_window() - var host_size: Vector2i = host_window.size - var compact: bool = ( - host_size.x <= COMPACT_FILE_DIALOG_LIMIT.x - or host_size.y <= COMPACT_FILE_DIALOG_LIMIT.y - ) - if not compact: - apply_utility_theme(dialog) - dialog.min_size = Vector2i(200, 70) - dialog.max_size = Vector2i.ZERO - dialog.popup_centered_ratio(0.75) - return - if _compact_file_dialog_theme == null: - _compact_file_dialog_theme = _utility_theme.duplicate(true) - _compact_file_dialog_theme.default_font_size = ( - COMPACT_FILE_DIALOG_FONT_SIZE - ) - dialog.theme = _compact_file_dialog_theme - var available_size := Vector2i( - maxi(1, host_size.x - COMPACT_FILE_DIALOG_MARGIN.x * 2), - maxi(1, host_size.y - COMPACT_FILE_DIALOG_MARGIN.y * 2), - ) - dialog.min_size = Vector2i.ZERO - dialog.max_size = available_size - dialog.popup_centered(available_size) - # FileDialog's desktop-oriented intrinsic minimum can exceed a compact - # 4:3 display. The compact font keeps its controls usable while this final - # clamp guarantees the embedded window cannot escape the physical screen. - dialog.size = available_size - if dialog.get_viewport().gui_embed_subwindows: - dialog.position = (host_size - available_size) / 2 - _finalize_compact_file_dialog.call_deferred( - dialog, host_size, available_size - ) - - -func _finalize_compact_file_dialog( - dialog: FileDialog, - host_size: Vector2i, - requested_size: Vector2i, -) -> void: - await get_tree().process_frame - if dialog == null or not dialog.visible: - return - # Embedded windows report their content origin below the title bar. Account - # for that inset after Godot has laid the window out so its bottom and right - # edges retain the same safe margin as its decorated top and left edges. - var safe_bottom_right: Vector2i = host_size - COMPACT_FILE_DIALOG_MARGIN - var fitted_size: Vector2i = requested_size - fitted_size.x -= maxi( - 0, dialog.position.x + fitted_size.x - safe_bottom_right.x - ) - fitted_size.y -= maxi( - 0, dialog.position.y + fitted_size.y - safe_bottom_right.y - ) - dialog.size = Vector2i(maxi(1, fitted_size.x), maxi(1, fitted_size.y)) - - func readable_font() -> Font: return STANDARD_FONT diff --git a/ui/settings_panel.gd b/ui/settings_panel.gd index 768980a..a29cade 100644 --- a/ui/settings_panel.gd +++ b/ui/settings_panel.gd @@ -472,7 +472,7 @@ func _choose_data_folder() -> void: _interface_fonts.apply_utility_theme(_data_folder_dialog) add_child(_data_folder_dialog) _data_folder_dialog.current_dir = _data_root.root_path.get_base_dir() - _interface_fonts.popup_file_dialog(_data_folder_dialog) + _data_folder_dialog.popup_centered_ratio(0.75) func _change_data_folder(path: String) -> void: @@ -540,7 +540,7 @@ func _choose_identity_export(identity_type: String) -> void: add_child(_export_file_dialog) _export_file_dialog.current_dir = suggested.get_base_dir() _export_file_dialog.current_file = suggested.get_file() - _interface_fonts.popup_file_dialog(_export_file_dialog) + _export_file_dialog.popup_centered_ratio(0.75) func _identity_export_file_selected(path: String) -> void: @@ -565,7 +565,7 @@ func _choose_identity_import(identity_type: String) -> void: _interface_fonts.apply_utility_theme(_backup_file_dialog) add_child(_backup_file_dialog) _backup_file_dialog.current_dir = _data_root.identity_backup_directory() - _interface_fonts.popup_file_dialog(_backup_file_dialog) + _backup_file_dialog.popup_centered_ratio(0.75) func _identity_import_file_selected(path: String) -> void: