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:
parent
1db1a5b754
commit
3b84bfe3a0
97 changed files with 7869 additions and 982 deletions
|
|
@ -27,6 +27,7 @@ const NetworkSessionType = preload("res://network/network_session.gd")
|
|||
const SavedServerStoreType = preload("res://network/saved_server_store.gd")
|
||||
const JoinGamePageType = preload("res://ui/network/join_game_page.gd")
|
||||
const NewGameSetupPageType = preload("res://ui/new_game_setup_page.gd")
|
||||
const SaveSlotsPageType = preload("res://ui/save_slots_page.gd")
|
||||
const CurrencyPresentationType = preload(
|
||||
"res://ui/currency_presentation.gd"
|
||||
)
|
||||
|
|
@ -89,6 +90,13 @@ const QUICK_MENU_ENTER_SCALE: float = 0.97
|
|||
|
||||
signal new_game_requested(world_layout: StringName, world_seed: int)
|
||||
signal continue_game_requested
|
||||
signal slot_play_requested(slot_id: String)
|
||||
signal new_slot_requested(
|
||||
display_name: String,
|
||||
world_layout: StringName,
|
||||
world_seed: int,
|
||||
duplicate_source_slot_id: String,
|
||||
)
|
||||
signal quit_requested
|
||||
signal join_game_requested(endpoint: String)
|
||||
|
||||
|
|
@ -97,7 +105,7 @@ enum ConfirmationAction {
|
|||
DELETE_SAVE,
|
||||
}
|
||||
|
||||
@onready var _continue_button: BubbleButtonType = %ContinueButton
|
||||
@onready var _play_button: BubbleButtonType = %PlayButton
|
||||
@onready var _new_game_button: BubbleButtonType = %NewGameButton
|
||||
@onready var _settings_button: BubbleButtonType = %SettingsButton
|
||||
@onready var _credits_button: BubbleButtonType = %CreditsButton
|
||||
|
|
@ -134,6 +142,7 @@ enum ConfirmationAction {
|
|||
@onready var _join_game_page: JoinGamePageType = %JoinGamePage
|
||||
@onready var _new_game_setup_page: NewGameSetupPageType = %NewGameSetupPage
|
||||
@onready var _credits_page: TitleCreditsPageType = %CreditsPage
|
||||
@onready var _save_slots_page: SaveSlotsPageType = %SaveSlotsPage
|
||||
|
||||
var _save_manager: SaveManagerType
|
||||
var _settings_manager: SettingsManagerType
|
||||
|
|
@ -183,17 +192,17 @@ func _ready() -> void:
|
|||
).strip_edges()
|
||||
if not release_version.is_empty():
|
||||
_playtest_label.text = "v%s" % release_version
|
||||
_continue_button.pressed.connect(_on_continue_pressed)
|
||||
_continue_button.mouse_entered.connect(
|
||||
_play_button.pressed.connect(_on_continue_pressed)
|
||||
_play_button.mouse_entered.connect(
|
||||
_on_continue_stats_hover_changed.bind(true)
|
||||
)
|
||||
_continue_button.mouse_exited.connect(
|
||||
_play_button.mouse_exited.connect(
|
||||
_on_continue_stats_hover_changed.bind(false)
|
||||
)
|
||||
_continue_button.focus_entered.connect(
|
||||
_play_button.focus_entered.connect(
|
||||
_on_continue_stats_focus_changed.bind(true)
|
||||
)
|
||||
_continue_button.focus_exited.connect(
|
||||
_play_button.focus_exited.connect(
|
||||
_on_continue_stats_focus_changed.bind(false)
|
||||
)
|
||||
_new_game_button.pressed.connect(_on_new_game_pressed)
|
||||
|
|
@ -214,6 +223,9 @@ func _ready() -> void:
|
|||
_emit_navigation_bubble_flurry
|
||||
)
|
||||
_credits_page.back_requested.connect(_close_credits)
|
||||
_save_slots_page.back_requested.connect(_close_save_slots)
|
||||
_save_slots_page.play_requested.connect(_on_slot_play_requested)
|
||||
_save_slots_page.create_requested.connect(_on_new_slot_requested)
|
||||
_bubble_field.configure(_get_title_buttons())
|
||||
_bubble_field.motion_scale = 0.0
|
||||
_decorative_fish_timer.timeout.connect(_on_decorative_fish_timer_timeout)
|
||||
|
|
@ -237,14 +249,18 @@ func _ready() -> void:
|
|||
|
||||
func setup(
|
||||
save_manager: SaveManagerType,
|
||||
save_slots: PlayerSaveSlotCatalog,
|
||||
settings_manager: SettingsManagerType,
|
||||
network_session: NetworkSessionType,
|
||||
saved_servers: SavedServerStoreType,
|
||||
server_trust: ServerTrustStore,
|
||||
discovery: DiscoveryClient,
|
||||
data_root: PlayerDataRoot,
|
||||
interface_fonts: InterfaceFontController,
|
||||
) -> void:
|
||||
_save_manager = save_manager
|
||||
_settings_manager = settings_manager
|
||||
_save_slots_page.setup(save_slots, data_root, interface_fonts)
|
||||
_join_game_page.setup(
|
||||
network_session, saved_servers, false, server_trust, discovery
|
||||
)
|
||||
|
|
@ -276,6 +292,7 @@ func reopen() -> void:
|
|||
_join_game_page.close_page()
|
||||
_new_game_setup_page.close_page()
|
||||
_credits_page.close_page()
|
||||
_save_slots_page.close_page()
|
||||
_refresh_save_inspection()
|
||||
show()
|
||||
_start_decorative_presentation()
|
||||
|
|
@ -304,11 +321,15 @@ func open_join_game_page(endpoint: String = "") -> void:
|
|||
_confirmation_page.hide_page()
|
||||
_credits_page.close_page()
|
||||
_new_game_setup_page.close_page()
|
||||
_save_slots_page.close_page()
|
||||
_join_game_page.open_page(endpoint)
|
||||
|
||||
|
||||
func report_network_error(message: String) -> void:
|
||||
_join_game_page.set_status(message)
|
||||
if _save_slots_page.visible:
|
||||
_save_slots_page.set_status(message)
|
||||
return
|
||||
if not _join_game_page.visible:
|
||||
_feedback_label.text = _center_feedback_text(message)
|
||||
_feedback_label.show()
|
||||
|
|
@ -321,6 +342,7 @@ func _open_join_game() -> void:
|
|||
or _is_confirmation_active()
|
||||
or _new_game_setup_page.visible
|
||||
or _credits_page.visible
|
||||
or _save_slots_page.visible
|
||||
):
|
||||
return
|
||||
open_join_game_page()
|
||||
|
|
@ -342,6 +364,7 @@ func _open_credits() -> void:
|
|||
or _join_game_page.visible
|
||||
or _new_game_setup_page.visible
|
||||
or _credits_page.visible
|
||||
or _save_slots_page.visible
|
||||
):
|
||||
return
|
||||
_hide_continue_stats_context()
|
||||
|
|
@ -469,12 +492,10 @@ func _update_responsive_title_stage() -> void:
|
|||
|
||||
func _get_title_buttons() -> Array[BubbleButton]:
|
||||
return [
|
||||
_continue_button,
|
||||
_new_game_button,
|
||||
_play_button,
|
||||
_join_game_button,
|
||||
_settings_button,
|
||||
_credits_button,
|
||||
_delete_button,
|
||||
_quit_button,
|
||||
]
|
||||
|
||||
|
|
@ -583,6 +604,11 @@ func _input(event: InputEvent) -> void:
|
|||
_new_game_setup_page.request_back()
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
if _save_slots_page.visible:
|
||||
if event.is_action_pressed("ui_cancel"):
|
||||
_save_slots_page.request_back()
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
if (
|
||||
_title_settings_transition_active
|
||||
or _title_entry_transition_active
|
||||
|
|
@ -631,6 +657,7 @@ func _handle_primary_menu_focus_input(event: InputEvent) -> bool:
|
|||
or _settings_panel.visible
|
||||
or _new_game_setup_page.visible
|
||||
or _credits_page.visible
|
||||
or _save_slots_page.visible
|
||||
):
|
||||
return false
|
||||
if event is InputEventMouseMotion:
|
||||
|
|
@ -656,7 +683,7 @@ func _handle_primary_menu_focus_input(event: InputEvent) -> bool:
|
|||
|
||||
|
||||
func _get_first_available_menu_button() -> Button:
|
||||
return _continue_button if not _continue_button.disabled else _new_game_button
|
||||
return _play_button
|
||||
|
||||
|
||||
func _primary_menu_has_focus() -> bool:
|
||||
|
|
@ -868,7 +895,7 @@ func _update_continue_stats_visibility() -> void:
|
|||
)
|
||||
requested_visible = (
|
||||
requested_visible
|
||||
and not _continue_button.disabled
|
||||
and not _play_button.disabled
|
||||
and _inspection != null
|
||||
and _inspection.status == SaveInspectionType.Status.VALID_SUPPORTED
|
||||
and not _awaiting_start_input
|
||||
|
|
@ -940,14 +967,70 @@ func _on_continue_pressed() -> void:
|
|||
_action_in_progress
|
||||
or _is_confirmation_active()
|
||||
or _settings_panel.visible
|
||||
or _inspection == null
|
||||
or not _inspection.can_continue()
|
||||
or _save_slots_page.visible
|
||||
):
|
||||
return
|
||||
_hide_continue_stats_context()
|
||||
_modal_restore_navigation_focus = _navigation_focus_active
|
||||
_set_title_bubbles_interactive(false)
|
||||
_release_primary_menu_focus()
|
||||
_presentation_center.hide()
|
||||
_start_prompt_center.hide()
|
||||
_join_game_page.close_page()
|
||||
_credits_page.close_page()
|
||||
_new_game_setup_page.close_page()
|
||||
_save_slots_page.open_page()
|
||||
|
||||
|
||||
func _close_save_slots() -> void:
|
||||
_save_slots_page.close_page()
|
||||
_presentation_center.show()
|
||||
_button_center.show()
|
||||
_start_prompt_center.hide()
|
||||
_set_title_bubbles_interactive(true)
|
||||
var restore_navigation_focus: bool = _modal_restore_navigation_focus
|
||||
_modal_restore_navigation_focus = false
|
||||
if restore_navigation_focus:
|
||||
_navigation_focus_active = true
|
||||
_play_button.grab_focus()
|
||||
else:
|
||||
_navigation_focus_active = false
|
||||
_release_title_focus()
|
||||
_update_continue_stats_visibility()
|
||||
|
||||
|
||||
func _on_slot_play_requested(slot_id: String) -> void:
|
||||
if _action_in_progress or not _save_slots_page.visible:
|
||||
return
|
||||
_action_in_progress = true
|
||||
continue_game_requested.emit()
|
||||
slot_play_requested.emit(slot_id)
|
||||
_action_in_progress = false
|
||||
if visible:
|
||||
_save_slots_page.finish_request_if_pending(
|
||||
"the save slot could not be started."
|
||||
)
|
||||
|
||||
|
||||
func _on_new_slot_requested(
|
||||
display_name: String,
|
||||
world_layout: StringName,
|
||||
world_seed: int,
|
||||
duplicate_source_slot_id: String,
|
||||
) -> void:
|
||||
if _action_in_progress or not _save_slots_page.visible:
|
||||
return
|
||||
_action_in_progress = true
|
||||
new_slot_requested.emit(
|
||||
display_name,
|
||||
world_layout,
|
||||
world_seed,
|
||||
duplicate_source_slot_id,
|
||||
)
|
||||
_action_in_progress = false
|
||||
if visible:
|
||||
_save_slots_page.finish_request_if_pending(
|
||||
"the save slot could not be created."
|
||||
)
|
||||
|
||||
|
||||
func _on_new_game_pressed() -> void:
|
||||
|
|
@ -1252,6 +1335,7 @@ func _open_settings() -> void:
|
|||
or _action_in_progress
|
||||
or _settings_panel.visible
|
||||
or _credits_page.visible
|
||||
or _save_slots_page.visible
|
||||
or _title_settings_transition_active
|
||||
):
|
||||
return
|
||||
|
|
@ -1467,12 +1551,12 @@ func _restore_settings_focus() -> void:
|
|||
|
||||
func _refresh_save_inspection() -> void:
|
||||
_inspection = _save_manager.inspect_save()
|
||||
_continue_button.disabled = not _inspection.can_continue()
|
||||
_play_button.disabled = false
|
||||
_delete_button.disabled = not _inspection.can_delete()
|
||||
_feedback_label.text = _center_feedback_text(_inspection.message)
|
||||
if _inspection.status == SaveInspectionType.Status.VALID_SUPPORTED:
|
||||
_feedback_label.text = _get_continue_stats_text()
|
||||
if _continue_button.disabled:
|
||||
if not _inspection.can_continue():
|
||||
_hide_continue_stats_context()
|
||||
else:
|
||||
_update_continue_stats_visibility()
|
||||
|
|
@ -1485,12 +1569,10 @@ func _focus_initial_button() -> void:
|
|||
or _awaiting_start_input
|
||||
or not _button_center.visible
|
||||
or _credits_page.visible
|
||||
or _save_slots_page.visible
|
||||
):
|
||||
return
|
||||
if not _continue_button.disabled:
|
||||
_continue_button.grab_focus()
|
||||
else:
|
||||
_new_game_button.grab_focus()
|
||||
_play_button.grab_focus()
|
||||
_navigation_focus_active = true
|
||||
_update_continue_stats_visibility()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue