Add starter RV progression and fishing feedback

This commit is contained in:
Alexander Sellite 2026-08-31 16:03:55 -04:00
parent eed361681a
commit d8bf59bca0
47 changed files with 2268 additions and 467 deletions

View file

@ -40,6 +40,9 @@ const FishingShopInteractionType = preload(
const DecorShopInteractionType = preload(
"res://world/decor_shop_interaction.gd"
)
const RVUpgradeInteractionType = preload(
"res://world/rv_upgrade_interaction.gd"
)
const PlayerStorageInteractionType = preload(
"res://world/player_storage_interaction.gd"
)
@ -148,6 +151,13 @@ const PLAYER_MENU_PATTERN_SCROLL_VELOCITY := Vector2(-7.0, -5.0)
const SHOP_PATTERN_SCALE: float = 1.75
const DEDICATED_IDLE_PHYSICS_TICKS_PER_SECOND: int = 10
const DEDICATED_ACTIVE_PHYSICS_TICKS_PER_SECOND: int = 30
const HOME_ENTRY_INPUT_OWNER: StringName = &"home_entry_transition"
const HOME_EXIT_INPUT_OWNER: StringName = &"home_exit_transition"
const HOME_EXIT_FADE_GENERATION: int = -104729
const HOME_ENTRY_WALK_DISTANCE: float = 1.05
const HOME_ENTRY_WALK_DURATION: float = 0.55
const HOME_EXIT_WALK_DISTANCE: float = 1.25
const HOME_EXIT_WALK_DURATION: float = 0.62
@export var fish_catalog: FishPoolType
@export var pelican_buyer_profile: FishBuyerProfileType
@ -260,6 +270,7 @@ var _gameplay_started: bool = false
var _save_slots := PlayerSaveSlotCatalogType.new()
var _shop_interaction: FishingShopInteractionType
var _decor_shop_interaction: DecorShopInteractionType
var _rv_upgrade_shop_interaction: RVUpgradeInteractionType
var _storage_interaction: PlayerStorageInteractionType
var _title_music_tween: Tween
var _title_music_transition_generation: int = 0
@ -290,6 +301,12 @@ var _pending_existing_root_path := ""
var _local_recovery_attempt_id: String = ""
var _dedicated_runtime: bool = false
var _dedicated_metrics_accumulator: float = 0.0
var _home_exit_transition_active: bool = false
var _home_exit_waiting_for_network: bool = false
var _home_exit_start_position: Vector3 = Vector3.ZERO
var _home_exit_walk_tween: Tween
var _home_entry_transition_active: bool = false
var _home_entry_walk_tween: Tween
@onready var _shoreline_ambience: ShorelineAmbience = %ShorelineAmbience
var _rain_ambience: RainAmbienceType
@ -743,6 +760,7 @@ func _initialize_application(dedicated: bool) -> void:
_player.wallet,
_asset_reservations
)
_player.cooler_capacity.bind_home_state(_home_state)
_player.bag.setup(item_catalog)
_player.inventory_layout.setup(
_player.bag,
@ -760,19 +778,24 @@ func _initialize_application(dedicated: bool) -> void:
)
_shop_interaction = _test_world.get_fishing_shop()
_decor_shop_interaction = _test_world.get_decor_shop()
_rv_upgrade_shop_interaction = _test_world.get_rv_upgrade_shop()
_storage_interaction = _test_world.get_player_storage()
if not dedicated:
if not _home_state.changed.is_connected(_on_home_state_changed):
_home_state.changed.connect(_on_home_state_changed)
_shop_interaction.setup_local_player(_player)
_shop_interaction.local_player_range_changed.connect(
_on_shop_range_changed
)
_game_ui.set_shop_npc_player_in_range(
_shop_interaction.is_local_player_in_range()
)
_refresh_shop_npc_speech_state()
_decor_shop_interaction.setup_local_player(_player)
_decor_shop_interaction.local_player_range_changed.connect(
_on_decor_shop_range_changed
)
_rv_upgrade_shop_interaction.setup_local_player(_player)
_rv_upgrade_shop_interaction.local_player_range_changed.connect(
_on_rv_upgrade_shop_range_changed
)
_storage_interaction.setup_local_player(_player)
_storage_interaction.local_player_range_changed.connect(
_on_storage_range_changed
@ -947,6 +970,7 @@ func _initialize_application(dedicated: bool) -> void:
_home_state,
_network_home,
_decor_shop_interaction,
_rv_upgrade_shop_interaction,
)
_fishing_spot.setup(
_player,
@ -987,6 +1011,7 @@ func _initialize_application(dedicated: bool) -> void:
_shop_interaction,
_storage_interaction,
_decor_shop_interaction,
_rv_upgrade_shop_interaction,
_player.item_effects,
_player.cooler_capacity,
_network_session,
@ -1024,6 +1049,12 @@ func _initialize_application(dedicated: bool) -> void:
_home_decor_controller.begin_decorating
)
_network_home.local_space_changed.connect(_on_local_home_space_changed)
_network_home.local_transition_finished.connect(
_on_local_home_transition_finished
)
_game_ui.get_screen_fade().transition_completed.connect(
_on_home_exit_fade_transition_completed
)
_game_ui.rv_storage_requested.connect(_on_rv_storage_requested)
_game_ui.setup_data_and_identity(
_data_root,
@ -1539,6 +1570,9 @@ func _input(event: InputEvent) -> void:
if _game_ui.get_decor_shop().consume_escape():
get_viewport().set_input_as_handled()
return
if _game_ui.get_rv_upgrade_shop().consume_escape():
get_viewport().set_input_as_handled()
return
if fishing_shop.consume_escape():
get_viewport().set_input_as_handled()
return
@ -1653,17 +1687,12 @@ func _unhandled_input(event: InputEvent) -> void:
return
var home_owner: String = _network_home.get_local_home_owner_fingerprint()
if not home_owner.is_empty():
if _home_world.is_avatar_near_interior_exit(_player, home_owner):
if _network_home.request_exit():
_game_ui.set_storage_prompt_visible(false)
get_viewport().set_input_as_handled()
return
if (
_network_home.can_local_decorate()
and _fishing_spot.can_open_fishing_shop()
and _game_ui.get_player_storage().open_storage_from_home(true)
):
_game_ui.set_storage_prompt_visible(false)
_refresh_home_door_prompt({})
get_viewport().set_input_as_handled()
return
var nearby_home_owner: String = _home_world.find_nearby_exterior_owner(
@ -1671,9 +1700,18 @@ func _unhandled_input(event: InputEvent) -> void:
)
if not nearby_home_owner.is_empty():
if _network_home.request_enter(nearby_home_owner):
_game_ui.set_storage_prompt_visible(false)
_refresh_home_door_prompt({})
get_viewport().set_input_as_handled()
return
if (
_rv_upgrade_shop_interaction != null
and _rv_upgrade_shop_interaction.is_local_player_in_range()
and _fishing_spot.can_open_fishing_shop()
and _game_ui.get_rv_upgrade_shop().open_shop()
):
_game_ui.set_shop_prompt_visible(false)
get_viewport().set_input_as_handled()
return
if (
_decor_shop_interaction != null
and _decor_shop_interaction.is_local_player_in_range()
@ -1703,14 +1741,24 @@ func _process(delta: float) -> void:
)
_print_dedicated_metrics()
return
_maybe_begin_home_exit_transition()
var show_rv_upgrade_prompt := _can_show_rv_upgrade_shop_prompt()
var show_decor_shop_prompt := _can_show_decor_shop_prompt()
var show_shop_prompt := show_decor_shop_prompt or _can_show_shop_prompt()
var show_shop_prompt := (
show_rv_upgrade_prompt
or show_decor_shop_prompt
or _can_show_shop_prompt()
)
var shop_prompt_anchor := (
_decor_shop_interaction.get_prompt_anchor_position()
if show_decor_shop_prompt
_rv_upgrade_shop_interaction.get_prompt_anchor_position()
if show_rv_upgrade_prompt
else (
_shop_interaction.get_prompt_anchor_position()
if _shop_interaction != null else Vector3.ZERO
_decor_shop_interaction.get_prompt_anchor_position()
if show_decor_shop_prompt
else (
_shop_interaction.get_prompt_anchor_position()
if _shop_interaction != null else Vector3.ZERO
)
)
)
_game_ui.set_shop_prompt_visible(
@ -1718,11 +1766,7 @@ func _process(delta: float) -> void:
shop_prompt_anchor,
)
var home_prompt := _get_home_prompt()
_game_ui.set_storage_prompt_visible(
not home_prompt.is_empty(),
home_prompt.get("anchor", Vector3.ZERO),
str(home_prompt.get("label", "enter RV")),
)
_refresh_home_door_prompt(home_prompt)
_game_ui.set_rv_storage_button_visible(
_can_show_rv_storage_button()
)
@ -2577,12 +2621,23 @@ func _refresh_active_world_bindings() -> void:
_decor_shop_interaction.local_player_range_changed.disconnect(
_on_decor_shop_range_changed
)
if is_instance_valid(_rv_upgrade_shop_interaction):
if _rv_upgrade_shop_interaction.local_player_range_changed.is_connected(
_on_rv_upgrade_shop_range_changed
):
_rv_upgrade_shop_interaction.local_player_range_changed.disconnect(
_on_rv_upgrade_shop_range_changed
)
_shop_interaction = _test_world.get_fishing_shop()
_decor_shop_interaction = _test_world.get_decor_shop()
_rv_upgrade_shop_interaction = _test_world.get_rv_upgrade_shop()
_storage_interaction = _test_world.get_player_storage()
_network_sale.set_shop_interaction(_shop_interaction)
_network_shop.set_shop_interaction(_shop_interaction)
_network_shop.set_decor_shop_interaction(_decor_shop_interaction)
_network_shop.set_rv_upgrade_shop_interaction(
_rv_upgrade_shop_interaction
)
if _dedicated_runtime:
return
if _shop_interaction != null:
@ -2600,10 +2655,16 @@ func _refresh_active_world_bindings() -> void:
_decor_shop_interaction.local_player_range_changed.connect(
_on_decor_shop_range_changed
)
if _rv_upgrade_shop_interaction != null:
_rv_upgrade_shop_interaction.setup_local_player(_player)
_rv_upgrade_shop_interaction.local_player_range_changed.connect(
_on_rv_upgrade_shop_range_changed
)
_game_ui.set_world_interactions(
_shop_interaction,
_storage_interaction,
_decor_shop_interaction,
_rv_upgrade_shop_interaction,
)
_on_shop_range_changed(
_shop_interaction != null
@ -2617,6 +2678,10 @@ func _refresh_active_world_bindings() -> void:
_decor_shop_interaction != null
and _decor_shop_interaction.is_local_player_in_range()
)
_on_rv_upgrade_shop_range_changed(
_rv_upgrade_shop_interaction != null
and _rv_upgrade_shop_interaction.is_local_player_in_range()
)
func _on_water_recovery_starting() -> void:
@ -2631,6 +2696,7 @@ func _on_water_recovery_starting() -> void:
_game_ui.get_pause_menu().close_for_water_recovery()
_game_ui.get_fishing_shop().close_for_water_recovery()
_game_ui.get_decor_shop().close_for_water_recovery()
_game_ui.get_rv_upgrade_shop().close_for_water_recovery()
_game_ui.get_player_storage().close_for_water_recovery()
@ -2679,6 +2745,7 @@ func _on_active_hotbar_item_changed(
and item != null
and item.is_available()
and _player.bag.owns_item(item_id)
and not String(_network_home.get_local_space()).begins_with("rv:")
)
var active_is_catching_net: bool = (
item_id == FishingShopStockType.CRAB_NET_ID
@ -2828,6 +2895,8 @@ func _finish_quit() -> void:
func _exit_tree() -> void:
_cancel_home_entry_transition()
_cancel_home_exit_transition(true)
if _dedicated_runtime and _network_session != null:
_network_session.disconnect_session("Dedicated server stopping.")
if _title_music_tween != null:
@ -2845,52 +2914,46 @@ func _exit_tree() -> void:
func _on_shop_range_changed(in_range: bool) -> void:
_game_ui.set_shop_npc_player_in_range(
in_range
or (
_decor_shop_interaction != null
and _decor_shop_interaction.is_local_player_in_range()
)
)
_refresh_shop_npc_speech_state()
if not in_range:
_game_ui.get_fishing_shop().close_for_range_exit()
_game_ui.set_shop_prompt_visible(_can_show_shop_prompt())
_game_ui.set_shop_prompt_visible(_can_show_any_shop_prompt())
func _on_storage_range_changed(in_range: bool) -> void:
if not in_range:
_game_ui.get_player_storage().close_for_range_exit()
var prompt := _get_home_prompt()
_game_ui.set_storage_prompt_visible(
not prompt.is_empty(),
prompt.get("anchor", Vector3.ZERO),
str(prompt.get("label", "enter RV")),
)
_refresh_home_door_prompt(prompt)
_game_ui.set_rv_storage_button_visible(
_can_show_rv_storage_button()
)
func _on_decor_shop_range_changed(in_range: bool) -> void:
_game_ui.set_shop_npc_player_in_range(
in_range
or (
_shop_interaction != null
and _shop_interaction.is_local_player_in_range()
)
)
_refresh_shop_npc_speech_state()
if not in_range:
_game_ui.get_decor_shop().close_for_range_exit()
_game_ui.set_shop_prompt_visible(
_can_show_shop_prompt() or _can_show_decor_shop_prompt()
)
_game_ui.set_shop_prompt_visible(_can_show_any_shop_prompt())
func _on_rv_upgrade_shop_range_changed(in_range: bool) -> void:
_refresh_shop_npc_speech_state()
if not in_range:
_game_ui.get_rv_upgrade_shop().close_for_range_exit()
_game_ui.set_shop_prompt_visible(_can_show_any_shop_prompt())
func _on_home_state_changed() -> void:
_refresh_shop_npc_speech_state()
func _on_local_home_space_changed(
space_id: StringName,
_owner_fingerprint: String,
owner_fingerprint: String,
) -> void:
var inside_rv: bool = String(space_id).begins_with("rv:")
_refresh_active_hotbar_item()
_fishing_spot.set_fishing_environment_enabled(not inside_rv)
_game_ui.set_rv_interior_camera_mode(
inside_rv
@ -2898,6 +2961,171 @@ func _on_local_home_space_changed(
_game_ui.set_rv_storage_button_visible(
_can_show_rv_storage_button()
)
if inside_rv and not _home_exit_transition_active:
_begin_home_entry_transition(owner_fingerprint)
elif not inside_rv and _home_entry_transition_active:
_cancel_home_entry_transition()
func _begin_home_entry_transition(owner_fingerprint: String) -> void:
if owner_fingerprint.is_empty() or _home_entry_transition_active:
return
_home_entry_transition_active = true
_refresh_home_door_prompt({})
_player.set_local_input_suppressed(HOME_ENTRY_INPUT_OWNER, true)
_player.set_camera_input_suppressed(HOME_ENTRY_INPUT_OWNER, true)
var inward_direction: Vector3 = (
-_home_world.get_interior_exit_direction(owner_fingerprint)
)
_player.face_world_direction(inward_direction)
_player.begin_animation_action(PlayerType.CHARACTER_WALKING_ANIMATION)
_home_entry_walk_tween = create_tween()
_home_entry_walk_tween.set_trans(Tween.TRANS_SINE)
_home_entry_walk_tween.set_ease(Tween.EASE_IN_OUT)
_home_entry_walk_tween.tween_property(
_player,
"global_position",
_player.global_position + inward_direction * HOME_ENTRY_WALK_DISTANCE,
HOME_ENTRY_WALK_DURATION,
)
_home_entry_walk_tween.finished.connect(
_cancel_home_entry_transition,
CONNECT_ONE_SHOT,
)
func _cancel_home_entry_transition() -> void:
if _home_entry_walk_tween != null and _home_entry_walk_tween.is_valid():
_home_entry_walk_tween.kill()
_home_entry_walk_tween = null
if is_instance_valid(_player):
_player.end_animation_action()
_player.set_local_input_suppressed(HOME_ENTRY_INPUT_OWNER, false)
_player.set_camera_input_suppressed(HOME_ENTRY_INPUT_OWNER, false)
_home_entry_transition_active = false
func _maybe_begin_home_exit_transition() -> void:
if (
_home_exit_transition_active
or _home_entry_transition_active
or not _gameplay_started
or _water_recovery.is_recovery_active()
or not _fishing_spot.can_open_fishing_shop()
or _game_ui.get_player_storage().visible
or _game_ui.get_fishing_shop().visible
or _game_ui.get_decor_shop().visible
or _game_ui.get_rv_upgrade_shop().visible
):
return
var owner_fingerprint: String = (
_network_home.get_local_home_owner_fingerprint()
)
if (
owner_fingerprint.is_empty()
or not _home_world.has_avatar_crossed_interior_exit_threshold(
_player,
owner_fingerprint,
)
):
return
_begin_home_exit_transition(owner_fingerprint)
func _begin_home_exit_transition(owner_fingerprint: String) -> void:
_home_exit_transition_active = true
_home_exit_waiting_for_network = false
_home_exit_start_position = _player.global_position
_refresh_home_door_prompt({})
_player.set_local_input_suppressed(HOME_EXIT_INPUT_OWNER, true)
_player.set_camera_input_suppressed(HOME_EXIT_INPUT_OWNER, true)
var exit_direction: Vector3 = _home_world.get_interior_exit_direction(
owner_fingerprint
)
_player.face_world_direction(exit_direction)
_player.begin_animation_action(PlayerType.CHARACTER_WALKING_ANIMATION)
_home_exit_walk_tween = create_tween()
_home_exit_walk_tween.set_trans(Tween.TRANS_SINE)
_home_exit_walk_tween.set_ease(Tween.EASE_IN_OUT)
_home_exit_walk_tween.tween_property(
_player,
"global_position",
_player.global_position + exit_direction * HOME_EXIT_WALK_DISTANCE,
HOME_EXIT_WALK_DURATION,
)
_home_exit_walk_tween.finished.connect(
_on_home_exit_walk_finished,
CONNECT_ONE_SHOT,
)
func _on_home_exit_walk_finished() -> void:
_home_exit_walk_tween = null
if not _home_exit_transition_active:
return
# End the walking action on the same frame translation ends. The fade and
# authoritative relocation may take longer, but a stationary avatar must
# never keep cycling its walk animation during that wait.
_player.end_animation_action()
_game_ui.get_screen_fade().fade_to_black(HOME_EXIT_FADE_GENERATION)
func _on_home_exit_fade_transition_completed(
generation: int,
faded_to_black: bool,
) -> void:
if (
generation != HOME_EXIT_FADE_GENERATION
or not _home_exit_transition_active
):
return
if faded_to_black:
# A local host can apply the authoritative transition synchronously from
# request_exit(). Mark this first so its completion signal is not lost.
_home_exit_waiting_for_network = true
var request_started: bool = _network_home.request_exit()
if not request_started:
_home_exit_waiting_for_network = false
_player.global_position = _home_exit_start_position
_game_ui.get_screen_fade().fade_from_black(
HOME_EXIT_FADE_GENERATION
)
return
_cancel_home_exit_transition(false)
func _on_local_home_transition_finished(
accepted: bool,
_message: String,
) -> void:
if (
not _home_exit_transition_active
or not _home_exit_waiting_for_network
):
return
_home_exit_waiting_for_network = false
if not accepted:
_player.global_position = _home_exit_start_position
_game_ui.get_screen_fade().fade_from_black(HOME_EXIT_FADE_GENERATION)
func _cancel_home_exit_transition(reset_fade: bool) -> void:
if _home_exit_walk_tween != null and _home_exit_walk_tween.is_valid():
_home_exit_walk_tween.kill()
_home_exit_walk_tween = null
if is_instance_valid(_player):
_player.end_animation_action()
_player.set_local_input_suppressed(HOME_EXIT_INPUT_OWNER, false)
_player.set_camera_input_suppressed(HOME_EXIT_INPUT_OWNER, false)
if (
reset_fade
and is_instance_valid(_game_ui)
and is_instance_valid(_game_ui.get_screen_fade())
):
_game_ui.get_screen_fade().reset_immediately()
_home_exit_transition_active = false
_home_exit_waiting_for_network = false
_home_exit_start_position = Vector3.ZERO
func _on_rv_storage_requested() -> void:
@ -2913,6 +3141,7 @@ func _can_show_shop_prompt() -> bool:
and _shop_interaction.is_local_player_in_range()
and not _game_ui.get_fishing_shop().visible
and not _game_ui.get_decor_shop().visible
and not _game_ui.get_rv_upgrade_shop().visible
and not _water_recovery.is_recovery_active()
and _fishing_spot.can_open_fishing_shop()
)
@ -2925,15 +3154,96 @@ func _can_show_decor_shop_prompt() -> bool:
and _decor_shop_interaction.is_local_player_in_range()
and not _game_ui.get_decor_shop().visible
and not _game_ui.get_fishing_shop().visible
and not _game_ui.get_rv_upgrade_shop().visible
and not _water_recovery.is_recovery_active()
and _fishing_spot.can_open_fishing_shop()
)
func _can_show_rv_upgrade_shop_prompt() -> bool:
return (
_gameplay_started
and _rv_upgrade_shop_interaction != null
and _rv_upgrade_shop_interaction.is_local_player_in_range()
and not _game_ui.get_rv_upgrade_shop().visible
and not _game_ui.get_fishing_shop().visible
and not _game_ui.get_decor_shop().visible
and not _water_recovery.is_recovery_active()
and _fishing_spot.can_open_fishing_shop()
)
func _can_show_any_shop_prompt() -> bool:
return (
_can_show_shop_prompt()
or _can_show_decor_shop_prompt()
or _can_show_rv_upgrade_shop_prompt()
)
func _is_any_shop_npc_in_range() -> bool:
return (
(
_shop_interaction != null
and _shop_interaction.is_local_player_in_range()
)
or (
_decor_shop_interaction != null
and _decor_shop_interaction.is_local_player_in_range()
)
or (
_rv_upgrade_shop_interaction != null
and _rv_upgrade_shop_interaction.is_local_player_in_range()
)
)
func _refresh_shop_npc_speech_state() -> void:
var interaction := _get_active_shop_npc_interaction()
var dialogue_text: String = ""
if interaction != null:
dialogue_text = interaction.get_dialogue_text(_home_state.get_tier())
_game_ui.set_shop_npc_player_in_range(
interaction != null,
dialogue_text,
)
func _get_active_shop_npc_interaction() -> FishingShopInteractionType:
# Match the interaction and prompt priority used above: RV, decor, fishing.
if (
_rv_upgrade_shop_interaction != null
and _rv_upgrade_shop_interaction.is_local_player_in_range()
):
return _rv_upgrade_shop_interaction
if (
_decor_shop_interaction != null
and _decor_shop_interaction.is_local_player_in_range()
):
return _decor_shop_interaction
if (
_shop_interaction != null
and _shop_interaction.is_local_player_in_range()
):
return _shop_interaction
return null
func _can_show_storage_prompt() -> bool:
return not _get_home_prompt().is_empty()
func _refresh_home_door_prompt(prompt: Dictionary) -> void:
# Home entry belongs to the doorway in world space. Keep the retired HUD
# prompt hidden so it cannot overlap the compact door-mounted sign.
_game_ui.set_storage_prompt_visible(false)
_home_world.set_exterior_prompt_visible(
str(prompt.get("owner_fingerprint", "")),
not prompt.is_empty(),
str(prompt.get("label", "enter home")),
)
func _can_show_rv_storage_button() -> bool:
return (
_gameplay_started
@ -2950,6 +3260,7 @@ func _get_home_prompt() -> Dictionary:
or _game_ui.get_player_storage().visible
or _game_ui.get_fishing_shop().visible
or _game_ui.get_decor_shop().visible
or _game_ui.get_rv_upgrade_shop().visible
or _water_recovery.is_recovery_active()
or not _fishing_spot.can_open_fishing_shop()
):
@ -2958,20 +3269,12 @@ func _get_home_prompt() -> Dictionary:
_network_home.get_local_home_owner_fingerprint()
)
if not owner_fingerprint.is_empty():
if _home_world.is_avatar_near_interior_exit(
_player, owner_fingerprint
):
return {
"label": "leave RV",
"anchor": _home_world.get_interior_prompt_position(
owner_fingerprint
),
}
return {}
var nearby_owner: String = _home_world.find_nearby_exterior_owner(_player)
if nearby_owner.is_empty():
return {}
return {
"label": "enter RV",
"label": "enter home",
"anchor": _home_world.get_exterior_prompt_position(nearby_owner),
"owner_fingerprint": nearby_owner,
}