feat: expand progression and multiplayer systems

Add named save slots, progression import/export, and a unified play flow. Add live friend requests, presence, invitations, and relationship controls without durable discovery-server social storage. Advance the network protocol with isolated channels, movement reconciliation, late-join recovery, fishing replication, and animation synchronization. Preserve per-species catch totals, refine generated-world startup and water recovery, and complete the related input and interface improvements.
This commit is contained in:
Alexander Sellite 2026-08-23 20:48:38 -04:00
parent 1db1a5b754
commit 3b84bfe3a0
97 changed files with 7869 additions and 982 deletions

View file

@ -86,6 +86,7 @@ enum PresentationMode {
%ControllerSensitivityValue
)
@onready var _invert_y_toggle: Button = %InvertYToggle
@onready var _swap_scroll_toggle: Button = %SwapScrollToggle
@onready var _on_screen_keyboard_toggle: Button = %OnScreenKeyboardToggle
@onready var _auto_click_toggle: Button = %AutoClickToggle
@onready var _auto_click_interval_slider: HSlider = (
@ -112,6 +113,7 @@ var _auto_click_interval_value: float = 0.20
var _mouse_sensitivity: float = 0.005
var _controller_sensitivity: float = 2.5
var _invert_camera_y: bool = false
var _swap_hotbar_camera_scroll: bool = false
var _on_screen_keyboard_enabled: bool = false
var _chat_dock_right: bool = false
var _chat_mobile_mode: bool = false
@ -125,7 +127,6 @@ var _environment_volume: float = 1.0
var _network_profile: NetworkProfilePreferences
var _network_session: NetworkSession
var _data_root: PlayerDataRoot
var _progression_saves: PlayerSaveManager
var _identity_backups: IdentityBackupService
var _player_identity: PlayerIdentityStore
var _host_identity: HostIdentityStore
@ -135,8 +136,6 @@ var _controller_mapping_panel: ControllerMappingPanelType
var _keyboard_mouse_mapping_manager: KeyboardMouseMappingManagerType
var _keyboard_mouse_mapping_panel: KeyboardMouseMappingPanelType
var _data_folder_dialog: FileDialog
var _progression_import_dialog: FileDialog
var _progression_export_dialog: FileDialog
var _backup_file_dialog: FileDialog
var _export_file_dialog: FileDialog
var _passphrase_dialog: ConfirmationDialog
@ -146,7 +145,6 @@ var _pending_identity_operation := ""
var _pending_identity_type := ""
var _pending_identity_path := ""
var _pending_import_data: Dictionary = {}
var _pending_progression_path := ""
func _notification(what: int) -> void:
@ -239,6 +237,7 @@ func _connect_controls() -> void:
_on_controller_sensitivity_changed
)
_invert_y_toggle.toggled.connect(_set_invert_y)
_swap_scroll_toggle.toggled.connect(_set_swap_scroll)
_on_screen_keyboard_toggle.toggled.connect(_set_on_screen_keyboard)
_auto_click_toggle.toggled.connect(_set_auto_click)
_auto_click_interval_slider.value_changed.connect(
@ -248,8 +247,6 @@ func _connect_controls() -> void:
%KeyboardMapping.pressed.connect(_open_keyboard_mouse_mapping)
%OpenDataFolder.pressed.connect(_open_data_folder)
%ChangeDataFolder.pressed.connect(_choose_data_folder)
%ExportProgression.pressed.connect(_choose_progression_export)
%ImportProgression.pressed.connect(_choose_progression_import)
%ExportPlayerIdentity.pressed.connect(
_choose_identity_export.bind("player")
)
@ -343,7 +340,6 @@ func setup_keyboard_mouse_mapping(
func setup_data_and_identity(
data_root: PlayerDataRoot,
progression_saves: PlayerSaveManager,
identity_backups: IdentityBackupService,
player_identity: PlayerIdentityStore,
host_identity: HostIdentityStore,
@ -351,7 +347,6 @@ func setup_data_and_identity(
interface_fonts: InterfaceFontController,
) -> void:
_data_root = data_root
_progression_saves = progression_saves
_identity_backups = identity_backups
_player_identity = player_identity
_host_identity = host_identity
@ -619,11 +614,6 @@ func _refresh_data_page() -> void:
_data_root.override_active
or (_network_session != null and _network_session.is_session_active())
)
%ExportProgression.disabled = _progression_saves == null
%ImportProgression.disabled = (
_progression_saves == null
or (_network_session != null and _network_session.is_session_active())
)
var fingerprint: String = (
_player_identity.fingerprint if _player_identity != null else ""
)
@ -674,122 +664,6 @@ func _copy_player_fingerprint() -> void:
_feedback.text = "full player fingerprint copied for server operator setup."
func _choose_progression_export() -> void:
if _progression_saves == null or _data_root == null:
_feedback.text = "progression export is unavailable."
return
var timestamp: String = Time.get_datetime_string_from_system().replace(
":", "-"
)
var suggested: String = _data_root.progression_backup_directory().path_join(
"NETfishing-progression-%s%s"
% [timestamp, PlayerSaveManager.ARCHIVE_EXTENSION]
)
if _progression_export_dialog == null:
_progression_export_dialog = FileDialog.new()
_progression_export_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE
_progression_export_dialog.access = FileDialog.ACCESS_FILESYSTEM
_progression_export_dialog.use_native_dialog = false
_progression_export_dialog.filters = PackedStringArray([
"*.nfsave ; NETfishing progression archive",
])
_progression_export_dialog.file_selected.connect(
_progression_export_file_selected
)
_interface_fonts.apply_utility_theme(_progression_export_dialog)
add_child(_progression_export_dialog)
_progression_export_dialog.current_dir = suggested.get_base_dir()
_progression_export_dialog.current_file = suggested.get_file()
_interface_fonts.popup_file_dialog(_progression_export_dialog)
func _progression_export_file_selected(path: String) -> void:
var destination: String = (
path
if path.ends_with(PlayerSaveManager.ARCHIVE_EXTENSION)
else path + PlayerSaveManager.ARCHIVE_EXTENSION
)
var result: Dictionary = _progression_saves.export_progression_archive(
destination
)
_feedback.text = str(
result.get("message", "progression export failed.")
)
func _choose_progression_import() -> void:
if _progression_saves == null or _data_root == null:
_feedback.text = "progression import is unavailable."
return
if _network_session != null and _network_session.is_session_active():
_feedback.text = "return to title before importing progression."
return
if _progression_import_dialog == null:
_progression_import_dialog = FileDialog.new()
_progression_import_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
_progression_import_dialog.access = FileDialog.ACCESS_FILESYSTEM
_progression_import_dialog.use_native_dialog = false
_progression_import_dialog.filters = PackedStringArray([
"*.nfsave ; NETfishing progression archive",
])
_progression_import_dialog.file_selected.connect(
_progression_import_file_selected
)
_interface_fonts.apply_utility_theme(_progression_import_dialog)
add_child(_progression_import_dialog)
_progression_import_dialog.current_dir = (
_data_root.progression_backup_directory()
)
_interface_fonts.popup_file_dialog(_progression_import_dialog)
func _progression_import_file_selected(path: String) -> void:
var inspected: Dictionary = (
_progression_saves.inspect_progression_archive(path)
)
if not bool(inspected.get("ok", false)):
_feedback.text = str(
inspected.get("message", "progression archive could not be opened.")
)
return
_pending_progression_path = path
var dialog := ConfirmationDialog.new()
dialog.title = "replace saved progression?"
dialog.ok_button_text = "import progression"
dialog.dialog_text = (
"this will replace the current progression after making a backup.\n\n"
+ "fish: %d\ndiscovered: %d\nworld: %s\nworld seed: %d\n\n"
+ "identities, settings, friends, bans, and trusted servers are unchanged."
) % [
int(inspected.get("catch_count", 0)),
int(inspected.get("discovered_species_count", 0)),
WorldLayout.display_name(inspected.get(
"world_layout",
String(WorldLayout.GENERATED),
)),
int(inspected.get("world_seed", 0)),
]
dialog.confirmed.connect(_confirm_progression_import.bind(dialog))
dialog.canceled.connect(dialog.queue_free)
_interface_fonts.apply_utility_theme(dialog)
add_child(dialog)
dialog.popup_centered(Vector2i(640, 390))
_configure_confirmation_dialog.call_deferred(
dialog, dialog.get_cancel_button()
)
func _confirm_progression_import(dialog: ConfirmationDialog) -> void:
var result: Dictionary = _progression_saves.import_progression_archive(
_pending_progression_path
)
_feedback.text = str(
result.get("message", "progression import failed.")
)
_pending_progression_path = ""
dialog.queue_free()
func _choose_data_folder() -> void:
if _data_root == null or _data_root.override_active:
_feedback.text = "the data folder is externally managed."
@ -1067,6 +941,7 @@ func _apply_settings() -> void:
edited.mouse_camera_sensitivity = _mouse_sensitivity
edited.controller_camera_sensitivity = _controller_sensitivity
edited.invert_camera_y = _invert_camera_y
edited.swap_hotbar_camera_scroll = _swap_hotbar_camera_scroll
edited.on_screen_keyboard_enabled = _on_screen_keyboard_enabled
edited.chat_dock_right = _chat_dock_right
edited.chat_mobile_mode = _chat_mobile_mode
@ -1099,6 +974,7 @@ func _load_controls() -> void:
_mouse_sensitivity = settings.mouse_camera_sensitivity
_controller_sensitivity = settings.controller_camera_sensitivity
_invert_camera_y = settings.invert_camera_y
_swap_hotbar_camera_scroll = settings.swap_hotbar_camera_scroll
_on_screen_keyboard_enabled = settings.on_screen_keyboard_enabled
_chat_dock_right = settings.chat_dock_right
_chat_mobile_mode = settings.chat_mobile_mode
@ -1125,6 +1001,7 @@ func _load_controls() -> void:
_controller_sensitivity
)
_invert_y_toggle.set_pressed_no_signal(_invert_camera_y)
_swap_scroll_toggle.set_pressed_no_signal(_swap_hotbar_camera_scroll)
_on_screen_keyboard_toggle.set_pressed_no_signal(
_on_screen_keyboard_enabled
)
@ -1142,6 +1019,9 @@ func _refresh_value_labels() -> void:
"on" if _fullscreen_toggle.button_pressed else "off"
)
_invert_y_toggle.text = "on" if _invert_camera_y else "off"
_swap_scroll_toggle.text = (
"on" if _swap_hotbar_camera_scroll else "off"
)
_on_screen_keyboard_toggle.text = (
"on" if _on_screen_keyboard_enabled else "off"
)
@ -1255,6 +1135,11 @@ func _set_invert_y(enabled: bool) -> void:
_refresh_value_labels()
func _set_swap_scroll(enabled: bool) -> void:
_swap_hotbar_camera_scroll = enabled
_refresh_value_labels()
func _set_on_screen_keyboard(enabled: bool) -> void:
_on_screen_keyboard_enabled = enabled
_refresh_value_labels()