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

@ -6,9 +6,11 @@ signal fish_quality_discovered(fish_id: StringName, quality: int)
signal collection_changed
const FishQualityType = preload("res://fish/fish_quality.gd")
const MAX_CATCH_COUNT: int = 1000000000
var _discovered: Dictionary[StringName, bool] = {}
var _quality_masks: Dictionary[StringName, int] = {}
var _catch_counts: Dictionary[StringName, int] = {}
func has_discovered(fish_id: StringName) -> bool:
@ -41,6 +43,26 @@ func mark_quality_discovered(fish_id: StringName, quality: int) -> void:
collection_changed.emit()
func record_catch(fish_id: StringName, quality: int) -> void:
if fish_id.is_empty() or not FishQualityType.is_valid(quality):
return
var species_was_discovered: bool = has_discovered(fish_id)
if not species_was_discovered:
_discovered[fish_id] = true
var previous_mask: int = _quality_masks.get(fish_id, 0)
var next_mask: int = previous_mask | FishQualityType.bit_for(quality)
_quality_masks[fish_id] = next_mask
_catch_counts[fish_id] = mini(
int(_catch_counts.get(fish_id, 0)) + 1,
MAX_CATCH_COUNT,
)
if not species_was_discovered:
fish_discovered.emit(fish_id)
if next_mask != previous_mask:
fish_quality_discovered.emit(fish_id, quality)
collection_changed.emit()
func get_discovered_ids() -> Array[StringName]:
var discovered_ids: Array[StringName] = []
for fish_id: StringName in _discovered:
@ -65,6 +87,14 @@ func get_discovered_quality_masks() -> Dictionary[StringName, int]:
return _quality_masks.duplicate()
func get_catch_count(fish_id: StringName) -> int:
return _catch_counts.get(fish_id, 0) if has_discovered(fish_id) else 0
func get_catch_counts() -> Dictionary[StringName, int]:
return _catch_counts.duplicate()
func has_mastered(fish_id: StringName) -> bool:
return get_quality_mask(fish_id) == FishQualityType.ALL_TIERS_MASK
@ -77,6 +107,22 @@ func replace_discovered_ids(fish_ids: Array[StringName]) -> bool:
func replace_discovery_state(
fish_ids: Array[StringName],
quality_masks: Dictionary[StringName, int],
) -> bool:
var preserved_counts: Dictionary[StringName, int] = {}
for fish_id: StringName in fish_ids:
if _catch_counts.has(fish_id):
preserved_counts[fish_id] = _catch_counts[fish_id]
return replace_collection_state(
fish_ids,
quality_masks,
preserved_counts,
)
func replace_collection_state(
fish_ids: Array[StringName],
quality_masks: Dictionary[StringName, int],
catch_counts: Dictionary[StringName, int],
) -> bool:
var replacement: Dictionary[StringName, bool] = {}
for fish_id: StringName in fish_ids:
@ -95,7 +141,19 @@ func replace_discovery_state(
return false
if mask != 0:
replacement_masks[fish_id] = mask
var replacement_counts: Dictionary[StringName, int] = {}
for fish_id: StringName in catch_counts:
var count: int = catch_counts[fish_id]
if (
fish_id.is_empty()
or not replacement.has(fish_id)
or count <= 0
or count > MAX_CATCH_COUNT
):
return false
replacement_counts[fish_id] = count
_discovered = replacement
_quality_masks = replacement_masks
_catch_counts = replacement_counts
collection_changed.emit()
return true