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
|
|
@ -46,7 +46,7 @@ class Barrier:
|
|||
# Fight movement uses one shared baseline. Species and quality difficulty comes
|
||||
# from barrier placement and health, while reel upgrades affect only the
|
||||
# player's progress speed.
|
||||
const CHASE_START_DELAY: float = 1.0
|
||||
const CHASE_START_DELAY: float = 1.5
|
||||
const CHASE_START_OFFSET: float = 0.04
|
||||
const CHASE_SPEED: float = 0.07
|
||||
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ signal showcase_changed(
|
|||
)
|
||||
signal bite_activated
|
||||
signal bite_prompt_changed(is_visible: bool)
|
||||
signal fishing_input_priority_changed(active: bool)
|
||||
signal ready_for_equipment_refresh
|
||||
|
||||
enum FishingState {
|
||||
|
|
@ -182,6 +183,7 @@ var _network_active_barrier_index: int = -1
|
|||
var _bite_rng: RandomNumberGenerator = RandomNumberGenerator.new()
|
||||
var _pending_cleanup_message: String = ""
|
||||
var _bite_confirmation_pending: bool = false
|
||||
var _fishing_input_priority_active: bool = false
|
||||
var _bite_confirmation_requested: bool = false
|
||||
|
||||
|
||||
|
|
@ -458,6 +460,7 @@ func _secure_showcase_catch_for_recovery() -> void:
|
|||
func _exit_tree() -> void:
|
||||
_stop_fight_audio()
|
||||
_stop_reeling_audio()
|
||||
_set_fishing_input_priority(false)
|
||||
_showcase_restore_generation += 1
|
||||
if (
|
||||
state == FishingState.SHOWING_CATCH
|
||||
|
|
@ -777,6 +780,8 @@ func confirm_pending_bite() -> void:
|
|||
|
||||
|
||||
func _set_bite_confirmation_pending(is_pending: bool) -> void:
|
||||
if is_pending:
|
||||
_set_fishing_input_priority(true)
|
||||
if _bite_confirmation_pending == is_pending:
|
||||
if not is_pending:
|
||||
_bite_confirmation_requested = false
|
||||
|
|
@ -786,6 +791,17 @@ func _set_bite_confirmation_pending(is_pending: bool) -> void:
|
|||
bite_prompt_changed.emit(is_pending)
|
||||
|
||||
|
||||
func _set_fishing_input_priority(active: bool) -> void:
|
||||
if _fishing_input_priority_active == active:
|
||||
return
|
||||
_fishing_input_priority_active = active
|
||||
fishing_input_priority_changed.emit(active)
|
||||
|
||||
|
||||
func is_fishing_input_priority_active() -> bool:
|
||||
return _fishing_input_priority_active
|
||||
|
||||
|
||||
func is_returning() -> bool:
|
||||
return state == FishingState.RETURNING
|
||||
|
||||
|
|
@ -1126,6 +1142,7 @@ func _activate_bite(confirmation_override: bool = false) -> void:
|
|||
):
|
||||
_cancel_attempt()
|
||||
return
|
||||
_set_fishing_input_priority(true)
|
||||
if (
|
||||
not confirmation_override
|
||||
and _active_lure_has_effect(&"deferred_fight")
|
||||
|
|
@ -1346,7 +1363,7 @@ func _store_catch_progression(fish_catch: FishCatchType) -> void:
|
|||
)
|
||||
)
|
||||
_local_inventory.add_catch(fish_catch)
|
||||
_local_collection_log.mark_quality_discovered(
|
||||
_local_collection_log.record_catch(
|
||||
fish_catch.fish_id,
|
||||
fish_catch.quality,
|
||||
)
|
||||
|
|
@ -1414,6 +1431,7 @@ func _finalize_attempt_cleanup(
|
|||
cooldown_message: String,
|
||||
restore_movement: bool = true,
|
||||
) -> void:
|
||||
_set_bite_confirmation_pending(false)
|
||||
_showcase_restore_generation += 1
|
||||
if _active_player != null:
|
||||
_active_player.set_fighting_visual(false)
|
||||
|
|
@ -1458,6 +1476,7 @@ func _finalize_attempt_cleanup(
|
|||
_state_time_remaining = cooldown_duration
|
||||
_cooldown_status = cooldown_message
|
||||
status_changed.emit(_cooldown_status)
|
||||
_set_fishing_input_priority(false)
|
||||
|
||||
|
||||
func _return_to_ready() -> void:
|
||||
|
|
@ -1466,6 +1485,7 @@ func _return_to_ready() -> void:
|
|||
_cooldown_status = ""
|
||||
status_changed.emit("")
|
||||
ready_for_equipment_refresh.emit()
|
||||
_set_fishing_input_priority(false)
|
||||
|
||||
|
||||
func _cancel_attempt() -> void:
|
||||
|
|
@ -1775,6 +1795,7 @@ func _on_network_bite_started(_attempt_id: String) -> void:
|
|||
if state != FishingState.WAITING_FOR_BITE:
|
||||
return
|
||||
_stop_reeling_audio()
|
||||
_set_fishing_input_priority(true)
|
||||
_set_bite_confirmation_pending(false)
|
||||
state = FishingState.FIGHTING
|
||||
if _active_player != null:
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ var _showcase_tween: Tween
|
|||
var _return_showcase_catch: FishCatch
|
||||
var _bobber_idle_elapsed: float = 0.0
|
||||
var _bobber_base_scale: Vector3 = Vector3.ONE
|
||||
var _attempt_id: String = ""
|
||||
var _observer_fighting_active: bool = false
|
||||
|
||||
|
||||
func setup(owning_player: Player) -> void:
|
||||
|
|
@ -43,7 +45,11 @@ func setup(owning_player: Player) -> void:
|
|||
cleanup()
|
||||
|
||||
|
||||
func show_cast(origin: Vector3, target: Vector3) -> void:
|
||||
func show_cast(
|
||||
origin: Vector3,
|
||||
target: Vector3,
|
||||
attempt_id: String = "",
|
||||
) -> void:
|
||||
if (
|
||||
_owner == null
|
||||
or not origin.is_finite()
|
||||
|
|
@ -52,6 +58,8 @@ func show_cast(origin: Vector3, target: Vector3) -> void:
|
|||
return
|
||||
_kill_cast_tween()
|
||||
_active = true
|
||||
_attempt_id = attempt_id
|
||||
_observer_fighting_active = false
|
||||
_bobber_base_scale = Vector3.ONE * _owner.get_character_visual_scale()
|
||||
_target = origin
|
||||
_pending_target = target
|
||||
|
|
@ -86,10 +94,48 @@ func update_bobber(world_position: Vector3) -> void:
|
|||
_redraw_line()
|
||||
|
||||
|
||||
func synchronize_active(
|
||||
attempt_id: String,
|
||||
world_position: Vector3,
|
||||
fighting: bool,
|
||||
) -> void:
|
||||
if (
|
||||
_owner == null
|
||||
or attempt_id.is_empty()
|
||||
or not world_position.is_finite()
|
||||
):
|
||||
return
|
||||
if _active and not _attempt_id.is_empty() and _attempt_id != attempt_id:
|
||||
cleanup()
|
||||
if _attempt_id.is_empty():
|
||||
_attempt_id = attempt_id
|
||||
if not _active:
|
||||
_active = true
|
||||
_bobber_base_scale = Vector3.ONE * _owner.get_character_visual_scale()
|
||||
_target = world_position
|
||||
_pending_target = world_position
|
||||
_bobber_idle_elapsed = 0.0
|
||||
_bobber.global_position = world_position
|
||||
_bobber.scale = _bobber_base_scale
|
||||
_bobber.visible = true
|
||||
_line.visible = true
|
||||
_owner.set_active_item_is_rod(true)
|
||||
_owner.set_fishing_visual(true)
|
||||
_redraw_line()
|
||||
else:
|
||||
update_bobber(world_position)
|
||||
if fighting != _observer_fighting_active:
|
||||
_observer_fighting_active = fighting
|
||||
_owner.set_fighting_visual(fighting)
|
||||
if not fighting:
|
||||
_owner.set_fishing_visual(true)
|
||||
|
||||
|
||||
func show_bite() -> void:
|
||||
if not _active:
|
||||
return
|
||||
if _owner != null and is_instance_valid(_owner):
|
||||
_observer_fighting_active = true
|
||||
_owner.set_fighting_visual(true)
|
||||
var tween: Tween = create_tween()
|
||||
tween.tween_property(_bobber, "scale", _bobber_base_scale * 0.7, 0.08)
|
||||
|
|
@ -131,6 +177,8 @@ func cleanup() -> void:
|
|||
_kill_return_tween()
|
||||
_kill_showcase_tween()
|
||||
_active = false
|
||||
_attempt_id = ""
|
||||
_observer_fighting_active = false
|
||||
_pending_target = Vector3.ZERO
|
||||
_return_showcase_catch = null
|
||||
_bobber_idle_elapsed = 0.0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue