extends Node3D const FishingSpotType = preload("res://fishing/fishing_spot.gd") const FishBuyerProfileType = preload("res://economy/fish_buyer_profile.gd") const FishPoolType = preload("res://fish/fish_pool.gd") const GameUIType = preload("res://ui/game_ui.gd") const PlayerType = preload("res://player/player.gd") const TestWorldType = preload("res://world/test_world.gd") const WaterRecoveryControllerType = preload( "res://world/water_recovery_controller.gd" ) const PlayerSaveManagerType = preload( "res://save/player_save_manager.gd" ) const PlayerSettingsManagerType = preload( "res://settings/player_settings_manager.gd" ) const PlayerSettingsType = preload("res://settings/player_settings.gd") const TitleScreenType = preload("res://ui/title_screen.gd") const PauseMenuType = preload("res://ui/pause_menu.gd") const ItemCatalogType = preload("res://items/item_catalog.gd") const ItemDataType = preload("res://items/item_data.gd") const FishingShopType = preload("res://ui/fishing_shop.gd") const FishingShopInteractionType = preload( "res://world/fishing_shop_interaction.gd" ) const UIPixelationPresenterType = preload( "res://ui/ui_pixelation_presenter.gd" ) const PixelationResetOverlayType = preload( "res://ui/pixelation_reset_overlay.gd" ) const WorldPixelationPostprocessType = preload( "res://main/world_pixelation_postprocess.gd" ) const NetworkSessionType = preload("res://network/network_session.gd") const NetworkProfilePreferencesType = preload( "res://network/network_profile_preferences.gd" ) const SavedServerStoreType = preload( "res://network/saved_server_store.gd" ) const PlayerSpawnServiceType = preload( "res://network/player_spawn_service.gd" ) const NetworkFishingServiceType = preload( "res://network/network_fishing_service.gd" ) const NetworkSaleServiceType = preload( "res://network/network_sale_service.gd" ) const NetworkShopServiceType = preload( "res://network/network_shop_service.gd" ) const NetworkItemUseServiceType = preload( "res://network/network_item_use_service.gd" ) const NetworkChatServiceType = preload( "res://network/network_chat_service.gd" ) const NetworkMailServiceType = preload( "res://network/network_mail_service.gd" ) const PlayerAssetReservationServiceType = preload( "res://progression/player_asset_reservation_service.gd" ) const PlayerAppearanceStoreType = preload( "res://progression/player_appearance_store.gd" ) const NetworkProfileServiceType = preload( "res://network/network_profile_service.gd" ) const TITLE_MUSIC_SILENCE_DB: float = -80.0 @export var fish_catalog: FishPoolType @export var pelican_buyer_profile: FishBuyerProfileType @export var main_shop_buyer_profile: FishBuyerProfileType @export var item_catalog: ItemCatalogType @export_category("Title Music") @export_range(-40.0, 0.0, 0.5) var title_music_volume_db: float = -6.0 @export_range(0.0, 10.0, 0.05) var title_music_fade_out_seconds: float = 5.0 @onready var _test_world: TestWorldType = $TestWorld @onready var _player: PlayerType = %Player @onready var _fishing_spot: FishingSpotType = %FishingSpot @onready var _game_ui: GameUIType = %GameUI @onready var _water_recovery: WaterRecoveryControllerType = %WaterRecovery @onready var _save_manager: PlayerSaveManagerType = %PlayerSaveManager @onready var _settings_manager: PlayerSettingsManagerType = %PlayerSettingsManager @onready var _interface_fonts: InterfaceFontController = %InterfaceFontController @onready var _title_music: AudioStreamPlayer = %TitleMusic @onready var _ui_pixelation: UIPixelationPresenterType = %UIPresentation @onready var _pixelation_reset: PixelationResetOverlayType = ( %PixelationResetOverlay ) @onready var _world_pixelation: WorldPixelationPostprocessType = ( %WorldPixelationPostprocess ) @onready var _network_session: NetworkSessionType = %NetworkSession @onready var _data_root: PlayerDataRoot = %PlayerDataRoot @onready var _identity_backups: IdentityBackupService = %IdentityBackupService @onready var _network_profile: NetworkProfilePreferencesType = ( %NetworkProfilePreferences ) @onready var _player_identity: PlayerIdentityStore = %PlayerIdentityStore @onready var _host_identity: HostIdentityStore = %HostIdentityStore @onready var _known_players: KnownPlayerStore = %KnownPlayerStore @onready var _server_trust: ServerTrustStore = %ServerTrustStore @onready var _relationships: PlayerRelationshipStore = %PlayerRelationshipStore @onready var _host_bans: HostBanStore = %HostBanStore @onready var _saved_servers: SavedServerStoreType = %SavedServerStore @onready var _player_spawn_service: PlayerSpawnServiceType = ( %PlayerSpawnService ) @onready var _network_fishing: NetworkFishingServiceType = ( %NetworkFishingService ) @onready var _network_sale: NetworkSaleServiceType = %NetworkSaleService @onready var _network_shop: NetworkShopServiceType = %NetworkShopService @onready var _network_item_use: NetworkItemUseServiceType = ( %NetworkItemUseService ) @onready var _network_chat: NetworkChatServiceType = %NetworkChatService @onready var _network_mail: NetworkMailServiceType = %NetworkMailService @onready var _network_player_list: NetworkPlayerListService = %NetworkPlayerListService @onready var _asset_reservations: PlayerAssetReservationServiceType = ( %PlayerAssetReservationService ) @onready var _appearance_store: PlayerAppearanceStoreType = %PlayerAppearanceStore @onready var _network_profile_service: NetworkProfileServiceType = ( %NetworkProfileService ) @onready var _players_root: Node3D = $Players @onready var _title_background: ColorRect = %TitleBackground @onready var _player_menu_backdrop: ColorRect = %PlayerMenuBackdrop var _gameplay_started: bool = false var _shop_interaction: FishingShopInteractionType var _title_music_tween: Tween var _title_music_transition_generation: int = 0 var _title_music_requested: bool = false var _quit_in_progress: bool = false var _join_requested_from_title: bool = false var _join_requested_from_pause: bool = false var _pending_join_endpoint: String = "" var _server_trust_dialog: ConfirmationDialog var _pending_trust_changed: bool = false var _identity_notice_dialog: AcceptDialog var _data_setup_dialog: ConfirmationDialog var _data_folder_dialog: FileDialog var _data_folder_picker_generation: int = 0 var _restore_data_setup_after_picker: bool = false var _application_initialized := false var _pending_existing_root_path := "" var _local_recovery_attempt_id: String = "" func _ready() -> void: DisplayServer.window_set_title("NETfishing") get_window().size_changed.connect(_resize_native_overlays) _resize_native_overlays() if not _settings_manager.settings_changed.is_connected( _apply_runtime_settings ): _settings_manager.settings_changed.connect(_apply_runtime_settings) _settings_manager.load_settings() _interface_fonts.set_readable_font_enabled( _settings_manager.current_settings.use_readable_interface_font ) if _data_root.resolve(): _configure_portable_stores() _initialize_after_data_root() return _show_data_root_setup() func _configure_portable_stores() -> void: _save_manager.configure_storage( _data_root.path_for(&"player_save"), _data_root ) _network_profile.configure_storage( _data_root.path_for(&"network_profile"), _data_root ) _appearance_store.configure_storage( _data_root.path_for(&"player_appearance"), _data_root ) _saved_servers.configure_storage( _data_root.path_for(&"saved_servers"), _data_root ) _known_players.configure_storage( _data_root.path_for(&"known_players"), _data_root ) _relationships.configure_storage( _data_root.path_for(&"player_relationships"), _data_root ) _server_trust.configure_storage( _data_root.path_for(&"server_trust"), _data_root ) _host_bans.configure_storage( _data_root.path_for(&"host_bans"), _data_root ) func _initialize_after_data_root() -> void: if _application_initialized: return _application_initialized = true _player.global_transform = _test_world.get_player_spawn_transform() _player.velocity = Vector3.ZERO _player_spawn_service.setup( _players_root, _player, _test_world.get_player_spawn_transform() ) _network_session.setup( _network_profile, _saved_servers, _player_spawn_service, _player_identity, _host_identity, _known_players, _server_trust, _host_bans, ) _identity_backups.setup(_data_root, _player_identity, _host_identity) _network_profile_service.setup( _network_session, _network_profile, _appearance_store, _player_spawn_service, ) _network_player_list.setup( _network_session, _relationships, _host_bans, _known_players, _player_spawn_service, _network_chat, _network_mail, ) _network_session.set_local_appearance_snapshot( _appearance_store.get_snapshot() ) _network_session.join_authenticated.connect( _on_network_join_authenticated ) _network_session.connection_error.connect( _on_network_connection_error ) _network_session.server_trust_required.connect( _on_server_trust_required ) _network_session.peer_identity_observed.connect( _on_peer_identity_observed ) _network_session.server_lost.connect(_on_network_server_lost) _network_session.remote_recovery_requested.connect( _on_remote_recovery_requested ) _network_session.remote_recovery_presentation_changed.connect( _on_remote_recovery_presentation_changed ) _player.fish_sale_service.setup( _player.inventory, _player.wallet, _asset_reservations ) _player.bag.setup(item_catalog) _player.hotbar.setup(_player.bag, item_catalog) _shop_interaction = _test_world.get_fishing_shop() _shop_interaction.setup_local_player(_player) _shop_interaction.local_player_range_changed.connect( _on_shop_range_changed ) _save_manager.setup( _player.inventory, _player.collection_log, _player.wallet, fish_catalog, _player.bag, _player.hotbar, item_catalog, _player.fishing_upgrades, _player.cooler_capacity ) _save_manager.set_autosave_enabled(false) _asset_reservations.setup( _player.wallet, _player.inventory, _player.bag, item_catalog ) _player.inventory.set_reservation_service(_asset_reservations) _network_mail.setup( _network_session, _asset_reservations, _player.wallet, _player.inventory, _player.bag, _player.collection_log, _player.cooler_capacity, item_catalog, fish_catalog, _save_manager ) _network_item_use.setup( _network_session, _player_spawn_service, item_catalog, _player.bag, _player.item_effects, _save_manager, _asset_reservations ) _network_chat.setup(_network_session) _network_fishing.setup( _network_session, _player_spawn_service, _fishing_spot, _player.inventory, _player.collection_log, _player.cooler_capacity, _save_manager, item_catalog, fish_catalog, _network_item_use ) _network_sale.setup( _network_session, _player_spawn_service, _network_fishing, _player.inventory, _player.wallet, _player.fish_sale_service, _save_manager, fish_catalog, pelican_buyer_profile, _test_world.get_pelican_convenience_landmark(), _asset_reservations ) _network_shop.setup( _network_session, _player_spawn_service, _network_fishing, _shop_interaction, _player.wallet, _player.bag, item_catalog, _player.fishing_upgrades, _player.cooler_capacity, _save_manager, _asset_reservations ) _fishing_spot.setup( _player, _player.inventory, _player.collection_log, _player.bag, _player.hotbar, item_catalog, _player.fishing_upgrades, _player.item_effects, _network_item_use, _player.cooler_capacity, _network_session, _network_fishing ) _game_ui.setup( _player, _player.inventory, _player.collection_log, _player.wallet, _player.fish_sale_service, pelican_buyer_profile, fish_catalog, _fishing_spot, _player.bag, _player.hotbar, item_catalog, main_shop_buyer_profile, _player.fishing_upgrades, _shop_interaction, _player.item_effects, _player.cooler_capacity, _network_session, _network_sale, _network_shop, _network_chat, _network_profile, _player_spawn_service, _network_mail, _asset_reservations, _network_profile_service, _network_player_list, ) _game_ui.setup_data_and_identity( _data_root, _identity_backups, _player_identity, _host_identity, _network_session, _interface_fonts, ) _data_root.conflict_detected.connect(_on_portable_conflict) _data_root.status_changed.connect(_on_data_root_status) if ( PortableFileGuard.has_syncthing_conflict( _data_root.root_path.path_join("player") ) or PortableFileGuard.has_syncthing_conflict( _data_root.root_path.path_join("social") ) ): call_deferred( "_on_data_root_status", "Syncthing conflict copies were found. Review the data folder.", ) _water_recovery.setup( _player, _fishing_spot, _game_ui, _game_ui.get_screen_fade(), _test_world.get_player_water_triggers(), _test_world.get_safe_respawn_points() ) _ui_pixelation.effective_pixel_size_changed.connect( _on_effective_ui_pixel_size_changed ) _game_ui.pixelation_settings_visibility_changed.connect( _pixelation_reset.set_settings_open ) _game_ui.crisp_reset_focus_requested.connect( _pixelation_reset.focus_reset_button ) _game_ui.interactive_pointer_ui_changed.connect( _ui_pixelation.set_interactive_ui_open ) _game_ui.player_menu_backdrop_visibility_changed.connect( _set_player_menu_backdrop_visible ) _pixelation_reset.return_to_settings_requested.connect( _game_ui.focus_open_settings_back_button ) _pixelation_reset.reset_requested.connect(_reset_pixelation) _apply_runtime_settings(_settings_manager.current_settings) _set_gameplay_active(false) var title_screen: TitleScreenType = _game_ui.get_title_screen() title_screen.new_game_requested.connect(_on_new_game_requested) title_screen.continue_game_requested.connect(_on_continue_game_requested) title_screen.quit_requested.connect(_on_quit_requested) title_screen.setup( _save_manager, _settings_manager, _network_session, _saved_servers, _server_trust, ) var pause_menu: PauseMenuType = _game_ui.get_pause_menu() pause_menu.setup( _player, _save_manager, _settings_manager, _fishing_spot, _network_session, _saved_servers, _server_trust, ) title_screen.join_game_requested.connect(_on_title_join_game_requested) pause_menu.join_game_requested.connect(_on_pause_join_game_requested) pause_menu.return_to_title_requested.connect( _on_return_to_title_requested ) pause_menu.reset_progress_requested.connect( _on_reset_progress_requested ) pause_menu.quit_requested.connect(_on_quit_requested) pause_menu.menu_visibility_changed.connect( _game_ui.set_system_menu_open ) _player.hotbar.selected_slot_changed.connect( _on_active_hotbar_item_changed ) _player.bag.contents_changed.connect(_refresh_active_hotbar_item) _fishing_spot.ready_for_equipment_refresh.connect( _refresh_active_hotbar_item ) _water_recovery.recovery_starting.connect( _on_water_recovery_starting ) _water_recovery.recovery_finished.connect( _on_water_recovery_finished ) _water_recovery.local_respawn_completed.connect( _on_local_respawn_completed ) _on_active_hotbar_item_changed( _player.hotbar.get_selected_slot(), _player.hotbar.get_selected_item_id() ) _show_title_music(true) func _show_data_root_setup() -> void: if _data_setup_dialog == null: _data_setup_dialog = ConfirmationDialog.new() _data_setup_dialog.title = "NETfishing player data" _data_setup_dialog.ok_button_text = "Use This Folder" _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_dialog.add_button( "Choose Another Folder", false, "choose" ) _data_setup_dialog.add_button( "Keep Current Location", false, "current" ) _data_setup_dialog.custom_action.connect(_on_data_setup_action) _interface_fonts.apply_utility_theme(_data_setup_dialog) add_child(_data_setup_dialog) var default_path: String = _data_root.default_visible_path() var legacy: bool = PortableDataMigration.legacy_files_present() _data_setup_dialog.dialog_text = ( ("Move your existing NETfishing data to an easy-to-find folder." if legacy else "Choose where NETfishing stores your player data.") + "\n\n%s\n\nYou can choose a Syncthing folder." % (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 _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)) func _use_default_data_root() -> void: var path: String = _data_root.default_visible_path() if path.is_empty(): _show_folder_picker() return _activate_selected_data_path(path) func _on_data_setup_action(action: StringName) -> void: if action == &"choose": _show_folder_picker() elif action == &"current": _activate_selected_data_path( ProjectSettings.globalize_path(PlayerDataRoot.APP_DATA_PORTABLE_PATH), true ) func _show_folder_picker() -> void: if _data_folder_dialog == null: _data_folder_dialog = FileDialog.new() _data_folder_dialog.file_mode = FileDialog.FILE_MODE_OPEN_DIR _data_folder_dialog.access = FileDialog.ACCESS_FILESYSTEM _data_folder_dialog.use_native_dialog = false _data_folder_dialog.dir_selected.connect(_activate_selected_data_path) _data_folder_dialog.canceled.connect(_on_data_folder_picker_canceled) _interface_fonts.apply_utility_theme(_data_folder_dialog) add_child(_data_folder_dialog) if _data_folder_dialog.visible: return _data_folder_picker_generation += 1 var generation: int = _data_folder_picker_generation _restore_data_setup_after_picker = ( _data_setup_dialog != null and _data_setup_dialog.visible ) if _data_setup_dialog != null: _data_setup_dialog.hide() _open_folder_picker_after_modal.call_deferred(generation) func _open_folder_picker_after_modal(generation: int) -> void: await get_tree().process_frame if ( generation != _data_folder_picker_generation or _data_folder_dialog == null or _data_folder_dialog.visible ): return _data_folder_dialog.current_dir = ( _data_root.default_visible_path().get_base_dir() if not _data_root.default_visible_path().is_empty() else OS.get_system_dir(OS.SYSTEM_DIR_DOCUMENTS) ) _data_folder_dialog.popup_centered_ratio(0.75) func _on_data_folder_picker_canceled() -> void: _data_folder_picker_generation += 1 var generation: int = _data_folder_picker_generation var should_restore_setup: bool = _restore_data_setup_after_picker _restore_data_setup_after_picker = false if _data_folder_dialog != null: _data_folder_dialog.hide() if should_restore_setup: _restore_data_setup_after_picker_after_modal.call_deferred(generation) func _restore_data_setup_after_picker_after_modal(generation: int) -> void: await get_tree().process_frame if generation != _data_folder_picker_generation: return _show_data_root_setup() func _activate_selected_data_path(path: String, app_data: bool = false) -> void: _restore_data_setup_after_picker = false var ok: bool = false if PortableDataMigration.legacy_files_present(): var result: Dictionary = ( PortableDataMigration.adopt_legacy_app_data(_data_root) if app_data else PortableDataMigration.migrate_legacy_to(_data_root, path) ) ok = bool(result.get("ok", false)) if not ok: if bool(result.get("requires_existing_root_decision", false)): _show_existing_root_choice(path) return _show_data_error(str(result.get("message", "Migration failed."))) return else: ok = _data_root.select_new_root(path, app_data) if not ok: _show_data_error(_data_root.error_message) return _data_setup_dialog.hide() _configure_portable_stores() _initialize_after_data_root() func _show_existing_root_choice(path: String) -> void: _pending_existing_root_path = path var dialog: ConfirmationDialog = ConfirmationDialog.new() dialog.title = "Existing NETfishing data" dialog.ok_button_text = "Use Data Already in Selected Folder" dialog.cancel_button_text = "Cancel" dialog.dialog_text = ( "This folder already contains NETfishing player data.\n\n" + "Choose which complete data set to use. Data is never merged automatically." ) dialog.add_button( "Replace with This Device's Data", false, "replace" ) dialog.confirmed.connect(func() -> void: if _data_root.use_existing_root(_pending_existing_root_path): dialog.queue_free() _data_setup_dialog.hide() _configure_portable_stores() _initialize_after_data_root() else: _show_data_error(_data_root.error_message) ) dialog.custom_action.connect(func(action: StringName) -> void: if action != &"replace": return var result: Dictionary = PortableDataMigration.replace_existing_with_legacy( _data_root, _pending_existing_root_path ) if bool(result.get("ok", false)): dialog.queue_free() _data_setup_dialog.hide() _configure_portable_stores() _initialize_after_data_root() else: _show_data_error(str(result.get("message", "Migration failed."))) ) _interface_fonts.apply_utility_theme(dialog) add_child(dialog) dialog.popup_centered(Vector2i(680, 360)) func _show_data_error(message: String) -> void: if _data_setup_dialog != null: _data_setup_dialog.dialog_text = message _data_setup_dialog.popup_centered(Vector2i(640, 300)) func _on_portable_conflict(message: String, _path: String) -> void: var dialog: AcceptDialog = AcceptDialog.new() _interface_fonts.apply_utility_theme(dialog) dialog.title = "Player data conflict" dialog.dialog_text = ( message + "\n\nDo not play the same profile on two devices at the same time." ) dialog.add_button("Open Data Folder", false, "open") dialog.custom_action.connect(func(action: StringName) -> void: if action == &"open": _data_root.open_folder() ) dialog.confirmed.connect(dialog.queue_free) _game_ui.add_child(dialog) dialog.popup_centered(Vector2i(560, 300)) func _on_data_root_status(message: String) -> void: if "Syncthing conflict" not in message: return var dialog: AcceptDialog = AcceptDialog.new() _interface_fonts.apply_utility_theme(dialog) dialog.title = "Synced data needs review" dialog.dialog_text = message dialog.add_button("Open Data Folder", false, "open") dialog.custom_action.connect(func(action: StringName) -> void: if action == &"open": _data_root.open_folder() ) dialog.confirmed.connect(dialog.queue_free) _game_ui.add_child(dialog) dialog.popup_centered(Vector2i(560, 260)) func _on_server_trust_required( endpoint: String, expected_fingerprint: String, received_fingerprint: String, is_changed: bool, ) -> void: if _server_trust_dialog == null: _server_trust_dialog = ConfirmationDialog.new() _server_trust_dialog.title = "Server identity" _server_trust_dialog.ok_button_text = "Trust & Connect" _server_trust_dialog.cancel_button_text = "Cancel" _server_trust_dialog.confirmed.connect(_confirm_server_trust) _server_trust_dialog.canceled.connect( _network_session.resolve_server_trust.bind(false) ) _game_ui.add_child(_server_trust_dialog) _pending_trust_changed = is_changed if is_changed: _server_trust_dialog.dialog_text = ( "Server identity changed.\n\n" + "This may mean the server was reinstalled, moved, or is being " + "impersonated.\n\nExpected:\n%s\n\nReceived:\n%s" % [ NetworkIdentityCrypto.format_fingerprint(expected_fingerprint), NetworkIdentityCrypto.format_fingerprint(received_fingerprint), ] ) _server_trust_dialog.ok_button_text = "Trust New Identity" else: _server_trust_dialog.dialog_text = ( "First time connecting to this server.\n\n%s\n\nHost identity:\n%s" % [ endpoint, NetworkIdentityCrypto.format_fingerprint(received_fingerprint), ] ) _server_trust_dialog.ok_button_text = "Trust & Connect" _server_trust_dialog.popup_centered(Vector2i(560, 360)) func _confirm_server_trust() -> void: if _pending_trust_changed: _pending_trust_changed = false _server_trust_dialog.dialog_text = ( "Replace the saved server identity pin?\n\n" + "Only continue if you expected this server identity to change." ) _server_trust_dialog.ok_button_text = "Replace Pin" _server_trust_dialog.popup_centered(Vector2i(520, 260)) return _network_session.resolve_server_trust(true) func _on_peer_identity_observed(_peer_id: int, status: String) -> void: if status != "New identity using a familiar name": return if _identity_notice_dialog == null: _identity_notice_dialog = AcceptDialog.new() _identity_notice_dialog.title = "Player identity" _game_ui.add_child(_identity_notice_dialog) _identity_notice_dialog.dialog_text = ( "New identity using a familiar name.\n\n" + "This may be a different player using the same display name." ) _identity_notice_dialog.popup_centered(Vector2i(480, 240)) func _input(event: InputEvent) -> void: if ( not event.is_action_pressed("ui_cancel") or (event is InputEventKey and event.echo) ): return var title_screen: TitleScreenType = _game_ui.get_title_screen() if title_screen.visible or not _gameplay_started: return var pause_menu: PauseMenuType = _game_ui.get_pause_menu() var fishing_shop: FishingShopType = _game_ui.get_fishing_shop() if fishing_shop.consume_escape(): get_viewport().set_input_as_handled() return if pause_menu.handle_escape(): get_viewport().set_input_as_handled() return if _game_ui.consume_player_menu_escape(): get_viewport().set_input_as_handled() return if _fishing_spot.handle_escape_action(): get_viewport().set_input_as_handled() return if ( not _water_recovery.is_recovery_active() and _fishing_spot.can_open_system_menu() ): _game_ui.close_player_menu_for_game_menu() pause_menu.open_menu() get_viewport().set_input_as_handled() func _unhandled_input(event: InputEvent) -> void: if ( not _gameplay_started or not event.is_action_pressed("interact") or (event is InputEventKey and event.echo) ): return if ( _shop_interaction != null and _shop_interaction.is_local_player_in_range() and _fishing_spot.can_open_fishing_shop() and _game_ui.get_fishing_shop().open_shop() ): _game_ui.set_shop_prompt_visible(false) get_viewport().set_input_as_handled() func _process(_delta: float) -> void: _game_ui.set_shop_prompt_visible(_can_show_shop_prompt()) func _apply_runtime_settings(settings: PlayerSettingsType) -> void: if settings == null: return _interface_fonts.set_readable_font_enabled( settings.use_readable_interface_font ) _apply_world_pixelation(settings.world_pixel_size) _ui_pixelation.set_pixel_size(settings.ui_pixel_size) _game_ui.get_title_screen().set_world_pixelation( settings.world_pixel_size ) _player.apply_camera_settings( settings.mouse_camera_sensitivity, settings.controller_camera_sensitivity, settings.invert_camera_y ) _fishing_spot.configure_accessibility_auto_click( settings.auto_click_enabled, settings.auto_click_interval ) func _apply_world_pixelation(pixel_size: int) -> void: var root_viewport: Viewport = get_viewport() root_viewport.scaling_3d_mode = Viewport.SCALING_3D_MODE_NEAREST root_viewport.scaling_3d_scale = 1.0 root_viewport.msaa_3d = Viewport.MSAA_DISABLED root_viewport.screen_space_aa = Viewport.SCREEN_SPACE_AA_DISABLED root_viewport.use_taa = false _world_pixelation.set_pixel_size(pixel_size) func _on_effective_ui_pixel_size_changed( _requested_pixel_size: int, effective_pixel_size: int, ) -> void: _game_ui.set_effective_ui_pixel_size(effective_pixel_size) func _reset_pixelation() -> void: if _settings_manager.apply_pixelation( PlayerSettingsType.DEFAULT_WORLD_PIXEL_SIZE, PlayerSettingsType.DEFAULT_UI_PIXEL_SIZE ): _game_ui.refresh_open_settings_panel() func _set_gameplay_active(active: bool) -> void: _gameplay_started = active _title_background.visible = not active _world_pixelation.set_gameplay_active(active) _ui_pixelation.set_gameplay_active(active) if not active: _player.item_effects.reset_all() _player.set_movement_enabled(active) _player.set_camera_input_enabled(active) _player.set_camera_active(active) _fishing_spot.set_gameplay_input_enabled(active) _water_recovery.set_recovery_enabled(active) _game_ui.set_gameplay_ui_enabled(active) _save_manager.set_autosave_enabled(active) if active: _refresh_active_hotbar_item() else: _set_player_menu_backdrop_visible(false) func _set_player_menu_backdrop_visible(is_visible: bool) -> void: _player_menu_backdrop.visible = is_visible and _gameplay_started func _resize_native_overlays() -> void: if not is_node_ready(): return _player_menu_backdrop.position = Vector2.ZERO _player_menu_backdrop.size = Vector2(get_window().size) func _on_new_game_requested() -> void: if _gameplay_started or _quit_in_progress: return if not _prepare_private_host(): return if ( not _save_manager.delete_progression_save() or not _save_manager.initialize_new_game() ): _network_session.disconnect_session("New Game setup failed.") _game_ui.get_title_screen().report_network_error( "Could not initialize local progression. Existing data was preserved where possible." ) return _enter_gameplay() func _on_continue_game_requested() -> void: if _gameplay_started or _quit_in_progress: return if not _prepare_private_host(): return if not _save_manager.load_player_data(): _network_session.disconnect_session("Continue failed.") _game_ui.get_title_screen().report_network_error( "Failed to load save. The original was preserved." ) return _enter_gameplay() func _prepare_private_host() -> bool: _join_requested_from_title = false _join_requested_from_pause = false if _network_session.state in [ NetworkSessionType.State.CONNECTION_FAILED, NetworkSessionType.State.SERVER_LOST, ]: _network_session.reset_failure() if not _network_session.start_private_host(): _game_ui.get_title_screen().report_network_error( "Could not start the private multiplayer session." ) return false return true func _enter_gameplay() -> void: _fade_out_title_music() _game_ui.get_title_screen().hide() _set_gameplay_active(true) func _on_return_to_title_requested() -> void: if _quit_in_progress: return var pause_menu: PauseMenuType = _game_ui.get_pause_menu() pause_menu.close_for_title_transition() _network_session.disconnect_session("Returned to title.") _set_gameplay_active(false) _game_ui.get_title_screen().reopen() _show_title_music(true) func _on_title_join_game_requested(endpoint: String) -> void: if _quit_in_progress or _gameplay_started: return if _network_session.state != NetworkSessionType.State.INACTIVE: _network_session.disconnect_session("Preparing direct connection.") _join_requested_from_title = true _join_requested_from_pause = false _pending_join_endpoint = endpoint if not _network_session.join_direct(endpoint): _join_requested_from_title = false func _on_pause_join_game_requested(endpoint: String) -> void: if _quit_in_progress or not _gameplay_started: return if not _save_manager.save_if_dirty(): _game_ui.get_pause_menu().report_network_error( "Could not save progression before leaving this session." ) return _join_requested_from_pause = true _join_requested_from_title = false _pending_join_endpoint = endpoint _game_ui.get_pause_menu().close_for_title_transition() _network_session.disconnect_session("Connecting to another game.") if not _network_session.join_direct(endpoint): _handle_failed_session_switch( "Could not begin the direct connection." ) func _on_network_join_authenticated() -> void: var connected_endpoint: ConnectionEndpoint = ( _network_session.get_current_endpoint() ) var server_metadata: Dictionary = ( _network_session.get_last_server_metadata() ) if connected_endpoint != null: _saved_servers.record_successful_connection( connected_endpoint, int(server_metadata.get("max_players", 0)), str(server_metadata.get("server_display_name", "")), int(server_metadata.get("protocol_version", 0)), int(server_metadata.get("player_count", 0)), ) if _join_requested_from_title: var inspection = _save_manager.inspect_save() var progression_ready: bool = ( _save_manager.load_player_data() if inspection.can_continue() else _save_manager.initialize_new_game() ) if not progression_ready: _network_session.disconnect_session( "Local progression could not be prepared." ) _game_ui.get_title_screen().report_network_error( "Your local progression could not be prepared." ) _join_requested_from_title = false return _fade_out_title_music() _game_ui.get_title_screen().hide() _set_gameplay_active(true) elif _join_requested_from_pause: _set_gameplay_active(true) _join_requested_from_title = false _join_requested_from_pause = false _pending_join_endpoint = "" func _on_network_connection_error(message: String) -> void: var failed_endpoint: ConnectionEndpoint = ( _network_session.get_current_endpoint() ) if failed_endpoint != null: _saved_servers.record_connection_failure( failed_endpoint, _connection_result_code(message), ) if _join_requested_from_title: _game_ui.get_title_screen().report_network_error(message) elif _join_requested_from_pause: _handle_failed_session_switch(message) func _connection_result_code(message: String) -> String: var lowered: String = message.to_lower() if lowered.contains("protocol"): return "PROTOCOL_MISMATCH" if lowered.contains("full"): return "SERVER_FULL" if lowered.contains("cancel"): return "CANCELLED" if lowered.contains("timed out"): return "TIMEOUT" return "UNAVAILABLE" func _handle_failed_session_switch(message: String) -> void: _join_requested_from_pause = false _set_gameplay_active(false) var title_screen: TitleScreenType = _game_ui.get_title_screen() title_screen.reopen() title_screen.open_join_game_page(_pending_join_endpoint) title_screen.report_network_error(message) _show_title_music(true) func _on_network_server_lost() -> void: if not _gameplay_started: return _set_gameplay_active(false) var title_screen: TitleScreenType = _game_ui.get_title_screen() title_screen.reopen() title_screen.open_join_game_page(_pending_join_endpoint) title_screen.report_network_error("The server connection was lost.") _show_title_music(true) func _on_local_respawn_completed(entry_position: Vector3) -> void: if _network_session.is_host(): _network_session.publish_authoritative_teleport( _player.get_network_peer_id() ) elif _network_session.is_joined_client(): _network_session.request_safe_respawn(entry_position) func _on_remote_recovery_requested( peer_id: int, entry_position: Vector3, ) -> void: if not _network_session.is_host(): return var avatar: PlayerType = _player_spawn_service.get_avatar(peer_id) if avatar == null: return _network_fishing.cancel_peer_attempt( peer_id, "Fishing attempt ended." ) var target_position: Vector3 = _test_world.get_player_spawn_transform().origin var nearest_distance: float = INF for point: SafeRespawnPoint in _test_world.get_safe_respawn_points(): if point == null or not point.enabled: continue var distance: float = point.get_horizontal_distance_squared( entry_position ) if distance < nearest_distance: nearest_distance = distance target_position = point.global_position target_position.y += _water_recovery.respawn_height_offset avatar.global_position = target_position avatar.velocity = Vector3.ZERO _network_session.publish_authoritative_teleport(peer_id) func _on_reset_progress_requested() -> void: if _quit_in_progress: return var pause_menu: PauseMenuType = _game_ui.get_pause_menu() if not _save_manager.delete_progression_save(): pause_menu.report_reset_failure() return if not _save_manager.initialize_new_game(): pause_menu.report_reset_failure() return pause_menu.close_for_title_transition() _set_gameplay_active(false) _game_ui.get_title_screen().reopen() _show_title_music(true) func _on_water_recovery_starting() -> void: _local_recovery_attempt_id = ( "recovery:%s" % Crypto.new().generate_random_bytes(16).hex_encode() ) _network_session.request_recovery_presentation( true, _local_recovery_attempt_id ) _game_ui.close_player_menu_for_water_recovery() _game_ui.get_pause_menu().close_for_water_recovery() _game_ui.get_fishing_shop().close_for_water_recovery() func _on_water_recovery_finished() -> void: if _local_recovery_attempt_id.is_empty(): return _network_session.request_recovery_presentation( false, _local_recovery_attempt_id ) _local_recovery_attempt_id = "" func _on_remote_recovery_presentation_changed( peer_id: int, active: bool, _attempt_id: String, ) -> void: var avatar: PlayerType = _player_spawn_service.get_avatar(peer_id) if avatar == null or avatar == _player: return avatar.set_water_recovery_active(active) avatar.set_remote_recovery_presentation(active) func _on_active_hotbar_item_changed( _slot_index: int, item_id: StringName, ) -> void: if _fishing_spot.state != FishingSpotType.FishingState.READY: return var item = item_catalog.get_item_by_id(item_id) var active_is_rod: bool = ( item != null and item.category == ItemDataType.Category.ROD and _player.bag.owns_item(item_id) ) _player.set_active_item_is_rod(active_is_rod) _network_item_use.submit_local_equipped(item_id, active_is_rod or ( item != null and _player.bag.owns_item(item_id) )) _fishing_spot.refresh_active_item_status() func _refresh_active_hotbar_item() -> void: _on_active_hotbar_item_changed( _player.hotbar.get_selected_slot(), _player.hotbar.get_selected_item_id() ) func _on_quit_requested() -> void: if _quit_in_progress: return _quit_in_progress = true _settings_manager.save_if_dirty() if _gameplay_started: _save_manager.save_if_dirty() _network_session.disconnect_session("Application closing.") _title_music_requested = false _replace_title_music_transition() _finish_quit() func _show_title_music(restart_from_beginning: bool = false) -> void: _title_music_requested = true _replace_title_music_transition() if restart_from_beginning: _title_music.stop() _title_music.volume_db = title_music_volume_db if restart_from_beginning or not _title_music.playing: _title_music.play(0.0) func _fade_out_title_music(on_complete: Callable = Callable()) -> void: _title_music_requested = false var generation: int = _replace_title_music_transition() if not _title_music.playing: _title_music.volume_db = title_music_volume_db if on_complete.is_valid(): on_complete.call() return if title_music_fade_out_seconds <= 0.0: _complete_title_music_fade_out(generation, on_complete) return _title_music_tween = create_tween() _title_music_tween.set_trans(Tween.TRANS_CUBIC) _title_music_tween.set_ease(Tween.EASE_IN) _title_music_tween.tween_property( _title_music, "volume_db", TITLE_MUSIC_SILENCE_DB, title_music_fade_out_seconds ) _title_music_tween.finished.connect( _complete_title_music_fade_out.bind(generation, on_complete), CONNECT_ONE_SHOT ) func _replace_title_music_transition() -> int: _title_music_transition_generation += 1 if _title_music_tween != null: _title_music_tween.kill() _title_music_tween = null return _title_music_transition_generation func _complete_title_music_fade_out( generation: int, on_complete: Callable, ) -> void: if ( generation != _title_music_transition_generation or _title_music_requested ): return _title_music_tween = null _title_music.stop() _title_music.volume_db = title_music_volume_db if on_complete.is_valid(): on_complete.call() func _finish_quit() -> void: _title_music.queue_free() await get_tree().process_frame get_tree().quit() func _exit_tree() -> void: if _title_music_tween != null: _title_music_tween.kill() _title_music_tween = null if is_instance_valid(_title_music): _title_music.stop() _title_music.stream = null func _on_shop_range_changed(in_range: bool) -> void: if not in_range: _game_ui.get_fishing_shop().close_for_range_exit() _game_ui.set_shop_prompt_visible(_can_show_shop_prompt()) func _can_show_shop_prompt() -> bool: return ( _gameplay_started and _shop_interaction != null and _shop_interaction.is_local_player_in_range() and not _game_ui.get_fishing_shop().visible and not _water_recovery.is_recovery_active() and _fishing_spot.can_open_fishing_shop() )