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

@ -124,6 +124,7 @@ static func migrate_active_to(
for relative: String in [
"player/player_save.nfsave",
"player/player_save.json",
"player/save_slots.json",
"player/network_profile.json",
"player/player_appearance.json",
"social/saved_servers.json",
@ -142,6 +143,16 @@ static func migrate_active_to(
):
_remove_tree(staging)
return {"ok": false, "message": "Migration validation failed for %s." % relative}
var source_slots: String = data_root.root_path.path_join("player/saves")
if (
DirAccess.dir_exists_absolute(source_slots)
and not _copy_progression_slots(
source_slots,
staging.path_join("player/saves"),
)
):
_remove_tree(staging)
return {"ok": false, "message": "Migration validation failed for save slots."}
if DirAccess.dir_exists_absolute(normalized):
DirAccess.remove_absolute(normalized)
if DirAccess.rename_absolute(staging, normalized) != OK:
@ -257,7 +268,7 @@ static func _copy_verified_owned_file(
source: String,
destination: String,
) -> Dictionary:
if source.get_file() != "player_save.nfsave":
if source.get_extension().to_lower() != "nfsave":
return _copy_verified_json(source, destination)
var decoded: Dictionary = ProgressionSaveCodec.read_local_save(source)
if not bool(decoded.get("ok", false)):
@ -281,12 +292,42 @@ static func _valid_owned_data(filename: String, data: Dictionary) -> bool:
if filename == "player_save.json":
var version: int = int(data.get("save_version", -1))
return version >= 1 and version <= PlayerSaveManager.SAVE_VERSION
if filename == "save_slots.json":
return (
int(data.get("format_version", -1)) == PlayerSaveSlotCatalog.FORMAT_VERSION
and typeof(data.get("active_slot_id")) == TYPE_STRING
and typeof(data.get("slots")) == TYPE_ARRAY
)
return (
filename not in LEGACY_FILES
or int(data.get("format_version", -1)) == 1
)
static func _copy_progression_slots(source: String, destination: String) -> bool:
if DirAccess.make_dir_recursive_absolute(destination) != OK:
return false
var access := DirAccess.open(source)
if access == null:
return false
access.list_dir_begin()
var filename: String = access.get_next()
while not filename.is_empty():
if (
not access.current_is_dir()
and filename.get_extension().to_lower() == "nfsave"
and not bool(_copy_verified_owned_file(
source.path_join(filename),
destination.path_join(filename),
).get("ok", false))
):
access.list_dir_end()
return false
filename = access.get_next()
access.list_dir_end()
return true
static func _copy_bytes(source: String, destination: String) -> bool:
return _write_bytes(destination, PortableFileGuard.read_bytes(source))